diff --git a/types/relay-runtime/experimental.d.ts b/types/relay-runtime/experimental.d.ts index 64fb38f77fc658..c76e9dfca8be93 100644 --- a/types/relay-runtime/experimental.d.ts +++ b/types/relay-runtime/experimental.d.ts @@ -1,3 +1,27 @@ +import { DataID } from "./lib/util/RelayRuntimeTypes"; + +export { resolverDataInjector } from "./lib/store/live-resolvers/resolverDataInjector"; export { observeFragment } from "./lib/store/observeFragmentExperimental"; export { observeQuery } from "./lib/store/observeQueryExperimental"; export { waitForFragmentData } from "./lib/store/waitForFragmentExperimental"; + +export type IdOf<_A extends string, Typename extends undefined | string = undefined> = Typename extends undefined + ? { id: DataID } + : { id: DataID; __typename: Typename }; + +interface ErrorResult { + ok: false; + errors: readonly E[]; +} + +interface OkayResult { + ok: true; + value: T; +} + +// The type returned by fields annotated with `@catch` +export type Result = OkayResult | ErrorResult; + +export function isValueResult(input: Result): input is OkayResult; + +export function isErrorResult(input: Result): input is ErrorResult; diff --git a/types/relay-runtime/index.d.ts b/types/relay-runtime/index.d.ts index 0fa5f0ce691d79..c54148d2958d19 100644 --- a/types/relay-runtime/index.d.ts +++ b/types/relay-runtime/index.d.ts @@ -154,6 +154,7 @@ export { RelayModernRecord as Record } from "./lib/store/RelayModernRecord"; export { default as Store } from "./lib/store/RelayModernStore"; export { RelayRecordSource as RecordSource } from "./lib/store/RelayRecordSource"; +export { type IdOf, isErrorResult, isValueResult, type Result } from "./experimental"; export { createFragmentSpecResolver } from "./lib/store/createFragmentSpecResolver"; export { readInlineData } from "./lib/store/readInlineData"; export { createOperationDescriptor, createRequestDescriptor } from "./lib/store/RelayModernOperationDescriptor"; @@ -185,6 +186,7 @@ export { ROOT_TYPE, TYPENAME_KEY, } from "./lib/store/RelayStoreUtils"; +export { readFragment } from "./lib/store/ResolverFragments"; // Extensions import RelayDefaultHandlerProvider from "./lib/handlers/RelayDefaultHandlerProvider"; @@ -267,16 +269,3 @@ export type FragmentRefs = { // This is a utility type for converting from a data type to a fragment reference that will resolve to that data type. export type FragmentRef = Fragment extends _RefType ? _FragmentRefs : never; - -interface ErrorResult { - ok: false; - errors: readonly Error[]; -} - -interface OkayResult { - ok: true; - value: T; -} - -// The type returned by fields annotated with `@catch` -export type Result = OkayResult | ErrorResult; diff --git a/types/relay-runtime/lib/store/live-resolvers/resolverDataInjector.d.ts b/types/relay-runtime/lib/store/live-resolvers/resolverDataInjector.d.ts new file mode 100644 index 00000000000000..84cd33db4a5f45 --- /dev/null +++ b/types/relay-runtime/lib/store/live-resolvers/resolverDataInjector.d.ts @@ -0,0 +1,20 @@ +import type { GraphQLTaggedNode } from "../../query/RelayModernGraphQLTag"; +import type { FragmentType } from "../RelayStoreTypes"; + +/** + * This a higher order function that returns a relay resolver that can read the data for + * the fragment`. + * + * - fragment: contains fragment Reader AST with resolver's data dependencies. + * - resolverFn: original resolver function that expects a data from the fragment + * - (optional) fieldName: individual field that needs to be read out of the fragment. + * + * This will not call the `resolverFn` if the fragment data for it is null/undefined. + * The compiler generates calls to this function, ensuring the correct set of arguments. + */ +export function resolverDataInjector( + fragment: GraphQLTaggedNode, + _resolverFn: unknown, + fieldName?: string, + isRequiredField?: boolean, +): (fragmentKey: FragmentType, args: unknown) => unknown; diff --git a/types/relay-runtime/package.json b/types/relay-runtime/package.json index 8f7c429923b686..f5ff4136d43bb4 100644 --- a/types/relay-runtime/package.json +++ b/types/relay-runtime/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@types/relay-runtime", - "version": "19.0.9999", + "version": "20.1.9999", "projects": [ "https://github.com/facebook/relay", "https://facebook.github.io/relay" diff --git a/types/relay-runtime/relay-runtime-tests.tsx b/types/relay-runtime/relay-runtime-tests.tsx index c3361c65480d7d..b52e400269ebd0 100644 --- a/types/relay-runtime/relay-runtime-tests.tsx +++ b/types/relay-runtime/relay-runtime-tests.tsx @@ -15,7 +15,9 @@ import { getRefetchMetadata, getRequest, graphql, + isErrorResult, isPromise, + isValueResult, LiveState, Network, PreloadableConcreteRequest, @@ -23,6 +25,7 @@ import { QueryResponseCache, ReaderFragment, ReaderInlineDataFragment, + readFragment, readInlineData, RecordProxy, RecordSource, @@ -35,7 +38,13 @@ import { suspenseSentinel, Variables, } from "relay-runtime"; -import { observeFragment, observeQuery, waitForFragmentData } from "relay-runtime/experimental"; +import { + IdOf, + observeFragment, + observeQuery, + resolverDataInjector, + waitForFragmentData, +} from "relay-runtime/experimental"; import type { HandlerProvider } from "relay-runtime/lib/handlers/RelayDefaultHandlerProvider"; import * as multiActorEnvironment from "relay-runtime/multi-actor-environment"; @@ -701,8 +710,6 @@ function multiActors() { // Relay Resolvers // ~~~~~~~~~~~~~~~~~~~~~~~ -const { readFragment } = __internal.ResolverFragments; - // Regular fragment. interface UserComponent_user { readonly id: string; @@ -921,6 +928,20 @@ export function handleResult(result: Result) { } } +// eslint-disable-next-line @definitelytyped/no-unnecessary-generics +export function handleValueResult(result: Result) { + if (isValueResult(result)) { + const value: T = result.value; + } +} + +// eslint-disable-next-line @definitelytyped/no-unnecessary-generics +export function handleErrorResult(result: Result) { + if (isErrorResult(result)) { + const errors: readonly E[] = result.errors; + } +} + // ~~~~~~~~~~~~~~~~~~ // Metadata // ~~~~~~~~~~~~~~~~~~ @@ -1048,3 +1069,61 @@ function observeQueryTest() { subscription.unsubscribe(); } + +// ~~~~~~~~~~~~~~~~~~ +// resolverDataInjector +// ~~~~~~~~~~~~~~~~~~ + +const MyResolverType__id_graphql: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "MyResolverType__id", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null, + }, + ], + }, + ], + "type": "MyResolverType", + "abstractKey": null, +}; + +interface MyResolverType { + id: string; +} + +function myResolverTypeRelayModelInstanceResolver(id: DataID): MyResolverType { + return { + id, + }; +} + +export const resolverModule = resolverDataInjector( + MyResolverType__id_graphql, + myResolverTypeRelayModelInstanceResolver, + "id", + true, +); + +// ~~~~~~~~~~~~~~~~~~ +// Client edge resolver +// ~~~~~~~~~~~~~~~~~~ + +export function myDog(): IdOf<"Dog"> { + return { id: "5" }; +} + +type AnimalTypenames = "Cat" | "Dog"; + +export function myAnimal(): IdOf<"Animal", AnimalTypenames> { + return Math.random() > 0.5 ? { id: "5", __typename: "Dog" } : { id: "6", __typename: "Cat" }; +}