diff --git a/package.json b/package.json index 20c1f9760..49a35515c 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "graphql": "14 - 16" }, "devDependencies": { - "@tsconfig/node16": "^16.1.1", + "@tsconfig/node16": "^16.1.2", "@types/body-parser": "^1.19.5", "@types/express": "^4.17.21", "@types/json-bigint": "^1.0.4", @@ -104,10 +104,10 @@ "get-port": "^7.1.0", "graphql": "^16.8.1", "graphql-tag": "^2.12.6", - "happy-dom": "^14.3.1", + "happy-dom": "^14.3.6", "json-bigint": "^1.0.0", "tsx": "^4.7.1", - "type-fest": "^4.13.1", + "type-fest": "^4.14.0", "typescript": "^5.4.3", "vitest": "^1.4.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66d470a22..f1b626f9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,8 +23,8 @@ dependencies: devDependencies: '@tsconfig/node16': - specifier: ^16.1.1 - version: 16.1.1 + specifier: ^16.1.2 + version: 16.1.2 '@types/body-parser': specifier: ^1.19.5 version: 1.19.5 @@ -89,8 +89,8 @@ devDependencies: specifier: ^2.12.6 version: 2.12.6(graphql@16.8.1) happy-dom: - specifier: ^14.3.1 - version: 14.3.1 + specifier: ^14.3.6 + version: 14.3.6 json-bigint: specifier: ^1.0.0 version: 1.0.0 @@ -98,14 +98,14 @@ devDependencies: specifier: ^4.7.1 version: 4.7.1 type-fest: - specifier: ^4.13.1 - version: 4.13.1 + specifier: ^4.14.0 + version: 4.14.0 typescript: specifier: ^5.4.3 version: 5.4.3 vitest: specifier: ^1.4.0 - version: 1.4.0(@types/node@20.11.30)(happy-dom@14.3.1) + version: 1.4.0(@types/node@20.11.30)(happy-dom@14.3.6) packages: @@ -911,7 +911,7 @@ packages: string-length: 6.0.0 strip-ansi: 7.1.0 ts-toolbelt: 9.6.0 - type-fest: 4.13.1 + type-fest: 4.14.0 zod: 3.22.4 dev: false @@ -1375,8 +1375,8 @@ packages: - supports-color dev: true - /@tsconfig/node16@16.1.1: - resolution: {integrity: sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==} + /@tsconfig/node16@16.1.2: + resolution: {integrity: sha512-dQ4IMJsdighPkzI8YNdjZy9Ky2ZAj1m2EkCUfQ575djffboaVJrkl5RmHmmc4MEF7LXVv501aO8KeG5Aa43VHA==} dev: true /@types/accepts@1.3.7: @@ -3061,8 +3061,8 @@ packages: resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - /happy-dom@14.3.1: - resolution: {integrity: sha512-uv2mE7jUH0S3cTnDPqNQj+J+Z5wOevqzopc7e8URXtcCH2STubCjPFVyEJ1ONGSv/aL/uvNwo5WWjsinpWpADQ==} + /happy-dom@14.3.6: + resolution: {integrity: sha512-fUb3dn0iuyyxRGqwFoU5iy6wjozxt/Qw7zGeRMockbBlpOegrV7Y0HIYBMQw8X4s7qpu55Tu7cNFoRM8s9VW5A==} engines: {node: '>=16.0.0'} dependencies: entities: 4.5.0 @@ -4522,8 +4522,8 @@ packages: engines: {node: '>=10'} dev: true - /type-fest@4.13.1: - resolution: {integrity: sha512-ASMgM+Vf2cLwDMt1KXSkMUDSYCxtckDJs8zsaVF/mYteIsiARKCVtyXtcK38mIKbLTctZP8v6GMqdNaeI3fo7g==} + /type-fest@4.14.0: + resolution: {integrity: sha512-on5/Cw89wwqGZQu+yWO0gGMGu8VNxsaW9SB2HE8yJjllEk7IDTwnSN1dUVldYILhYPN5HzD7WAaw2cc/jBfn0Q==} engines: {node: '>=16'} /type-is@1.6.18: @@ -4674,7 +4674,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.3(@types/node@20.11.30) + vite: 5.2.6(@types/node@20.11.30) transitivePeerDependencies: - '@types/node' - less @@ -4686,8 +4686,8 @@ packages: - terser dev: true - /vite@5.2.3(@types/node@20.11.30): - resolution: {integrity: sha512-+i1oagbvkVIhEy9TnEV+fgXsng13nZM90JQbrcPrf6DvW2mXARlz+DK7DLiDP+qeKoD1FCVx/1SpFL1CLq9Mhw==} + /vite@5.2.6(@types/node@20.11.30): + resolution: {integrity: sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4722,7 +4722,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest@1.4.0(@types/node@20.11.30)(happy-dom@14.3.1): + /vitest@1.4.0(@types/node@20.11.30)(happy-dom@14.3.6): resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -4757,7 +4757,7 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 - happy-dom: 14.3.1 + happy-dom: 14.3.6 local-pkg: 0.5.0 magic-string: 0.30.8 pathe: 1.1.2 @@ -4766,7 +4766,7 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.2.3(@types/node@20.11.30) + vite: 5.2.6(@types/node@20.11.30) vite-node: 1.4.0(@types/node@20.11.30) why-is-node-running: 2.2.2 transitivePeerDependencies: diff --git a/src/ResultSet/ResultSet.ts b/src/ResultSet/ResultSet.ts index 0c4245009..87021dfef 100644 --- a/src/ResultSet/ResultSet.ts +++ b/src/ResultSet/ResultSet.ts @@ -27,7 +27,7 @@ export type Object<$SelectionSet, $Node extends Schema.Named.Object, $Index exte */ ? { - [$Key in keyof $Node['fields'] as $Node['fields'][$Key] extends Schema.Field.Field | {'typeUnwrapped':{kind:'Scalar'}} ? $Key : never]: + [$Key in keyof $Node['fields'] as $Node['fields'][$Key] extends Schema.Field.Field | {'typeUnwrapped':{kind:'Scalar'}} ? $Key : never]: // eslint-disable-next-line // @ts-ignore infinite depth issue, can this be fixed? Field<$SelectionSet, Schema.Field.As<$Node['fields'][$Key]>, $Index> @@ -74,18 +74,18 @@ type Field<$SelectionSet, $Field extends Schema.Field.Field, $Index extends Sche // dprint-ignore type FieldType< $SelectionSet, - $Type extends Schema.Field.Type.Any, + $Type extends Schema.Field.Type.Output.Any, $Index extends Schema.Index > =Simplify< - $Type extends Schema.Field.Type.__typename ? $Value : - $Type extends Schema.Field.Type.Nullable ? null | FieldType<$SelectionSet, $InnerType, $Index> : - $Type extends Schema.Field.Type.List ? Array> : - $Type extends Schema.Named.Enum ? $Members[number] : - $Type extends Schema.Named.Scalar.Any ? ReturnType<$Type['constructor']> : - $Type extends Schema.Named.Object ? Object<$SelectionSet,$Type,$Index> : - $Type extends Schema.Named.Interface ? Interface<$SelectionSet,$Type,$Index> : - $Type extends Schema.Named.Union ? Union<$SelectionSet,$Type,$Index> : - TSError<'FieldType', `Unknown type`, { $Type: $Type }> + $Type extends Schema.Field.Type.Output.__typename ? $Value : + $Type extends Schema.Field.Type.Output.Nullable ? null | FieldType<$SelectionSet, $InnerType, $Index> : + $Type extends Schema.Field.Type.Output.List ? Array> : + $Type extends Schema.Named.Enum ? $Members[number] : + $Type extends Schema.Named.Scalar.Any ? ReturnType<$Type['constructor']> : + $Type extends Schema.Named.Object ? Object<$SelectionSet,$Type,$Index> : + $Type extends Schema.Named.Interface ? Interface<$SelectionSet,$Type,$Index> : + $Type extends Schema.Named.Union ? Union<$SelectionSet,$Type,$Index> : + TSError<'FieldType', `Unknown type`, { $Type: $Type }> > // dprint-ignore diff --git a/src/Schema/Field/Field.ts b/src/Schema/Field/Field.ts index 83912f8f1..a5d72cfbc 100644 --- a/src/Schema/Field/Field.ts +++ b/src/Schema/Field/Field.ts @@ -1,7 +1,6 @@ import type { NamedType } from '../NamedType/__.js' import type { Scalar } from '../NamedType/Scalar/_.js' -import type * as Type from './Type.js' -import { unwrap } from './Type.js' +import * as Type from './Type.js' export type * as Type from './Type.js' @@ -17,33 +16,27 @@ export type Number<$Args extends Args | null = null> = Field export type Boolean<$Args extends Args | null = null> = Field -export namespace Input { - export type Nullable = Type.Nullable - export type List = Type.List - export type Any = Scalar.Any | List | Nullable -} - // export interface Args<$Fields extends Record = Record> { export interface Args<$Fields extends any = any> { - allOptional: Exclude<$Fields[keyof $Fields], Type.Nullable> extends never ? true : false + allOptional: Exclude<$Fields[keyof $Fields], Type.Output.Nullable> extends never ? true : false fields: $Fields } -export const field = <$Type extends Type.Any, $Args extends null | Args = null>( +export const field = <$Type extends Type.Output.Any, $Args extends null | Args = null>( type: $Type, args: $Args = null as $Args, ): Field<$Type, $Args> => { return { // eslint-disable-next-line // @ts-ignore infinite depth issue, can this be fixed? - typeUnwrapped: unwrap(type), + typeUnwrapped: Type.Output.unwrap(type), type, args, } } export type Field<$Type extends any = any, $Args extends Args | null = Args | null> = { - typeUnwrapped: Type.Unwrap<$Type> + typeUnwrapped: Type.Output.Unwrap<$Type> type: $Type args: $Args } diff --git a/src/Schema/Field/Type.ts b/src/Schema/Field/Type.ts index 63e26b4ed..c2ee37e75 100644 --- a/src/Schema/Field/Type.ts +++ b/src/Schema/Field/Type.ts @@ -1,38 +1,51 @@ import type { TSError } from '../../lib/TSError.js' import type { NamedType } from '../NamedType/__.js' -export interface __typename<$Type extends string = string> { - kind: 'typename' - type: $Type -} -export interface Nullable<$Type extends Any> { - kind: 'nullable' - type: $Type -} -export interface List<$Type extends Any> { - kind: 'list' - type: $Type +export namespace Base { + export interface Nullable<$Type> { + kind: 'nullable' + type: $Type + } + export interface List<$Type> { + kind: 'list' + type: $Type + } } -export type Any = List | __typename | Nullable | NamedType.Any +export namespace Output { + export interface __typename<$Type extends string = string> { + kind: 'typename' + type: $Type + } + export type Nullable<$Type extends Any> = Base.Nullable<$Type> + export type List<$Type extends Any> = Base.List<$Type> + + export type Any = Output.List | __typename | Base.Nullable | NamedType.AnyOutput -export const __typename = <$Type extends string>(type: $Type): __typename<$Type> => ({ kind: `typename`, type }) -export const nullable = <$Type extends __typename | List>(type: $Type): Nullable<$Type> => ({ - kind: `nullable`, - type, -}) -export const list = <$Type extends Any>(type: $Type): List<$Type> => ({ kind: `list`, type }) + export const __typename = <$Type extends string>(type: $Type): __typename<$Type> => ({ kind: `typename`, type }) + export const nullable = <$Type extends __typename | List>(type: $Type): Nullable<$Type> => ({ + kind: `nullable`, + type, + }) + export const list = <$Type extends Any>(type: $Type): List<$Type> => ({ kind: `list`, type }) -// todo extends any because of infinite depth issue in generated schema types -// dprint-ignore -export type Unwrap<$Type extends any> = + // todo extends any because of infinite depth issue in generated schema types + // dprint-ignore + export type Unwrap<$Type extends any> = $Type extends List ? Unwrap<$innerType> : $Type extends Nullable ? Unwrap<$innerType> : $Type extends __typename ? $Type['type'] : - $Type extends NamedType.Any ? $Type : + $Type extends NamedType.AnyOutput ? $Type : TSError<'Unwrap', 'Unknown $Type', { $Type: $Type }> -export const unwrap = <$Type extends Any>(type: $Type): Unwrap<$Type> => { - // @ts-expect-error fixme - return type.kind === `named` ? type.type : unwrap(type.type) + export const unwrap = <$Type extends Any>(type: $Type): Unwrap<$Type> => { + // @ts-expect-error fixme + return type.kind === `named` ? type.type : unwrap(type.type) + } +} + +export namespace Input { + export type Nullable<$InnerType extends Any = Any> = Base.Nullable<$InnerType> + export type List<$InnerType extends Any = Any> = Base.List<$InnerType> + export type Any = List | Nullable | NamedType.AnyInput } diff --git a/src/Schema/NamedType/Enum.ts b/src/Schema/NamedType/Enum.ts index cec53ca24..81a096297 100644 --- a/src/Schema/NamedType/Enum.ts +++ b/src/Schema/NamedType/Enum.ts @@ -1,5 +1,3 @@ -import type { __typename } from '../__.js' - export interface Enum< $Name extends string = string, $Members extends [string, ...string[]] = [string, ...string[]], diff --git a/src/Schema/NamedType/InputObjet.ts b/src/Schema/NamedType/InputObjet.ts new file mode 100644 index 000000000..2b660cb58 --- /dev/null +++ b/src/Schema/NamedType/InputObjet.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/ban-types */ + +type Fields = Record + +export interface InputObject< + $Name extends string = string, + $Fields extends Fields = Fields, +> { + kind: 'InputObject' + name: $Name + fields: $Fields +} + +export const InputObject = <$Name extends string, $Fields extends Record>( + name: $Name, + fields: $Fields, +): InputObject<$Name, $Fields> => ({ + kind: `InputObject`, + name: name, + fields: { + ...fields, + }, +}) diff --git a/src/Schema/NamedType/Interface.ts b/src/Schema/NamedType/Interface.ts index 59cf32ff4..7940f4dfa 100644 --- a/src/Schema/NamedType/Interface.ts +++ b/src/Schema/NamedType/Interface.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-types */ -import type { __typename } from '../__.js' import type { Field } from '../Field/Field.js' import type { Object } from './Object.js' diff --git a/src/Schema/NamedType/NamedType.ts b/src/Schema/NamedType/NamedType.ts index 1def4305b..3035c387b 100644 --- a/src/Schema/NamedType/NamedType.ts +++ b/src/Schema/NamedType/NamedType.ts @@ -2,12 +2,15 @@ import type { Digit, Letter } from '../../lib/prelude.js' import type { Enum } from './Enum.js' +import type { InputObject } from './InputObjet.js' import type { Interface } from './Interface.js' import type { Object } from './Object.js' import type { Scalar } from './Scalar/_.js' import type { Union } from './Union.js' -export type Any = Interface | Enum | Object | Scalar.Any | Union +export type AnyOutput = Interface | Enum | Object | Scalar.Any | Union +export type AnyInput = Enum | Scalar.Any | InputObject +export type Any = AnyOutput | AnyInput /** * @see http://spec.graphql.org/draft/#sec-Names diff --git a/src/Schema/NamedType/Object.ts b/src/Schema/NamedType/Object.ts index efd3b53b6..529d3493a 100644 --- a/src/Schema/NamedType/Object.ts +++ b/src/Schema/NamedType/Object.ts @@ -2,12 +2,12 @@ import type { Field } from '../Field/Field.js' import { field } from '../Field/Field.js' -import { __typename } from '../Field/Type.js' +import { Output } from '../Field/Type.js' export type Fields = Record> export type ObjectFields = { - __typename: Field<__typename> + __typename: Field } & Fields export interface Object< @@ -16,7 +16,7 @@ export interface Object< > { kind: 'Object' fields: { - __typename: Field<__typename<$Name>> + __typename: Field> } & $Fields } @@ -26,7 +26,7 @@ export const Object = <$Name extends string, $Fields extends Record => ({ kind: `Object`, fields: { - __typename: field(__typename(name)), + __typename: field(Output.__typename(name)), ...fields, }, }) diff --git a/src/Schema/NamedType/Union.ts b/src/Schema/NamedType/Union.ts index e3a1eaa4e..659be56dc 100644 --- a/src/Schema/NamedType/Union.ts +++ b/src/Schema/NamedType/Union.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-types */ -import type { __typename } from '../__.js' import type { Object } from './Object.js' export type Union< diff --git a/src/Schema/NamedType/_.ts b/src/Schema/NamedType/_.ts index c2650fcae..40e0b6247 100644 --- a/src/Schema/NamedType/_.ts +++ b/src/Schema/NamedType/_.ts @@ -1,4 +1,5 @@ export * from './Enum.js' +export * from './InputObjet.js' export * from './Interface.js' export * from './NamedType.js' export * from './Object.js' diff --git a/src/Schema/__.ts b/src/Schema/__.ts index cee87341a..a346e7390 100644 --- a/src/Schema/__.ts +++ b/src/Schema/__.ts @@ -1,5 +1,4 @@ export * as Schema from './_.js' -export { Args, As, Field } from './Field/__.js' -export { __typename, List, Nullable } from './Field/Type.js' +export { Args, As, Field, Input, Output } from './Field/__.js' export { Index } from './Index.js' export * from './NamedType/_.js' diff --git a/src/SelectionSet/SelectionSet.test-d.ts b/src/SelectionSet/SelectionSet.test-d.ts index 5b373b501..ba8be4b68 100644 --- a/src/SelectionSet/SelectionSet.test-d.ts +++ b/src/SelectionSet/SelectionSet.test-d.ts @@ -173,7 +173,7 @@ test(`Query`, () => { // @ts-expect-error invalid enum value assertType({ stringWithArgEnum: { $: { ABCEnum: 1 } } }) - // input list + // list arg assertType({ stringWithListArg: { $: { ints: [1, 2, 3] } } }) assertType({ stringWithListArg: { $: { ints: [] } } }) assertType({ stringWithListArg: { $: { ints: [null] } } }) @@ -184,6 +184,13 @@ test(`Query`, () => { // @ts-expect-error missing non-null "ints" arg assertType({ stringWithListArgRequired: { $: { ints: null } } }) + // input object arg + assertType({ stringWithArgInputObjectRequired: { $: { input: { id: ``, idRequired: `` } } } }) + assertType({ stringWithArgInputObjectRequired: { $: { input: { id: null, idRequired: `` } } } }) + assertType({ stringWithArgInputObjectRequired: { $: { input: { idRequired: `` } } } }) + // @ts-expect-error missing "idRequired" field + assertType({ stringWithArgInputObjectRequired: { $: { input: {} } } }) + // all-optional + scalar + directive assertType({ stringWithArgs: { $: { boolean: true }, $skip: true } }) // builder interface diff --git a/src/SelectionSet/SelectionSet.ts b/src/SelectionSet/SelectionSet.ts index c3013bc3c..0fe58ee7e 100644 --- a/src/SelectionSet/SelectionSet.ts +++ b/src/SelectionSet/SelectionSet.ts @@ -225,26 +225,28 @@ $Field['args'] extends Schema.Field.Args ? $Field['args']['allOptional'] extend NoArgsIndicator // dprint-ignore -export type Args<$Args extends Schema.Field.Args> = -& { - [ - Key in keyof $Args['fields'] as $Args['fields'][Key] extends Schema.Field.Nullable ? never : Key - ]: InferTypeInput<$Args['fields'][Key]> -} -& { - [ - Key in keyof $Args['fields'] as $Args['fields'][Key] extends Schema.Field.Nullable ? Key : never - ]?: null | InferTypeInput<$Args['fields'][Key]> -} +export type Args<$Args extends Schema.Field.Args> = ArgFields<$Args['fields']> + +export type ArgFields<$ArgFields extends Schema.Named.InputObject['fields']> = + & { + [ + Key in keyof $ArgFields as $ArgFields[Key] extends Schema.Field.Input.Nullable ? never : Key + ]: InferTypeInput<$ArgFields[Key]> + } + & { + [ + Key in keyof $ArgFields as $ArgFields[Key] extends Schema.Field.Input.Nullable ? Key : never + ]?: null | InferTypeInput<$ArgFields[Key]> + } -// todo input objects // dprint-ignore type InferTypeInput<$InputType extends Schema.Field.Input.Any> = - $InputType extends Schema.Field.Input.Nullable ? InferTypeInput<$InputType['type']> | null : - $InputType extends Schema.Field.Input.List ? InferTypeInput<$InputType['type']>[] : - $InputType extends Schema.Named.Enum ? $Members[number] : - $InputType extends Schema.Named.Scalar.Any ? ReturnType<$InputType['constructor']> : - TSError<'InferTypeInput', 'Unknown $InputType', { $InputType: $InputType }> // never + $InputType extends Schema.Field.Input.Nullable ? InferTypeInput<$InnerType> | null : + $InputType extends Schema.Field.Input.List ? InferTypeInput<$InnerType>[] : + $InputType extends Schema.Named.InputObject ? ArgFields<$Fields> : + $InputType extends Schema.Named.Enum ? $Members[number] : + $InputType extends Schema.Named.Scalar.Any ? ReturnType<$InputType['constructor']> : + TSError<'InferTypeInput', 'Unknown $InputType', { $InputType: $InputType }> // never /** * @see https://spec.graphql.org/draft/#sec-Type-System.Directives.Built-in-Directives @@ -267,13 +269,3 @@ export interface FieldDirectives { */ $stream?: boolean | { if?: boolean; label?: string; initialCount?: number } } - -// type UnwrapListAndNullableFieldType<$Field extends Schema.Field> = UnwrapListAndNullableFieldType_<$Field['type']> - -// // dprint-ignore -// type UnwrapListAndNullableFieldType_ = -// FT extends Schema.FieldTypeList ? UnwrapListAndNullableFieldType_ : -// FT extends Schema.FieldTypeNullable ? UnwrapListAndNullableFieldType_ : -// FT extends Schema.FieldTypeLiteral ? FT : -// FT extends Schema.FieldTypeNamed ? FT -// : TSError<'UnwrapFieldType_', 'FT case not handled', { FT: FT }> diff --git a/src/generator/generator.ts b/src/generator/generator.ts index b14b28d87..ab7f7efa6 100644 --- a/src/generator/generator.ts +++ b/src/generator/generator.ts @@ -18,6 +18,7 @@ import type { AnyNamedClassName, ClassToName, Describable, + NamedNameToClass, NameToClassNamedType, TypeMapByKind, } from '../lib/graphql.js' @@ -45,12 +46,12 @@ type AnyGraphQLFieldsType = | GraphQLInputObjectType const defineReferenceRenderers = < - $Renderers extends { [ClassName in keyof NameToClass]: any }, + $Renderers extends { [ClassName in keyof NamedNameToClass]: any }, >( renderers: { [ClassName in keyof $Renderers]: ( config: Config, - node: ClassName extends keyof NameToClass ? InstanceType + node: ClassName extends keyof NamedNameToClass ? InstanceType : never, ) => string }, @@ -86,8 +87,10 @@ const defineConcreteRenderers = < } const dispatchToReferenceRenderer = (config: Config, node: AnyClass): string => + // @ts-expect-error fixme getReferenceRenderer(node)(config, node as any) +// @ts-expect-error fixme const getReferenceRenderer = (node: N): (typeof referenceRenderers)[ClassToName] => { // @ts-expect-error lookup const renderer = referenceRenderers[node.constructor.name] // eslint-disable-line @@ -98,13 +101,11 @@ const getReferenceRenderer = (node: N): (typeof referenceRen } const referenceRenderers = defineReferenceRenderers({ - GraphQLNonNull: (config, node) => dispatchToReferenceRenderer(config, node.ofType), GraphQLEnumType: (_, node) => Code.propertyAccess(namespaceNames.GraphQLEnumType, node.name), GraphQLInputObjectType: (_, node) => Code.propertyAccess(namespaceNames.GraphQLInputObjectType, node.name), GraphQLInterfaceType: (_, node) => Code.propertyAccess(namespaceNames.GraphQLInterfaceType, node.name), GraphQLObjectType: (_, node) => Code.propertyAccess(namespaceNames.GraphQLObjectType, node.name), GraphQLUnionType: (_, node) => Code.propertyAccess(namespaceNames.GraphQLUnionType, node.name), - GraphQLList: (config, node) => `_.List<${(buildType(config, node.ofType))}>`, GraphQLScalarType: (_, node) => `_.Scalar.${node.name}`, }) @@ -134,7 +135,7 @@ const concreteRenderers = defineConcreteRenderers({ GraphQLInputObjectType: (config, node) => Code.TSDoc( getDocumentation(config, node), - Code.export$(Code.interface$(node.name, renderFields(config, node))), + Code.export$(Code.type(node.name, `_.InputObject<${Code.quote(node.name)}, ${renderInputFields(config, node)}>`)), ), GraphQLInterfaceType: (config, node) => { const implementors = config.typeMapByKind.GraphQLObjectType.filter(_ => @@ -144,7 +145,7 @@ const concreteRenderers = defineConcreteRenderers({ getDocumentation(config, node), Code.export$(Code.type( node.name, - `_.Interface<${Code.quote(node.name)}, ${renderFields(config, node)}, ${ + `_.Interface<${Code.quote(node.name)}, ${renderOutputFields(config, node)}, ${ Code.tuple(implementors.map(_ => `Object.${_.name}`)) }>`, )), @@ -153,7 +154,7 @@ const concreteRenderers = defineConcreteRenderers({ GraphQLObjectType: (config, node) => Code.TSDoc( getDocumentation(config, node), - Code.export$(Code.type(node.name, `_.Object<${Code.quote(node.name)}, ${renderFields(config, node)}>`)), + Code.export$(Code.type(node.name, `_.Object<${Code.quote(node.name)}, ${renderOutputFields(config, node)}>`)), ), GraphQLScalarType: () => ``, GraphQLUnionType: (config, node) => @@ -226,18 +227,44 @@ const getDocumentation = (config: Config, node: Describable) => { const defaultDescription = (node: Describable) => `There is no documentation for this ${getNodeDisplayName(node)}.` -const renderFields = (config: Config, node: AnyGraphQLFieldsType): string => { +const renderOutputFields = (config: Config, node: AnyGraphQLFieldsType): string => { return Code.object(Code.fields([ ...values(node.getFields()).map((field) => Code.TSDoc( getDocumentation(config, field), - Code.field(field.name, renderField(config, field)), + Code.field(field.name, renderOutputField(config, field)), ) ), ])) } -const buildType = (config: Config, node: AnyClass) => { +const renderInputFields = (config: Config, node: AnyGraphQLFieldsType): string => { + return Code.object(Code.fields([ + ...values(node.getFields()).map((field) => + Code.TSDoc( + getDocumentation(config, field), + Code.field(field.name, renderInputField(config, field)), + ) + ), + ])) +} + +const renderOutputField = (config: Config, field: AnyField): string => { + const type = buildType(`output`, config, field.type) + + const args = isGraphQLOutputField(field) && field.args.length > 0 + ? renderArgs(config, field.args) + : null + + return `_.Field<${type}${args ? `, ${args}` : ``}>` +} + +const renderInputField = (config: Config, field: AnyField): string => { + return buildType(`input`, config, field.type) +} + +const buildType = (direction: 'input' | 'output', config: Config, node: AnyClass) => { + const ns = direction === `input` ? `Input` : `Output` const { node: nodeInner, nullable } = unwrapNonNull(node) if (isNamedType(nodeInner)) { @@ -245,60 +272,37 @@ const buildType = (config: Config, node: AnyClass) => { // const namedTypeCode = `_.Named<${namedTypeReference}>` const namedTypeCode = namedTypeReference return nullable - ? `_.Nullable<${namedTypeCode}>` + ? `_.${ns}.Nullable<${namedTypeCode}>` : namedTypeCode } if (isListType(nodeInner)) { - const fieldType = `_.List<${buildType(config, nodeInner.ofType)}>` as any as string + const fieldType = `_.${ns}.List<${buildType(direction, config, nodeInner.ofType)}>` as any as string return nullable - ? `_.Nullable<${fieldType}>` + ? `_.${ns}.Nullable<${fieldType}>` : fieldType } throw new Error(`Unhandled type: ${String(node)}`) } -// const getNamedType = (config: Config, node: AnyClass): GraphQLNamedType => { -// if (isNamedType(node)) return node -// if (isNonNullType(node)) return getNamedType(config, node.ofType) -// if (isListType(node)) return getNamedType(config, node.ofType) -// throw new Error(`Unhandled type: ${String(node)}`) -// } - -const renderField = (config: Config, field: AnyField): string => { - const type = buildType(config, field.type) - - const args = isGraphQLOutputField(field) && field.args.length > 0 - ? renderArgs(config, field.args) - : null - - return `_.Field<${type}${args ? `, ${args}` : ``}>` -} - const renderArgs = (config: Config, args: readonly GraphQLArgument[]) => { let hasRequiredArgs = false const argsRendered = `_.Args<${ Code.object( Code.fields( args.map((arg) => { - const { node, nullable } = unwrapNonNull(arg.type) + const { nullable } = unwrapNonNull(arg.type) hasRequiredArgs = hasRequiredArgs || !nullable return Code.field( arg.name, - nullable - ? `_.Nullable<${dispatchToReferenceRenderer(config, node)}>` - : dispatchToReferenceRenderer(config, node), + buildType(`input`, config, arg.type), ) }), ), ) }>` return argsRendered - return Code.objectFrom({ - type: { type: argsRendered }, - // allOptional: { type: !hasRequiredArgs }, - }) } const unwrapNonNull = ( diff --git a/tests/ts/_/schema.graphql b/tests/ts/_/schema.graphql index f4edb7118..aab12c38a 100644 --- a/tests/ts/_/schema.graphql +++ b/tests/ts/_/schema.graphql @@ -8,6 +8,8 @@ type Query { stringWithArgEnum(ABCEnum:ABCEnum): String stringWithListArg(ints:[Int]): String stringWithListArgRequired(ints:[Int]!): String + stringWithArgInputObject(input:InputObject): String + stringWithArgInputObjectRequired(input:InputObject!): String object: Object listListIntNonNull: [[Int!]!]! listListInt: [[Int]] @@ -24,6 +26,11 @@ type Query { lowerCaseUnion: lowerCaseUnion } +input InputObject { + id: ID + idRequired: ID! +} + """ Union documentation. """ diff --git a/tests/ts/_/schema.ts b/tests/ts/_/schema.ts index 3c795cd83..151503ada 100644 --- a/tests/ts/_/schema.ts +++ b/tests/ts/_/schema.ts @@ -39,67 +39,81 @@ export namespace $ { export namespace Root { export type Query = _.Object<'Query', { - interface: _.Field<_.Nullable> - id: _.Field<_.Nullable<_.Scalar.ID>> + interface: _.Field<_.Output.Nullable> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> idNonNull: _.Field<_.Scalar.ID> - string: _.Field<_.Nullable<_.Scalar.String>> + string: _.Field<_.Output.Nullable<_.Scalar.String>> stringWithRequiredArg: _.Field< - _.Nullable<_.Scalar.String>, + _.Output.Nullable<_.Scalar.String>, _.Args<{ string: _.Scalar.String }> > stringWithArgs: _.Field< - _.Nullable<_.Scalar.String>, + _.Output.Nullable<_.Scalar.String>, _.Args<{ - string: _.Nullable<_.Scalar.String> - int: _.Nullable<_.Scalar.Int> - float: _.Nullable<_.Scalar.Float> - boolean: _.Nullable<_.Scalar.Boolean> - id: _.Nullable<_.Scalar.ID> + string: _.Input.Nullable<_.Scalar.String> + int: _.Input.Nullable<_.Scalar.Int> + float: _.Input.Nullable<_.Scalar.Float> + boolean: _.Input.Nullable<_.Scalar.Boolean> + id: _.Input.Nullable<_.Scalar.ID> }> > stringWithArgEnum: _.Field< - _.Nullable<_.Scalar.String>, + _.Output.Nullable<_.Scalar.String>, _.Args<{ - ABCEnum: _.Nullable + ABCEnum: _.Input.Nullable }> > stringWithListArg: _.Field< - _.Nullable<_.Scalar.String>, + _.Output.Nullable<_.Scalar.String>, _.Args<{ - ints: _.Nullable<_.List<_.Nullable<_.Scalar.Int>>> + ints: _.Input.Nullable<_.Input.List<_.Input.Nullable<_.Scalar.Int>>> }> > stringWithListArgRequired: _.Field< - _.Nullable<_.Scalar.String>, + _.Output.Nullable<_.Scalar.String>, _.Args<{ - ints: _.List<_.Nullable<_.Scalar.Int>> + ints: _.Input.List<_.Input.Nullable<_.Scalar.Int>> }> > - object: _.Field<_.Nullable> - listListIntNonNull: _.Field<_.List<_.List<_.Scalar.Int>>> - listListInt: _.Field<_.Nullable<_.List<_.Nullable<_.List<_.Nullable<_.Scalar.Int>>>>>> - listInt: _.Field<_.Nullable<_.List<_.Nullable<_.Scalar.Int>>>> - listIntNonNull: _.Field<_.List<_.Scalar.Int>> - objectNested: _.Field<_.Nullable> + stringWithArgInputObject: _.Field< + _.Output.Nullable<_.Scalar.String>, + _.Args<{ + input: _.Input.Nullable + }> + > + stringWithArgInputObjectRequired: _.Field< + _.Output.Nullable<_.Scalar.String>, + _.Args<{ + input: InputObject.InputObject + }> + > + object: _.Field<_.Output.Nullable> + listListIntNonNull: _.Field<_.Output.List<_.Output.List<_.Scalar.Int>>> + listListInt: _.Field< + _.Output.Nullable<_.Output.List<_.Output.Nullable<_.Output.List<_.Output.Nullable<_.Scalar.Int>>>>> + > + listInt: _.Field<_.Output.Nullable<_.Output.List<_.Output.Nullable<_.Scalar.Int>>>> + listIntNonNull: _.Field<_.Output.List<_.Scalar.Int>> + objectNested: _.Field<_.Output.Nullable> objectNonNull: _.Field objectWithArgs: _.Field< - _.Nullable, + _.Output.Nullable, _.Args<{ - string: _.Nullable<_.Scalar.String> - int: _.Nullable<_.Scalar.Int> - float: _.Nullable<_.Scalar.Float> - boolean: _.Nullable<_.Scalar.Boolean> - id: _.Nullable<_.Scalar.ID> + string: _.Input.Nullable<_.Scalar.String> + int: _.Input.Nullable<_.Scalar.Int> + float: _.Input.Nullable<_.Scalar.Float> + boolean: _.Input.Nullable<_.Scalar.Boolean> + id: _.Input.Nullable<_.Scalar.ID> }> > - fooBarUnion: _.Field<_.Nullable> + fooBarUnion: _.Field<_.Output.Nullable> /** * Query enum field documentation. */ - abcEnum: _.Field<_.Nullable> - lowerCaseUnion: _.Field<_.Nullable> + abcEnum: _.Field<_.Output.Nullable> + lowerCaseUnion: _.Field<_.Output.Nullable> }> } @@ -124,7 +138,10 @@ export namespace Enum { // ------------------------------------------------------------ // export namespace InputObject { - // -- no types -- + export type InputObject = _.InputObject<'InputObject', { + id: _.Input.Nullable<_.Scalar.ID> + idRequired: _.Scalar.ID + }> } // ------------------------------------------------------------ // @@ -133,7 +150,7 @@ export namespace InputObject { export namespace Interface { export type Interface = _.Interface<'Interface', { - id: _.Field<_.Nullable<_.Scalar.ID>> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> }, [Object.Object1ImplementingInterface, Object.Object2ImplementingInterface]> } @@ -151,42 +168,42 @@ export namespace Object { * * @deprecated Field a is deprecated. */ - id: _.Field<_.Nullable<_.Scalar.ID>> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> }> export type Bar = _.Object<'Bar', { - int: _.Field<_.Nullable<_.Scalar.Int>> + int: _.Field<_.Output.Nullable<_.Scalar.Int>> }> export type ObjectNested = _.Object<'ObjectNested', { - id: _.Field<_.Nullable<_.Scalar.ID>> - object: _.Field<_.Nullable> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> + object: _.Field<_.Output.Nullable> }> export type lowerCaseObject = _.Object<'lowerCaseObject', { - id: _.Field<_.Nullable<_.Scalar.ID>> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> }> export type lowerCaseObject2 = _.Object<'lowerCaseObject2', { - int: _.Field<_.Nullable<_.Scalar.Int>> + int: _.Field<_.Output.Nullable<_.Scalar.Int>> }> export type Object = _.Object<'Object', { - string: _.Field<_.Nullable<_.Scalar.String>> - int: _.Field<_.Nullable<_.Scalar.Int>> - float: _.Field<_.Nullable<_.Scalar.Float>> - boolean: _.Field<_.Nullable<_.Scalar.Boolean>> - id: _.Field<_.Nullable<_.Scalar.ID>> + string: _.Field<_.Output.Nullable<_.Scalar.String>> + int: _.Field<_.Output.Nullable<_.Scalar.Int>> + float: _.Field<_.Output.Nullable<_.Scalar.Float>> + boolean: _.Field<_.Output.Nullable<_.Scalar.Boolean>> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> }> export type Object1ImplementingInterface = _.Object<'Object1ImplementingInterface', { - id: _.Field<_.Nullable<_.Scalar.ID>> - int: _.Field<_.Nullable<_.Scalar.Int>> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> + int: _.Field<_.Output.Nullable<_.Scalar.Int>> }> export type Object2ImplementingInterface = _.Object<'Object2ImplementingInterface', { - id: _.Field<_.Nullable<_.Scalar.ID>> - boolean: _.Field<_.Nullable<_.Scalar.Boolean>> + id: _.Field<_.Output.Nullable<_.Scalar.ID>> + boolean: _.Field<_.Output.Nullable<_.Scalar.Boolean>> }> } diff --git a/tests/ts/__snapshots__/generate.test.ts.snap b/tests/ts/__snapshots__/generate.test.ts.snap index ef3d076f3..b3b6722b2 100644 --- a/tests/ts/__snapshots__/generate.test.ts.snap +++ b/tests/ts/__snapshots__/generate.test.ts.snap @@ -41,49 +41,55 @@ Boolean: boolean export namespace Root { export type Query = _.Object<"Query", { -interface: _.Field<_.Nullable> -id: _.Field<_.Nullable<_.Scalar.ID>> +interface: _.Field<_.Output.Nullable> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> idNonNull: _.Field<_.Scalar.ID> -string: _.Field<_.Nullable<_.Scalar.String>> -stringWithRequiredArg: _.Field<_.Nullable<_.Scalar.String>, _.Args<{ +string: _.Field<_.Output.Nullable<_.Scalar.String>> +stringWithRequiredArg: _.Field<_.Output.Nullable<_.Scalar.String>, _.Args<{ string: _.Scalar.String }>> -stringWithArgs: _.Field<_.Nullable<_.Scalar.String>, _.Args<{ -string: _.Nullable<_.Scalar.String> -int: _.Nullable<_.Scalar.Int> -float: _.Nullable<_.Scalar.Float> -boolean: _.Nullable<_.Scalar.Boolean> -id: _.Nullable<_.Scalar.ID> +stringWithArgs: _.Field<_.Output.Nullable<_.Scalar.String>, _.Args<{ +string: _.Input.Nullable<_.Scalar.String> +int: _.Input.Nullable<_.Scalar.Int> +float: _.Input.Nullable<_.Scalar.Float> +boolean: _.Input.Nullable<_.Scalar.Boolean> +id: _.Input.Nullable<_.Scalar.ID> }>> -stringWithArgEnum: _.Field<_.Nullable<_.Scalar.String>, _.Args<{ -ABCEnum: _.Nullable +stringWithArgEnum: _.Field<_.Output.Nullable<_.Scalar.String>, _.Args<{ +ABCEnum: _.Input.Nullable }>> -stringWithListArg: _.Field<_.Nullable<_.Scalar.String>, _.Args<{ -ints: _.Nullable<_.List<_.Nullable<_.Scalar.Int>>> +stringWithListArg: _.Field<_.Output.Nullable<_.Scalar.String>, _.Args<{ +ints: _.Input.Nullable<_.Input.List<_.Input.Nullable<_.Scalar.Int>>> }>> -stringWithListArgRequired: _.Field<_.Nullable<_.Scalar.String>, _.Args<{ -ints: _.List<_.Nullable<_.Scalar.Int>> +stringWithListArgRequired: _.Field<_.Output.Nullable<_.Scalar.String>, _.Args<{ +ints: _.Input.List<_.Input.Nullable<_.Scalar.Int>> }>> -object: _.Field<_.Nullable> -listListIntNonNull: _.Field<_.List<_.List<_.Scalar.Int>>> -listListInt: _.Field<_.Nullable<_.List<_.Nullable<_.List<_.Nullable<_.Scalar.Int>>>>>> -listInt: _.Field<_.Nullable<_.List<_.Nullable<_.Scalar.Int>>>> -listIntNonNull: _.Field<_.List<_.Scalar.Int>> -objectNested: _.Field<_.Nullable> +stringWithArgInputObject: _.Field<_.Output.Nullable<_.Scalar.String>, _.Args<{ +input: _.Input.Nullable +}>> +stringWithArgInputObjectRequired: _.Field<_.Output.Nullable<_.Scalar.String>, _.Args<{ +input: InputObject.InputObject +}>> +object: _.Field<_.Output.Nullable> +listListIntNonNull: _.Field<_.Output.List<_.Output.List<_.Scalar.Int>>> +listListInt: _.Field<_.Output.Nullable<_.Output.List<_.Output.Nullable<_.Output.List<_.Output.Nullable<_.Scalar.Int>>>>>> +listInt: _.Field<_.Output.Nullable<_.Output.List<_.Output.Nullable<_.Scalar.Int>>>> +listIntNonNull: _.Field<_.Output.List<_.Scalar.Int>> +objectNested: _.Field<_.Output.Nullable> objectNonNull: _.Field -objectWithArgs: _.Field<_.Nullable, _.Args<{ -string: _.Nullable<_.Scalar.String> -int: _.Nullable<_.Scalar.Int> -float: _.Nullable<_.Scalar.Float> -boolean: _.Nullable<_.Scalar.Boolean> -id: _.Nullable<_.Scalar.ID> +objectWithArgs: _.Field<_.Output.Nullable, _.Args<{ +string: _.Input.Nullable<_.Scalar.String> +int: _.Input.Nullable<_.Scalar.Int> +float: _.Input.Nullable<_.Scalar.Float> +boolean: _.Input.Nullable<_.Scalar.Boolean> +id: _.Input.Nullable<_.Scalar.ID> }>> -fooBarUnion: _.Field<_.Nullable> +fooBarUnion: _.Field<_.Output.Nullable> /** * Query enum field documentation. */ -abcEnum: _.Field<_.Nullable> -lowerCaseUnion: _.Field<_.Nullable> +abcEnum: _.Field<_.Output.Nullable> +lowerCaseUnion: _.Field<_.Output.Nullable> }> } @@ -108,8 +114,10 @@ export type ABCEnum = _.Enum<"ABCEnum", ["A", "B", "C"] > // ------------------------------------------------------------ // export namespace InputObject { -// -- no types -- - +export type InputObject = _.InputObject<"InputObject", { +id: _.Input.Nullable<_.Scalar.ID> +idRequired: _.Scalar.ID +}> } // ------------------------------------------------------------ // @@ -118,7 +126,7 @@ export namespace InputObject { export namespace Interface { export type Interface = _.Interface<"Interface", { -id: _.Field<_.Nullable<_.Scalar.ID>> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> }, [Object.Object1ImplementingInterface, Object.Object2ImplementingInterface]> } @@ -136,42 +144,42 @@ export type Foo = _.Object<"Foo", { * * @deprecated Field a is deprecated. */ -id: _.Field<_.Nullable<_.Scalar.ID>> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> }> export type Bar = _.Object<"Bar", { -int: _.Field<_.Nullable<_.Scalar.Int>> +int: _.Field<_.Output.Nullable<_.Scalar.Int>> }> export type ObjectNested = _.Object<"ObjectNested", { -id: _.Field<_.Nullable<_.Scalar.ID>> -object: _.Field<_.Nullable> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> +object: _.Field<_.Output.Nullable> }> export type lowerCaseObject = _.Object<"lowerCaseObject", { -id: _.Field<_.Nullable<_.Scalar.ID>> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> }> export type lowerCaseObject2 = _.Object<"lowerCaseObject2", { -int: _.Field<_.Nullable<_.Scalar.Int>> +int: _.Field<_.Output.Nullable<_.Scalar.Int>> }> export type Object = _.Object<"Object", { -string: _.Field<_.Nullable<_.Scalar.String>> -int: _.Field<_.Nullable<_.Scalar.Int>> -float: _.Field<_.Nullable<_.Scalar.Float>> -boolean: _.Field<_.Nullable<_.Scalar.Boolean>> -id: _.Field<_.Nullable<_.Scalar.ID>> +string: _.Field<_.Output.Nullable<_.Scalar.String>> +int: _.Field<_.Output.Nullable<_.Scalar.Int>> +float: _.Field<_.Output.Nullable<_.Scalar.Float>> +boolean: _.Field<_.Output.Nullable<_.Scalar.Boolean>> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> }> export type Object1ImplementingInterface = _.Object<"Object1ImplementingInterface", { -id: _.Field<_.Nullable<_.Scalar.ID>> -int: _.Field<_.Nullable<_.Scalar.Int>> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> +int: _.Field<_.Output.Nullable<_.Scalar.Int>> }> export type Object2ImplementingInterface = _.Object<"Object2ImplementingInterface", { -id: _.Field<_.Nullable<_.Scalar.ID>> -boolean: _.Field<_.Nullable<_.Scalar.Boolean>> +id: _.Field<_.Output.Nullable<_.Scalar.ID>> +boolean: _.Field<_.Output.Nullable<_.Scalar.Boolean>> }> }