Pothos

Type variants

Variants

It is often useful to be able to define multiple object types based on the same table. This can be done using a feature called variants. The variants API consists of 3 parts:

  • A variant option that can be passed instead of a name on drizzleObjects
  • The ability to pass an ObjectRef to the type option of t.relation and other similar fields
  • A t.field method that works similar to t.relation, but is used to define a GraphQL field that references a variant of the same record.
// Viewer type representing the current user
export const Viewer = builder.drizzleObject('users', {
  variant: 'Viewer',
  select: {},
  fields: (t) => ({
    id: t.exposeID('id'),
    // A reference to the normal user type so normal user fields can be queried
    user: t.variant('users'),
    // Adding drafts to View allows a user to fetch their own drafts without exposing it for Other Users in the API
    drafts: t.relation('posts', {
      query: {
        where: {
          published: false,
        },
        orderBy: {
          updatedAt: 'desc',
        },
      },
    }),
  }),
});

builder.queryType({
  fields: (t) => ({
    me: t.drizzleField({
      // We can use the ref returned by builder.drizzleObject to define our `drizzleField`
      type: Viewer,
      resolve: (query, root, args, ctx) =>
        db.query.users.findFirst(
          query({
            where: {
              id: ctx.user.id,
            },
          }),
        ),
    }),
  }),
});

builder.drizzleNode('users', {
  name: 'User',
  fields: (t) => ({
    firstName: t.exposeString('firstName'),
    // This field will resolve to the Viewer type, but be set to null if the user is not the current user
    viewer: t.variant(Viewer, {
      isNull: (user, args, ctx) => user.id !== ctx.user?.id,
    }),
  }),
});

A t.variant field can have a select of its own. It is planned along with the variant's type-level selection when the variant is queried through that field:

builder.drizzleNode('users', {
  name: 'User',
  fields: (t) => ({
    viewer: t.variant(Viewer, {
      // loaded with the row when `viewer` is selected
      select: { columns: { email: true } },
      isNull: (user, args, ctx) => user.id !== ctx.user?.id,
    }),
  }),
});

Two variants of one table selected for the same row have their type-level selections merged into a single query, which can fail if they disagree. See Conflicting selections between variants.

On this page