If you have common fields or arguments that are shared across multiple types (but you don't want to use an interface to share the common logic) you can write helper functions to generate these fields for you.
import { ObjectRef } from '@pothos/core';
import builder from './builder';
function addCommonFields(refs: ObjectRef<unknown, { id: string }>[]) {
for (const ref of refs) {
builder.objectFields(ref, (t) => ({
id: t.exposeID('id', {}),
idLength: t.int({
resolve: (parent) => parent.id.length,
}),
}));
}
}
const WithCommonFields1 = builder.objectRef<{ id: string }>('WithCommonFields1').implement({});
const WithCommonFields2 = builder.objectRef<{ id: string }>('WithCommonFields2').implement({});
addCommonFields([WithCommonFields1, WithCommonFields2]);
This will apply the id
and idLength
fields to both of the object types. The ObjectRef
type is
what is returned when creating an object (or when calling builder.objectRef
). It takes 2 generic
parameters: The first is the shape a resolver is expected to resolve to for that type, and the
second is the shape of the parent arg when defining a field on that type. These 2 are generally the
same, but can differ for some special cases (like with loadableObject
from the dataloader plugin,
which allows resolvers to resolve to an ID
rather than the actual object). In this case, we only
care about the second parameter since we are defining fields.
If you want to define fields on an interface, you can use InterfaceRef
instead. If your helper
accepts both, you can differentiate the refs by using ref.kind
which will be either Object
or
Interface
.
Args are a little more complicated fields on objects and interfaces. Pothos infers the shape of args for your resolvers, so you can't just add on more args later. Instead, we can define a helper that returns a set of args to apply to your field. To make this work, we need to get a few extra types:
import { InputFieldBuilder } from '@pothos/core';
export interface SchemaTypes {
Scalars: {
ID: {
Input: string;
Output: string;
};
};
}
export type TypesWithDefaults = PothosSchemaTypes.ExtendDefaultTypes<SchemaTypes>;
const builder = new SchemaBuilder<SchemaTypes>({});
function createCommonArgs(arg: ArgBuilder<TypesWithDefault>) {
return {
id: arg.id({}),
reason: arg({ type: 'String', required: false }),
};
}
builder.mutationType({
fields: (t) => ({
mutation1: t.boolean({
args: {
...createCommonArgs(t.arg),
},
resolve: (parent, args) => !!args.reason,
}),
mutation2: t.boolean({
args: {
...createCommonArgs(t.arg),
},
resolve: (parent, args) => !!args.reason,
}),
}),
});
In this example SchemaTypes
are the types that will be provided to the builder when it is created.
Internally Pothos extends these with some default types. This extended set of types is what gets
passed around in many of Pothos's internal types. To correctly type our helper function, we need to
create a version of SchemaTypes
with the same defaults Pothos adds in (TypesWithDefaults
). Once
we have TypesWithDefaults
we can define a helper function that accepts an arg builder
(ArgBuilder<TypesWithDefaults>
) and creates a set of arguments.
The last step is to call your helper with t.arg
(the arg builder), and spread the returned args
into the args object for the current field.
Input fields are similar to args, and also all need to be present when the type is defined so that Pothos can infer the correct types.
import { InputFieldBuilder } from '@pothos/core';
import builder, { TypesWithDefault } from './builder';
function createInputFields(t: InputFieldBuilder<TypesWithDefault, 'InputObject'>) {
return {
id: t.id({}),
reason: t.field({ type: 'String', required: false }),
};
}
builder.inputType('InputWithCommonFields1', {
fields: (t) => ({
...createInputFields(t),
}),
});
builder.inputType('InputWithCommonFields2', {
fields: (t) => ({
...createInputFields(t),
}),
});