Skip to content

Commit 145e778

Browse files
committed
improve transient field usability
1 parent d4f83e5 commit 145e778

File tree

3 files changed

+74
-132
lines changed

3 files changed

+74
-132
lines changed

src/field-resolver.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,9 @@ export function lazy<TypeWithTransientFields, Field>(
2525
export type FieldResolver<TypeWithTransientFields, Field> = Field | Lazy<TypeWithTransientFields, Field>;
2626
/** The type of `defaultFields` option of `defineFactory` function. */
2727
export type DefaultFieldsResolver<Type, TransientFields> = {
28-
[FieldName in keyof Type]: FieldResolver<Type & TransientFields, DeepReadonly<DeepOptional<Type>[FieldName]>>;
29-
};
30-
/** The type of `transientFields` option of `defineFactory` function. */
31-
export type TransientFieldsResolver<Type, TransientFields> = {
32-
[FieldName in keyof TransientFields]: FieldResolver<
28+
[FieldName in keyof (Type & TransientFields)]: FieldResolver<
3329
Type & TransientFields,
34-
DeepReadonly<DeepOptional<TransientFields>[FieldName]>
30+
DeepReadonly<DeepOptional<Type & TransientFields>[FieldName]>
3531
>;
3632
};
3733
/** The type of `inputFields` option of `build` method. */
@@ -54,13 +50,12 @@ export type ResolvedFields<FieldsResolver extends Record<string, FieldResolver<u
5450
export async function resolveFields<
5551
Type extends Record<string, unknown>,
5652
TransientFields extends Record<string, unknown>,
57-
_TransientFieldsResolver extends TransientFieldsResolver<Type, TransientFields>,
5853
_DefaultFieldsResolver extends DefaultFieldsResolver<Type, TransientFields>,
5954
_InputFieldsResolver extends InputFieldsResolver<Type, TransientFields>,
6055
>(
56+
fieldNames: readonly (keyof Type)[],
6157
seq: number,
6258
defaultFieldsResolver: _DefaultFieldsResolver,
63-
transientFieldsResolver: _TransientFieldsResolver,
6459
inputFieldsResolver: _InputFieldsResolver,
6560
): Promise<Merge<ResolvedFields<_DefaultFieldsResolver>, Pick<ResolvedFields<_InputFieldsResolver>, keyof Type>>> {
6661
type TypeWithTransientFields = Type & TransientFields;
@@ -87,8 +82,6 @@ export async function resolveFields<
8782
const fieldResolver =
8883
fieldName in inputFieldsResolver
8984
? inputFieldsResolver[fieldName as keyof _InputFieldsResolver]
90-
: fieldName in transientFieldsResolver
91-
? transientFieldsResolver[fieldName as keyof _TransientFieldsResolver]
9285
: defaultFieldsResolver[fieldName as keyof _DefaultFieldsResolver];
9386

9487
// eslint-disable-next-line require-atomic-updates
@@ -107,5 +100,5 @@ export async function resolveFields<
107100
}
108101

109102
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Use any type as it is impossible to match types.
110-
return Object.fromEntries(Object.entries(fields).filter(([key]) => key in defaultFieldsResolver)) as any;
103+
return Object.fromEntries(Object.entries(fields).filter(([key]) => fieldNames.includes(key))) as any;
111104
}

src/index.test.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
lazy,
77
defineUserFactory,
88
defineAuthorFactory,
9-
defineAuthorFactoryWithTransientFields,
9+
AuthorFactoryDefineOptions,
10+
AuthorFactoryInterface,
1011
} from './index.js';
1112

