From 4364da1f3ec3fb336c4ec9630b013827591c1668 Mon Sep 17 00:00:00 2001 From: Dan Lepadatu Date: Thu, 9 Mar 2023 09:08:27 +0100 Subject: [PATCH 1/3] test(plugin-directives): assert astNode has defaultValue for inputs and args --- .../tests/__snapshots__/index.test.ts.snap | 17 +++++- .../tests/example/schema/index.ts | 54 ++++++++++++++----- .../plugin-directives/tests/index.test.ts | 48 +++++++++++++++++ 3 files changed, 104 insertions(+), 15 deletions(-) diff --git a/packages/plugin-directives/tests/__snapshots__/index.test.ts.snap b/packages/plugin-directives/tests/__snapshots__/index.test.ts.snap index 3b9e7457c..b1531a545 100644 --- a/packages/plugin-directives/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-directives/tests/__snapshots__/index.test.ts.snap @@ -5,6 +5,7 @@ exports[`extends example schema generates expected schema 1`] = ` enum EN { ONE + TWO } interface IF { @@ -15,12 +16,26 @@ input In { test: String } +input MyInput { + booleanWithDefault: Boolean = false + enumWithDefault: EN = TWO + id: ID! + idWithDefault: ID = 123 + ids: [ID!]! + idsWithDefault: [ID!] = [123, 456] + stringWithDefault: String = "default string" +} + +input MyOtherInput { + booleanWithDefault: Boolean = false +} + type Obj { field: String! } type Query { - test(arg1: String): String! + test(arg1: String, myInput: MyInput, myOtherInput: MyOtherInput = {}): String! } union UN = Obj" diff --git a/packages/plugin-directives/tests/example/schema/index.ts b/packages/plugin-directives/tests/example/schema/index.ts index a86f8e500..181b26108 100644 --- a/packages/plugin-directives/tests/example/schema/index.ts +++ b/packages/plugin-directives/tests/example/schema/index.ts @@ -1,6 +1,44 @@ import { rateLimitDirective } from 'graphql-rate-limit-directive'; import builder from '../builder'; +const myInput = builder.inputType('MyInput', { + fields: (t) => ({ + id: t.id({ required: true }), + idWithDefault: t.id({ required: false, defaultValue: '123' }), + booleanWithDefault: t.boolean({ required: false, defaultValue: false }), + enumWithDefault: t.field({ + defaultValue: 2, + type: myEnum, + }), + stringWithDefault: t.string({ required: false, defaultValue: 'default string' }), + ids: t.idList({ required: true }), + idsWithDefault: t.idList({ required: false, defaultValue: ['123', '456'] }), + }), +}); + +const myOtherInput = builder.inputType('MyOtherInput', { + fields: (t) => ({ + booleanWithDefault: t.boolean({ required: false, defaultValue: false }), + }), +}); + +const myEnum = builder.enumType('EN', { + directives: { + e: { foo: 123 }, + }, + values: { + ONE: { + value: 1, + directives: { + ev: { foo: 123 }, + }, + }, + TWO: { + value: 2, + }, + }, +}); + builder.queryType({ directives: { o: { @@ -27,6 +65,8 @@ builder.queryType({ a: { foo: 123 }, }, }), + myOtherInput: t.arg({ type: myOtherInput, required: false, defaultValue: {} }), + myInput: t.arg({ type: myInput, required: false }), }, resolve: () => 'hi', }), @@ -74,20 +114,6 @@ builder.unionType('UN', { types: [Obj], }); -builder.enumType('EN', { - directives: { - e: { foo: 123 }, - }, - values: { - ONE: { - value: 1, - directives: { - ev: { foo: 123 }, - }, - }, - }, -}); - builder.scalarType('Date', { directives: { s: { foo: 123 }, diff --git a/packages/plugin-directives/tests/index.test.ts b/packages/plugin-directives/tests/index.test.ts index 3a1b5d698..5b9cd40cf 100644 --- a/packages/plugin-directives/tests/index.test.ts +++ b/packages/plugin-directives/tests/index.test.ts @@ -1,8 +1,12 @@ import { execute, GraphQLEnumType, + GraphQLInputField, + GraphQLInputFieldMap, GraphQLInputObjectType, GraphQLObjectType, + InputValueDefinitionNode, + Kind, lexicographicSortSchema, printSchema, } from 'graphql'; @@ -42,6 +46,50 @@ describe('extends example schema', () => { ).toStrictEqual({ if: { foo: 123 } }); }); + it('constructs the astNode with defaultValue for inputs and args', () => { + const types = schema.getTypeMap(); + + const testField = (types.Query as GraphQLObjectType).getFields().test; + + const arg1 = testField.args[0]; + const myInput = testField.args[1]; + const myOtherInput = testField.args[2]; + + expect(arg1.astNode?.defaultValue).toBeUndefined(); + expect(myInput.astNode?.defaultValue).toBeUndefined(); + expect(myOtherInput.astNode?.defaultValue).toBeDefined(); + expect(myOtherInput.astNode?.defaultValue).toStrictEqual({ + kind: Kind.OBJECT, + fields: [], + }); + + const MyInput = (types.MyInput as GraphQLInputObjectType).getFields(); + + expect(MyInput.booleanWithDefault.astNode?.defaultValue).toStrictEqual({ + kind: Kind.BOOLEAN, + value: false, + }); + expect(MyInput.enumWithDefault.astNode?.defaultValue).toStrictEqual({ + kind: Kind.ENUM, + value: 'TWO', + }); + expect(MyInput.idWithDefault.astNode?.defaultValue).toStrictEqual({ + kind: Kind.INT, + value: '123', + }); + expect(MyInput.stringWithDefault.astNode?.defaultValue).toStrictEqual({ + kind: Kind.STRING, + value: 'default string', + }); + expect(MyInput.idsWithDefault.astNode?.defaultValue).toStrictEqual({ + kind: Kind.LIST, + values: [ + { kind: Kind.INT, value: '123' }, + { kind: Kind.INT, value: '456' }, + ], + }); + }); + it('gatsby format', () => { const builder = new SchemaBuilder<{ Directives: { From f368ed9bb409ff4e5f0e0d0be17b7d8e7a2f6c55 Mon Sep 17 00:00:00 2001 From: Dan Lepadatu Date: Thu, 9 Mar 2023 09:09:18 +0100 Subject: [PATCH 2/3] fix(plugin-directives): add defaultValue to astNode for inputs/args --- packages/deno/packages/core/config-store.ts | 8 ++++++-- packages/deno/packages/plugin-directives/mock-ast.ts | 6 +++++- packages/deno/packages/plugin-relay/README.md | 2 +- packages/plugin-directives/src/mock-ast.ts | 8 ++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/deno/packages/core/config-store.ts b/packages/deno/packages/core/config-store.ts index 452c6a3a4..dce1b4ffc 100644 --- a/packages/deno/packages/core/config-store.ts +++ b/packages/deno/packages/core/config-store.ts @@ -296,19 +296,23 @@ export default class ConfigStore { prepareForBuild() { this.pending = false; const fns = this.addFieldFns; + const interfaces = this.pendingInterfaces; + const unions = this.pendingUnionTypes; this.addFieldFns = []; + this.pendingInterfaces = new Map(); + this.pendingUnionTypes = new Map(); fns.forEach((fn) => void fn()); if (this.pendingRefResolutions.size > 0) { throw new PothosSchemaError(`Missing implementations for some references (${[...this.pendingRefResolutions.keys()] .map((ref) => this.describeRef(ref)) .join(", ")}).`); } - for (const [typeName, unionFns] of this.pendingUnionTypes) { + for (const [typeName, unionFns] of unions) { for (const fn of unionFns) { this.addUnionTypes(typeName, fn); } } - for (const [typeName, interfacesFns] of this.pendingInterfaces) { + for (const [typeName, interfacesFns] of interfaces) { for (const fn of interfacesFns) { this.addInterfaces(typeName, fn); } diff --git a/packages/deno/packages/plugin-directives/mock-ast.ts b/packages/deno/packages/plugin-directives/mock-ast.ts index 01d94fe5c..4135a135f 100644 --- a/packages/deno/packages/plugin-directives/mock-ast.ts +++ b/packages/deno/packages/plugin-directives/mock-ast.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ /* eslint-disable no-param-reassign */ import './global-types.ts'; -import { ArgumentNode, ConstDirectiveNode, DirectiveNode, EnumValueDefinitionNode, FieldDefinitionNode, GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLFieldMap, GraphQLInputField, GraphQLInputFieldMap, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLType, GraphQLUnionType, InputValueDefinitionNode, Kind, ListTypeNode, NamedTypeNode, OperationTypeNode, parseValue, TypeNode, ValueNode, } from 'https://cdn.skypack.dev/graphql?dts'; +import { ArgumentNode, astFromValue, ConstDirectiveNode, ConstValueNode, DirectiveNode, EnumValueDefinitionNode, FieldDefinitionNode, GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLFieldMap, GraphQLInputField, GraphQLInputFieldMap, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLType, GraphQLUnionType, InputValueDefinitionNode, Kind, ListTypeNode, NamedTypeNode, OperationTypeNode, parseValue, TypeNode, ValueNode, } from 'https://cdn.skypack.dev/graphql?dts'; import type { DirectiveList } from './types.ts'; export default function mockAst(schema: GraphQLSchema) { const types = schema.getTypeMap(); @@ -175,11 +175,13 @@ function fieldNodes(fields: GraphQLFieldMap): FieldDefinitionN function inputFieldNodes(fields: GraphQLInputFieldMap): InputValueDefinitionNode[] { return Object.keys(fields).map((fieldName) => { const field: GraphQLInputField = fields[fieldName]; + const defaultValueNode = astFromValue(field.defaultValue, field.type) as ConstValueNode; field.astNode = { kind: Kind.INPUT_VALUE_DEFINITION, description: field.description ? { kind: Kind.STRING, value: field.description } : undefined, name: { kind: Kind.NAME, value: fieldName }, type: typeNode(field.type), + defaultValue: field.defaultValue === undefined ? undefined : defaultValueNode, directives: directiveNodes(field.extensions?.directives as DirectiveList, field.deprecationReason), }; return field.astNode!; @@ -187,11 +189,13 @@ function inputFieldNodes(fields: GraphQLInputFieldMap): InputValueDefinitionNode } function argumentNodes(args: readonly GraphQLArgument[]): InputValueDefinitionNode[] { return args.map((arg): InputValueDefinitionNode => { + const defaultValueNode = astFromValue(arg.defaultValue, arg.type) as ConstValueNode; arg.astNode = { kind: Kind.INPUT_VALUE_DEFINITION, description: arg.description ? { kind: Kind.STRING, value: arg.description } : undefined, name: { kind: Kind.NAME, value: arg.name }, type: typeNode(arg.type), + defaultValue: arg.defaultValue === undefined ? undefined : defaultValueNode, directives: directiveNodes(arg.extensions?.directives as DirectiveList, arg.deprecationReason), }; return arg.astNode; diff --git a/packages/deno/packages/plugin-relay/README.md b/packages/deno/packages/plugin-relay/README.md index 5409ffddb..5a7340778 100644 --- a/packages/deno/packages/plugin-relay/README.md +++ b/packages/deno/packages/plugin-relay/README.md @@ -178,7 +178,7 @@ builder.node(NumberThing, { the `Node` interface the first time it is used. The `resolve` function for `id` should return a number or string, which will be converted to a globalID. The relay plugin adds to new query fields `node` and `nodes` which can be used to directly fetch nodes using global IDs by calling the -provided `loadOne` or `laodMany` method. Each node will only be loaded once by id, and cached if the +provided `loadOne` or `loadMany` method. Each node will only be loaded once by id, and cached if the same node is loaded multiple times inn the same request. You can provide `loadWithoutCache` or `loadManyWithoutCache` instead if caching is not desired, or you are already using a caching datasource like a dataloader. diff --git a/packages/plugin-directives/src/mock-ast.ts b/packages/plugin-directives/src/mock-ast.ts index 2b3e8f0ef..42d5565fd 100644 --- a/packages/plugin-directives/src/mock-ast.ts +++ b/packages/plugin-directives/src/mock-ast.ts @@ -4,7 +4,9 @@ import './global-types'; import { ArgumentNode, + astFromValue, ConstDirectiveNode, + ConstValueNode, DirectiveNode, EnumValueDefinitionNode, FieldDefinitionNode, @@ -237,11 +239,14 @@ function inputFieldNodes(fields: GraphQLInputFieldMap): InputValueDefinitionNode return Object.keys(fields).map((fieldName) => { const field: GraphQLInputField = fields[fieldName]; + const defaultValueNode = astFromValue(field.defaultValue, field.type) as ConstValueNode; + field.astNode = { kind: Kind.INPUT_VALUE_DEFINITION, description: field.description ? { kind: Kind.STRING, value: field.description } : undefined, name: { kind: Kind.NAME, value: fieldName }, type: typeNode(field.type), + defaultValue: field.defaultValue === undefined ? undefined : defaultValueNode, directives: directiveNodes( field.extensions?.directives as DirectiveList, field.deprecationReason, @@ -254,11 +259,14 @@ function inputFieldNodes(fields: GraphQLInputFieldMap): InputValueDefinitionNode function argumentNodes(args: readonly GraphQLArgument[]): InputValueDefinitionNode[] { return args.map((arg): InputValueDefinitionNode => { + const defaultValueNode = astFromValue(arg.defaultValue, arg.type) as ConstValueNode; + arg.astNode = { kind: Kind.INPUT_VALUE_DEFINITION, description: arg.description ? { kind: Kind.STRING, value: arg.description } : undefined, name: { kind: Kind.NAME, value: arg.name }, type: typeNode(arg.type), + defaultValue: arg.defaultValue === undefined ? undefined : defaultValueNode, directives: directiveNodes( arg.extensions?.directives as DirectiveList, arg.deprecationReason, From 671a993569dfbf3a271ae578c3f87bf3e1e12261 Mon Sep 17 00:00:00 2001 From: Michael Hayes Date: Thu, 9 Mar 2023 18:07:35 -0800 Subject: [PATCH 3/3] Create .changeset/nasty-trainers-remember.md --- .changeset/nasty-trainers-remember.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nasty-trainers-remember.md diff --git a/.changeset/nasty-trainers-remember.md b/.changeset/nasty-trainers-remember.md new file mode 100644 index 000000000..e72aeccf5 --- /dev/null +++ b/.changeset/nasty-trainers-remember.md @@ -0,0 +1,5 @@ +--- +"@pothos/plugin-directives": patch +--- + +Add ast nodes for defaultalues for args and input fields