Union types are defined with a list of object types:
const builder = new SchemaBuilder<{
  Objects: {
    GiraffeStringFact: { factKind: 'string'; fact: string };
    GiraffeNumericFact: { factKind: 'number'; fact: string; value: number };
  };
}>({});
builder.objectType('GiraffeStringFact', {
  fields: (t) => ({
    fact: t.exposeString('fact', {}),
  }),
});
const GiraffeNumericFact = builder.objectType('GiraffeNumericFact', {
  fields: (t) => ({
    fact: t.exposeString('fact', {}),
    value: t.exposeFloat('value', {}),
  }),
});
const GiraffeFact = builder.unionType('GiraffeFact', {
  types: ['GiraffeStringFact', GiraffeNumericFact],
  resolveType: (fact) => {
    switch (fact.factKind) {
      case 'number':
        return GiraffeNumericFact;
      case 'string':
        return 'GiraffeStringFact';
    }
  },
});
The types array can either contain Object type names defined in SchemaTypes, or and Object Ref
created by object type. builder.objectType, builder.objectRef or other method, or a class that
was used to implement an object type.
The resolveType function will be called with each item returned by a field that returns the
unionType, and is used to determine which concrete the value corresponds to. It is usually good to
have a shared property you can use to differentiate your union members.
builder.queryField('giraffeFacts', (t) =>
  t.field({
    type: [GiraffeFact],
    resolve: () => {
      const fact1 = {
        factKind: 'string',
        fact: 'A giraffe’s spots are much like human fingerprints. No two individual giraffes have exactly the same pattern',
      } as const;
      const fact2 = {
        factKind: 'number',
        fact: 'Top speed (MPH)',
        value: 35,
      } as const;
      return [fact1, fact2];
    },
  }),
);