diff --git a/.changeset/eight-ligers-develop.md b/.changeset/eight-ligers-develop.md new file mode 100644 index 000000000..38bea0586 --- /dev/null +++ b/.changeset/eight-ligers-develop.md @@ -0,0 +1,6 @@ +--- +'@pothos/plugin-relay': patch +'@pothos/deno': patch +--- + +Fix a few issues with globalID parsing diff --git a/.changeset/kind-snails-pull.md b/.changeset/kind-snails-pull.md new file mode 100644 index 000000000..e29620a57 --- /dev/null +++ b/.changeset/kind-snails-pull.md @@ -0,0 +1,6 @@ +--- +'@pothos/plugin-dataloader': minor +'@pothos/deno': minor +--- + +Add default isTypeOf for loadableNode diff --git a/.changeset/tricky-suns-bake.md b/.changeset/tricky-suns-bake.md new file mode 100644 index 000000000..4d908efc9 --- /dev/null +++ b/.changeset/tricky-suns-bake.md @@ -0,0 +1,5 @@ +--- +'@pothos/plugin-prisma': patch +--- + +Fix name option for prismaNode diff --git a/examples/complex-app/CHANGELOG.md b/examples/complex-app/CHANGELOG.md index b0f57de68..9bb1f6bac 100644 --- a/examples/complex-app/CHANGELOG.md +++ b/examples/complex-app/CHANGELOG.md @@ -1,5 +1,15 @@ # @pothos-examples/complex-app +## 1.4.28 + +### Patch Changes + +- Updated dependencies [d60cb49e] + - @pothos/plugin-relay@3.36.0 + - @pothos/plugin-dataloader@3.14.0 + - @pothos/plugin-prisma@3.41.0 + - @pothos/plugin-scope-auth@3.18.0 + ## 1.4.27 ### Patch Changes diff --git a/examples/complex-app/package.json b/examples/complex-app/package.json index 27018adbc..1b3927231 100644 --- a/examples/complex-app/package.json +++ b/examples/complex-app/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.4.27", + "version": "1.4.28", "name": "@pothos-examples/complex-app", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/envelope-helix-fastify/CHANGELOG.md b/examples/envelope-helix-fastify/CHANGELOG.md index 70d4f9cd7..0aa2368c9 100644 --- a/examples/envelope-helix-fastify/CHANGELOG.md +++ b/examples/envelope-helix-fastify/CHANGELOG.md @@ -1,5 +1,12 @@ # @pothos-examples/envelope-helix-fastify +## 2.4.20 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + ## 2.4.19 ### Patch Changes diff --git a/examples/envelope-helix-fastify/package.json b/examples/envelope-helix-fastify/package.json index cc3c7ce7a..fc28876b0 100644 --- a/examples/envelope-helix-fastify/package.json +++ b/examples/envelope-helix-fastify/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.4.19", + "version": "2.4.20", "name": "@pothos-examples/envelope-helix-fastify", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/graphql-shield/CHANGELOG.md b/examples/graphql-shield/CHANGELOG.md index c037daf4d..c686aef98 100644 --- a/examples/graphql-shield/CHANGELOG.md +++ b/examples/graphql-shield/CHANGELOG.md @@ -1,5 +1,12 @@ # @pothos-examples/graphql-shield +## 2.2.20 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + ## 2.2.19 ### Patch Changes diff --git a/examples/graphql-shield/package.json b/examples/graphql-shield/package.json index b82da67b9..959265210 100644 --- a/examples/graphql-shield/package.json +++ b/examples/graphql-shield/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.2.19", + "version": "2.2.20", "name": "@pothos-examples/graphql-shield", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/helix/CHANGELOG.md b/examples/helix/CHANGELOG.md index 0445a4f8e..7f5ba47c7 100644 --- a/examples/helix/CHANGELOG.md +++ b/examples/helix/CHANGELOG.md @@ -1,5 +1,12 @@ # @pothos-examples/helix +## 2.4.20 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + ## 2.4.19 ### Patch Changes diff --git a/examples/helix/package.json b/examples/helix/package.json index 31fb5f77a..232aa459b 100644 --- a/examples/helix/package.json +++ b/examples/helix/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.4.19", + "version": "2.4.20", "name": "@pothos-examples/helix", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/nextjs/CHANGELOG.md b/examples/nextjs/CHANGELOG.md index 5ad10fd6d..3c86a9052 100644 --- a/examples/nextjs/CHANGELOG.md +++ b/examples/nextjs/CHANGELOG.md @@ -1,5 +1,12 @@ # @pothos-examples/nextjs +## 2.6.3 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + ## 2.6.2 ### Patch Changes diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 33511e36a..ff9fdba42 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@pothos-examples/nextjs", - "version": "2.6.2", + "version": "2.6.3", "private": true, "scripts": { "generate": "graphql-codegen", diff --git a/examples/open-telemetry/CHANGELOG.md b/examples/open-telemetry/CHANGELOG.md index c99860f3d..5a58ac021 100644 --- a/examples/open-telemetry/CHANGELOG.md +++ b/examples/open-telemetry/CHANGELOG.md @@ -1,5 +1,14 @@ # @pothos-examples/open-telemetry +## 1.2.20 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + - @pothos/plugin-tracing@0.5.7 + - @pothos/tracing-opentelemetry@0.6.8 + ## 1.2.19 ### Patch Changes diff --git a/examples/open-telemetry/package.json b/examples/open-telemetry/package.json index fd0bed17b..cccf0bc76 100644 --- a/examples/open-telemetry/package.json +++ b/examples/open-telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@pothos-examples/open-telemetry", - "version": "1.2.19", + "version": "1.2.20", "main": "index.js", "license": "MIT", "dependencies": { diff --git a/examples/prisma-federation/CHANGELOG.md b/examples/prisma-federation/CHANGELOG.md index 08a6f1c32..875f055d2 100644 --- a/examples/prisma-federation/CHANGELOG.md +++ b/examples/prisma-federation/CHANGELOG.md @@ -1,5 +1,15 @@ # @pothos-examples/relay +## 2.7.4 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + - @pothos/plugin-directives@3.9.2 + - @pothos/plugin-federation@3.9.1 + - @pothos/plugin-prisma@3.41.0 + ## 2.7.3 ### Patch Changes diff --git a/examples/prisma-federation/package.json b/examples/prisma-federation/package.json index dea97a8eb..0454fe78c 100644 --- a/examples/prisma-federation/package.json +++ b/examples/prisma-federation/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.7.3", + "version": "2.7.4", "name": "@pothos-examples/prisma-federation", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/prisma-relay/CHANGELOG.md b/examples/prisma-relay/CHANGELOG.md index 55e67f0eb..c8e2f2e65 100644 --- a/examples/prisma-relay/CHANGELOG.md +++ b/examples/prisma-relay/CHANGELOG.md @@ -1,5 +1,13 @@ # @pothos-examples/prisma-relay +## 2.7.26 + +### Patch Changes + +- Updated dependencies [d60cb49e] + - @pothos/plugin-relay@3.36.0 + - @pothos/plugin-prisma@3.41.0 + ## 2.7.25 ### Patch Changes diff --git a/examples/prisma-relay/package.json b/examples/prisma-relay/package.json index 0f003c272..f09cc86d5 100644 --- a/examples/prisma-relay/package.json +++ b/examples/prisma-relay/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.7.25", + "version": "2.7.26", "name": "@pothos-examples/prisma-relay", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/prisma-smart-subscriptions-apollo/CHANGELOG.md b/examples/prisma-smart-subscriptions-apollo/CHANGELOG.md index 4299b1fcd..20cda5570 100644 --- a/examples/prisma-smart-subscriptions-apollo/CHANGELOG.md +++ b/examples/prisma-smart-subscriptions-apollo/CHANGELOG.md @@ -1,5 +1,14 @@ # @pothos-examples/prisma-smart-subscriptions-apollo +## 2.5.3 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + - @pothos/plugin-prisma@3.41.0 + - @pothos/plugin-smart-subscriptions@3.7.0 + ## 2.5.2 ### Patch Changes diff --git a/examples/prisma-smart-subscriptions-apollo/package.json b/examples/prisma-smart-subscriptions-apollo/package.json index 821c2acb6..119e18e7b 100644 --- a/examples/prisma-smart-subscriptions-apollo/package.json +++ b/examples/prisma-smart-subscriptions-apollo/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.5.2", + "version": "2.5.3", "name": "@pothos-examples/prisma-smart-subscriptions-apollo", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/prisma-subscriptions/CHANGELOG.md b/examples/prisma-subscriptions/CHANGELOG.md index 3b0209e07..977631aa1 100644 --- a/examples/prisma-subscriptions/CHANGELOG.md +++ b/examples/prisma-subscriptions/CHANGELOG.md @@ -1,5 +1,13 @@ # @pothos-examples/prisma-subscriptions +## 2.4.25 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + - @pothos/plugin-prisma@3.41.0 + ## 2.4.24 ### Patch Changes diff --git a/examples/prisma-subscriptions/package.json b/examples/prisma-subscriptions/package.json index be371ac75..248d38c7b 100644 --- a/examples/prisma-subscriptions/package.json +++ b/examples/prisma-subscriptions/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.4.24", + "version": "2.4.25", "name": "@pothos-examples/prisma-subscriptions", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/prisma/CHANGELOG.md b/examples/prisma/CHANGELOG.md index d0732aaae..29d3973d1 100644 --- a/examples/prisma/CHANGELOG.md +++ b/examples/prisma/CHANGELOG.md @@ -1,5 +1,13 @@ # @pothos-examples/relay +## 2.4.25 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + - @pothos/plugin-prisma@3.41.0 + ## 2.4.24 ### Patch Changes diff --git a/examples/prisma/package.json b/examples/prisma/package.json index 2ccd46d28..975a72743 100644 --- a/examples/prisma/package.json +++ b/examples/prisma/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.4.24", + "version": "2.4.25", "name": "@pothos-examples/prisma", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/relay-windowed-pagination/CHANGELOG.md b/examples/relay-windowed-pagination/CHANGELOG.md index 9a38659d2..68cdc6e27 100644 --- a/examples/relay-windowed-pagination/CHANGELOG.md +++ b/examples/relay-windowed-pagination/CHANGELOG.md @@ -1,5 +1,20 @@ # @pothos-examples/prisma-relay +## 2.7.26 + +### Patch Changes + +- Updated dependencies [d60cb49e] + - @pothos/plugin-relay@3.36.0 + +## 2.7.25 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + - @pothos/plugin-relay@3.35.0 + ## 2.7.24 ### Patch Changes diff --git a/examples/relay-windowed-pagination/package.json b/examples/relay-windowed-pagination/package.json index 2664722c6..f1bae9ffe 100644 --- a/examples/relay-windowed-pagination/package.json +++ b/examples/relay-windowed-pagination/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.7.24", + "version": "2.7.26", "name": "@pothos-examples/relay-windowed-pagination", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/simple-classes/CHANGELOG.md b/examples/simple-classes/CHANGELOG.md index 75302e8ca..195ae639a 100644 --- a/examples/simple-classes/CHANGELOG.md +++ b/examples/simple-classes/CHANGELOG.md @@ -1,5 +1,12 @@ # @pothos-examples/simple-classes +## 2.4.20 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + ## 2.4.19 ### Patch Changes diff --git a/examples/simple-classes/package.json b/examples/simple-classes/package.json index 7ac533ca6..2de2a0c6a 100644 --- a/examples/simple-classes/package.json +++ b/examples/simple-classes/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.4.19", + "version": "2.4.20", "name": "@pothos-examples/simple-classes", "main": "src/index.ts", "types": "src/index.ts", diff --git a/examples/simple-interfaces/CHANGELOG.md b/examples/simple-interfaces/CHANGELOG.md index d0a294170..3134677af 100644 --- a/examples/simple-interfaces/CHANGELOG.md +++ b/examples/simple-interfaces/CHANGELOG.md @@ -1,5 +1,12 @@ # @pothos-examples/simple-interfaces +## 2.4.20 + +### Patch Changes + +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + ## 2.4.19 ### Patch Changes diff --git a/examples/simple-interfaces/package.json b/examples/simple-interfaces/package.json index de09047b5..b1caca094 100644 --- a/examples/simple-interfaces/package.json +++ b/examples/simple-interfaces/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "2.4.19", + "version": "2.4.20", "name": "@pothos-examples/simple-interfaces", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 3d74353f9..bef60fb9b 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 3.27.0 + +### Minor Changes + +- 42bf6190: Allow unionType to receive types as a thunk + ## 3.26.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index b1fb6b2f9..c554fdb9a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@pothos/core", - "version": "3.26.0", + "version": "3.27.0", "description": "Pothos (formerly GiraphQL) is a plugin based schema builder for creating code-first GraphQL schemas in typescript", "main": "./lib/index.js", "types": "./dts/index.d.ts", diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts index 8ebe7bfae..46ea21d27 100644 --- a/packages/core/src/builder.ts +++ b/packages/core/src/builder.ts @@ -423,15 +423,17 @@ export default class SchemaBuilder { ParentShape >(name); - options.types.forEach((type) => { - verifyRef(type); - }); + if (Array.isArray(options.types)) { + options.types.forEach((type) => { + verifyRef(type); + }); + } const config: PothosUnionTypeConfig = { kind: 'Union', graphqlKind: 'Union', name, - types: (options.types || []) as ObjectParam[], + types: [], description: options.description, resolveType: options.resolveType as GraphQLTypeResolver, pothosOptions: options as unknown as PothosSchemaTypes.UnionTypeOptions, @@ -439,6 +441,7 @@ export default class SchemaBuilder { }; this.configStore.addTypeConfig(config, ref); + this.configStore.addUnionTypes(name, options.types); return ref; } diff --git a/packages/core/src/config-store.ts b/packages/core/src/config-store.ts index ef7bb0a66..6e2210812 100644 --- a/packages/core/src/config-store.ts +++ b/packages/core/src/config-store.ts @@ -26,6 +26,7 @@ import type { InputType, InputTypeParam, InterfaceParam, + ObjectParam, OutputRef, OutputType, PothosFieldConfig, @@ -62,6 +63,8 @@ export default class ConfigStore { private pendingInterfaces = new Map InterfaceParam[])[]>(); + private pendingUnionTypes = new Map ObjectParam[])[]>(); + private pendingRefResolutions = new Map< ConfigurableRef, ((config: PothosTypeConfig) => void)[] @@ -98,6 +101,31 @@ export default class ConfigStore { return this.refsToName.has(typeParam); } + addUnionTypes(typeName: string, unionTypes: ObjectParam[] | (() => ObjectParam[])) { + if (typeof unionTypes === 'function' && this.pending) { + if (this.pendingUnionTypes.has(typeName)) { + this.pendingUnionTypes.get(typeName)!.push(unionTypes); + } else { + this.pendingUnionTypes.set(typeName, [unionTypes]); + } + } else { + const typeConfig = this.getTypeConfig(typeName); + + if (typeConfig.graphqlKind !== 'Union') { + throw new PothosSchemaError( + `Can not add types to ${typeName} because it is a ${typeConfig.kind}`, + ); + } + + typeConfig.types = [ + ...typeConfig.types, + ...((typeof unionTypes === 'function' + ? unionTypes() + : unionTypes) as ObjectParam[]), + ]; + } + } + addInterfaces( typeName: string, interfaces: InterfaceParam[] | (() => InterfaceParam[]), @@ -428,6 +456,12 @@ export default class ConfigStore { ); } + for (const [typeName, unionFns] of this.pendingUnionTypes) { + for (const fn of unionFns) { + this.addUnionTypes(typeName, fn); + } + } + for (const [typeName, interfacesFns] of this.pendingInterfaces) { for (const fn of interfacesFns) { this.addInterfaces(typeName, fn); diff --git a/packages/core/src/types/global/type-options.ts b/packages/core/src/types/global/type-options.ts index bb6818ed9..4d3342caa 100644 --- a/packages/core/src/types/global/type-options.ts +++ b/packages/core/src/types/global/type-options.ts @@ -98,7 +98,7 @@ declare global { Member extends ObjectParam = ObjectParam, ResolveType = unknown, > extends BaseTypeOptions { - types: Member[]; + types: Member[] | (() => Member[]); resolveType?: ResolveType & (( parent: ParentShape, diff --git a/packages/core/tests/examples/giraffes/unions.ts b/packages/core/tests/examples/giraffes/unions.ts index e42b191f1..774acdfb9 100644 --- a/packages/core/tests/examples/giraffes/unions.ts +++ b/packages/core/tests/examples/giraffes/unions.ts @@ -14,7 +14,7 @@ const GiraffeNumericFact = builder.objectType('GiraffeNumericFact', { }); const GiraffeFact = builder.unionType('GiraffeFact', { - types: ['GiraffeStringFact', GiraffeNumericFact], + types: () => ['GiraffeStringFact', GiraffeNumericFact], resolveType: (fact) => { switch (fact.factKind) { case 'number': diff --git a/packages/deno/CHANGELOG.md b/packages/deno/CHANGELOG.md index 898f52621..7d8946ecd 100644 --- a/packages/deno/CHANGELOG.md +++ b/packages/deno/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 3.38.0 + +### Minor Changes + +- 42bf6190: Allow unionType to receive types as a thunk + ## 3.37.0 ### Minor Changes diff --git a/packages/deno/package.json b/packages/deno/package.json index 00d77495f..08ce2925f 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -1,6 +1,6 @@ { "name": "@pothos/deno", - "version": "3.37.0", + "version": "3.38.0", "description": "Deno compatible versions of Pothos packages", "repository": { "type": "git", diff --git a/packages/deno/packages/core/builder.ts b/packages/deno/packages/core/builder.ts index ead29f850..ad6dadef7 100644 --- a/packages/deno/packages/core/builder.ts +++ b/packages/deno/packages/core/builder.ts @@ -248,20 +248,23 @@ export default class SchemaBuilder { } unionType, ResolveType>(name: string, options: PothosSchemaTypes.UnionTypeOptions) { const ref = new UnionRef, ParentShape>(name); - options.types.forEach((type) => { - verifyRef(type); - }); + if (Array.isArray(options.types)) { + options.types.forEach((type) => { + verifyRef(type); + }); + } const config: PothosUnionTypeConfig = { kind: "Union", graphqlKind: "Union", name, - types: (options.types || []) as ObjectParam[], + types: [], description: options.description, resolveType: options.resolveType as GraphQLTypeResolver, pothosOptions: options as unknown as PothosSchemaTypes.UnionTypeOptions, extensions: options.extensions, }; this.configStore.addTypeConfig(config, ref); + this.configStore.addUnionTypes(name, options.types); return ref; } enumType>(param: Param, options: EnumTypeOptions) { diff --git a/packages/deno/packages/core/config-store.ts b/packages/deno/packages/core/config-store.ts index 29bb80f3a..452c6a3a4 100644 --- a/packages/deno/packages/core/config-store.ts +++ b/packages/deno/packages/core/config-store.ts @@ -11,7 +11,7 @@ import InputFieldRef from './refs/input-field.ts'; import InputListRef from './refs/input-list.ts'; import ListRef from './refs/list.ts'; import OutputTypeRef from './refs/output.ts'; -import type { ConfigurableRef, FieldMap, GraphQLFieldKind, InputFieldMap, InputRef, InputType, InputTypeParam, InterfaceParam, OutputRef, OutputType, PothosFieldConfig, PothosObjectTypeConfig, PothosTypeConfig, SchemaTypes, TypeParam, } from './types/index.ts'; +import type { ConfigurableRef, FieldMap, GraphQLFieldKind, InputFieldMap, InputRef, InputType, InputTypeParam, InterfaceParam, ObjectParam, OutputRef, OutputType, PothosFieldConfig, PothosObjectTypeConfig, PothosTypeConfig, SchemaTypes, TypeParam, } from './types/index.ts'; import { unwrapListParam } from './utils/index.ts'; export default class ConfigStore { typeConfigs = new Map(); @@ -23,6 +23,7 @@ export default class ConfigStore { private fieldRefsToConfigs = new Map[]>(); private pendingFields = new Map | OutputType>(); private pendingInterfaces = new Map InterfaceParam[])[]>(); + private pendingUnionTypes = new Map ObjectParam[])[]>(); private pendingRefResolutions = new Map, ((config: PothosTypeConfig) => void)[]>(); private fieldRefCallbacks = new Map) => void)[]>(); private pending = true; @@ -46,6 +47,28 @@ export default class ConfigStore { } return this.refsToName.has(typeParam); } + addUnionTypes(typeName: string, unionTypes: ObjectParam[] | (() => ObjectParam[])) { + if (typeof unionTypes === "function" && this.pending) { + if (this.pendingUnionTypes.has(typeName)) { + this.pendingUnionTypes.get(typeName)!.push(unionTypes); + } + else { + this.pendingUnionTypes.set(typeName, [unionTypes]); + } + } + else { + const typeConfig = this.getTypeConfig(typeName); + if (typeConfig.graphqlKind !== "Union") { + throw new PothosSchemaError(`Can not add types to ${typeName} because it is a ${typeConfig.kind}`); + } + typeConfig.types = [ + ...typeConfig.types, + ...((typeof unionTypes === "function" + ? unionTypes() + : unionTypes) as ObjectParam[]), + ]; + } + } addInterfaces(typeName: string, interfaces: InterfaceParam[] | (() => InterfaceParam[])) { if (typeof interfaces === "function" && this.pending) { if (this.pendingInterfaces.has(typeName)) { @@ -280,6 +303,11 @@ export default class ConfigStore { .map((ref) => this.describeRef(ref)) .join(", ")}).`); } + for (const [typeName, unionFns] of this.pendingUnionTypes) { + for (const fn of unionFns) { + this.addUnionTypes(typeName, fn); + } + } for (const [typeName, interfacesFns] of this.pendingInterfaces) { for (const fn of interfacesFns) { this.addInterfaces(typeName, fn); diff --git a/packages/deno/packages/core/types/global/type-options.ts b/packages/deno/packages/core/types/global/type-options.ts index c796aac2b..fc4f1c10f 100644 --- a/packages/deno/packages/core/types/global/type-options.ts +++ b/packages/deno/packages/core/types/global/type-options.ts @@ -42,7 +42,7 @@ declare global { resolveType?: ResolveType & ((parent: Shape, context: Types["Context"], info: GraphQLResolveInfo, type: GraphQLUnionType) => MaybePromise | string | null | undefined>); } export interface UnionTypeOptions = ObjectParam, ResolveType = unknown> extends BaseTypeOptions { - types: Member[]; + types: Member[] | (() => Member[]); resolveType?: ResolveType & ((parent: ParentShape, context: Types["Context"], info: GraphQLResolveInfo, type: GraphQLUnionType) => MaybePromise); } export interface ScalarTypeOptions extends BaseTypeOptions { diff --git a/packages/deno/packages/plugin-dataloader/README.md b/packages/deno/packages/plugin-dataloader/README.md index 64254bc85..c7836ee40 100644 --- a/packages/deno/packages/plugin-dataloader/README.md +++ b/packages/deno/packages/plugin-dataloader/README.md @@ -306,7 +306,7 @@ const UserNode = builder.loadableNode('UserNode', { id: { resolve: (user) => user.id, }, - // For loadable objects we always need to include an isTypeOf check + // For loadable nodes, we need to include an isTypeOf check if the first arg is a string isTypeOf: (obj) => obj instanceof User, load: (ids: string[], context: ContextType) => context.loadUsersById(ids), fields: (t) => ({}), diff --git a/packages/deno/packages/plugin-dataloader/global-types.ts b/packages/deno/packages/plugin-dataloader/global-types.ts index f5610e40c..75b73e211 100644 --- a/packages/deno/packages/plugin-dataloader/global-types.ts +++ b/packages/deno/packages/plugin-dataloader/global-types.ts @@ -16,9 +16,9 @@ declare global { loadableInterface: ? ShapeFromTypeParam : object, Key extends bigint | number | string, Interfaces extends InterfaceParam[], NameOrRef extends InterfaceParam | string, CacheKey = Key>(nameOrRef: NameOrRef, options: LoadableInterfaceOptions) => LoadableInterfaceRef; loadableObjectRef: (name: string, options: DataLoaderOptions) => ImplementableLoadableObjectRef; loadableInterfaceRef: (name: string, options: DataLoaderOptions) => ImplementableLoadableInterfaceRef; - loadableNodeRef: (name: string, options: DataLoaderOptions & LoadableNodeId) => ImplementableLoadableNodeRef; + loadableNodeRef: (name: string, options: DataLoaderOptions & LoadableNodeId) => ImplementableLoadableNodeRef; loadableUnion: , CacheKey = Key, Shape = ShapeFromTypeParam>(name: string, options: LoadableUnionOptions) => LoadableUnionRef; - loadableNode: "relay" extends PluginName ? ? ShapeFromTypeParam : object, Key extends bigint | number | string, Interfaces extends InterfaceParam[], NameOrRef extends ObjectParam | string, CacheKey = Key>(nameOrRef: NameOrRef, options: LoadableNodeOptions) => Omit, "implement"> : "@pothos/plugin-relay is required to use this method"; + loadableNode: "relay" extends PluginName ? ? ShapeFromTypeParam : object, Interfaces extends InterfaceParam[], NameOrRef extends ObjectParam | string, IDShape extends bigint | number | string = string, Key extends bigint | number | string = IDShape, CacheKey = Key>(nameOrRef: NameOrRef, options: LoadableNodeOptions) => Omit, "implement"> : "@pothos/plugin-relay is required to use this method"; } export interface RootFieldBuilder { loadable: , Key, CacheKey, ResolveReturnShape, Nullable extends FieldNullability = Types["DefaultFieldNullability"]>(options: LoadableFieldOptions) => FieldRef; diff --git a/packages/deno/packages/plugin-dataloader/refs/node.ts b/packages/deno/packages/plugin-dataloader/refs/node.ts index fb7ea72a8..b1678342b 100644 --- a/packages/deno/packages/plugin-dataloader/refs/node.ts +++ b/packages/deno/packages/plugin-dataloader/refs/node.ts @@ -3,10 +3,10 @@ import { GraphQLResolveInfo } from 'https://cdn.skypack.dev/graphql?dts'; import { FieldRef, InterfaceRef, PothosObjectTypeConfig, SchemaTypes } from '../../core/index.ts'; import { DataLoaderOptions, LoadableNodeId } from '../types.ts'; import { ImplementableLoadableObjectRef } from './object.ts'; -export class ImplementableLoadableNodeRef extends ImplementableLoadableObjectRef { - parseId: ((id: string, ctx: object) => Key) | undefined; +export class ImplementableLoadableNodeRef extends ImplementableLoadableObjectRef { + parseId: ((id: string, ctx: object) => IDShape) | undefined; private idOptions; - constructor(builder: PothosSchemaTypes.SchemaBuilder, name: string, { id, ...options }: DataLoaderOptions & LoadableNodeId) { + constructor(builder: PothosSchemaTypes.SchemaBuilder, name: string, { id, ...options }: DataLoaderOptions & LoadableNodeId) { super(builder, name, options); this.idOptions = id; this.parseId = id.parse; diff --git a/packages/deno/packages/plugin-dataloader/schema-builder.ts b/packages/deno/packages/plugin-dataloader/schema-builder.ts index eb6449e3f..fe34a1233 100644 --- a/packages/deno/packages/plugin-dataloader/schema-builder.ts +++ b/packages/deno/packages/plugin-dataloader/schema-builder.ts @@ -1,5 +1,6 @@ // @ts-nocheck -import SchemaBuilder, { InterfaceParam, ObjectParam, PothosSchemaError, SchemaTypes, ShapeFromTypeParam, } from '../core/index.ts'; +import type { GraphQLResolveInfo } from 'https://cdn.skypack.dev/graphql?dts'; +import SchemaBuilder, { InterfaceParam, ObjectParam, OutputRef, PothosSchemaError, SchemaTypes, ShapeFromTypeParam, } from '../core/index.ts'; import { ImplementableLoadableNodeRef } from './refs/index.ts'; import { ImplementableLoadableInterfaceRef } from './refs/interface.ts'; import { ImplementableLoadableObjectRef } from './refs/object.ts'; @@ -61,7 +62,7 @@ schemaBuilderProto.loadableUnion = function loadableUnion ? ShapeFromTypeParam : object, Key extends DataloaderKey, Interfaces extends InterfaceParam[], NameOrRef extends ObjectParam | string, CacheKey = Key>(this: PothosSchemaTypes.SchemaBuilder, nameOrRef: NameOrRef, options: LoadableNodeOptions) { +schemaBuilderProto.loadableNode = function loadableNode ? ShapeFromTypeParam : object, Interfaces extends InterfaceParam[], NameOrRef extends ObjectParam | string, IDShape extends bigint | number | string = string, Key extends bigint | number | string = IDShape, CacheKey = Key>(this: PothosSchemaTypes.SchemaBuilder, nameOrRef: NameOrRef, options: LoadableNodeOptions) { if (typeof (this as PothosSchemaTypes.SchemaBuilder & Record) .nodeInterfaceRef !== "function") { throw new PothosSchemaError("builder.loadableNode requires @pothos/plugin-relay to be installed"); @@ -73,13 +74,37 @@ schemaBuilderProto.loadableNode = function loadableNode(this, name, options); + const ref = new ImplementableLoadableNodeRef(this, name, options); ref.implement({ ...options, extensions: { ...options.extensions, pothosParseGlobalID: options.id.parse, }, + isTypeOf: options.isTypeOf ?? + (typeof nameOrRef === "function" + ? (maybeNode: unknown, context: object, info: GraphQLResolveInfo) => { + if (!maybeNode) { + return false; + } + if (maybeNode instanceof (nameOrRef 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 === name; + } + } + catch { + // ignore + } + return false; + } + : undefined), }); if (typeof nameOrRef !== "string") { this.configStore.associateRefWithName(nameOrRef, name); diff --git a/packages/deno/packages/plugin-dataloader/types.ts b/packages/deno/packages/plugin-dataloader/types.ts index 5b2d70b6d..40d585717 100644 --- a/packages/deno/packages/plugin-dataloader/types.ts +++ b/packages/deno/packages/plugin-dataloader/types.ts @@ -55,4 +55,4 @@ export interface LoadableNodeId IDShape; }; } -export type LoadableNodeOptions[], NameOrRef extends ObjectParam | string, CacheKey> = DataloaderObjectTypeOptions & LoadableNodeId; +export type LoadableNodeOptions[], NameOrRef extends ObjectParam | string, IDShape extends bigint | number | string = string, Key extends bigint | number | string = IDShape, CacheKey = Key> = DataloaderObjectTypeOptions & LoadableNodeId; diff --git a/packages/deno/packages/plugin-relay/index.ts b/packages/deno/packages/plugin-relay/index.ts index cfb2cd8b0..19b501c50 100644 --- a/packages/deno/packages/plugin-relay/index.ts +++ b/packages/deno/packages/plugin-relay/index.ts @@ -15,7 +15,9 @@ export class PothosRelayPlugin extends BasePlugin, fieldConfig: PothosOutputFieldConfig): GraphQLFieldResolver { const argMappings = mapInputFields(fieldConfig.args, this.buildCache, (inputField) => { if (inputField.extensions?.isRelayGlobalID) { - return (inputField.extensions?.relayGlobalIDFor ?? true) as true | { + return (inputField.extensions?.relayGlobalIDFor ?? + inputField.extensions?.relayGlobalIDAlwaysParse ?? + false) as boolean | { typename: string; parseId: (id: string, ctx: object) => unknown; }[]; @@ -25,7 +27,7 @@ export class PothosRelayPlugin extends BasePlugin internalDecodeGlobalID(this.builder, String(globalID), ctx, info, Array.isArray(mappings.value) ? mappings.value : true)); + const argMapper = createInputValueMapper(argMappings, (globalID, mappings, ctx: Types["Context"], info: GraphQLResolveInfo) => internalDecodeGlobalID(this.builder, String(globalID), ctx, info, mappings.value ?? false)); return (parent, args, context, info) => resolver(parent, argMapper(args, undefined, context, info), context, info); } override wrapSubscribe(subscribe: GraphQLFieldResolver | undefined, fieldConfig: PothosOutputFieldConfig): GraphQLFieldResolver | undefined { diff --git a/packages/deno/packages/plugin-relay/schema-builder.ts b/packages/deno/packages/plugin-relay/schema-builder.ts index 57230920b..4d0aa554b 100644 --- a/packages/deno/packages/plugin-relay/schema-builder.ts +++ b/packages/deno/packages/plugin-relay/schema-builder.ts @@ -101,7 +101,12 @@ schemaBuilderProto.nodeInterfaceRef = function nodeInterfaceRef() { ...this.options.relayOptions.nodeQueryOptions, type: ref as InterfaceRef, args: { - id: t.arg.globalID({ required: true }), + id: t.arg.globalID({ + required: true, + extensions: { + relayGlobalIDAlwaysParse: true, + }, + }), }, resolve: resolveNodeFn ? (root, args, context, info) => resolveNodeFn(root, args as { @@ -134,7 +139,12 @@ schemaBuilderProto.nodeInterfaceRef = function nodeInterfaceRef() { ...this.options.relayOptions.nodesQueryOptions, type: [ref], args: { - ids: t.arg.globalIDList({ required: true }), + ids: t.arg.globalIDList({ + required: true, + extensions: { + relayGlobalIDAlwaysParse: true, + }, + }), }, resolve: resolveNodesFn ? (root, args, context, info) => resolveNodesFn(root, args as { diff --git a/packages/deno/packages/plugin-relay/utils/global-ids.ts b/packages/deno/packages/plugin-relay/utils/global-ids.ts index 43643d0fc..58c62755b 100644 --- a/packages/deno/packages/plugin-relay/utils/global-ids.ts +++ b/packages/deno/packages/plugin-relay/utils/global-ids.ts @@ -4,9 +4,10 @@ export function encodeGlobalID(typename: string, id: bigint | number | string) { return encodeBase64(`${typename}:${id}`); } export function decodeGlobalID(globalID: string) { - const [typename, id] = decodeBase64(globalID).split(":"); + const decoded = decodeBase64(globalID).split(":"); + const [typename, id] = decoded; if (!typename || !id) { throw new PothosValidationError(`Invalid global ID: ${globalID}`); } - return { typename, id }; + return { typename, id: decoded.length > 2 ? decoded.slice(1).join(":") : id }; } diff --git a/packages/plugin-dataloader/README.md b/packages/plugin-dataloader/README.md index 64254bc85..c7836ee40 100644 --- a/packages/plugin-dataloader/README.md +++ b/packages/plugin-dataloader/README.md @@ -306,7 +306,7 @@ const UserNode = builder.loadableNode('UserNode', { id: { resolve: (user) => user.id, }, - // For loadable objects we always need to include an isTypeOf check + // For loadable nodes, we need to include an isTypeOf check if the first arg is a string isTypeOf: (obj) => obj instanceof User, load: (ids: string[], context: ContextType) => context.loadUsersById(ids), fields: (t) => ({}), diff --git a/packages/plugin-dataloader/src/schema-builder.ts b/packages/plugin-dataloader/src/schema-builder.ts index 0a67f9584..23c711313 100644 --- a/packages/plugin-dataloader/src/schema-builder.ts +++ b/packages/plugin-dataloader/src/schema-builder.ts @@ -1,6 +1,8 @@ +import type { GraphQLResolveInfo } from 'graphql'; import SchemaBuilder, { InterfaceParam, ObjectParam, + OutputRef, PothosSchemaError, SchemaTypes, ShapeFromTypeParam, @@ -166,6 +168,33 @@ schemaBuilderProto.loadableNode = function loadableNode< ...options.extensions, pothosParseGlobalID: options.id.parse, }, + isTypeOf: + options.isTypeOf ?? + (typeof nameOrRef === 'function' + ? (maybeNode: unknown, context: object, info: GraphQLResolveInfo) => { + if (!maybeNode) { + return false; + } + + if (maybeNode instanceof (nameOrRef 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 === name; + } + } catch { + // ignore + } + + return false; + } + : undefined), }); if (typeof nameOrRef !== 'string') { diff --git a/packages/plugin-dataloader/tests/__snapshots__/index.test.ts.snap b/packages/plugin-dataloader/tests/__snapshots__/index.test.ts.snap index f59ed595f..95502cb9b 100644 --- a/packages/plugin-dataloader/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-dataloader/tests/__snapshots__/index.test.ts.snap @@ -351,8 +351,12 @@ exports[`dataloader > queries > valid queries 1`] = ` "classThing": { "id": "Q2xhc3NMb2FkYWJsZVRoaW5nOjEyMw==", }, + "classThingNode": { + "__typename": "ClassLoadableThing", + "id": "Q2xhc3NMb2FkYWJsZVRoaW5nOjE=", + }, "classThingRef": { - "id": "Q2xhc3NMb2FkYWJsZVRoaW5nOjEyMw==", + "id": "Q2xhc3NMb2FkYWJsZVRoaW5nOjE=", }, "counts": [ { diff --git a/packages/plugin-dataloader/tests/example/schema/nodes.ts b/packages/plugin-dataloader/tests/example/schema/nodes.ts index b0c153027..e4ef9393b 100644 --- a/packages/plugin-dataloader/tests/example/schema/nodes.ts +++ b/packages/plugin-dataloader/tests/example/schema/nodes.ts @@ -11,21 +11,25 @@ const UserNode = builder.loadableNodeRef('UserNode', { load: (keys: string[], context: ContextType) => { countCall(context, userNodeCounts, keys.length); return Promise.resolve( - keys.map((id) => (Number(id) > 0 ? { id: Number(id) } : new Error(`Invalid ID ${id}`))), + keys.map((id) => + Number(id) > 0 ? { objType: 'UserNode', id: Number(id) } : new Error(`Invalid ID ${id}`), + ), ); }, }); builder.objectType(UserNode, { interfaces: [TestInterface], - isTypeOf: (obj) => - typeof obj === 'object' && obj !== null && Object.prototype.hasOwnProperty.call(obj, 'id'), + isTypeOf: (obj) => (obj as any).objType === 'UserNode', fields: (t) => ({}), }); class ClassThing { - id: number = 123; + id: number; name: string = 'some name'; + constructor(id = 123) { + this.id = id; + } } const ClassThingRef = builder.loadableNode(ClassThing, { diff --git a/packages/plugin-dataloader/tests/index.test.ts b/packages/plugin-dataloader/tests/index.test.ts index ea4f5294f..ee06d3f0f 100644 --- a/packages/plugin-dataloader/tests/index.test.ts +++ b/packages/plugin-dataloader/tests/index.test.ts @@ -158,6 +158,10 @@ describe('dataloader', () => { threeToMany: oneToMany(id: 3) { id } + classThingNode: node(id: "Q2xhc3NMb2FkYWJsZVRoaW5nOjE=") { + __typename + id + } } `; diff --git a/packages/plugin-prisma/src/schema-builder.ts b/packages/plugin-prisma/src/schema-builder.ts index 75fcc11f2..dc0e0043c 100644 --- a/packages/plugin-prisma/src/schema-builder.ts +++ b/packages/plugin-prisma/src/schema-builder.ts @@ -93,6 +93,7 @@ schemaBuilderProto.prismaNode = function prismaNode( const extendedOptions = { ...options, + name, variant, interfaces: [interfaceRef], findUnique, diff --git a/packages/plugin-prisma/tests/__snapshots__/index.test.ts.snap b/packages/plugin-prisma/tests/__snapshots__/index.test.ts.snap index 52b69c798..0ae5da722 100644 --- a/packages/plugin-prisma/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-prisma/tests/__snapshots__/index.test.ts.snap @@ -61,11 +61,6 @@ type FindUniqueRelations { withUniqueNode: WithUniqueNode! } -type Follow { - from: User! - to: User! -} - type Media { url: String! } @@ -300,7 +295,7 @@ type SelectUserFollowingConnection { type SelectUserFollowingConnectionEdge { cursor: String! - node: Follow! + node: UserFollow! } type SelectUserPostsConnection { @@ -362,6 +357,11 @@ type UserCommentsConnectionEdge { union UserDirectProfileWithErrorsResult = BaseError | Profile +type UserFollow { + from: User! + to: User! +} + type UserFollowingConnection { edges: [UserFollowingConnectionEdge]! pageInfo: PageInfo! @@ -369,7 +369,7 @@ type UserFollowingConnection { type UserFollowingConnectionEdge { cursor: String! - node: Follow! + node: UserFollow! } union UserOrProfile = Profile | User diff --git a/packages/plugin-prisma/tests/example/schema/index.ts b/packages/plugin-prisma/tests/example/schema/index.ts index bbe6eb4af..63fe737f3 100644 --- a/packages/plugin-prisma/tests/example/schema/index.ts +++ b/packages/plugin-prisma/tests/example/schema/index.ts @@ -107,6 +107,7 @@ const ViewerNode = builder.prismaNode('User', { }); builder.prismaObject('Follow', { + name: 'UserFollow', fields: (t) => ({ to: t.relation('to'), from: t.relation('from'), diff --git a/packages/plugin-relay/CHANGELOG.md b/packages/plugin-relay/CHANGELOG.md index deeb54d5c..08b2b6ca9 100644 --- a/packages/plugin-relay/CHANGELOG.md +++ b/packages/plugin-relay/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 3.36.0 + +### Minor Changes + +- d60cb49e: handle string contining ':' in global ID + ## 3.35.0 ### Minor Changes diff --git a/packages/plugin-relay/package.json b/packages/plugin-relay/package.json index 9ac934625..da763fee5 100644 --- a/packages/plugin-relay/package.json +++ b/packages/plugin-relay/package.json @@ -1,6 +1,6 @@ { "name": "@pothos/plugin-relay", - "version": "3.35.0", + "version": "3.36.0", "description": "A Pothos plugin for adding relay style connections, nodes, and cursor based pagination to your GraphQL schema", "main": "./lib/index.js", "types": "./dts/index.d.ts", diff --git a/packages/plugin-relay/src/index.ts b/packages/plugin-relay/src/index.ts index 2d01a96ae..245863b7c 100644 --- a/packages/plugin-relay/src/index.ts +++ b/packages/plugin-relay/src/index.ts @@ -27,9 +27,9 @@ export class PothosRelayPlugin extends BasePlugin { const argMappings = mapInputFields(fieldConfig.args, this.buildCache, (inputField) => { if (inputField.extensions?.isRelayGlobalID) { - return (inputField.extensions?.relayGlobalIDFor ?? true) as - | true - | { typename: string; parseId: (id: string, ctx: object) => unknown }[]; + return (inputField.extensions?.relayGlobalIDFor ?? + inputField.extensions?.relayGlobalIDAlwaysParse ?? + false) as boolean | { typename: string; parseId: (id: string, ctx: object) => unknown }[]; } return null; @@ -42,13 +42,7 @@ export class PothosRelayPlugin extends BasePlugin - internalDecodeGlobalID( - this.builder, - String(globalID), - ctx, - info, - Array.isArray(mappings.value) ? mappings.value : true, - ), + internalDecodeGlobalID(this.builder, String(globalID), ctx, info, mappings.value ?? false), ); return (parent, args, context, info) => diff --git a/packages/plugin-relay/src/schema-builder.ts b/packages/plugin-relay/src/schema-builder.ts index 0d2f656f4..b7f06e831 100644 --- a/packages/plugin-relay/src/schema-builder.ts +++ b/packages/plugin-relay/src/schema-builder.ts @@ -158,7 +158,12 @@ schemaBuilderProto.nodeInterfaceRef = function nodeInterfaceRef() { ...this.options.relayOptions.nodeQueryOptions, type: ref as InterfaceRef, args: { - id: t.arg.globalID({ required: true }), + id: t.arg.globalID({ + required: true, + extensions: { + relayGlobalIDAlwaysParse: true, + }, + }), }, resolve: resolveNodeFn ? (root, args, context, info) => @@ -198,7 +203,12 @@ schemaBuilderProto.nodeInterfaceRef = function nodeInterfaceRef() { ...this.options.relayOptions.nodesQueryOptions, type: [ref], args: { - ids: t.arg.globalIDList({ required: true }), + ids: t.arg.globalIDList({ + required: true, + extensions: { + relayGlobalIDAlwaysParse: true, + }, + }), }, resolve: resolveNodesFn ? (root, args, context, info) => diff --git a/packages/plugin-relay/src/utils/global-ids.ts b/packages/plugin-relay/src/utils/global-ids.ts index 666e3a209..2ec02d9c1 100644 --- a/packages/plugin-relay/src/utils/global-ids.ts +++ b/packages/plugin-relay/src/utils/global-ids.ts @@ -5,11 +5,12 @@ export function encodeGlobalID(typename: string, id: bigint | number | string) { } export function decodeGlobalID(globalID: string) { - const [typename, id] = decodeBase64(globalID).split(':'); + const decoded = decodeBase64(globalID).split(':'); + const [typename, id] = decoded; if (!typename || !id) { throw new PothosValidationError(`Invalid global ID: ${globalID}`); } - return { typename, id }; + return { typename, id: decoded.length > 2 ? decoded.slice(1).join(':') : id }; } diff --git a/packages/plugin-relay/tests/__snapshots__/index.test.ts.snap b/packages/plugin-relay/tests/__snapshots__/index.test.ts.snap index 45ac44025..b49a781a9 100644 --- a/packages/plugin-relay/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-relay/tests/__snapshots__/index.test.ts.snap @@ -46,6 +46,18 @@ input GlobalIDInput { otherList: [OtherInput!] = [{someField: \\"abc\\"}] } +type IDResult { + arg: String! + id: String! + idType: String! + typename: String! +} + +type IDWithColon implements Node { + id: ID! + idString: String! +} + type Mutation { answerPoll(answer: Int!, id: ID!): Poll! createPoll(answers: [String!]!, question: String!): Poll! @@ -128,7 +140,10 @@ type PollAnswersUsingOffsetConnectionEdge { type Query { batchNumbers(after: ID, before: ID, first: Int, last: Int): QueryBatchNumbersConnection! cursorConnection(after: ID, before: ID, first: Int, last: Int): QueryCursorConnection! + echoIDs(genericNumberThingID: ID!, globalID: ID!, numberThingID: ID!): [IDResult!]! extraNode: Node + idWithColon(id: ID!): IDWithColon! + idsWithColon(ids: [ID!]!): [IDWithColon!]! inputGlobalID(id: ID!, inputObj: GlobalIDInput!, normalId: ID!): String! moreNodes: [Node]! node(id: ID!): Node diff --git a/packages/plugin-relay/tests/examples/relay/schema/numbers.ts b/packages/plugin-relay/tests/examples/relay/schema/numbers.ts index e7a5846a4..348758ea5 100644 --- a/packages/plugin-relay/tests/examples/relay/schema/numbers.ts +++ b/packages/plugin-relay/tests/examples/relay/schema/numbers.ts @@ -1,6 +1,17 @@ import { resolveArrayConnection, resolveOffsetConnection } from '../../../../src'; import builder from '../builder'; +class IDWithColon { + id: string; + + constructor(id: string) { + if (!id.includes(':')) { + throw new TypeError(`Expected id to have a colon, saw ${id}`); + } + this.id = id; + } +} + class NumberThing { id: number; @@ -25,6 +36,16 @@ class BatchLoadableNumberThing { } } +const IDWithColonRef = builder.node(IDWithColon, { + name: 'IDWithColon', + id: { + resolve: (n) => n.id, + }, + fields: (t) => ({ + idString: t.exposeString('id'), + }), +}); + const NumberThingRef = builder.node(NumberThing, { id: { resolve: (n) => n.id, @@ -215,6 +236,26 @@ builder.queryField('sharedEdgeConnection', (t) => ), ); +builder.queryField('idWithColon', (t) => + t.field({ + type: IDWithColonRef, + args: { + id: t.arg.globalID({ required: true, for: [IDWithColonRef] }), + }, + resolve: (root, args) => new IDWithColon(args.id.id), + }), +); + +builder.queryField('idsWithColon', (t) => + t.field({ + type: [IDWithColonRef], + args: { + ids: t.arg.globalIDList({ required: true, for: [IDWithColonRef] }), + }, + resolve: (root, args) => args.ids.map((id) => new IDWithColon(id.id)), + }), +); + builder.queryField('numberThingByID', (t) => t.field({ type: NumberThing, @@ -236,3 +277,38 @@ builder.queryField('numberThingsByIDs', (t) => resolve: (root, args) => args.ids.map(({ id }) => new NumberThing(id)), }), ); + +const IDResult = builder + .objectRef<{ + id: unknown; + typename: string; + arg: string; + }>('IDResult') + .implement({ + fields: (t) => ({ + id: t.string({ + resolve: (n) => String(n.id), + }), + typename: t.exposeString('typename', {}), + arg: t.exposeString('arg', {}), + idType: t.string({ + resolve: (n) => typeof n.id, + }), + }), + }); + +builder.queryField('echoIDs', (t) => + t.field({ + type: [IDResult], + args: { + globalID: t.arg.globalID({ required: true }), + numberThingID: t.arg.globalID({ required: true, for: [NumberThingRef] }), + genericNumberThingID: t.arg.globalID({ required: true, for: [NumberThing] }), + }, + resolve: (_, args) => [ + { ...args.globalID, arg: 'globalID' }, + { ...args.numberThingID, arg: 'numberThingID' }, + { ...args.genericNumberThingID, arg: 'genericNumberThingID' }, + ], + }), +); diff --git a/packages/plugin-relay/tests/index.test.ts b/packages/plugin-relay/tests/index.test.ts index eb9f6322d..84015ed6c 100644 --- a/packages/plugin-relay/tests/index.test.ts +++ b/packages/plugin-relay/tests/index.test.ts @@ -513,6 +513,14 @@ describe('relay example schema', () => { it('parses ids', async () => { const query = gql` query { + idWithColon(id: "SURXaXRoQ29sb246MTp0ZXN0") { + id + idString + } + idsWithColon(ids: ["SURXaXRoQ29sb246MTp0ZXN0", "SURXaXRoQ29sb246Mjp0ZXN0OmV4YW1wbGU="]) { + id + idString + } numberThingByID(id: "TnVtYmVyOjE=") { id number @@ -529,6 +537,16 @@ describe('relay example schema', () => { id number } + echoIDs( + globalID: "TnVtYmVyOjE=" + numberThingID: "TnVtYmVyOjE=" + genericNumberThingID: "TnVtYmVyOjE=" + ) { + id + typename + arg + idType + } } `; @@ -541,6 +559,40 @@ describe('relay example schema', () => { expect(result).toMatchInlineSnapshot(` { "data": { + "echoIDs": [ + { + "arg": "globalID", + "id": "1", + "idType": "string", + "typename": "Number", + }, + { + "arg": "numberThingID", + "id": "1", + "idType": "number", + "typename": "Number", + }, + { + "arg": "genericNumberThingID", + "id": "1", + "idType": "string", + "typename": "Number", + }, + ], + "idWithColon": { + "id": "SURXaXRoQ29sb246MTp0ZXN0", + "idString": "1:test", + }, + "idsWithColon": [ + { + "id": "SURXaXRoQ29sb246MTp0ZXN0", + "idString": "1:test", + }, + { + "id": "SURXaXRoQ29sb246Mjp0ZXN0OmV4YW1wbGU=", + "idString": "2:test:example", + }, + ], "invalid": null, "invalidList": null, "numberThingByID": { diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 5262a125a..5e0aa683e 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -4,6 +4,13 @@ ### Patch Changes +- Updated dependencies [42bf6190] + - @pothos/core@3.27.0 + +## null + +### Patch Changes + - Updated dependencies [ec411ea1] - @pothos/core@3.26.0 diff --git a/website/pages/docs/api/schema-builder.mdx b/website/pages/docs/api/schema-builder.mdx index 07c3796cc..7724c1c5b 100644 --- a/website/pages/docs/api/schema-builder.mdx +++ b/website/pages/docs/api/schema-builder.mdx @@ -295,7 +295,7 @@ all types in SchemaTypes, or import the actual implementation of each interface ```typescript type UnionTypeOptions = { description?: string; - types: Member[]; + types: Member[] | (() => Member[]); resolveType: (parent: UnionShape, context) => MaybePromise; }; ``` diff --git a/website/pages/docs/guide/circular-references.mdx b/website/pages/docs/guide/circular-references.mdx index 933f9ff05..963d55676 100644 --- a/website/pages/docs/guide/circular-references.mdx +++ b/website/pages/docs/guide/circular-references.mdx @@ -45,7 +45,7 @@ loaded, these types of Circular imports should work without causing any issues. import { Post } from './post' export const User = builder.objectType('User', { - fields: t => ({ posts: t.t.expose('posts', { type: [Post]}) }), + fields: t => ({ posts: t.expose('posts', { type: [Post]}) }), }) // post.ts diff --git a/website/pages/docs/plugins/dataloader.mdx b/website/pages/docs/plugins/dataloader.mdx index 3248fff9d..5a04bb01c 100644 --- a/website/pages/docs/plugins/dataloader.mdx +++ b/website/pages/docs/plugins/dataloader.mdx @@ -320,7 +320,7 @@ const UserNode = builder.loadableNode('UserNode', { id: { resolve: (user) => user.id, }, - // For loadable objects we always need to include an isTypeOf check + // For loadable nodes, we need to include an isTypeOf check if the first arg is a string isTypeOf: (obj) => obj instanceof User, load: (ids: string[], context: ContextType) => context.loadUsersById(ids), fields: (t) => ({}),