@graphql-ts/schema

Search for an npm package
import {
GraphQLFieldConfigMap,
GraphQLFieldExtensions,
GraphQLInputType,
GraphQLInterfaceType,
GraphQLInterfaceTypeExtensions,
GraphQLIsTypeOfFn,
GraphQLList,
GraphQLObjectType,
GraphQLObjectTypeExtensions,
GraphQLOutputType,
GraphQLResolveInfo,
GraphQLTypeResolver,
GraphQLUnionType,
GraphQLType,
} from "graphql/type/definition";
import {
Arg,
InferValueFromArgs,
InputType,
ScalarType,
EnumType,
ListType,
NonNullType,
} from "./api-without-context";
import type { NullableType, Type } from "./type";
import type {
object,
field,
interface as interfaceFunc,
} from "./api-with-context";
export type __toMakeTypeScriptEmitImportsForItemsOnlyUsedInJSDoc = [
typeof interfaceFunc,
typeof field,
typeof object,
NullableType<any>,
Type<any>
];
/**
* Any output list type. This type only exists because of limitations in circular types.
*
* If you want to represent any list input type, you should do
* `ListType<OutputType<Context>>`.
*/
type OutputListType<Context> = {
kind: "list";
of: OutputType<Context>;
graphQLType: GraphQLList<GraphQLType>;
__context: (context: Context) => void;
};
/**
* Any nullable GraphQL **output** type for a given `Context`.
*
* See also:
*
* - {@link NullableType}
* - {@link InputType}
* - {@link OutputType}
*/
export type NullableOutputType<Context> =
| ScalarType<any>
| ObjectType<any, Context>
| UnionType<any, Context>
| InterfaceType<any, any, Context>
| EnumType<any>
| OutputListType<Context>;
/**
* Any GraphQL **output** type for a given `Context`.
*
* See also:
*
* - {@link Type}
* - {@link InputType}
* - {@link NullableOutputType}
*/
export type OutputType<Context> =
| NullableOutputType<Context>
| NonNullType<NullableOutputType<Context>>;
type OutputListTypeForInference<Of extends OutputType<any>> = ListType<Of>;
type InferValueFromOutputTypeWithoutAddingNull<Type extends OutputType<any>> =
Type extends ScalarType<infer Value>
? Value
: Type extends EnumType<infer Values>
? Values[keyof Values]["value"]
: Type extends OutputListTypeForInference<infer Value>
? // the `object` bit is here because graphql checks `typeof maybeIterable === 'object'`
// which means that things like `string` won't be allowed
// (which is probably a good thing because returning a string from a resolver that needs
// a graphql list of strings is almost definitely not what you want and if it is, use Array.from)
// sadly functions that are iterables will be allowed by this type but not allowed by graphql-js
// (though tbh, i think the chance of that causing problems is quite low)
object & Iterable<InferValueFromOutputType<Value>>
: Type extends ObjectType<infer Source, any>
? Source
: Type extends UnionType<infer Source, any>
? Source
: Type extends InterfaceType<infer Source, any, any>
? Source
: never;
type OutputNonNullTypeForInference<Of extends NullableOutputType<any>> =
NonNullType<Of>;
export type InferValueFromOutputType<Type extends OutputType<any>> =
MaybePromise<
Type extends OutputNonNullTypeForInference<infer Value>
? InferValueFromOutputTypeWithoutAddingNull<Value>
: InferValueFromOutputTypeWithoutAddingNull<Type> | null | undefined
>;
/** A GraphQL object type which should be created using {@link object `graphql.object`}. */
export type ObjectType<Source, Context> = {
kind: "object";
graphQLType: GraphQLObjectType;
__context: (context: Context) => void;
__source: Source;
};
type MaybePromise<T> = Promise<T> | T;
export type FieldResolver<
Source,
Args extends Record<string, Arg<InputType>>,
TType extends OutputType<Context>,
Context
> = (
source: Source,
args: InferValueFromArgs<Args>,
context: Context,
info: GraphQLResolveInfo
) => InferValueFromOutputType<TType>;
/**
* A GraphQL output field for an {@link ObjectType} which should be created using
* {@link field `graphql.field`}.
*/
export type Field<
Source,
Args extends Record<string, Arg<InputType>>,
TType extends OutputType<Context>,
Key extends string,
Context
> = {
args?: Args;
type: TType;
__key: (key: Key) => void;
__source: (source: Source) => void;
__context: (context: Context) => void;
resolve?: FieldResolver<Source, Args, TType, Context>;
deprecationReason?: string;
description?: string;
extensions?: Readonly<
GraphQLFieldExtensions<Source, Context, InferValueFromArgs<Args>>
>;
};
export type InterfaceField<
Args extends Record<string, Arg<InputType>>,
Type extends OutputType<Context>,
Context
> = {
args?: Args;
type: Type;
deprecationReason?: string;
description?: string;
extensions?: Readonly<
GraphQLFieldExtensions<any, any, InferValueFromArgs<Args>>
>;
};
type SomeTypeThatIsntARecordOfArgs = string;
type FieldFuncResolve<
Source,
Args extends { [Key in keyof Args]: Arg<InputType> },
Type extends OutputType<Context>,
Key extends string,
Context
> =
// the tuple is here because we _don't_ want this to be distributive
// if this was distributive then it would optional when it should be required e.g.
// graphql.object<{ id: string } | { id: boolean }>()({
// name: "Node",
// fields: {
// id: graphql.field({
// type: graphql.nonNull(graphql.ID),
// }),
// },
// });
[Key] extends [keyof Source]
? Source[Key] extends
| InferValueFromOutputType<Type>
| ((
args: InferValueFromArgs<Args>,
context: Context,
info: GraphQLResolveInfo
) => InferValueFromOutputType<Type>)
? {
resolve?: FieldResolver<
Source,
SomeTypeThatIsntARecordOfArgs extends Args ? {} : Args,
Type,
Context
>;
}
: {
resolve: FieldResolver<
Source,
SomeTypeThatIsntARecordOfArgs extends Args ? {} : Args,
Type,
Context
>;
}
: {
resolve: FieldResolver<
Source,
SomeTypeThatIsntARecordOfArgs extends Args ? {} : Args,
Type,
Context
>;
};
type FieldFuncArgs<
Source,
Args extends { [Key in keyof Args]: Arg<InputType> },
Type extends OutputType<Context>,
Key extends string,
Context
> = {
args?: Args;
type: Type;
deprecationReason?: string;
description?: string;
extensions?: Readonly<GraphQLFieldExtensions<Source, unknown>>;
} & FieldFuncResolve<Source, Args, Type, Key, Context>;
export type FieldFunc<Context> = <
Source,
Type extends OutputType<Context>,
Key extends string,
Args extends { [Key in keyof Args]: Arg<InputType> } = {}
>(
field: FieldFuncArgs<Source, Args, Type, Key, Context>
) => Field<Source, Args, Type, Key, Context>;
function bindFieldToContext<Context>(): FieldFunc<Context> {
return function field(field) {
if (!field.type) {
throw new Error("A type must be passed to graphql.field()");
}
return field as any;
};
}
export type InterfaceToInterfaceFields<
Interface extends InterfaceType<any, any, any>
> = Interface extends InterfaceType<any, infer Fields, any> ? Fields : never;
type InterfaceFieldToOutputField<
Source,
Context,
TField extends InterfaceField<any, any, Context>,
Key extends string
> = TField extends InterfaceField<infer Args, infer OutputType, Context>
? Field<Source, Args, OutputType, Key, Context>
: never;
type InterfaceFieldsToOutputFields<
Source,
Context,
Fields extends { [Key in keyof Fields]: InterfaceField<any, any, Context> }
> = {
[Key in keyof Fields]: InterfaceFieldToOutputField<
Source,
Context,
Fields[Key],
Extract<Key, string>
>;
};
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
x: infer R
) => any
? R
: never;
export type InterfacesToOutputFields<
Source,
Context,
Interfaces extends readonly InterfaceType<Source, any, Context>[]
> = UnionToIntersection<
InterfaceFieldsToOutputFields<
Source,
Context,
InterfaceToInterfaceFields<Interfaces[number]>
>
>;
/**
* Creates a GraphQL object type.
*
* See the docs of {@link object `graphql.object`} for more details.
*/
export type ObjectTypeFunc<Context> = <
Source
>(youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction?: {
youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction: true;
}) => <
Fields extends {
[Key in keyof Fields]: Field<
Source,
any,
any,
Extract<Key, string>,
Context
>;
} &
InterfacesToOutputFields<Source, Context, Interfaces>,
Interfaces extends readonly InterfaceType<Source, any, Context>[] = []
>(config: {
name: string;
fields: Fields | (() => Fields);
/**
* A description of the object type that is visible when introspected.
*
* ```ts
* type Person = { name: string };
*
* const Person = graphql.object<Person>()({
* name: "Person",
* description: "A person does things!",
* fields: {
* name: graphql.field({ type: graphql.String }),
* },
* });
* // ==
* graphql`
* """
* A person does things!
* """
* type Person {
* name: String
* }
* `;
* ```
*/
description?: string;
/**
* A number of interfaces that the object type implements. See
* `graphql.interface` for more information.
*
* ```ts
* const Node = graphql.interface<{ kind: string }>()({
* name: "Node",
* resolveType: (source) => source.kind,
* fields: {
* id: graphql.interfaceField({ type: graphql.ID }),
* },
* });
*
* const Person = graphql.object<{ kind: "Person"; id: string }>()({
* name: "Person",
* interfaces: [Node],
* fields: {
* id: graphql.field({ type: graphql.ID }),
* },
* });
* ```
*/
interfaces?: [...Interfaces];
isTypeOf?: GraphQLIsTypeOfFn<unknown, Context>;
extensions?: Readonly<GraphQLObjectTypeExtensions<Source, Context>>;
}) => ObjectType<Source, Context>;
function bindObjectTypeToContext<Context>(): ObjectTypeFunc<Context> {
return function object() {
return function objectInner(config) {
return {
kind: "object",
name: config.name,
graphQLType: new GraphQLObjectType({
name: config.name,
description: config.description,
isTypeOf: config.isTypeOf,
interfaces: config.interfaces?.map((x) => x.graphQLType),
fields: () => {
const fields =
typeof config.fields === "function"
? config.fields()
: config.fields;
return buildFields(fields);
},
extensions: config.extensions,
}),
__source: undefined as any,
__context: undefined as any,
};
};
};
}
function buildFields(
fields: Record<
string,
Field<any, Record<string, Arg<InputType>>, OutputType<any>, any, any>
>
): GraphQLFieldConfigMap<any, any> {
return Object.fromEntries(
Object.entries(fields).map(([key, val]) => [
key,
{
type: val.type.graphQLType as GraphQLOutputType,
resolve: val.resolve,
deprecationReason: val.deprecationReason,
description: val.description,
args: Object.fromEntries(
Object.entries(val.args || {}).map(([key, val]) => [
key,
{
type: val.type.graphQLType as GraphQLInputType,
description: val.description,
defaultValue: val.defaultValue,
deprecationReason: val.deprecationReason,
},
])
),
extensions: val.extensions,
},
])
);
}
export type UnionType<Source, Context> = {
kind: "union";
__source: Source;
__context: (context: Context) => void;
graphQLType: GraphQLUnionType;
};
export type UnionTypeFunc<Context> = <
TObjectType extends ObjectType<any, Context>
>(config: {
name: string;
description?: string;
types: TObjectType[];
resolveType?: (
type: TObjectType["__source"],
context: Context,
info: GraphQLResolveInfo,
abstractType: GraphQLUnionType
) => string;
}) => UnionType<TObjectType["__source"], Context>;
function bindUnionTypeToContext<Context>(): UnionTypeFunc<Context> {
return function union(config) {
return {
kind: "union",
graphQLType: new GraphQLUnionType({
name: config.name,
description: config.description,
types: config.types.map((x) => x.graphQLType),
resolveType: config.resolveType as any,
}),
__source: undefined as any,
__context: undefined as any,
};
};
}
export type FieldsFunc<Context> = <
Source
>(youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction?: {
youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction: true;
}) => <
Fields extends {
[Key in keyof Fields]: Field<
Source,
any,
any,
Extract<Key, string>,
Context
>;
}
>(
fields: Fields
) => Fields;
function bindFieldsToContext<Context>(): FieldsFunc<Context> {
return function fields() {
return function fieldsInner(fields) {
return fields;
};
};
}
type InterfaceFieldFuncArgs<
Source,
Args extends { [Key in keyof Args]: Arg<InputType> },
Type extends OutputType<Context>,
Context
> = {
args?: Args;
type: Type;
deprecationReason?: string;
description?: string;
extensions?: Readonly<GraphQLFieldExtensions<Source, unknown>>;
};
export type InterfaceFieldFunc<Context> = <
Source,
Type extends OutputType<Context>,
Args extends { [Key in keyof Args]: Arg<InputType> } = {}
>(
field: InterfaceFieldFuncArgs<Source, Args, Type, Context>
) => InterfaceField<Args, Type, Context>;
function bindInterfaceFieldToContext<Context>(): InterfaceFieldFunc<Context> {
return function interfaceField(field) {
return field as any;
};
}
export type InterfaceType<
Source,
Fields extends Record<
string,
InterfaceField<any, OutputType<Context>, Context>
>,
Context
> = {
kind: "interface";
__source: (source: Source) => void;
__context: (context: Context) => void;
__fields: Fields;
graphQLType: GraphQLInterfaceType;
};
export type InterfaceTypeFunc<Context> = <
Source
>(youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction?: {
youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction: true;
}) => <
Fields extends {
[Key in keyof Fields]: InterfaceField<any, OutputType<Context>, Context>;
} &
UnionToIntersection<InterfaceToInterfaceFields<Interfaces[number]>>,
Interfaces extends readonly InterfaceType<Source, any, Context>[] = []
>(config: {
name: string;
description?: string;
deprecationReason?: string;
interfaces?: [...Interfaces];
resolveType?: GraphQLTypeResolver<Source, Context>;
fields: Fields | (() => Fields);
extensions?: Readonly<GraphQLInterfaceTypeExtensions>;
}) => InterfaceType<Source, Fields, Context>;
function bindInterfaceTypeToContext<Context>(): InterfaceTypeFunc<Context> {
return function interfaceType() {
return function interfaceInner(config) {
return {
kind: "interface",
graphQLType: new GraphQLInterfaceType({
name: config.name,
description: config.description,
resolveType: config.resolveType,
interfaces: config.interfaces?.map((x) => x.graphQLType),
extensions: config.extensions,
fields: () => {
const fields =
typeof config.fields === "function"
? config.fields()
: config.fields;
return buildFields(fields as any);
},
}),
__source: undefined as any,
__context: undefined as any,
__fields: undefined as any,
};
};
};
}
export type GraphQLSchemaAPIWithContext<Context> = {
/**
* Creates a GraphQL object type.
*
* Note this is an **output** type, if you want an input object, use
* `graphql.inputObject`.
*
* When calling `graphql.object`, you must provide a type parameter that is
* the source of the object type. The source is what you receive as the first
* argument of resolvers on this type and what you must return from resolvers
* of fields that return this type.
*
* ```ts
* const Person = graphql.object<{ name: string }>()({
* name: "Person",
* fields: {
* name: graphql.field({ type: graphql.String }),
* },
* });
* // ==
* graphql`
* type Person {
* name: String
* }
* `;
* ```
*
* ## Writing resolvers
*
* To do anything other than just return a field from the source type, you
* need to provide a resolver.
*
* Note: TypeScript will force you to provide a resolve function if the field
* in the source type and the GraphQL field don't match
*
* ```ts
* const Person = graphql.object<{ name: string }>()({
* name: "Person",
* fields: {
* name: graphql.field({ type: graphql.String }),
* excitedName: graphql.field({
* type: graphql.String,
* resolve(source, args, context, info) {
* return `${source.name}!`;
* },
* }),
* },
* });
* ```
*
* ## Circularity
*
* GraphQL types will often contain references to themselves and to make
* TypeScript allow that, you need have an explicit type annotation of
* `graphql.ObjectType<Source>` along with making `fields` a function that
* returns the object.
*
* ```ts
* type PersonSource = { name: string; friends: PersonSource[] };
*
* const Person: graphql.ObjectType<PersonSource> =
* graphql.object<PersonSource>()({
* name: "Person",
* fields: () => ({
* name: graphql.field({ type: graphql.String }),
* friends: graphql.field({ type: graphql.list(Person) }),
* }),
* });
* ```
*/
object: ObjectTypeFunc<Context>;
/**
* Create a GraphQL union type.
*
* A union type represents an object that could be one of a list of types.
* Note it is similar to an {@link InterfaceType} except that a union doesn't
* imply having a common set of fields among the member types.
*
* ```ts
* const A = graphql.object<{ __typename: "A" }>()({
* name: "A",
* fields: {
* something: graphql.field({ type: graphql.String }),
* },
* });
* const B = graphql.object<{ __typename: "B" }>()({
* name: "B",
* fields: {
* differentThing: graphql.field({ type: graphql.String }),
* },
* });
* const AOrB = graphql.union({
* name: "AOrB",
* types: [A, B],
* });
* ```
*/
union: UnionTypeFunc<Context>;
/**
* Creates a GraphQL field.
*
* These will generally be passed directly to the `fields` object in a
* `graphql.object` call.
*
* ```ts
* const Something = graphql.object<{ thing: string }>()({
* name: "Something",
* fields: {
* thing: graphql.field({ type: graphql.String }),
* },
* });
* ```
*/
field: FieldFunc<Context>;
/**
* A helper to easily share fields across object and interface types.
*
* ```ts
* const nodeFields = graphql.fields<{ id: string }>()({
* id: graphql.field({ type: graphql.ID }),
* });
*
* const Node = graphql.field({
* name: "Node",
* fields: nodeFields,
* });
*
* const Person = graphql.object<{
* __typename: "Person";
* id: string;
* name: string;
* }>()({
* name: "Person",
* interfaces: [Node],
* fields: {
* ...nodeFields,
* name: graphql.field({ type: graphql.String }),
* },
* });
* ```
*
* ## Why use `graphql.fields` instead of just creating an object?
*
* The definition of Field in `@graphql-ts/schema` has some special things,
* let's look at the definition of it:
*
* ```ts
* type Field<
* Source,
* Args extends Record<string, Arg<InputType>>,
* TType extends OutputType<Context>,
* Key extends string,
* Context
* > = ...;
* ```
*
* There's two especially notable bits in there which need to be inferred from
* elsewhere, the `Source` and `Key` type params.
*
* The `Source` is pretty simple and it's quite simple to see why
* `graphql.fields` is useful here. You could explicitly write it with
* resolvers on the first arg but you'd have to do that on every field which
* would get very repetitive and wouldn't work for fields without resolvers.
*
* ```ts
* const someFields = graphql.fields<{ name: string }>()({
* name: graphql.field({ type: graphql.String }),
* });
* ```
*
* The `Key` type param might seem a bit more strange though. What it's saying
* is that *the key that a field is at is part of its TypeScript type*.
*
* This is important to be able to represent the fact that a resolver is
* optional if the `Source` has a property at the `Key` that matches the output type.
*
* ```ts
* // this is allowed
* const someFields = graphql.fields<{ name: string }>()({
* name: graphql.field({ type: graphql.String }),
* });
*
* const someFields = graphql.fields<{ name: string }>()({
* someName: graphql.field({
* // a resolver is required here since the Source is missing a `someName` property
* type: graphql.String,
* }),
* });
* ```
*
* Note that there is no similar function for {@link Arg args} since they don't
* need special type parameters like {@link Field} does so you can create a
* regular object and put {@link Arg args} in it if you want to share them.
*/
fields: FieldsFunc<Context>;
/**
* Creates a GraphQL interface field.
*
* These will generally be passed directly to `fields` object in a
* {@link interfaceFunc `graphql.interface`} call. Interfaces fields are
* similar to {@link Field regular fields} except that they **don't define how
* the field is resolved**.
*
* ```ts
* const Entity = graphql.interface()({
* name: "Entity",
* fields: {
* name: graphql.interfaceField({ type: graphql.String }),
* },
* });
* ```
*
* Note that {@link Field regular fields} are assignable to
* {@link InterfaceField interface fields} but the opposite is not true. This
* means that you can use a regular field in an {@link InterfaceType interface type}.
*/
interfaceField: InterfaceFieldFunc<Context>;
/**
* Creates a GraphQL interface type that can be implemented by other GraphQL
* object and interface types.
*
* ```ts
* const Entity = graphql.interface()({
* name: "Entity",
* fields: {
* name: graphql.interfaceField({ type: graphql.String }),
* },
* });
*
* type PersonSource = { __typename: "Person"; name: string };
*
* const Person = graphql.object<PersonSource>()({
* name: "Person",
* interfaces: [Entity],
* fields: {
* name: graphql.field({ type: graphql.String }),
* },
* });
*
* type OrganisationSource = {
* __typename: "Organisation";
* name: string;
* };
*
* const Organisation = graphql.object<OrganisationSource>()({
* name: "Organisation",
* interfaces: [Entity],
* fields: {
* name: graphql.field({ type: graphql.String }),
* },
* });
* ```
*
* ## Resolving Types
*
* When using GraphQL interface and union types, there needs to a way to
* determine which concrete object type has been returned from a resolver.
* With `graphql-js` and `@graphql-ts/schema`, this is done with `isTypeOf` on
* object types and `resolveType` on interface and union types. Note
* `@graphql-ts/schema` **does not aim to strictly type the implementation of
* `resolveType` and `isTypeOf`**. If you don't provide `resolveType` or
* `isTypeOf`, a `__typename` property on the source type will be used, if
* that fails, an error will be thrown at runtime.
*
* ## Fields vs Interface Fields
*
* You might have noticed that `graphql.interfaceField` was used instead of
* `graphql.field` for the fields on the interfaces. This is because
* **interfaces aren't defining implementation of fields** which means that
* fields on an interface don't need define resolvers.
*
* ## Sharing field implementations
*
* Even though interfaces don't contain field implementations, you may still
* want to share field implementations between interface implementations. You
* can use `graphql.fields` to do that. See `graphql.fields` for more
* information about why you should use `graphql.fields` instead of just
* defining an object the fields and spreading that.
*
* ```ts
* const nodeFields = graphql.fields<{ id: string }>({
* id: graphql.field({ type: graphql.ID }),
* });
*
* const Node = graphql.field({
* name: "Node",
* fields: nodeFields,
* });
*
* const Person = graphql.object<{
* __typename: "Person";
* id: string;
* name: string;
* }>()({
* name: "Person",
* interfaces: [Node],
* fields: {
* ...nodeFields,
* name: graphql.field({ type: graphql.String }),
* },
* });
* ```
*/
interface: InterfaceTypeFunc<Context>;
};
export function bindGraphQLSchemaAPIToContext<
Context
>(): GraphQLSchemaAPIWithContext<Context> {
return {
object: bindObjectTypeToContext<Context>(),
union: bindUnionTypeToContext<Context>(),
field: bindFieldToContext<Context>(),
fields: bindFieldsToContext<Context>(),
interfaceField: bindInterfaceFieldToContext<Context>(),
interface: bindInterfaceTypeToContext<Context>(),
};
}