mizdra/graphql-codegen-typescript-fabbrica

Allow Transient Fields in `defineTypeFactory`

mizdra opened this issue · 1 comments

mizdra commented

Problem

Currently, Transient Fields are only allowed in defineTypeFactoryWithTransientFields.

declare function defineAuthorFactoryWithTransientFields<
  _TransientFieldsResolver extends TransientFieldsResolver<Author, Record<string, unknown>>,
  TOptions extends AuthorFactoryDefineOptions<ResolvedFields<_TransientFieldsResolver>>,
>(
  transientFields: _TransientFieldsResolver,
  options: TOptions,
): AuthorFactoryInterface<ResolvedFields<_TransientFieldsResolver>, TOptions>;

const BookFactory = defineBookFactory({
  defaultFields: {
    id: lazy(({ seq }) => `Book-${seq}`),
    title: lazy(({ seq }) => `ゆゆ式 ${seq}巻`),
    author: undefined,
  },
});
const AuthorFactory = defineAuthorFactoryWithTransientFields(
  {
    bookCount: 0,
  },
  {
    defaultFields: {
      id: lazy(({ seq }) => `Author-${seq}`),
      name: '三上小又',
      books: lazy(async ({ get }) => {
        const bookCount = await get('bookCount');
        // eslint-disable-next-line max-nested-callbacks
        return Promise.all(Array.from({ length: bookCount }, async () => BookFactory.build()));
      }),
    },
  },
);

This confuses users as they have to use an unusual API. In addition, it is awkward to have to add one more argument.

Solution

I want defineTypeFactory to allow Transient Fields. The user specifies the type of Transient Fields using the type argument. Also, the default value of Transient Fields is specified by the defaultFields option.

declare function defineAuthorFactory<
  TransientFields extends Record<string, unknown>,
  TOptions extends AuthorFactoryDefineOptions<TransientFields>,
>(
  options: TOptions,
): AuthorFactoryInterface<TransientFields, TOptions>;

const BookFactory = defineBookFactory({
  defaultFields: {
    id: lazy(({ seq }) => `Book-${seq}`),
    title: lazy(({ seq }) => `ゆゆ式 ${seq}巻`),
    author: undefined,
  },
});
type BookTransientFields = {
  bookCount: number;
};
const AuthorFactory = defineAuthorFactory<BookTransientFields>({
  defaultFields: {
    id: lazy(({ seq }) => `Author-${seq}`),
    name: '三上小又',
    books: lazy(async ({ get }) => {
      const bookCount = await get('bookCount');
      // eslint-disable-next-line max-nested-callbacks
      return Promise.all(Array.from({ length: bookCount }, async () => BookFactory.build()));
    }),
    bookCount: 0,
  },
});

Additional context

As you can see from the sample code, defineTypeFactory currently has a type argument TOptions. This type argument is inferred from the type of the options argument. This allows the user to strictly type the return value of defineTypeFactory without having to specify the type argument.

However, it causes problems when TransientFields is added to the type arguments of defineTypeFactory. The user must explicitly pass the type TransientFields from the function caller, but then the type of the options argument is not inferred.

TypeScript does not support partial inference of type arguments. Therefore, an implementation of this feature is currently not possible. We will probably have to wait for the following issue to be resolved.

mizdra commented

As a temporary workaround, defineAutorFactoryWithTransientFields can be defined by yourself: #20