import type { UnnamedFieldDefinitionNode, UnnamedInputValueDefinitionNode } from './AST' import type { InputTypeConfig, OutputTypeConfig } from './config' import type { Resolver, Subscription, TypeResolver } from './Resolver' import type { ScalarFunctions } from './Scalar' import type { ReadonlyRecord } from '@principia/base/Record' import type { Compute } from '@principia/base/util/compute' import type { _A, ExcludeMatchingProperties } from '@principia/base/util/types' import type { FieldDefinitionNode, InputObjectTypeDefinitionNode, InputValueDefinitionNode, InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode, ScalarTypeDefinitionNode, UnionTypeDefinitionNode } from 'graphql' import * as A from '@principia/base/Array' import * as R from '@principia/base/Record' import { addNameToUnnamedFieldDefinitionNode, createInputObjectTypeDefinitionNode, createInputValueDefinitionNode, createInterfaceTypeDefinitionNode, createNamedTypeNode, createObjectTypeDefinitionNode, createStringValueNode, createUnionTypeDefinitionNode, createUnnamedFieldDefinitionNode, createUnnamedInputValueDefinitionNode, getTypeName } from './AST' export class GQLField { readonly _tag = 'GQLField' readonly _R!: (_: R) => void readonly _E!: () => E readonly _A!: () => A readonly ast: UnnamedFieldDefinitionNode constructor( readonly type: AnyField, readonly args: InputRecord | undefined, readonly resolve: Resolver ) { this.ast = createUnnamedFieldDefinitionNode({ arguments: args ? R.ifoldl_(args, A.empty(), (k, b, a: GQLInputField) => [ ...b, createInputValueDefinitionNode({ defaultValue: a.config.defaultValue, description: a.config.description, list: a.config.list, name: k, nullable: a.config.nullable, typeName: getTypeName(a.ast) }) ]) : [], description: type.config.description, list: type.config.list, nullable: type.config.nullable, typeName: getTypeName(type.ast) }) } static fromAST( ast: UnnamedFieldDefinitionNode, resolve: Resolver ): GQLField { return Object.setPrototypeOf( { _tag: 'GQLField', ast, resolve }, GQLField.prototype ) } [':'](description: string): GQLField { return GQLField.fromAST({ ...this.ast, description: createStringValueNode(description, false) }, this.resolve) } } export class GQLScalarField { readonly _tag = 'GQLScalarField' readonly _A!: () => A readonly ast: UnnamedFieldDefinitionNode constructor(typeName: string, readonly config: OutputTypeConfig = {}) { this.ast = createUnnamedFieldDefinitionNode({ ...config, typeName }) } } export class GQLObjectField { readonly _R!: (_: R) => void readonly _E!: () => E readonly _A!: () => A readonly _Root!: Root readonly _Ctx!: Ctx readonly _tag = 'GQLObjectField' readonly ast: UnnamedFieldDefinitionNode constructor(typeName: string, readonly config: OutputTypeConfig = {}) { this.ast = createUnnamedFieldDefinitionNode({ ...config, typeName }) } } export class GQLObject { readonly _R!: (_: R) => void readonly _E!: () => E readonly _A!: () => A readonly _Ctx!: Ctx readonly _Root!: Root readonly _tag = 'GQLObject' readonly ast: ObjectTypeDefinitionNode readonly resolvers: ReadonlyRecord constructor( readonly name: N, readonly fields: { [K in keyof A]: AnyOutput }, readonly interfaces: ReadonlyArray> = [] ) { this.ast = createObjectTypeDefinitionNode({ name, fields: [ ...A.chain_(interfaces, (a) => a.ast.fields || []), ...R.ifoldl_(fields, [] as ReadonlyArray, (k, b, a: NonNullable>) => A.append_(b, addNameToUnnamedFieldDefinitionNode(a.ast, k)) ) ], interfaces: A.map_(interfaces, (a) => a.name) }) this.resolvers = { ...A.foldl_(interfaces || [], {}, (b, a) => ({ ...b, ...a.resolvers })), ...R.ifoldl_(fields, {}, (k, b, a: AnyOutput) => { if (a._tag === 'GQLField') { return { ...b, [k]: a.resolve } } return b }) } } } export class GQLUnion>, Ctx, A> { readonly _A!: () => A readonly _E!: () => never readonly _R!: (_: unknown) => void readonly _Ctx!: Ctx readonly _tag = 'GQLUnion' readonly ast: UnionTypeDefinitionNode constructor(readonly name: N, readonly types: Types, readonly resolveType: TypeResolver>) { this.ast = createUnionTypeDefinitionNode({ name, types: A.map_(types, (a) => createNamedTypeNode(a.name)) }) } } export class GQLUnionField { readonly _A!: () => A readonly _E!: () => never readonly _R!: (_: unknown) => void readonly _Ctx!: Ctx readonly _tag = 'GQLUnionField' readonly ast: UnnamedFieldDefinitionNode constructor(typeName: string, readonly config: OutputTypeConfig = {}) { this.ast = createUnnamedFieldDefinitionNode({ ...config, typeName }) } } export class GQLInterface { readonly _A!: () => A readonly _E!: () => E readonly _R!: (_: R) => void readonly _Ctx!: Ctx readonly _tag = 'GQLInterface' readonly ast: InterfaceTypeDefinitionNode readonly resolvers: ReadonlyRecord constructor(readonly name: N, readonly fields: ReadonlyRecord, readonly resolveType: any) { this.ast = createInterfaceTypeDefinitionNode({ name, fields: R.ifoldl_(fields, [] as ReadonlyArray, (k, b, a: NonNullable>) => A.append_(b, addNameToUnnamedFieldDefinitionNode(a.ast, k)) ) }) this.resolvers = R.ifoldl_(fields, {}, (k, b, a: AnyOutput) => { if (a._tag === 'GQLField') { return { ...b, [k]: a.resolve } } return b }) } } export class GQLSubscription { readonly _R!: (_: R) => void readonly _A!: () => A readonly _tag = 'GQLSubscription' readonly ast: ReadonlyArray readonly resolvers: ReadonlyRecord> constructor(readonly fields: ReadonlyRecord>) { this.ast = R.ifoldl_( fields, [] as ReadonlyArray, (k, b, a: NonNullable>) => A.append_(b, addNameToUnnamedFieldDefinitionNode(a.ast, k)) ) this.resolvers = R.map_(fields, (f) => f.resolve) } } export class GQLSubscriptionField { readonly _A!: () => A readonly _R!: (_: R) => void readonly _tag = 'GQLSubscriptionField' readonly ast: UnnamedFieldDefinitionNode constructor( type: AnyField, args: InputRecord | undefined, readonly resolve: Subscription<{}, any, any, any, any, any, any, any> ) { this.ast = createUnnamedFieldDefinitionNode({ description: type.config.description, list: type.config.list, nullable: type.config.nullable, typeName: getTypeName(type.ast), arguments: args ? R.ifoldl_(args, A.empty(), (k, b, a) => [ ...b, createInputValueDefinitionNode({ defaultValue: a.config.defaultValue, description: a.config.description, list: a.config.list, name: k, nullable: a.config.nullable, typeName: getTypeName(a.ast) }) ]) : [] }) } } export class GQLExtendObject, R, E, A> { readonly _R!: (_: R) => void readonly _E!: () => E readonly _A!: () => A readonly _tag = 'GQLExtendObject' readonly ast: ReadonlyArray readonly resolvers: ReadonlyRecord constructor(readonly object: O, readonly fields: Record) { this.ast = R.ifoldl_(fields as any, A.empty(), (k, acc, v: NonNullable>) => { switch (v._tag) { /* * case "RecursiveType": * return [ * ...acc, * createFieldDefinitionNode({ * description: v.config?.description, * list: v.config?.list, * name: k, * nullable: v.config?.nullable, * typeName: v.name * }) * ]; */ case 'GQLField': return [...acc, addNameToUnnamedFieldDefinitionNode(v.ast, k)] case 'GQLScalarField': return [...acc, addNameToUnnamedFieldDefinitionNode(v.ast, k)] case 'GQLObjectField': return [...acc, addNameToUnnamedFieldDefinitionNode(v.ast, k)] case 'GQLUnionField': return [...acc, addNameToUnnamedFieldDefinitionNode(v.ast, k)] } }) this.resolvers = R.ifoldl_(fields as any, {}, (k, acc, v: AnyOutput) => { if (v._tag === 'GQLField') { return { ...acc, [k]: v.resolve } } return acc }) } get name(): O['name'] { return this.object.name } } export class GQLInputObject { readonly _A!: () => A readonly _E!: () => never readonly _R!: (_: unknown) => void readonly _tag = 'GQLInputObject' readonly ast: InputObjectTypeDefinitionNode constructor(readonly name: N, readonly fields: { [K in keyof A]: GQLInputField }) { this.ast = createInputObjectTypeDefinitionNode({ fields: R.ifoldl_(fields, [] as InputValueDefinitionNode[], (k, acc, v) => { return [ ...acc, createInputValueDefinitionNode({ defaultValue: v.config?.defaultValue, description: v.config?.description || v.ast.description?.value, name: k, nullable: v.config.nullable, typeName: getTypeName(v.ast) }) ] }), name }) } } export class GQLInputField { readonly _A!: () => A readonly _tag = 'GQLInputField' readonly ast: UnnamedInputValueDefinitionNode constructor(typeName: string, readonly config: InputTypeConfig = {}) { this.ast = createUnnamedInputValueDefinitionNode({ ...config, typeName }) } } export class GQLScalar { readonly _A!: () => O readonly _R!: (_: R) => void readonly _tag = 'GQLScalar' constructor(readonly ast: ScalarTypeDefinitionNode, readonly name: N, readonly fns: ScalarFunctions) {} } export type AnyField = GQLScalarField | GQLObjectField | GQLUnionField export type AnyOutput = AnyField | GQLField export type InputRecord = Record> type Widen = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T export type FieldRecord = Partial<{ [K in keyof Root]: Root[K] extends { [x: string]: any } ? | GQLObjectField }> | GQLField }> : Root[K] extends Iterable ? GQLScalarField> | GQLField> : | GQLScalarField> | GQLField | GQLObjectField> | GQLUnionField }> & { [K in Exclude]: | GQLField | GQLScalarField | GQLObjectField | GQLSubscriptionField | GQLUnionField } export type FieldResolverRecord> = Compute< ExcludeMatchingProperties< { [K in keyof Fs]: Fs[K] extends GQLField ? Resolver : never }, never >, 'flat' > export type AnyRootType = | GQLInputObject | GQLObject | GQLExtendObject | GQLScalar | GQLSubscription | GQLUnion | GQLInterface export type AnyObjectType = GQLObject | GQLExtendObject