diff --git a/src/__tests__/fixtures/simpleSchema.ts b/src/__tests__/fixtures/simpleSchema.ts index 660c2634..f29e795c 100644 --- a/src/__tests__/fixtures/simpleSchema.ts +++ b/src/__tests__/fixtures/simpleSchema.ts @@ -425,4 +425,583 @@ export type simpleSchema = { }; }; }; + + typesList: [ + { + kind: 'INPUT_OBJECT'; + name: 'TodoPayload'; + fields: null; + inputFields: [ + { + name: 'title'; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }; + defaultValue: null; + }, + { + name: 'complete'; + type: { + kind: 'SCALAR'; + name: 'Boolean'; + ofType: null; + }; + defaultValue: null; + }, + ]; + interfaces: null; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'OBJECT'; + name: 'Query'; + fields: [ + { + name: 'todos'; + args: []; + type: { + kind: 'LIST'; + name: null; + ofType: { + kind: 'OBJECT'; + name: 'Todo'; + ofType: null; + }; + }; + }, + { + name: 'test'; + args: []; + type: { + kind: 'UNION'; + name: 'Search'; + ofType: null; + }; + }, + { + name: 'latestTodo'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'UNION'; + name: 'LatestTodoResult'; + ofType: null; + }; + }; + }, + ]; + inputFields: null; + interfaces: []; + enumValues: null; + possibleTypes: null; + }, + { + name: 'LatestTodoResult'; + kind: 'UNION'; + args: []; + possibleTypes: [ + { + kind: 'OBJECT'; + name: 'Todo'; + ofType: null; + }, + { + kind: 'OBJECT'; + name: 'NoTodosError'; + ofType: null; + }, + ]; + }, + { + kind: 'OBJECT'; + name: 'NoTodosError'; + interfaces: []; + fields: [ + { + name: 'message'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }; + }, + ]; + }, + { + kind: 'OBJECT'; + name: 'Todo'; + fields: [ + { + name: 'id'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'ID'; + ofType: null; + }; + }; + }, + { + name: 'text'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }; + }, + { + name: 'complete'; + args: []; + type: { + kind: 'SCALAR'; + name: 'Boolean'; + ofType: null; + }; + }, + { + name: 'test'; + args: []; + type: { + kind: 'ENUM'; + name: 'test'; + ofType: null; + }; + }, + { + name: 'author'; + args: []; + type: { + kind: 'OBJECT'; + name: 'Author'; + ofType: null; + }; + }, + ]; + inputFields: null; + interfaces: []; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'SCALAR'; + name: 'ID'; + fields: null; + inputFields: null; + interfaces: null; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'SCALAR'; + name: 'String'; + fields: null; + inputFields: null; + interfaces: null; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'SCALAR'; + name: 'Boolean'; + fields: null; + inputFields: null; + interfaces: null; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'OBJECT'; + name: 'Author'; + fields: [ + { + name: 'id'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'ID'; + ofType: null; + }; + }; + }, + { + name: 'name'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }; + }, + { + name: 'known'; + args: []; + type: { + kind: 'SCALAR'; + name: 'Boolean'; + ofType: null; + }; + }, + ]; + inputFields: null; + interfaces: []; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'OBJECT'; + name: 'Mutation'; + fields: [ + { + name: 'updateTodo'; + args: [ + { + name: 'id'; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'ID'; + ofType: null; + }; + }; + defaultValue: null; + }, + { + name: 'input'; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'INPUT_OBJECT'; + name: 'TodoPayload'; + ofType: null; + }; + }; + defaultValue: null; + }, + ]; + type: { + kind: 'SCALAR'; + name: 'Boolean'; + ofType: null; + }; + }, + { + name: 'toggleTodo'; + args: [ + { + name: 'id'; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'ID'; + ofType: null; + }; + }; + }, + ]; + type: { + kind: 'OBJECT'; + name: 'Todo'; + ofType: null; + }; + }, + ]; + inputFields: null; + interfaces: []; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'OBJECT'; + name: 'Subscription'; + fields: [ + { + name: 'newTodo'; + args: []; + type: { + kind: 'OBJECT'; + name: 'Todo'; + ofType: null; + }; + }, + ]; + inputFields: null; + interfaces: []; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'ENUM'; + name: 'test'; + fields: null; + inputFields: null; + interfaces: null; + enumValues: [{ name: 'value' }, { name: 'more' }]; + }, + { + kind: 'INTERFACE'; + name: 'ITodo'; + fields: [ + { + name: 'id'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'ID'; + ofType: null; + }; + }; + }, + { + name: 'text'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }; + }, + { + name: 'complete'; + args: []; + type: { + kind: 'SCALAR'; + name: 'Boolean'; + ofType: null; + }; + }, + { + name: 'author'; + args: []; + type: { + kind: 'OBJECT'; + name: 'Author'; + ofType: null; + }; + }, + ]; + inputFields: null; + interfaces: null; + enumValues: null; + possibleTypes: [ + { + kind: 'OBJECT'; + name: 'BigTodo'; + ofType: null; + }, + { + kind: 'OBJECT'; + name: 'SmallTodo'; + ofType: null; + }, + ]; + }, + { + kind: 'OBJECT'; + name: 'BigTodo'; + fields: [ + { + name: 'id'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'ID'; + ofType: null; + }; + }; + }, + { + name: 'text'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }; + }, + { + name: 'complete'; + args: []; + type: { + kind: 'SCALAR'; + name: 'Boolean'; + ofType: null; + }; + }, + { + name: 'author'; + args: []; + type: { + kind: 'OBJECT'; + name: 'Author'; + ofType: null; + }; + }, + { + name: 'wallOfText'; + args: []; + type: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }, + ]; + inputFields: null; + interfaces: [ + { + kind: 'INTERFACE'; + name: 'ITodo'; + ofType: null; + }, + ]; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'OBJECT'; + name: 'SmallTodo'; + fields: [ + { + name: 'id'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'ID'; + ofType: null; + }; + }; + }, + { + name: 'text'; + args: []; + type: { + kind: 'NON_NULL'; + name: null; + ofType: { + kind: 'SCALAR'; + name: 'String'; + ofType: null; + }; + }; + }, + { + name: 'complete'; + args: []; + type: { + kind: 'SCALAR'; + name: 'Boolean'; + ofType: null; + }; + }, + { + name: 'author'; + args: []; + type: { + kind: 'OBJECT'; + name: 'Author'; + ofType: null; + }; + }, + { + name: 'maxLength'; + args: []; + type: { + kind: 'SCALAR'; + name: 'Int'; + ofType: null; + }; + }, + ]; + inputFields: null; + interfaces: [ + { + kind: 'INTERFACE'; + name: 'ITodo'; + ofType: null; + }, + ]; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'SCALAR'; + name: 'Int'; + fields: null; + inputFields: null; + interfaces: null; + enumValues: null; + possibleTypes: null; + }, + { + kind: 'UNION'; + name: 'Search'; + fields: null; + inputFields: null; + interfaces: null; + enumValues: null; + possibleTypes: [ + { + kind: 'OBJECT'; + name: 'SmallTodo'; + ofType: null; + }, + { + kind: 'OBJECT'; + name: 'BigTodo'; + ofType: null; + }, + ]; + }, + ]; }; diff --git a/src/introspection.ts b/src/introspection.ts index 5730436e..f78076e0 100644 --- a/src/introspection.ts +++ b/src/introspection.ts @@ -221,11 +221,16 @@ type mapIntrospection< ? Query['__schema']['subscriptionType']['name'] : never; types: mapIntrospectionTypes; + introspection: Query; + typesList: Query['__schema']['types']; }; type getScalarTypeNames = - Schema['types'][keyof Schema['types']] extends infer Type - ? Type extends { kind: 'SCALAR' | 'ENUM'; name: any } + Schema['typesList'][number] extends infer Type + ? Type extends { + kind: 'ENUM' | 'SCALAR'; + name: any; + } ? Type['name'] : never : never; @@ -248,6 +253,8 @@ export type IntrospectionLikeType = { mutation?: any; subscription?: any; types: { [name: string]: any }; + introspection?: any; + typesList?: any; }; export type { mapIntrospectionTypes, mapIntrospection, getScalarType, getScalarTypeNames }; diff --git a/src/selection.ts b/src/selection.ts index ef5e2242..1b09d745 100644 --- a/src/selection.ts +++ b/src/selection.ts @@ -28,7 +28,8 @@ type _unwrapTypeRec< SelectionSet['selections'], Introspection['types'][Type['name']], Introspection, - Fragments + Fragments, + never > : unknown : Introspection['types'][Type['name']]['type'] @@ -73,8 +74,15 @@ type getFragmentSelection< Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, + UnionAccumulator, > = Node extends { kind: Kind.INLINE_FRAGMENT; selectionSet: any } - ? getSelection + ? getSelection< + Node['selectionSet']['selections'], + Type, + Introspection, + Fragments, + UnionAccumulator + > : Node extends { kind: Kind.FRAGMENT_SPREAD; name: any } ? Node['name']['value'] extends keyof Fragments ? Fragments[Node['name']['value']] extends { [$tada.ref]: any } @@ -83,10 +91,11 @@ type getFragmentSelection< Fragments[Node['name']['value']]['selectionSet']['selections'], Type, Introspection, - Fragments + Fragments, + UnionAccumulator > - : {} - : {}; + : {} | UnionAccumulator + : {} | UnionAccumulator; type getSpreadSubtype< Node, @@ -112,20 +121,29 @@ type getSelection< Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, + UnionAccumulator, > = obj< Type extends { kind: 'UNION' | 'INTERFACE'; possibleTypes: any } ? objValues<{ - [PossibleType in Type['possibleTypes']]: _getPossibleTypeSelectionRec< + [PossibleType in Type['possibleTypes']]: getPossibleTypeSelectionRec< Selections, PossibleType, Type, Introspection, - Fragments + Fragments, + UnionAccumulator >; }> : Type extends { kind: 'OBJECT'; name: any } - ? _getPossibleTypeSelectionRec - : {} + ? getPossibleTypeSelectionRec< + Selections, + Type['name'], + Type, + Introspection, + Fragments, + UnionAccumulator + > + : {} | UnionAccumulator >; type _getPossibleTypeSelectionRec< @@ -133,46 +151,82 @@ type _getPossibleTypeSelectionRec< PossibleType extends string, Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, - Fragments extends { [name: string]: any }, + Fragments extends { + [name: string]: any; + }, + UnionAccumulator, + IntersectionAcc, > = Selections extends [infer Node, ...infer Rest] - ? (Node extends { kind: Kind.FRAGMENT_SPREAD | Kind.INLINE_FRAGMENT } - ? getSpreadSubtype extends infer Subtype extends - ObjectLikeType - ? PossibleType extends getTypenameOfType - ? - | (isOptional extends true ? {} : never) - | getFragmentSelection - : {} - : Node extends { kind: Kind.FRAGMENT_SPREAD; name: any } - ? makeUndefinedFragmentRef - : {} - : Node extends { kind: Kind.FIELD; name: any; selectionSet: any } - ? isOptional extends true - ? { - [Prop in getFieldAlias]?: Node['name']['value'] extends '__typename' - ? PossibleType - : unwrapType< - Type['fields'][Node['name']['value']]['type'], - Node['selectionSet'], - Introspection, - Fragments, - getTypeDirective - >; - } - : { - [Prop in getFieldAlias]: Node['name']['value'] extends '__typename' - ? PossibleType - : unwrapType< - Type['fields'][Node['name']['value']]['type'], - Node['selectionSet'], - Introspection, - Fragments, - getTypeDirective - >; - } - : {}) & - _getPossibleTypeSelectionRec - : {}; + ? _getPossibleTypeSelectionRec< + Rest, + PossibleType, + Type, + Introspection, + Fragments, + UnionAccumulator, + | ((Node extends { kind: Kind.FRAGMENT_SPREAD | Kind.INLINE_FRAGMENT } + ? getSpreadSubtype extends infer Subtype extends + ObjectLikeType + ? PossibleType extends getTypenameOfType + ? getFragmentSelection< + Node, + Subtype, + Introspection, + Fragments, + isOptional extends true ? {} : never + > + : {} + : Node extends { kind: Kind.FRAGMENT_SPREAD; name: any } + ? makeUndefinedFragmentRef + : {} + : Node extends { kind: Kind.FIELD; name: any; selectionSet: any } + ? isOptional extends true + ? { + [Prop in getFieldAlias]?: Node['name']['value'] extends '__typename' + ? PossibleType + : unwrapType< + Type['fields'][Node['name']['value']]['type'], + Node['selectionSet'], + Introspection, + Fragments, + getTypeDirective + >; + } + : { + [Prop in getFieldAlias]: Node['name']['value'] extends '__typename' + ? PossibleType + : unwrapType< + Type['fields'][Node['name']['value']]['type'], + Node['selectionSet'], + Introspection, + Fragments, + getTypeDirective + >; + } + : {}) & + IntersectionAcc) + | UnionAccumulator + > + : IntersectionAcc | UnionAccumulator; + +type getPossibleTypeSelectionRec< + Selections, + PossibleType extends string, + Type extends ObjectLikeType, + Introspection extends IntrospectionLikeType, + Fragments extends { + [name: string]: any; + }, + UnionAcc, +> = _getPossibleTypeSelectionRec< + Selections, + PossibleType, + Type, + Introspection, + Fragments, + UnionAcc, + {} +>; type getOperationSelectionType< Definition, @@ -185,7 +239,7 @@ type getOperationSelectionType< } ? Introspection['types'][Introspection[Definition['operation']]] extends infer Type extends ObjectLikeType - ? getSelection + ? getSelection : {} : never; @@ -200,7 +254,7 @@ type getFragmentSelectionType< } ? Introspection['types'][Definition['typeCondition']['name']['value']] extends infer Type extends ObjectLikeType - ? getSelection + ? getSelection : never : never; diff --git a/src/variables.ts b/src/variables.ts index e4e82e21..d26c57cf 100644 --- a/src/variables.ts +++ b/src/variables.ts @@ -3,17 +3,36 @@ import type { IntrospectionLikeType } from './introspection'; import type { DocumentNodeLike } from './parser'; import type { obj } from './utils'; -type getInputObjectTypeRec< +type _getInputObjectTypeRec< InputFields, Introspection extends IntrospectionLikeType, + IntersectionAccumulator, > = InputFields extends [infer InputField, ...infer Rest] - ? (InputField extends { name: any; type: any } - ? InputField['type'] extends { kind: 'NON_NULL' } - ? { [Name in InputField['name']]: unwrapType } - : { [Name in InputField['name']]?: unwrapType } - : {}) & - getInputObjectTypeRec - : {}; + ? _getInputObjectTypeRec< + Rest, + Introspection, + (InputField extends { + name: any; + type: any; + } + ? InputField['type'] extends { + kind: 'NON_NULL'; + } + ? { + [Name in InputField['name']]: unwrapType; + } + : { + [Name in InputField['name']]?: unwrapType; + } + : {}) & + IntersectionAccumulator + > + : IntersectionAccumulator; + +type getInputObjectTypeRec< + InputFields, + Introspection extends IntrospectionLikeType, +> = _getInputObjectTypeRec; type getScalarType< TypeName,