1213
describe('integration test', () => {
@@ -240,29 +241,35 @@ describe('defineTypeFactory', () => {
240241
});
241242
describe('transientFields', () => {
242243
it('basic', async () => {
244+
// Define factory with transient fields
245+
type AuthorTransientFields = {
246+
bookCount: number;
247+
};
248+
function defineAuthorFactoryWithTransientFields<
249+
Options extends AuthorFactoryDefineOptions<AuthorTransientFields>,
250+
>(options: Options): AuthorFactoryInterface<AuthorTransientFields, Options> {
251+
return defineAuthorFactory(options);
252+
}
253+
243254
const BookFactory = defineBookFactory({
244255
defaultFields: {
245256
id: lazy(({ seq }) => `Book-${seq}`),
246257
title: lazy(({ seq }) => `ゆゆ式 ${seq}巻`),
247258
author: undefined,
248259
},
249260
});
250-
const AuthorFactory = defineAuthorFactoryWithTransientFields(
251-
{
261+
const AuthorFactory = defineAuthorFactoryWithTransientFields({
262+
defaultFields: {
263+
id: lazy(({ seq }) => `Author-${seq}`),
264+
name: '三上小又',
265+
books: lazy(async ({ get }) => {
266+
const bookCount = (await get('bookCount')) ?? 0;
267+
// eslint-disable-next-line max-nested-callbacks
268+
return Promise.all(Array.from({ length: bookCount }, async () => BookFactory.build()));
269+
}),
252270
bookCount: 0,
253271
},
254-
{
255-
defaultFields: {
256-
id: lazy(({ seq }) => `Author-${seq}`),
257-
name: '三上小又',
258-
books: lazy(async ({ get }) => {
259-
const bookCount = (await get('bookCount')) ?? 0;
260-
// eslint-disable-next-line max-nested-callbacks
261-
return Promise.all(Array.from({ length: bookCount }, async () => BookFactory.build()));
262-
}),
263-
},
264-
},
265-
);
272+
});
266273
const author1 = await AuthorFactory.build();
267274
expect(author1).toStrictEqual({
268275
id: 'Author-0',

src/index.ts

Lines changed: 48 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
type ResolvedFields,
33
type DefaultFieldsResolver,
4-
type TransientFieldsResolver,
54
type InputFieldsResolver,
65
resolveFields,
76
lazy,
@@ -27,39 +26,38 @@ export type User = {
2726
lastName: string;
2827
fullName: string;
2928
};
29+
const BookFieldNames = ['id', 'title', 'author'] as const;
30+
const AuthorFieldNames = ['id', 'name', 'books'] as const;
31+
const UserFieldNames = ['id', 'firstName', 'lastName', 'fullName'] as const;
3032

3133
// ---------- Book ----------
3234

33-
interface BookFactoryDefineOptions<TransientFields extends Record<string, unknown>> {
35+
export interface BookFactoryDefineOptions<TransientFields extends Record<string, unknown>> {
3436
defaultFields: DefaultFieldsResolver<Book, TransientFields>;
35-
// TODO: add transientFields to here
3637
}
37-
interface BookFactoryInterface<
38+
export interface BookFactoryInterface<
3839
TransientFields extends Record<string, unknown>,
39-
TOptions extends BookFactoryDefineOptions<TransientFields>,
40+
Options extends BookFactoryDefineOptions<TransientFields>,
4041
> {
41-
build(): Promise<ResolvedFields<TOptions['defaultFields']>>;
42+
build(): Promise<ResolvedFields<Options['defaultFields']>>;
4243
build<const T extends InputFieldsResolver<Book, TransientFields>>(
4344
inputFieldsResolver: T,
44-
): Promise<Pick<Merge<ResolvedFields<TOptions['defaultFields']>, ResolvedFields<T>>, keyof Book>>;
45+
): Promise<Pick<Merge<ResolvedFields<Options['defaultFields']>, ResolvedFields<T>>, keyof Book>>;
4546
resetSequence(): void;
4647
}
4748

4849
function defineBookFactoryInternal<
49-
_TransientFieldsResolver extends TransientFieldsResolver<Book, Record<string, unknown>>,
50-
TOptions extends BookFactoryDefineOptions<ResolvedFields<_TransientFieldsResolver>>,
51-
>(
52-
transientFieldsResolver: _TransientFieldsResolver,
53-
{ defaultFields: defaultFieldsResolver }: TOptions,
54-
): BookFactoryInterface<ResolvedFields<_TransientFieldsResolver>, TOptions> {
50+
TransientFields extends Record<string, unknown>,
51+
Options extends BookFactoryDefineOptions<TransientFields>,
52+
>({ defaultFields: defaultFieldsResolver }: Options): BookFactoryInterface<TransientFields, Options> {
5553
const seqKey = {};
5654
const getSeq = () => getSequenceCounter(seqKey);
5755
return {
58-
async build<const T extends InputFieldsResolver<Book, ResolvedFields<_TransientFieldsResolver>>>(
56+
async build<const T extends InputFieldsResolver<Book, TransientFields>>(
5957
inputFieldsResolver?: T,
60-
): Promise<Merge<ResolvedFields<TOptions['defaultFields']>, Pick<ResolvedFields<T>, keyof Book>>> {
58+
): Promise<Merge<ResolvedFields<Options['defaultFields']>, Pick<ResolvedFields<T>, keyof Book>>> {
6159
const seq = getSeq();
62-
return resolveFields(seq, defaultFieldsResolver, transientFieldsResolver ?? {}, inputFieldsResolver ?? ({} as T));
60+
return resolveFields(BookFieldNames, seq, defaultFieldsResolver, inputFieldsResolver ?? ({} as T));
6361
},
6462
resetSequence() {
6563
resetSequence(seqKey);
@@ -73,60 +71,40 @@ function defineBookFactoryInternal<
7371
* @param options
7472
* @returns factory {@link BookFactoryInterface}
7573
*/
76-
export function defineBookFactory<TOptions extends BookFactoryDefineOptions<{}>>(
77-
options: TOptions,
78-
): BookFactoryInterface<{}, TOptions> {
79-
return defineBookFactoryInternal({}, options);
80-
}
81-
82-
/**
83-
* Define factory for {@link Book} model with transient fields.
84-
*
85-
* @param options
86-
* @returns factory {@link BookFactoryInterface}
87-
*/
88-
export function defineBookFactoryWithTransientFields<
89-
_TransientFieldsResolver extends TransientFieldsResolver<Book, Record<string, unknown>>,
90-
TOptions extends BookFactoryDefineOptions<ResolvedFields<_TransientFieldsResolver>>,
91-
>(
92-
transientFields: _TransientFieldsResolver,
93-
options: TOptions,
94-
): BookFactoryInterface<ResolvedFields<_TransientFieldsResolver>, TOptions> {
95-
return defineBookFactoryInternal(transientFields, options);
74+
export function defineBookFactory<Options extends BookFactoryDefineOptions<{}>>(
75+
options: Options,
76+
): BookFactoryInterface<{}, Options> {
77+
return defineBookFactoryInternal(options);
9678
}
9779

9880
// ---------- Author ----------
9981

100-
interface AuthorFactoryDefineOptions<TransientFields extends Record<string, unknown>> {
82+
export interface AuthorFactoryDefineOptions<TransientFields extends Record<string, unknown>> {
10183
defaultFields: DefaultFieldsResolver<Author, TransientFields>;
102-
// TODO: add transientFields to here
10384
}
104-
interface AuthorFactoryInterface<
85+
export interface AuthorFactoryInterface<
10586
TransientFields extends Record<string, unknown>,
106-
TOptions extends AuthorFactoryDefineOptions<TransientFields>,
87+
Options extends AuthorFactoryDefineOptions<TransientFields>,
10788
> {
108-
build(): Promise<ResolvedFields<TOptions['defaultFields']>>;
89+
build(): Promise<ResolvedFields<Options['defaultFields']>>;
10990
build<const T extends InputFieldsResolver<Author, TransientFields>>(
11091
inputFieldsResolver: T,
111-
): Promise<Pick<Merge<ResolvedFields<TOptions['defaultFields']>, ResolvedFields<T>>, keyof Author>>;
92+
): Promise<Pick<Merge<ResolvedFields<Options['defaultFields']>, ResolvedFields<T>>, keyof Author>>;
11293
resetSequence(): void;
11394
}
11495

11596
function defineAuthorFactoryInternal<
116-
_TransientFieldsResolver extends TransientFieldsResolver<Author, Record<string, unknown>>,
117-
TOptions extends AuthorFactoryDefineOptions<ResolvedFields<_TransientFieldsResolver>>,
118-
>(
119-
transientFieldsResolver: _TransientFieldsResolver,
120-
{ defaultFields: defaultFieldsResolver }: TOptions,
121-
): AuthorFactoryInterface<ResolvedFields<_TransientFieldsResolver>, TOptions> {
97+
TransientFields extends Record<string, unknown>,
98+
Options extends AuthorFactoryDefineOptions<TransientFields>,
99+
>({ defaultFields: defaultFieldsResolver }: Options): AuthorFactoryInterface<TransientFields, Options> {
122100
const seqKey = {};
123101
const getSeq = () => getSequenceCounter(seqKey);
124102
return {
125-
async build<const T extends InputFieldsResolver<Author, ResolvedFields<_TransientFieldsResolver>>>(
103+
async build<const T extends InputFieldsResolver<Author, TransientFields>>(
126104
inputFieldsResolver?: T,
127-
): Promise<Merge<ResolvedFields<TOptions['defaultFields']>, Pick<ResolvedFields<T>, keyof Author>>> {
105+
): Promise<Merge<ResolvedFields<Options['defaultFields']>, Pick<ResolvedFields<T>, keyof Author>>> {
128106
const seq = getSeq();
129-
return resolveFields(seq, defaultFieldsResolver, transientFieldsResolver ?? {}, inputFieldsResolver ?? ({} as T));
107+
return resolveFields(AuthorFieldNames, seq, defaultFieldsResolver, inputFieldsResolver ?? ({} as T));
130108
},
131109
resetSequence() {
132110
resetSequence(seqKey);
@@ -140,60 +118,40 @@ function defineAuthorFactoryInternal<
140118
* @param options
141119
* @returns factory {@link AuthorFactoryInterface}
142120
*/
143-
export function defineAuthorFactory<TOptions extends AuthorFactoryDefineOptions<{}>>(
144-
options: TOptions,
145-
): AuthorFactoryInterface<{}, TOptions> {
146-
return defineAuthorFactoryInternal({}, options);
147-
}
148-
149-
/**
150-
* Define factory for {@link Author} model with transient fields.
151-
*
152-
* @param options
153-
* @returns factory {@link AuthorFactoryInterface}
154-
*/
155-
export function defineAuthorFactoryWithTransientFields<
156-
_TransientFieldsResolver extends TransientFieldsResolver<Author, Record<string, unknown>>,
157-
TOptions extends AuthorFactoryDefineOptions<ResolvedFields<_TransientFieldsResolver>>,
158-
>(
159-
transientFields: _TransientFieldsResolver,
160-
options: TOptions,
161-
): AuthorFactoryInterface<ResolvedFields<_TransientFieldsResolver>, TOptions> {
162-
return defineAuthorFactoryInternal(transientFields, options);
121+
export function defineAuthorFactory<Options extends AuthorFactoryDefineOptions<{}>>(
122+
options: Options,
123+
): AuthorFactoryInterface<{}, Options> {
124+
return defineAuthorFactoryInternal(options);
163125
}
164126

165127
// ---------- User ----------
166128

167-
interface UserFactoryDefineOptions<TransientFields extends Record<string, unknown>> {
129+
export interface UserFactoryDefineOptions<TransientFields extends Record<string, unknown>> {
168130
defaultFields: DefaultFieldsResolver<User, TransientFields>;
169-
// TODO: add transientFields to here
170131
}
171-
interface UserFactoryInterface<
132+
export interface UserFactoryInterface<
172133
TransientFields extends Record<string, unknown>,
173-
TOptions extends UserFactoryDefineOptions<TransientFields>,
134+
Options extends UserFactoryDefineOptions<TransientFields>,
174135
> {
175-
build(): Promise<ResolvedFields<TOptions['defaultFields']>>;
136+
build(): Promise<ResolvedFields<Options['defaultFields']>>;
176137
build<const T extends InputFieldsResolver<User, TransientFields>>(
177138
inputFieldsResolver: T,
178-
): Promise<Pick<Merge<ResolvedFields<TOptions['defaultFields']>, ResolvedFields<T>>, keyof User>>;
139+
): Promise<Pick<Merge<ResolvedFields<Options['defaultFields']>, ResolvedFields<T>>, keyof User>>;
179140
resetSequence(): void;
180141
}
181142

182143
function defineUserFactoryInternal<
183-
_TransientFieldsResolver extends TransientFieldsResolver<User, Record<string, unknown>>,
184-
TOptions extends UserFactoryDefineOptions<ResolvedFields<_TransientFieldsResolver>>,
185-
>(
186-
transientFieldsResolver: _TransientFieldsResolver,
187-
{ defaultFields: defaultFieldsResolver }: TOptions,
188-
): UserFactoryInterface<ResolvedFields<_TransientFieldsResolver>, TOptions> {
144+
TransientFields extends Record<string, unknown>,
145+
Options extends UserFactoryDefineOptions<TransientFields>,
146+
>({ defaultFields: defaultFieldsResolver }: Options): UserFactoryInterface<TransientFields, Options> {
189147
const seqKey = {};
190148
const getSeq = () => getSequenceCounter(seqKey);
191149
return {
192-
async build<const T extends InputFieldsResolver<User, ResolvedFields<_TransientFieldsResolver>>>(
150+
async build<const T extends InputFieldsResolver<User, TransientFields>>(
193151
inputFieldsResolver?: T,
194-
): Promise<Merge<ResolvedFields<TOptions['defaultFields']>, Pick<ResolvedFields<T>, keyof User>>> {
152+
): Promise<Merge<ResolvedFields<Options['defaultFields']>, Pick<ResolvedFields<T>, keyof User>>> {
195153
const seq = getSeq();
196-
return resolveFields(seq, defaultFieldsResolver, transientFieldsResolver ?? {}, inputFieldsResolver ?? ({} as T));
154+
return resolveFields(UserFieldNames, seq, defaultFieldsResolver, inputFieldsResolver ?? ({} as T));
197155
},
198156
resetSequence() {
199157
resetSequence(seqKey);
@@ -207,24 +165,8 @@ function defineUserFactoryInternal<
207165
* @param options
208166
* @returns factory {@link UserFactoryInterface}
209167
*/
210-
export function defineUserFactory<TOptions extends UserFactoryDefineOptions<{}>>(
211-
options: TOptions,
212-
): UserFactoryInterface<{}, TOptions> {
213-
return defineUserFactoryInternal({}, options);
214-
}
215-
216-
/**
217-
* Define factory for {@link User} model with transient fields.
218-
*
219-
* @param options
220-
* @returns factory {@link UserFactoryInterface}
221-
*/
222-
export function defineUserFactoryWithTransientFields<
223-
_TransientFieldsResolver extends TransientFieldsResolver<User, Record<string, unknown>>,
224-
TOptions extends UserFactoryDefineOptions<ResolvedFields<_TransientFieldsResolver>>,
225-
>(
226-
transientFields: _TransientFieldsResolver,
227-
options: TOptions,
228-
): UserFactoryInterface<ResolvedFields<_TransientFieldsResolver>, TOptions> {
229-
return defineUserFactoryInternal(transientFields, options);
168+
export function defineUserFactory<Options extends UserFactoryDefineOptions<{}>>(
169+
options: Options,
170+
): UserFactoryInterface<{}, Options> {
171+
return defineUserFactoryInternal(options);
230172
}

0 commit comments

Comments
 (0)