deno.land / x / pothos@release-1713397530 / packages / plugin-relay / schema-builder.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418// @ts-nocheckimport { defaultTypeResolver, GraphQLResolveInfo } from 'https://cdn.skypack.dev/graphql?dts';import SchemaBuilder, { completeValue, createContextCache, FieldMap, FieldRef, getTypeBrand, InputObjectRef, InterfaceParam, InterfaceRef, MaybePromise, ObjectFieldsShape, ObjectParam, ObjectRef, OutputRef, PothosValidationError, SchemaTypes, verifyRef, } from '../core/index.ts';import { NodeRef } from './node-ref.ts';import { ConnectionShape, GlobalIDShape, PageInfoShape } from './types.ts';import { capitalize, resolveNodes } from './utils/index.ts';const schemaBuilderProto = SchemaBuilder.prototype as PothosSchemaTypes.SchemaBuilder<SchemaTypes>;const pageInfoRefMap = new WeakMap<PothosSchemaTypes.SchemaBuilder<SchemaTypes>, ObjectRef<PageInfoShape>>();const nodeInterfaceRefMap = new WeakMap<PothosSchemaTypes.SchemaBuilder<SchemaTypes>, InterfaceRef<{}>>();export const connectionRefs = new WeakMap<PothosSchemaTypes.SchemaBuilder<SchemaTypes>, ObjectRef<ConnectionShape<SchemaTypes, unknown, boolean>>[]>();export const globalConnectionFieldsMap = new WeakMap<PothosSchemaTypes.SchemaBuilder<SchemaTypes>, ((ref: ObjectRef<ConnectionShape<SchemaTypes, unknown, boolean>>) => void)[]>();schemaBuilderProto.pageInfoRef = function pageInfoRef() { if (pageInfoRefMap.has(this)) { return pageInfoRefMap.get(this)!; } const ref = this.objectRef<PageInfoShape>("PageInfo"); pageInfoRefMap.set(this, ref); const { cursorType = "String", hasNextPageFieldOptions = {} as never, hasPreviousPageFieldOptions = {} as never, startCursorFieldOptions = {} as never, endCursorFieldOptions = {} as never, } = this.options.relayOptions; ref.implement({ ...this.options.relayOptions.pageInfoTypeOptions, fields: (t) => ({ hasNextPage: t.exposeBoolean("hasNextPage", { nullable: false, ...hasNextPageFieldOptions, }), hasPreviousPage: t.exposeBoolean("hasPreviousPage", { nullable: false, ...hasPreviousPageFieldOptions, }), startCursor: t.expose("startCursor", { nullable: true, ...(startCursorFieldOptions as {}), type: cursorType, }) as never, endCursor: t.expose("endCursor", { nullable: true, ...(endCursorFieldOptions as {}), type: cursorType, }) as never, }), }); return ref;};schemaBuilderProto.nodeInterfaceRef = function nodeInterfaceRef() { if (nodeInterfaceRefMap.has(this)) { return nodeInterfaceRefMap.get(this)!; } const ref = this.interfaceRef<{}>("Node"); nodeInterfaceRefMap.set(this, ref); ref.implement({ resolveType: (value, context, info, graphQLType) => { if (!value) { return defaultTypeResolver(value, context, info, graphQLType); } const typeBrand = getTypeBrand(value); if (typeBrand) { const type = this.configStore.getTypeConfig(typeBrand as string); return type.name; } try { if (typeof value === "object") { // eslint-disable-next-line no-underscore-dangle const typename = (value as { __typename: string; }).__typename; if (typename) { return typename; } // eslint-disable-next-line no-underscore-dangle const nodeRef = (value as { __type: OutputRef; }).__type; if (nodeRef) { const config = this.configStore.getTypeConfig(nodeRef); if (config) { return config.name; } } } } catch { // ignore } return defaultTypeResolver(value, context, info, graphQLType); }, ...this.options.relayOptions.nodeTypeOptions, fields: (t) => ({ [this.options.relayOptions?.idFieldName ?? "id"]: t.globalID({ ...this.options.relayOptions?.idFieldOptions, nullable: false, resolve: (parent) => { throw new PothosValidationError("id field not implemented"); }, }), }), }); const nodeQueryOptions = this.options.relayOptions?.nodeQueryOptions; if (nodeQueryOptions !== false) { const resolveNodeFn = nodeQueryOptions?.resolve; this.queryField("node", (t) => t.field({ nullable: true, ...this.options.relayOptions.nodeQueryOptions, type: ref as InterfaceRef<unknown>, args: { id: t.arg.globalID({ ...nodeQueryOptions?.args?.id, required: true, extensions: { relayGlobalIDAlwaysParse: true, ...nodeQueryOptions?.args?.id?.extensions, }, }), }, resolve: resolveNodeFn ? (root, args, context, info) => resolveNodeFn(root, args, context, info, (ids) => completeValue(resolveNodes(this, context, info, [args.id]), (nodes) => nodes[0])) as never : (root, args, context, info) => completeValue(resolveNodes(this, context, info, [args.id]), (nodes) => nodes[0]), }) as FieldRef<unknown>); } const nodesQueryOptions = this.options.relayOptions?.nodesQueryOptions; if (nodesQueryOptions !== false) { const resolveNodesFn = nodesQueryOptions?.resolve; this.queryField("nodes", (t) => t.field({ nullable: { list: false, items: true, }, ...this.options.relayOptions.nodesQueryOptions, type: [ref], args: { ids: t.arg.globalIDList({ ...nodesQueryOptions?.args?.ids, required: true, extensions: { relayGlobalIDAlwaysParse: true, ...nodesQueryOptions?.args?.ids?.extensions, }, }), }, resolve: resolveNodesFn ? (root, args, context, info) => resolveNodesFn(root, args as { ids: { id: string; typename: string; }[]; }, context, info, (ids) => resolveNodes(this, context, info, args.ids as { id: string; typename: string; }[])) as never : (root, args, context, info) => resolveNodes(this, context, info, args.ids as { id: string; typename: string; }[]) as never, })); } return ref;};schemaBuilderProto.node = function node(param, { interfaces, extensions, id, ...options }, fields) { verifyRef(param); const interfacesWithNode: () => InterfaceParam<SchemaTypes>[] = () => [ this.nodeInterfaceRef(), ...(typeof interfaces === "function" ? interfaces() : interfaces ?? []), ]; let nodeName!: string; const ref = this.objectType<[ ], ObjectParam<SchemaTypes>>(param, { ...(options as {}), extensions: { ...extensions, pothosParseGlobalID: id.parse, }, isTypeOf: options.isTypeOf ?? (typeof param === "function" ? (maybeNode: unknown, context: object, info: GraphQLResolveInfo) => { if (!maybeNode) { return false; } if (maybeNode instanceof (param as Function)) { return true; } const proto = Object.getPrototypeOf(maybeNode) as { constructor: unknown; }; try { if (proto?.constructor) { const config = this.configStore.getTypeConfig(proto.constructor as OutputRef); return config.name === nodeName; } } catch { // ignore } return false; } : undefined), interfaces: interfacesWithNode as () => [ ], }, fields); this.configStore.onTypeConfig(ref, (nodeConfig) => { nodeName = nodeConfig.name; this.objectField(ref, this.options.relayOptions.idFieldName ?? "id", (t) => t.globalID<{}, false, MaybePromise<GlobalIDShape<SchemaTypes>>>({ nullable: false, ...this.options.relayOptions.idFieldOptions, ...id, args: {}, resolve: (parent, args, context, info): MaybePromise<GlobalIDShape<SchemaTypes>> => // eslint-disable-next-line @typescript-eslint/no-unsafe-argument completeValue(id.resolve(parent, args, context, info), (globalId) => ({ type: nodeConfig.name, id: globalId, })), })); }); const nodeRef = new NodeRef(ref.name, { parseId: id.parse, }); this.configStore.associateRefWithName(nodeRef, ref.name); return nodeRef as never;};schemaBuilderProto.globalConnectionField = function globalConnectionField(name, field) { this.globalConnectionFields((t) => ({ [name]: field(t) }));};schemaBuilderProto.globalConnectionFields = function globalConnectionFields(fields) { const onRef = (ref: ObjectRef<ConnectionShape<SchemaTypes, unknown, boolean>>) => { this.configStore.onPrepare(() => { const config = this.configStore.getTypeConfig(ref); this.objectFields(ref, (t) => { const existingFields = this.configStore.getFields(config.name); const refs: FieldMap = {}; for (const [name, field] of Object.entries(fields(t as never))) { if (!existingFields.has(name)) { refs[name] = field; } } return refs; }); }); }; connectionRefs.get(this)?.forEach((ref) => void onRef(ref)); if (!globalConnectionFieldsMap.has(this)) { globalConnectionFieldsMap.set(this, []); } globalConnectionFieldsMap.get(this)!.push(onRef);};const mutationIdCache = createContextCache(() => new Map<string, string>());schemaBuilderProto.relayMutationField = function relayMutationField(fieldName, inputOptionsOrRef, { resolve, args, ...fieldOptions }, { name: payloadName = `${capitalize(fieldName)}Payload`, outputFields, interfaces, ...payloadOptions }) { const { relayOptions: { clientMutationIdInputOptions = {} as never, clientMutationIdFieldOptions = {} as never, mutationInputArgOptions = {} as never, }, } = this.options; const includeClientMutationId = this.options.relayOptions.clientMutationId !== "omit"; let inputRef: InputObjectRef<unknown> | null; let argName = "input"; if (!inputOptionsOrRef || inputOptionsOrRef instanceof InputObjectRef) { inputRef = inputOptionsOrRef; } else { const { name: inputName = `${capitalize(fieldName)}Input`, argName: argNameFromOptions = "input", inputFields, ...inputOptions } = inputOptionsOrRef; argName = argNameFromOptions; inputRef = this.inputType(inputName, { ...this.options.relayOptions?.defaultMutationInputTypeOptions, ...inputOptions, fields: (t) => ({ ...inputFields(t), ...(includeClientMutationId ? { clientMutationId: t.id({ ...clientMutationIdInputOptions, required: this.options.relayOptions.clientMutationId !== "optional", }), } : {}), }), }); } const payloadRef = this.objectRef<unknown>(payloadName).implement({ ...this.options.relayOptions?.defaultPayloadTypeOptions, ...payloadOptions, interfaces: interfaces as never, fields: (t) => ({ ...(outputFields as ObjectFieldsShape<SchemaTypes, unknown>)(t), ...(includeClientMutationId ? { clientMutationId: t.id({ nullable: this.options.relayOptions.clientMutationId === "optional", ...clientMutationIdFieldOptions, resolve: (parent, _args, context, info) => mutationIdCache(context).get(String(info.path.prev!.key))!, }), } : {}), }), }); this.mutationField(fieldName, (t) => t.field({ ...(fieldOptions as {}), type: payloadRef, args: { ...args, ...(inputRef ? { [argName]: t.arg({ ...(mutationInputArgOptions as {}), type: inputRef, required: true, }), } : {}), }, resolve: (root, fieldArgs, context, info) => { if (inputRef) { mutationIdCache(context).set(String(info.path.key), (fieldArgs as unknown as Record<string, { clientMutationId: string; }>)[argName] .clientMutationId); } return resolve(root, fieldArgs as never, context, info); }, })); // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return { inputType: inputRef, payloadType: payloadRef, } as never;};schemaBuilderProto.connectionObject = function connectionObject({ type, name: connectionName, edgesNullable: edgesNullableField, nodeNullable, edgesField, ...connectionOptions }, edgeOptionsOrRef) { verifyRef(type); const { edgesFieldOptions: { nullable: edgesNullable = { items: true, list: false }, ...edgesFieldOptions } = {} as never, pageInfoFieldOptions = {} as never, } = this.options.relayOptions; const connectionRef = this.objectRef<ConnectionShape<SchemaTypes, unknown, false>>(connectionName); const edgeRef = edgeOptionsOrRef instanceof ObjectRef ? edgeOptionsOrRef : this.edgeObject({ name: `${connectionName.replace(/Connection$/, "")}Edge`, ...edgeOptionsOrRef, nodeNullable, type, }); const connectionFields = connectionOptions.fields as unknown as ObjectFieldsShape<SchemaTypes, ConnectionShape<SchemaTypes, unknown, false>> | undefined; const { nodesOnConnection } = this.options.relayOptions; const edgesNullableOption = edgesNullableField ?? edgesNullable; const edgeListNullable = typeof edgesNullableOption === "object" ? edgesNullableOption.list : !!edgesNullableOption; const edgeItemsNullable = typeof edgesNullableOption === "object" && "items" in (edgesNullableOption as {}) ? edgesNullableOption.items : false; this.objectType(connectionRef, { ...(this.options.relayOptions?.defaultConnectionTypeOptions as {}), ...(connectionOptions as {}), fields: (t) => ({ pageInfo: t.field({ nullable: false, ...pageInfoFieldOptions, type: this.pageInfoRef(), resolve: (parent) => parent.pageInfo, }), edges: t.field({ nullable: (edgesNullableField ?? edgesNullable) as { list: false; items: true; }, ...edgesFieldOptions, ...edgesField, type: [edgeRef], resolve: (parent) => parent.edges as [ ], }), ...(nodesOnConnection ? { nodes: t.field({ ...(typeof nodesOnConnection === "object" ? nodesOnConnection : {}), type: [type], nullable: { list: edgeListNullable, items: edgeItemsNullable ?? nodeNullable ?? this.options.relayOptions?.nodeFieldOptions?.nullable ?? false, }, resolve: (con) => completeValue(con.edges, (edges) => edges?.map((e) => e?.node) ?? (edgeListNullable ? null : [])) as never, }), } : {}), ...connectionFields?.(t as never), }), }); if (!connectionRefs.has(this)) { connectionRefs.set(this, []); } connectionRefs.get(this)!.push(connectionRef); globalConnectionFieldsMap.get(this)?.forEach((fieldFn) => void fieldFn(connectionRef)); return connectionRef as never;};schemaBuilderProto.edgeObject = function edgeObject({ type, name: edgeName, nodeNullable: nodeFieldNullable, nodeField, ...edgeOptions }) { verifyRef(type); const { cursorType = "String", cursorFieldOptions = {} as never, nodeFieldOptions: { nullable: nodeNullable = false, ...nodeFieldOptions } = {} as never, } = this.options.relayOptions; const edgeRef = this.objectRef<{ cursor: string; node: unknown; }>(edgeName); const edgeFields = edgeOptions.fields as ObjectFieldsShape<SchemaTypes, { cursor: string; node: unknown; }> | undefined; this.objectType(edgeRef, { ...(this.options.relayOptions?.defaultEdgeTypeOptions as {}), ...edgeOptions, fields: (t) => ({ node: t.field({ nullable: nodeFieldNullable ?? nodeNullable, ...nodeFieldOptions, ...nodeField, type, resolve: (parent) => parent.node as never, }), cursor: t.expose("cursor", { nullable: false, type: cursorType, ...cursorFieldOptions, }) as never, ...edgeFields?.(t), }), }); return edgeRef as never;};
Version Info