From ad2a1f5c6ec00eec22fc298da57e27f915deafee Mon Sep 17 00:00:00 2001 From: mizdra Date: Sun, 27 Aug 2023 09:38:57 +0900 Subject: [PATCH 1/6] improve eslintrc --- .eslintrc.cjs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9c68fbd..faef932 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -7,6 +7,7 @@ module.exports = { parserOptions: { ecmaVersion: 2022, }, + reportUnusedDisableDirectives: true, env: { es2022: true, node: true, @@ -18,6 +19,16 @@ module.exports = { { files: ['*.ts', '*.tsx', '*.cts', '*.mts'], extends: ['@mizdra/mizdra/+typescript', '@mizdra/mizdra/+prettier'], + rules: { + '@typescript-eslint/ban-types': [ + 'error', + { + types: { + '{}': false, + }, + }, + ], + }, }, ], }; From 2d9b4680ce57d2cc54b7608d8d1d04f52e1496b3 Mon Sep 17 00:00:00 2001 From: mizdra Date: Thu, 24 Aug 2023 01:06:23 +0900 Subject: [PATCH 2/6] implement transient fields --- src/field-resolver.test.ts | 29 ++++--- src/field-resolver.ts | 86 +++++++++++++------- src/index.test.ts | 76 +++++++++++++++-- src/index.ts | 161 ++++++++++++++++++++++++++----------- 4 files changed, 262 insertions(+), 90 deletions(-) diff --git a/src/field-resolver.test.ts b/src/field-resolver.test.ts index 9a031bb..5c32a87 100644 --- a/src/field-resolver.test.ts +++ b/src/field-resolver.test.ts @@ -28,31 +28,36 @@ it('lazy', async () => { }); it('FieldResolver', () => { - type Type = { a: number }; - expectTypeOf>().toEqualTypeOf>(); + type TypeWithTransientFields = { a: number }; + expectTypeOf>().toEqualTypeOf< + number | Lazy<{ a: number }, number> + >(); }); it('DefaultFieldsResolver', () => { - type Type1 = { a: number; b: Type2[] }; - type Type2 = { c: number }; - expectTypeOf>().toEqualTypeOf<{ - a: number | undefined | Lazy; + type Type = { a: number; b: SubType[] }; + type SubType = { c: number }; + type TransientFields = { _a: number }; + expectTypeOf>().toEqualTypeOf<{ + a: number | undefined | Lazy; b: | readonly { readonly c: number | undefined }[] | undefined - | Lazy; + | Lazy; }>(); }); it('InputFieldsResolver', () => { - type Type1 = { a: number; b: Type2[] }; - type Type2 = { c: number }; - expectTypeOf>().toEqualTypeOf<{ - a?: number | undefined | Lazy; + type Type = { a: number; b: SubType[] }; + type SubType = { c: number }; + type TransientFields = { _a: number }; + expectTypeOf>().toEqualTypeOf<{ + a?: number | undefined | Lazy; b?: | readonly { readonly c: number | undefined }[] | undefined - | Lazy; + | Lazy; + _a?: number | undefined | Lazy; }>(); }); diff --git a/src/field-resolver.ts b/src/field-resolver.ts index afc4ee3..fd0dc89 100644 --- a/src/field-resolver.ts +++ b/src/field-resolver.ts @@ -1,31 +1,45 @@ import { DeepOptional, DeepReadonly, Merge } from './util.js'; -export type FieldResolverOptions = { +export type FieldResolverOptions = { seq: number; - get: (fieldName: FieldName) => Promise; + get: ( + fieldName: FieldName, + ) => Promise; }; -export class Lazy { - constructor(private readonly factory: (options: FieldResolverOptions) => Field | Promise) {} - async get(options: FieldResolverOptions): Promise { +export class Lazy { + constructor( + private readonly factory: (options: FieldResolverOptions) => Field | Promise, + ) {} + async get(options: FieldResolverOptions): Promise { return this.factory(options); } } /** Wrapper to delay field generation until needed. */ -export function lazy( - factory: (options: FieldResolverOptions) => Field | Promise, -): Lazy { +export function lazy( + factory: (options: FieldResolverOptions) => Field | Promise, +): Lazy { return new Lazy(factory); } -export type FieldResolver = Field | Lazy; +export type FieldResolver = Field | Lazy; /** The type of `defaultFields` option of `defineFactory` function. */ -export type DefaultFieldsResolver = { - [FieldName in keyof Type]: FieldResolver[FieldName]>>; +export type DefaultFieldsResolver = { + [FieldName in keyof Type]: FieldResolver[FieldName]>>; +}; +/** The type of `transientFields` option of `defineFactory` function. */ +export type TransientFieldsResolver = { + [FieldName in keyof TransientFields]: FieldResolver< + Type & TransientFields, + DeepReadonly[FieldName]> + >; }; /** The type of `inputFields` option of `build` method. */ -export type InputFieldsResolver = { - [FieldName in keyof Type]?: FieldResolver[FieldName]>>; +export type InputFieldsResolver = { + [FieldName in keyof (Type & TransientFields)]?: FieldResolver< + Type & TransientFields, + DeepReadonly[FieldName]> + >; }; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -39,19 +53,28 @@ export type ResolvedFields, - _DefaultFieldsResolver extends DefaultFieldsResolver = DefaultFieldsResolver, - _InputFieldsResolver extends InputFieldsResolver = InputFieldsResolver, + TransientFields extends Record, + _TransientFieldsResolver extends TransientFieldsResolver = TransientFieldsResolver< + Type, + TransientFields + >, + _DefaultFieldsResolver extends DefaultFieldsResolver = DefaultFieldsResolver< + Type, + TransientFields + >, + _InputFieldsResolver extends InputFieldsResolver = InputFieldsResolver, >( seq: number, defaultFieldsResolver: _DefaultFieldsResolver, + transientFieldsResolver: _TransientFieldsResolver, inputFieldsResolver: _InputFieldsResolver, -): Promise, ResolvedFields<_InputFieldsResolver>>> { +): Promise, Pick, keyof Type>>> { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Use any type as it is impossible to match types. const fields: any = {}; async function resolveField( - options: FieldResolverOptions, - fieldResolver: FieldResolver, + options: FieldResolverOptions, + fieldResolver: FieldResolver, ): Promise { if (fieldResolver instanceof Lazy) { return fieldResolver.get(options); @@ -60,22 +83,30 @@ export async function resolveFields< } } - async function resolveFieldAndUpdateCache( + async function resolveFieldAndUpdateCache( fieldName: FieldName, - ): Promise { + ): Promise<(Type & TransientFields)[FieldName]> { if (fieldName in fields) return fields[fieldName]; if (fieldName in inputFieldsResolver) { - // eslint-disable-next-line require-atomic-updates, no-await-in-loop -- The fields are resolved sequentially, so there is no possibility of a race condition. - fields[fieldName] = await resolveField(options, inputFieldsResolver[fieldName]); + // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. + fields[fieldName] = await resolveField(options, inputFieldsResolver[fieldName as keyof _InputFieldsResolver]); + return fields[fieldName]; + } else if (fieldName in transientFieldsResolver) { + // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. + fields[fieldName] = await resolveField( + options, + transientFieldsResolver[fieldName as keyof _TransientFieldsResolver], + ); + return fields[fieldName]; } else { - // eslint-disable-next-line require-atomic-updates, no-await-in-loop -- The fields are resolved sequentially, so there is no possibility of a race condition. - fields[fieldName] = await resolveField(options, defaultFieldsResolver[fieldName]); + // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. + fields[fieldName] = await resolveField(options, defaultFieldsResolver[fieldName as keyof _DefaultFieldsResolver]); + return fields[fieldName]; } - return fields[fieldName]; } - const options: FieldResolverOptions = { + const options: FieldResolverOptions = { seq, get: resolveFieldAndUpdateCache, }; @@ -85,5 +116,6 @@ export async function resolveFields< await resolveFieldAndUpdateCache(fieldName); } - return fields; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Use any type as it is impossible to match types. + return Object.fromEntries(Object.entries(fields).filter(([key]) => key in defaultFieldsResolver)) as any; } diff --git a/src/index.test.ts b/src/index.test.ts index d36da83..5a84493 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,6 +1,13 @@ import { expect, it, describe, assertType, expectTypeOf, vi } from 'vitest'; import { oneOf } from './test/util.js'; -import { defineBookFactory, resetAllSequence, lazy, defineUserFactory, defineAuthorFactory } from './index.js'; +import { + defineBookFactory, + resetAllSequence, + lazy, + defineUserFactory, + defineAuthorFactory, + defineAuthorFactoryWithTransientFields, +} from './index.js'; describe('integration test', () => { it('circular dependent type', async () => { @@ -209,8 +216,8 @@ describe('defineTypeFactory', () => { fullName: lazy(async ({ get }) => `${await get('firstName')} ${await get('lastName')}`), }, }); - const User = await UserFactory.build(); - expect(User).toStrictEqual({ + const user = await UserFactory.build(); + expect(user).toStrictEqual({ id: 'User-0', firstName: 'Komata', lastName: 'Mikami', @@ -221,14 +228,73 @@ describe('defineTypeFactory', () => { firstName: string; lastName: string; fullName: string; - }>(User); - expectTypeOf(User).not.toBeNever(); + }>(user); + expectTypeOf(user).not.toBeNever(); // The result of the field resolver is cached, so the resolver is called only once. expect(firstNameResolver).toHaveBeenCalledTimes(1); expect(lastNameResolver).toHaveBeenCalledTimes(1); }); }); + describe('transientFields', () => { + it('basic', async () => { + 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())); + }), + }, + }, + ); + const author1 = await AuthorFactory.build(); + expect(author1).toStrictEqual({ + id: 'Author-0', + name: '三上小又', + books: [], + }); + assertType<{ + id: string; + name: string; + books: { id: string; title: string; author: undefined }[]; + }>(author1); + expectTypeOf(author1).not.toBeNever(); + + const author2 = await AuthorFactory.build({ bookCount: 3 }); + expect(author2).toStrictEqual({ + id: 'Author-1', + name: '三上小又', + books: [ + { id: 'Book-0', title: 'ゆゆ式 0巻', author: undefined }, + { id: 'Book-1', title: 'ゆゆ式 1巻', author: undefined }, + { id: 'Book-2', title: 'ゆゆ式 2巻', author: undefined }, + ], + }); + assertType<{ + id: string; + name: string; + books: { + id: string; + title: string; + author: undefined; + }[]; + }>(author2); + }); + }); describe('resetAllSequence', () => { it('resets all sequence', async () => { const BookFactory = defineBookFactory({ diff --git a/src/index.ts b/src/index.ts index 96fb2e4..d0b021b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { type ResolvedFields, type DefaultFieldsResolver, + type TransientFieldsResolver, type InputFieldsResolver, resolveFields, lazy, @@ -20,7 +21,6 @@ export type Author = { name: string; books: Book[]; }; - export type User = { id: string; firstName: string; @@ -30,29 +30,36 @@ export type User = { // ---------- Book ---------- -interface BookFactoryDefineOptions { - defaultFields: DefaultFieldsResolver; +interface BookFactoryDefineOptions> { + defaultFields: DefaultFieldsResolver; + // TODO: add transientFields to here } -interface BookFactoryInterface { +interface BookFactoryInterface< + TransientFields extends Record, + TOptions extends BookFactoryDefineOptions, +> { build(): Promise>; - build>( + build>( inputFieldsResolver: T, - // eslint-disable-next-line @typescript-eslint/ban-types - ): Promise, ResolvedFields>>; + ): Promise, ResolvedFields>, keyof Book>>; resetSequence(): void; } -function defineBookFactoryInternal({ - defaultFields: defaultFieldsResolver, -}: TOptions): BookFactoryInterface { +function defineBookFactoryInternal< + _TransientFieldsResolver extends TransientFieldsResolver>, + TOptions extends BookFactoryDefineOptions>, +>( + transientFieldsResolver: _TransientFieldsResolver, + { defaultFields: defaultFieldsResolver }: TOptions, +): BookFactoryInterface, TOptions> { const seqKey = {}; const getSeq = () => getSequenceCounter(seqKey); return { - async build>( + async build>>( inputFieldsResolver?: T, - ): Promise, ResolvedFields>> { + ): Promise, Pick, keyof Book>>> { const seq = getSeq(); - return resolveFields(seq, defaultFieldsResolver, inputFieldsResolver ?? ({} as T)); + return resolveFields(seq, defaultFieldsResolver, transientFieldsResolver ?? {}, inputFieldsResolver ?? ({} as T)); }, resetSequence() { resetSequence(seqKey); @@ -66,37 +73,60 @@ function defineBookFactoryInternal({ * @param options * @returns factory {@link BookFactoryInterface} */ -export function defineBookFactory( +export function defineBookFactory>( options: TOptions, -): BookFactoryInterface { - return defineBookFactoryInternal(options); +): BookFactoryInterface<{}, TOptions> { + return defineBookFactoryInternal({}, options); +} + +/** + * Define factory for {@link Book} model with transient fields. + * + * @param options + * @returns factory {@link BookFactoryInterface} + */ +export function defineBookFactoryWithTransientFields< + _TransientFieldsResolver extends TransientFieldsResolver>, + TOptions extends BookFactoryDefineOptions>, +>( + transientFields: _TransientFieldsResolver, + options: TOptions, +): BookFactoryInterface, TOptions> { + return defineBookFactoryInternal(transientFields, options); } // ---------- Author ---------- -interface AuthorFactoryDefineOptions { - defaultFields: DefaultFieldsResolver; +interface AuthorFactoryDefineOptions> { + defaultFields: DefaultFieldsResolver; + // TODO: add transientFields to here } -interface AuthorFactoryInterface { +interface AuthorFactoryInterface< + TransientFields extends Record, + TOptions extends AuthorFactoryDefineOptions, +> { build(): Promise>; - build>( + build>( inputFieldsResolver: T, - // eslint-disable-next-line @typescript-eslint/ban-types - ): Promise, ResolvedFields>>; + ): Promise, ResolvedFields>, keyof Author>>; resetSequence(): void; } -function defineAuthorFactoryInternal({ - defaultFields: defaultFieldsResolver, -}: TOptions): AuthorFactoryInterface { +function defineAuthorFactoryInternal< + _TransientFieldsResolver extends TransientFieldsResolver>, + TOptions extends AuthorFactoryDefineOptions>, +>( + transientFieldsResolver: _TransientFieldsResolver, + { defaultFields: defaultFieldsResolver }: TOptions, +): AuthorFactoryInterface, TOptions> { const seqKey = {}; const getSeq = () => getSequenceCounter(seqKey); return { - async build>( + async build>>( inputFieldsResolver?: T, - ): Promise, ResolvedFields>> { + ): Promise, Pick, keyof Author>>> { const seq = getSeq(); - return resolveFields(seq, defaultFieldsResolver, inputFieldsResolver ?? ({} as T)); + return resolveFields(seq, defaultFieldsResolver, transientFieldsResolver ?? {}, inputFieldsResolver ?? ({} as T)); }, resetSequence() { resetSequence(seqKey); @@ -110,37 +140,60 @@ function defineAuthorFactoryInternal( +export function defineAuthorFactory>( options: TOptions, -): AuthorFactoryInterface { - return defineAuthorFactoryInternal(options); +): AuthorFactoryInterface<{}, TOptions> { + return defineAuthorFactoryInternal({}, options); +} + +/** + * Define factory for {@link Author} model with transient fields. + * + * @param options + * @returns factory {@link AuthorFactoryInterface} + */ +export function defineAuthorFactoryWithTransientFields< + _TransientFieldsResolver extends TransientFieldsResolver>, + TOptions extends AuthorFactoryDefineOptions>, +>( + transientFields: _TransientFieldsResolver, + options: TOptions, +): AuthorFactoryInterface, TOptions> { + return defineAuthorFactoryInternal(transientFields, options); } // ---------- User ---------- -interface UserFactoryDefineOptions { - defaultFields: DefaultFieldsResolver; +interface UserFactoryDefineOptions> { + defaultFields: DefaultFieldsResolver; + // TODO: add transientFields to here } -interface UserFactoryInterface { +interface UserFactoryInterface< + TransientFields extends Record, + TOptions extends UserFactoryDefineOptions, +> { build(): Promise>; - build>( + build>( inputFieldsResolver: T, - // eslint-disable-next-line @typescript-eslint/ban-types - ): Promise, ResolvedFields>>; + ): Promise, ResolvedFields>, keyof User>>; resetSequence(): void; } -function defineUserFactoryInternal({ - defaultFields: defaultFieldsResolver, -}: TOptions): UserFactoryInterface { +function defineUserFactoryInternal< + _TransientFieldsResolver extends TransientFieldsResolver>, + TOptions extends UserFactoryDefineOptions>, +>( + transientFieldsResolver: _TransientFieldsResolver, + { defaultFields: defaultFieldsResolver }: TOptions, +): UserFactoryInterface, TOptions> { const seqKey = {}; const getSeq = () => getSequenceCounter(seqKey); return { - async build>( + async build>>( inputFieldsResolver?: T, - ): Promise, ResolvedFields>> { + ): Promise, Pick, keyof User>>> { const seq = getSeq(); - return resolveFields(seq, defaultFieldsResolver, inputFieldsResolver ?? ({} as T)); + return resolveFields(seq, defaultFieldsResolver, transientFieldsResolver ?? {}, inputFieldsResolver ?? ({} as T)); }, resetSequence() { resetSequence(seqKey); @@ -154,8 +207,24 @@ function defineUserFactoryInternal({ * @param options * @returns factory {@link UserFactoryInterface} */ -export function defineUserFactory( +export function defineUserFactory>( + options: TOptions, +): UserFactoryInterface<{}, TOptions> { + return defineUserFactoryInternal({}, options); +} + +/** + * Define factory for {@link User} model with transient fields. + * + * @param options + * @returns factory {@link UserFactoryInterface} + */ +export function defineUserFactoryWithTransientFields< + _TransientFieldsResolver extends TransientFieldsResolver>, + TOptions extends UserFactoryDefineOptions>, +>( + transientFields: _TransientFieldsResolver, options: TOptions, -): UserFactoryInterface { - return defineUserFactoryInternal(options); +): UserFactoryInterface, TOptions> { + return defineUserFactoryInternal(transientFields, options); } From f572f8fb6fa258f327498392551052d3c14a887f Mon Sep 17 00:00:00 2001 From: mizdra Date: Sun, 27 Aug 2023 10:03:30 +0900 Subject: [PATCH 3/6] refactoring --- src/field-resolver.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/field-resolver.ts b/src/field-resolver.ts index fd0dc89..4f514fd 100644 --- a/src/field-resolver.ts +++ b/src/field-resolver.ts @@ -54,15 +54,9 @@ export type ResolvedFields, TransientFields extends Record, - _TransientFieldsResolver extends TransientFieldsResolver = TransientFieldsResolver< - Type, - TransientFields - >, - _DefaultFieldsResolver extends DefaultFieldsResolver = DefaultFieldsResolver< - Type, - TransientFields - >, - _InputFieldsResolver extends InputFieldsResolver = InputFieldsResolver, + _TransientFieldsResolver extends TransientFieldsResolver, + _DefaultFieldsResolver extends DefaultFieldsResolver, + _InputFieldsResolver extends InputFieldsResolver, >( seq: number, defaultFieldsResolver: _DefaultFieldsResolver, From 287564af4ae47a17c3921a4892939f10d8df5cc8 Mon Sep 17 00:00:00 2001 From: mizdra Date: Sun, 27 Aug 2023 11:41:54 +0900 Subject: [PATCH 4/6] refactoring --- src/field-resolver.ts | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/field-resolver.ts b/src/field-resolver.ts index 4f514fd..f2ea143 100644 --- a/src/field-resolver.ts +++ b/src/field-resolver.ts @@ -82,22 +82,16 @@ export async function resolveFields< ): Promise<(Type & TransientFields)[FieldName]> { if (fieldName in fields) return fields[fieldName]; - if (fieldName in inputFieldsResolver) { - // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. - fields[fieldName] = await resolveField(options, inputFieldsResolver[fieldName as keyof _InputFieldsResolver]); - return fields[fieldName]; - } else if (fieldName in transientFieldsResolver) { - // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. - fields[fieldName] = await resolveField( - options, - transientFieldsResolver[fieldName as keyof _TransientFieldsResolver], - ); - return fields[fieldName]; - } else { - // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. - fields[fieldName] = await resolveField(options, defaultFieldsResolver[fieldName as keyof _DefaultFieldsResolver]); - return fields[fieldName]; - } + const fieldResolver = + fieldName in inputFieldsResolver + ? inputFieldsResolver[fieldName as keyof _InputFieldsResolver] + : fieldName in transientFieldsResolver + ? transientFieldsResolver[fieldName as keyof _TransientFieldsResolver] + : defaultFieldsResolver[fieldName as keyof _DefaultFieldsResolver]; + + // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. + fields[fieldName] = await resolveField(options, fieldResolver); + return fields[fieldName]; } const options: FieldResolverOptions = { From fbd36bbb71db7b0701c363360873236dc649bb23 Mon Sep 17 00:00:00 2001 From: mizdra Date: Sun, 27 Aug 2023 11:42:26 +0900 Subject: [PATCH 5/6] refactoring --- src/field-resolver.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/field-resolver.ts b/src/field-resolver.ts index f2ea143..f24043c 100644 --- a/src/field-resolver.ts +++ b/src/field-resolver.ts @@ -63,12 +63,13 @@ export async function resolveFields< transientFieldsResolver: _TransientFieldsResolver, inputFieldsResolver: _InputFieldsResolver, ): Promise, Pick, keyof Type>>> { + type TypeWithTransientFields = Type & TransientFields; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Use any type as it is impossible to match types. const fields: any = {}; async function resolveField( - options: FieldResolverOptions, - fieldResolver: FieldResolver, + options: FieldResolverOptions, + fieldResolver: FieldResolver, ): Promise { if (fieldResolver instanceof Lazy) { return fieldResolver.get(options); @@ -77,9 +78,9 @@ export async function resolveFields< } } - async function resolveFieldAndUpdateCache( + async function resolveFieldAndUpdateCache( fieldName: FieldName, - ): Promise<(Type & TransientFields)[FieldName]> { + ): Promise { if (fieldName in fields) return fields[fieldName]; const fieldResolver = @@ -94,7 +95,7 @@ export async function resolveFields< return fields[fieldName]; } - const options: FieldResolverOptions = { + const options: FieldResolverOptions = { seq, get: resolveFieldAndUpdateCache, }; From 72b86e3747bb88e01a30085acadd82c45fcbe3f8 Mon Sep 17 00:00:00 2001 From: mizdra Date: Sun, 27 Aug 2023 12:10:06 +0900 Subject: [PATCH 6/6] refactoring --- src/field-resolver.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/field-resolver.ts b/src/field-resolver.ts index f24043c..7ff2a81 100644 --- a/src/field-resolver.ts +++ b/src/field-resolver.ts @@ -4,7 +4,7 @@ export type FieldResolverOptions = { seq: number; get: ( fieldName: FieldName, - ) => Promise; + ) => Promise; // FIXME: return type is wrong }; export class Lazy { @@ -64,23 +64,24 @@ export async function resolveFields< inputFieldsResolver: _InputFieldsResolver, ): Promise, Pick, keyof Type>>> { type TypeWithTransientFields = Type & TransientFields; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Use any type as it is impossible to match types. - const fields: any = {}; + const fields = {} as any; - async function resolveField( - options: FieldResolverOptions, - fieldResolver: FieldResolver, - ): Promise { + async function resolveField< + _FieldResolverOptions extends FieldResolverOptions, + _FieldResolver extends FieldResolver, + >(options: _FieldResolverOptions, fieldResolver: _FieldResolver): Promise> { if (fieldResolver instanceof Lazy) { return fieldResolver.get(options); } else { - return fieldResolver; + return fieldResolver as ResolvedField<_FieldResolver>; } } async function resolveFieldAndUpdateCache( fieldName: FieldName, - ): Promise { + ): Promise<(ResolvedFields<_DefaultFieldsResolver> & ResolvedFields<_InputFieldsResolver>)[FieldName]> { if (fieldName in fields) return fields[fieldName]; const fieldResolver = @@ -90,13 +91,14 @@ export async function resolveFields< ? transientFieldsResolver[fieldName as keyof _TransientFieldsResolver] : defaultFieldsResolver[fieldName as keyof _DefaultFieldsResolver]; - // eslint-disable-next-line require-atomic-updates -- The fields are resolved sequentially, so there is no possibility of a race condition. + // eslint-disable-next-line require-atomic-updates fields[fieldName] = await resolveField(options, fieldResolver); return fields[fieldName]; } const options: FieldResolverOptions = { seq, + // @ts-expect-error -- FIXME: return type is wrong get: resolveFieldAndUpdateCache, };