@graphql-ts/schema

Search for an npm package
import type {
GraphQLFieldExtensions,
GraphQLObjectTypeConfig,
GraphQLResolveInfo,
GraphQLUnionTypeConfig,
GraphQLArgumentConfig,
GraphQLInputFieldConfig,
GraphQLScalarTypeConfig,
FieldDefinitionNode,
} from "graphql";
import {
GArg,
GEnumType,
GEnumTypeConfig,
GEnumValueConfig,
GField,
GInputObjectType,
GInputObjectTypeConfig,
GInputType,
GInterfaceField,
GInterfaceType,
GInterfaceTypeConfig,
GList,
GNonNull,
GNullableInputType,
GNullableType,
GObjectType,
GOutputType,
GScalarType,
GType,
GUnionType,
InferValueFromOutputType,
InferValueFromArgs,
InferValueFromInputType,
} from "./types";
import {
GraphQLBoolean,
GraphQLID,
GraphQLInt,
GraphQLFloat,
GraphQLString,
} from "graphql";
import type { g } from "./g-for-doc-references";
type SomeTypeThatIsntARecordOfArgs = string;
type ImpliedResolver<
Args extends { [Key in keyof Args]: GArg<GInputType> },
Type extends GOutputType<Context>,
Context,
> =
| InferValueFromOutputType<Type>
| ((
args: InferValueFromArgs<Args>,
context: Context,
info: GraphQLResolveInfo
) => InferValueFromOutputType<Type>);
type Maybe<T> = T | null | undefined;
type FieldFuncArgs<
Source,
Args extends { [Key in keyof Args]: GArg<GInputType> },
Type extends GOutputType<Context>,
Context,
> = {
args?: Args;
type: Type;
description?: Maybe<string>;
deprecationReason?: Maybe<string>;
extensions?: Maybe<Readonly<GraphQLFieldExtensions<Source, Context>>>;
astNode?: Maybe<FieldDefinitionNode>;
};
/** @deprecated */
export type InterfaceToInterfaceFields<
Interface extends GInterfaceType<any, any, any>,
> = Interface extends GInterfaceType<any, infer Fields, any> ? Fields : never;
type InterfaceFieldsToOutputFields<
Source,
Context,
Fields extends { [key: string]: GInterfaceField<any, any, any> },
> = {
[Key in keyof Fields]: Fields[Key] extends GInterfaceField<
infer Args,
infer OutputType,
any
>
? GField<
Source,
Args,
OutputType,
Key extends keyof Source ? Source[Key] : unknown,
Context
>
: never;
};
/** @deprecated */
export type _InterfacesToOutputFields<
Source,
Context,
Interfaces extends readonly GInterfaceType<Source, any, Context>[],
> = InterfacesToOutputFields<Source, Context, Interfaces>;
export type { _InterfacesToOutputFields as InterfacesToOutputFields };
type InterfacesToOutputFields<
Source,
Context,
Interfaces extends readonly GInterfaceType<Source, any, any>[],
> = MergeTuple<
{
[Key in keyof Interfaces]: Interfaces[Key] extends GInterfaceType<
Source,
infer Fields,
any
>
? InterfaceFieldsToOutputFields<Source, Context, Fields>
: never;
},
{}
>;
export type InterfacesToInterfaceFields<
Interfaces extends readonly GInterfaceType<any, any, any>[],
> = MergeTuple<
{
[Key in keyof Interfaces]: Interfaces[Key] extends GInterfaceType<
any,
infer Fields,
any
>
? Fields
: never;
},
{}
>;
type MergeTuple<T, Merged> = T extends readonly [infer U, ...infer Rest]
? MergeTuple<Rest, Merged & U>
: Merged;
export type GWithContext<Context> = {
/**
* Creates a GraphQL object type.
*
* Note this is an **output** type, if you want an input object, use
* `g.inputObject`.
*
* When calling `g.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 = g.object<{ name: string }>()({
* name: "Person",
* fields: {
* name: g.field({ type: g.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 = g.object<{ name: string }>()({
* name: "Person",
* fields: {
* name: g.field({ type: g.String }),
* excitedName: g.field({
* type: g.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
* `g<typeof g.object<Source>>` along with making `fields` a function that
* returns the object.
*
* ```ts
* type PersonSource = { name: string; friends: PersonSource[] };
*
* const Person: g<typeof g.object<PersonSource>> =
* g.object<PersonSource>()({
* name: "Person",
* fields: () => ({
* name: g.field({ type: g.String }),
* friends: g.field({ type: g.list(Person) }),
* }),
* });
* ```
*/
object: <
Source,
>(youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction?: {
youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction: true;
}) => <
Fields extends {
[Key in keyof Fields]: GField<
Source,
any,
any,
Key extends keyof Source ? Source[Key] : unknown,
Context
>;
} & InterfaceFieldsToOutputFields<
Source,
Context,
InterfacesToInterfaceFields<Interfaces>
>,
const Interfaces extends readonly GInterfaceType<
Source,
any,
Context
>[] = [],
>(
config: {
fields: Fields | (() => Fields);
interfaces?: [...Interfaces];
} & Omit<GraphQLObjectTypeConfig<Source, Context>, "fields" | "interfaces">
) => GObjectType<Source, 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 GInterfaceType interface type} except that a
* union doesn't imply having a common set of fields among the member types.
*
* ```ts
* const A = g.object<{ __typename: "A" }>()({
* name: "A",
* fields: {
* something: g.field({ type: g.String }),
* },
* });
* const B = g.object<{ __typename: "B" }>()({
* name: "B",
* fields: {
* differentThing: g.field({ type: g.String }),
* },
* });
* const AOrB = g.union({
* name: "AOrB",
* types: [A, B],
* });
* ```
*/
union: <Type extends GObjectType<any, Context>>(
config: Flatten<
Omit<
GraphQLUnionTypeConfig<
Type extends GObjectType<infer Source, Context> ? Source : never,
Context
>,
"types"
> & {
types: readonly Type[] | (() => readonly Type[]);
}
>
) => GUnionType<
Type extends GObjectType<infer Source, Context> ? Source : never,
Context
>;
/**
* Creates a GraphQL field.
*
* These will generally be passed directly to the `fields` object in a
* `g.object` call.
*
* ```ts
* const Something = g.object<{ thing: string }>()({
* name: "Something",
* fields: {
* thing: g.field({ type: g.String }),
* },
* });
* ```
*/
field: <
Source,
Type extends GOutputType<Context>,
Resolve,
Args extends { [Key in keyof Args]: GArg<GInputType> } = {},
>(
field: FieldFuncArgs<Source, Args, Type, Context> &
(Resolve extends {}
? {
resolve: ((
source: Source,
args: InferValueFromArgs<
SomeTypeThatIsntARecordOfArgs extends Args ? {} : Args
>,
context: Context,
info: GraphQLResolveInfo
) => InferValueFromOutputType<Type>) &
Resolve;
}
: {
resolve?: ((
source: Source,
args: InferValueFromArgs<
SomeTypeThatIsntARecordOfArgs extends Args ? {} : Args
>,
context: Context,
info: GraphQLResolveInfo
) => InferValueFromOutputType<Type>) &
Resolve;
})
) => GField<
Source,
Args,
Type,
Resolve extends {} ? unknown : ImpliedResolver<Args, Type, Context>,
Context
>;
/**
* A helper to declare fields while providing the source type a single time
* rather than in every resolver.
*
* ```ts
* const nodeFields = g.fields<{ id: string }>()({
* id: g.field({ type: g.ID }),
* relatedIds: g.field({
* type: g.list(g.ID),
* resolve(source) {
* return loadRelatedIds(source.id);
* },
* }),
* otherRelatedIds: g.field({
* type: g.list(g.ID),
* resolve(source) {
* return loadOtherRelatedIds(source.id);
* },
* }),
* });
*
* const Person = g.object<{
* id: string;
* name: string;
* }>()({
* name: "Person",
* fields: {
* ...nodeFields,
* name: g.field({ type: g.String }),
* },
* });
* ```
*/
fields: <
Source,
>(youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction?: {
youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction: true;
}) => <
Fields extends Record<
string,
GField<Source, any, GOutputType<Context>, any, Context>
> & {
[Key in keyof Source]?: GField<
Source,
any,
GOutputType<Context>,
Source[Key],
Context
>;
},
>(
fields: Fields
) => Fields;
/**
* Creates a GraphQL interface field.
*
* These will generally be passed directly to the `fields` object in a
* {@link g.interface} call. Interfaces fields are similar to
* {@link GField regular fields} except that they **don't define how the field
* is resolved**.
*
* ```ts
* const Entity = g.interface()({
* name: "Entity",
* fields: {
* name: g.interfaceField({ type: g.String }),
* },
* });
* ```
*
* Note that {@link GField regular fields} are assignable to
* {@link GInterfaceField interface fields} but the opposite is not true. This
* means that you can use a regular field in an
* {@link GInterfaceType interface type}.
*/
interfaceField: <
Args extends { [Key in keyof Args]: GArg<GInputType> },
Type extends GOutputType<Context>,
>(
field: GInterfaceField<Args, Type, Context>
) => GInterfaceField<Args, Type, Context>;
/**
* Creates a GraphQL interface type that can be implemented by other GraphQL
* object and interface types.
*
* ```ts
* const Entity = g.interface()({
* name: "Entity",
* fields: {
* name: g.interfaceField({ type: g.String }),
* },
* });
*
* type PersonSource = { __typename: "Person"; name: string };
*
* const Person = g.object<PersonSource>()({
* name: "Person",
* interfaces: [Entity],
* fields: {
* name: g.field({ type: g.String }),
* },
* });
*
* type OrganisationSource = {
* __typename: "Organisation";
* name: string;
* };
*
* const Organisation = g.object<OrganisationSource>()({
* name: "Organisation",
* interfaces: [Entity],
* fields: {
* name: g.field({ type: g.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 `g.interfaceField` was used instead of
* `g.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 `g.fields` to do that. See `g.fields` for more information about
* why you should use `g.fields` instead of just defining an object the fields
* and spreading that.
*
* ```ts
* const nodeFields = g.fields<{ id: string }>({
* id: g.field({ type: g.ID }),
* });
*
* const Node = g.field({
* name: "Node",
* fields: nodeFields,
* });
*
* const Person = g.object<{
* __typename: "Person";
* id: string;
* name: string;
* }>()({
* name: "Person",
* interfaces: [Node],
* fields: {
* ...nodeFields,
* name: g.field({ type: g.String }),
* },
* });
* ```
*/
interface: <
Source,
>(youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction?: {
youOnlyNeedToPassATypeParameterToThisFunctionYouPassTheActualRuntimeArgsOnTheResultOfThisFunction: true;
}) => <
Fields extends {
[Key in keyof Fields]: GInterfaceField<
any,
GOutputType<Context>,
Context
>;
} & InterfacesToInterfaceFields<Interfaces>,
const Interfaces extends readonly GInterfaceType<
Source,
any,
Context
>[] = [],
>(
config: GInterfaceTypeConfig<Source, Fields, Interfaces, Context>
) => GInterfaceType<Source, Fields, Context>;
/**
* A shorthand to easily create {@link GEnumValueConfig enum values} to pass to
* {@link g.enum}.
*
* If you need to set a `description` or `deprecationReason` for an enum
* variant, you should pass values directly to `g.enum` without using
* `g.enumValues`.
*
* ```ts
* const MyEnum = g.enum({
* name: "MyEnum",
* values: g.enumValues(["a", "b"]),
* });
* ```
*
* ```ts
* const values = g.enumValues(["a", "b"]);
*
* assertDeepEqual(values, {
* a: { value: "a" },
* b: { value: "b" },
* });
* ```
*/
enumValues: <const Values extends readonly string[]>(
values: readonly [...Values]
) => {
[Key in Values[number]]: GEnumValueConfig<Key>;
};
/**
* Creates an {@link GEnumType enum type} with a number of
* {@link GEnumValueConfig enum values}.
*
* ```ts
* const MyEnum = g.enum({
* name: "MyEnum",
* values: g.enumValues(["a", "b"]),
* });
* // ==
* graphql`
* enum MyEnum {
* a
* b
* }
* `;
* ```
*
* ```ts
* const MyEnum = g.enum({
* name: "MyEnum",
* description: "My enum does things",
* values: {
* something: {
* description: "something something",
* value: "something",
* },
* thing: {
* description: "thing thing",
* deprecationReason: "something should be used instead of thing",
* value: "thing",
* },
* },
* });
* // ==
* graphql`
* """
* My enum does things
* """
* enum MyEnum {
* """
* something something
* """
* something
* """
* thing thing
* """
* thing @\deprecated(reason: "something should be used instead of thing")
* }
* `;)
* ```
*/
enum: <const Values extends Record<string, unknown>>(
config: GEnumTypeConfig<Values>
) => GEnumType<Values>;
/**
* Creates a {@link GArg GraphQL argument}.
*
* Args can can be used as arguments on output fields:
*
* ```ts
* g.field({
* type: g.String,
* args: {
* something: g.arg({ type: g.String }),
* },
* resolve(source, { something }) {
* return something || somethingElse;
* },
* });
* // ==
* graphql`(something: String): String`;
* ```
*
* Or as fields on input objects:
*
* ```ts
* const Something = g.inputObject({
* name: "Something",
* fields: {
* something: g.arg({ type: g.String }),
* },
* });
* // ==
* graphql`
* input Something {
* something: String
* }
* `;
* ```
*/
arg: <
Type extends GInputType,
DefaultValue extends InferValueFromInputType<Type> | undefined = undefined,
>(
arg: Flatten<
{
type: Type;
} & Omit<
GraphQLInputFieldConfig & GraphQLArgumentConfig,
"type" | "defaultValue"
>
> &
(undefined extends DefaultValue
? { defaultValue?: DefaultValue }
: { defaultValue: DefaultValue })
) => GArg<Type, DefaultValue extends undefined ? false : true>;
/**
* Creates an {@link GInputObjectType input object type}
*
* ```ts
* const Something = g.inputObject({
* name: "Something",
* fields: {
* something: g.arg({ type: g.String }),
* },
* });
* // ==
* graphql`
* input Something {
* something: String
* }
* `;
* ```
*
* ### Handling circular objects
*
* Circular input objects require explicitly specifying the fields on the
* object in the type because of TypeScript's limits with circularity.
*
* ```ts
* import { GInputObjectType } from "@graphql-ts/schema";
*
* type SomethingInputType = GInputObjectType<{
* something: g<typeof g.arg<SomethingInputType>>;
* }>;
* const Something: SomethingInputType = g.inputObject({
* name: "Something",
* fields: () => ({
* something: g.arg({ type: Something }),
* }),
* });
* ```
*
* You can specify all of your non-circular fields outside of the fields
* object and then use `typeof` to get the type to avoid writing the
* non-circular fields as types again.
*
* ```ts
* import { GInputObjectType } from "@graphql-ts/schema";
*
* const nonCircularFields = {
* thing: g.arg({ type: g.String }),
* };
* type SomethingInputType = GInputObjectType<
* typeof nonCircularFields & {
* something: g<typeof g.arg<SomethingInputType>>;
* }
* >;
* const Something: SomethingInputType = g.inputObject({
* name: "Something",
* fields: () => ({
* ...nonCircularFields,
* something: g.arg({ type: Something }),
* }),
* });
* ```
*/
inputObject: <
Fields extends {
[key: string]: IsOneOf extends true
? GArg<GNullableInputType, false>
: GArg<GInputType>;
},
IsOneOf extends boolean = false,
>(
config: GInputObjectTypeConfig<Fields, IsOneOf>
) => GInputObjectType<Fields, IsOneOf>;
/**
* Wraps any GraphQL type in a {@link GList list type}.
*
* ```ts
* const stringListType = g.list(g.String);
* // ==
* graphql`[String]`;
* ```
*
* When used as an input type, you will recieve an array of the inner type.
*
* ```ts
* g.field({
* type: g.String,
* args: { thing: g.arg({ type: g.list(g.String) }) },
* resolve(source, { thing }) {
* const theThing: undefined | null | Array<string | null> = thing;
* return "";
* },
* });
* ```
*
* When used as an output type, you can return an iterable of the inner type
* that also matches `typeof val === 'object'` so for example, you'll probably
* return an Array most of the time but you could also return a Set you
* couldn't return a string though, even though a string is an iterable, it
* doesn't match `typeof val === 'object'`.
*
* ```ts
* g.field({
* type: g.list(g.String),
* resolve() {
* return [""];
* },
* });
* ```
*
* ```ts
* g.field({
* type: g.list(g.String),
* resolve() {
* return new Set([""]);
* },
* });
* ```
*
* ```ts
* g.field({
* type: g.list(g.String),
* resolve() {
* // this will not be allowed
* return "some things";
* },
* });
* ```
*/
list: <Of extends GType<any>>(of: Of) => GList<Of>;
/**
* Wraps a {@link GNullableType nullable type} with a
* {@link GNonNull non-nullable type}.
*
* Types in GraphQL are always nullable by default so if you want to enforce
* that a type must always be there, you can use the non-null type.
*
* ```ts
* const nonNullableString = g.nonNull(g.String);
* // ==
* graphql`String!`;
* ```
*
* When using a non-null type as an input type, your resolver will never
* recieve null and consumers of your GraphQL API **must** provide a value for
* it unless you provide a default value.
*
* ```ts
* g.field({
* args: {
* someNonNullAndRequiredArg: g.arg({
* type: g.nonNull(g.String),
* }),
* someNonNullButOptionalArg: g.arg({
* type: g.nonNull(g.String),
* defaultValue: "some default",
* }),
* },
* type: g.String,
* resolve(source, args) {
* // both of these will always be a string
* args.someNonNullAndRequiredArg;
* args.someNonNullButOptionalArg;
*
* return "";
* },
* });
* // ==
* graphql`
* fieldName(
* someNonNullAndRequiredArg: String!
* someNonNullButOptionalArg: String! = "some default"
* ): String
* `;
* ```
*
* When using a non-null type as an output type, your resolver must never
* return null. If you do return null(which unless you do
* type-casting/ts-ignore/etc. `@graphql-ts/schema` will not let you do)
* graphql-js will return an error to consumers of your GraphQL API.
*
* Non-null types should be used very carefully on output types. If you have
* to do a fallible operation like a network request or etc. to get the value,
* it probably shouldn't be non-null. If you make a field non-null and doing
* the fallible operation fails, consumers of your GraphQL API will be unable
* to see any of the other fields on the object that the non-null field was
* on. For example, an id on some type is a good candidate for being non-null
* because if you have the item, you will already have the id so getting the
* id will never fail but fetching a related item from a database would be
* fallible so even if it will never be null in the success case, you should
* make it nullable.
*
* ```ts
* g.field({
* type: g.nonNull(g.String),
* resolve(source, args) {
* return "something";
* },
* });
* // ==
* graphql`
* fieldName: String!
* `;
* ```
*
* If you try to wrap another non-null type in a non-null type again, you will
* get a type error.
*
* ```ts
* // Argument of type 'NonNullType<ScalarType<string>>'
* // is not assignable to parameter of type 'NullableType'.
* g.nonNull(g.nonNull(g.String));
* ```
*/
nonNull: <Of extends GNullableType<any>>(of: Of) => GNonNull<Of>;
/**
* Creates a {@link GScalarType scalar type}.
*
* ```ts
* const BigInt = g.scalar({
* name: "BigInt",
* serialize(value) {
* if (typeof value !== "bigint")
* throw new GraphQLError(
* `unexpected value provided to BigInt scalar: ${value}`
* );
* return value.toString();
* },
* parseLiteral(value) {
* if (value.kind !== "StringValue")
* throw new GraphQLError("BigInt only accepts values as strings");
* return globalThis.BigInt(value.value);
* },
* parseValue(value) {
* if (typeof value === "bigint") return value;
* if (typeof value !== "string")
* throw new GraphQLError("BigInt only accepts values as strings");
* return globalThis.BigInt(value);
* },
* });
* // for fields on output types
* g.field({ type: someScalar });
*
* // for args on output fields or fields on input types
* g.arg({ type: someScalar });
* ```
*
* Note, while graphql-js allows you to express scalar types like the `ID`
* type which accepts integers and strings as both input values and return
* values from resolvers which are transformed into strings before calling
* resolvers and returning the query respectively, the type you use should be
* `string` for `ID` since that is what it is transformed into.
* `@graphql-ts/schema` doesn't currently express the coercion of scalars, you
* should instead convert values to the canonical form yourself before
* returning from resolvers.
*/
scalar: <Internal, External = Internal>(
config: GraphQLScalarTypeConfig<Internal, External>
) => GScalarType<Internal, External>;
ID: GScalarType<string>;
String: GScalarType<string>;
Float: GScalarType<number>;
Int: GScalarType<number>;
Boolean: GScalarType<boolean>;
};
/**
* The `gWithContext` function accepts a `Context` type parameter which binds
* the returned functions so they can be used to compose GraphQL types into a
* GraphQL schema.
*
* A simple schema with only a query type looks like this:
*
* ```ts
* import { gWithContext } from "@graphql-ts/schema";
* import { GraphQLSchema, graphql } from "graphql";
*
* type Context = {};
*
* const g = gWithContext<Context>();
* type g<T> = gWithContext.infer<T>;
*
* const Query = g.object()({
* name: "Query",
* fields: {
* hello: g.field({
* type: g.String,
* resolve() {
* return "Hello!";
* },
* }),
* },
* });
*
* const schema = new GraphQLSchema({
* query: Query,
* });
*
* graphql({
* source: `
* query {
* hello
* }
* `,
* schema,
* }).then((result) => {
* console.log(result);
* });
* ```
*
* You can use pass the `schema` to `ApolloServer` and other GraphQL servers.
*
* You can also create a more advanced schema with other object types, circular
* types, args, and mutations. See {@link GWithContext} for what the other
* functions on `g` do.
*
* ```ts
* import { gWithContext } from "@graphql-ts/schema";
* import { GraphQLSchema, graphql } from "graphql";
* import { deepEqual } from "node:assert";
*
* type Context = {
* todos: Map<string, TodoItem>;
* };
*
* const g = gWithContext<Context>();
* type g<T> = gWithContext.infer<T>;
*
* type TodoItem = {
* id: string;
* title: string;
* relatedTodos: string[];
* };
*
* const Todo: g<typeof g.object<TodoItem>> = g.object<TodoItem>()({
* name: "Todo",
* fields: () => ({
* id: g.field({ type: g.nonNull(g.ID) }),
* title: g.field({ type: g.nonNull(g.String) }),
* relatedTodos: g.field({
* type: g.list(Todo),
* resolve(source, _args, context) {
* return source.relatedTodos
* .map((id) => context.todos.get(id))
* .filter((todo) => todo !== undefined);
* },
* }),
* }),
* });
*
* const Query = g.object()({
* name: "Query",
* fields: {
* todos: g.field({
* type: g.list(Todo),
* resolve(_source, _args, context) {
* return context.todos.values();
* },
* }),
* },
* });
*
* const Mutation = g.object()({
* name: "Mutation",
* fields: {
* createTodo: g.field({
* args: {
* title: g.arg({ type: g.nonNull(g.String) }),
* relatedTodos: g.arg({
* type: g.nonNull(g.list(g.nonNull(g.ID))),
* defaultValue: [],
* }),
* },
* type: Todo,
* resolve(_source, { title, relatedTodos }, context) {
* const todo = { title, relatedTodos, id: crypto.randomUUID() };
* context.todos.set(todo.id, todo);
* return todo;
* },
* }),
* },
* });
*
* const schema = new GraphQLSchema({
* query: Query,
* mutation: Mutation,
* });
*
* (async () => {
* const contextValue: Context = { todos: new Map() };
* {
* const result = await graphql({
* source: `
* query {
* todos {
* title
* }
* }
* `,
* schema,
* contextValue,
* });
* deepEqual(result, { data: { todos: [] } });
* }
*
* {
* const result = await graphql({
* source: `
* mutation {
* createTodo(title: "Try graphql-ts") {
* title
* }
* }
* `,
* schema,
* contextValue,
* });
* deepEqual(result, {
* data: { createTodo: { title: "Try graphql-ts" } },
* });
* }
* {
* const result = await graphql({
* source: `
* query {
* todos {
* title
* }
* }
* `,
* schema,
* contextValue,
* });
* deepEqual(result, {
* data: { todos: [{ title: "Try graphql-ts" }] },
* });
* }
* })();
* ```
*/
export function gWithContext<Context>(): GWithContext<Context> {
return {
scalar(config) {
return new GScalarType(config);
},
list(of) {
return new GList(of);
},
nonNull(of) {
return new GNonNull(of);
},
inputObject(config) {
return new GInputObjectType(config);
},
enum(config) {
return new GEnumType(config);
},
union(config) {
return new GUnionType(config as any);
},
object() {
return function objectInner(config) {
return new GObjectType(config);
};
},
interface() {
return function interfaceInner(config) {
return new GInterfaceType(config);
};
},
fields() {
return function fieldsInner(fields) {
return fields;
};
},
field(field) {
if (!field.type) {
throw new Error("A type must be passed to g.field()");
}
return field as any;
},
interfaceField(field) {
if (!field.type) {
throw new Error("A type must be passed to g.interfaceField()");
}
return field;
},
arg(arg) {
if (!arg.type) {
throw new Error("A type must be passed to g.arg()");
}
return arg as any;
},
enumValues(values) {
return Object.fromEntries(
values.map((value) => [value, { value }])
) as any;
},
Int: GraphQLInt,
Float: GraphQLFloat,
String: GraphQLString,
Boolean: GraphQLBoolean,
ID: GraphQLID,
};
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export declare namespace gWithContext {
/**
* The `gWithContext.infer` type is useful particularly when defining circular
* types to resolve errors from TypeScript because of the circularity.
*
* We recommend aliasing `gWithContext.infer` to your `g` like this to make it
* easier to use:
*
* ```ts
* import { gWithContext } from "@graphql-ts/schema";
* type Context = {};
*
* const g = gWithContext<Context>();
* type g<T> = gWithContext.infer<T>;
*
* type PersonSource = { name: string; friends: PersonSource[] };
*
* const Person: g<typeof g.object<PersonSource>> =
* g.object<PersonSource>()({
* name: "Person",
* fields: () => ({
* name: g.field({ type: g.String }),
* friends: g.field({ type: g.list(Person) }),
* }),
* });
* ```
*/
export type infer<T> = T extends () => (args: any) => infer R
? R
: T extends (args: any) => infer R
? R
: never;
}
type Flatten<T> = {
[Key in keyof T]: T[Key];
} & {};