interface-forge
Version:
A TypeScript library for creating strongly typed mock data factories using Faker.js for test data generation
636 lines (616 loc) • 25.7 kB
TypeScript
import { Faker } from '@faker-js/faker';
import { LocaleDefinition } from '@faker-js/faker';
import { Randomizer } from '@faker-js/faker';
import { z } from 'zod/v4';
import { ZodObject } from 'zod/v4';
import { ZodType } from 'zod/v4';
declare type AfterBuildHook<T> = (obj: T) => Promise<T> | T;
declare type BeforeBuildHook<T> = (params: Partial<T>) => Partial<T> | Promise<Partial<T>>;
declare interface CreateManyOptions<T> {
adapter?: PersistenceAdapter<T>;
}
declare interface CreateOptions<T> {
adapter?: PersistenceAdapter<T>;
}
/**
* A factory class for generating type-safe mock data by extending Faker.js functionality.
* Provides methods for creating single instances, batches, and complex object compositions
* with support for circular references through depth control and persistence.
*
* @template T - The type of objects this factory generates
* @template O - The type of factory options
*/
declare class Factory<T, O extends FactoryOptions = FactoryOptions, F extends FactoryFunction<T> | PartialFactoryFunction<T> = FactoryFunction<T>> extends Faker {
#private;
readonly options?: {
maxDepth: number;
} & Omit<O, 'locale' | 'maxDepth' | 'randomizer'>;
protected afterBuildHooks: AfterBuildHook<T>[];
protected beforeBuildHooks: BeforeBuildHook<T>[];
protected readonly factory: F;
private defaultAdapter?;
constructor(factory: F, { locale, randomizer, ...rest }?: Partial<O>);
/**
* Adds a hook that will be executed after building the instance.
* Hooks are executed in the order they were added and can be either synchronous or asynchronous.
* This method returns the factory instance for method chaining.
*
* @param hook Function that receives the built instance and returns the modified instance
* @returns The current Factory instance for method chaining
*/
afterBuild(hook: AfterBuildHook<T>): this;
/**
* Generates an array of instances using the factory's schema.
* Supports both uniform overrides (same for all instances) and individual overrides per instance.
*
* @param size Number of instances to generate (must be non-negative integer)
* @param kwargs Either a single partial object (applied to all) or an array of partials (one per instance)
* @returns Array of generated instances
* @throws {Error} If size is negative or not an integer
*/
batch: (size: number, kwargs?: Partial<T> | Partial<T>[]) => T[];
/**
* Creates multiple instances asynchronously, allowing use of async factory functions.
* This method supports both synchronous and asynchronous factory functions and hooks.
*
* @param size Number of instances to generate (must be non-negative integer)
* @param kwargs Either a single partial object (applied to all) or an array of partials (one per instance)
* @returns Promise that resolves to an array of generated instances
* @throws {Error} If size is negative or not an integer
*
* @example
* ```typescript
* const UserFactory = new Factory<User>(async (factory) => ({
* id: factory.string.uuid(),
* email: factory.internet.email(),
* apiKey: await generateApiKey() // async operation
* }));
*
* const users = await UserFactory.batchAsync(5);
* ```
*/
batchAsync(size: number, kwargs?: Partial<T> | Partial<T>[]): Promise<F extends FactoryFunction<T> ? T[] : Partial<T>[]>;
/**
* Adds a hook that will be executed before building the instance.
* Hooks receive the partial parameters (kwargs) and can modify them before the instance is built.
* Multiple hooks are executed in the order they were added.
*
* @param hook Function that receives partial parameters and returns modified parameters
* @returns The current Factory instance for method chaining
*/
beforeBuild(hook: BeforeBuildHook<T>): this;
/**
* Generates a single instance of type T using the factory's schema.
* Properties can be overridden by passing a partial object.
* Synchronous hooks are automatically applied if registered.
* If async hooks are registered, a ConfigurationError is thrown.
*
* @param kwargs Properties to override in the generated instance
* @param options Factory options including fixture generation
* @returns A new instance with factory-generated values merged with any overrides
* @throws {ConfigurationError} If async hooks are registered
* @throws {FixtureError} If fixture operations fail
* @throws {FixtureValidationError} If fixture validation fails
*/
build: (kwargs?: Partial<T>, options?: Partial<O>) => F extends FactoryFunction<T> ? T : Partial<T>;
/**
* Builds an instance asynchronously with all registered hooks applied in sequence.
* This method supports both synchronous and asynchronous hooks.
* Hooks are executed in the order they were registered.
*
* @param kwargs Optional properties to override in the generated instance
* @param options Factory options including fixture generation
* @returns A promise that resolves to the built and processed instance
* @throws {Error} If any hook throws an error during execution
* @throws {FixtureError} If fixture operations fail
* @throws {FixtureValidationError} If fixture validation fails
*/
buildAsync(kwargs?: Partial<T>, options?: Partial<O>): Promise<F extends FactoryFunction<T> ? T : Partial<T>>;
/**
* Creates a new factory by merging this factory's schema with additional properties.
* Composed properties can be static values or other factory instances.
*
* @template U The composed type (must extend the base type T)
* @param composition Object mapping property names to values or factories
* @returns A new factory that generates objects with combined properties
*/
compose<U extends T>(composition: FactoryComposition<U>): Factory<U>;
/**
* Creates and persists a single instance using the factory's schema.
* Uses the configured persistence adapter if available.
*
* @param kwargs Optional properties to override in the generated instance
* @param options Options including an optional persistence adapter
* @returns Promise that resolves with the persisted instance
* @throws {ConfigurationError} If no persistence adapter is configured
*
* @example
* ```typescript
* // With default adapter
* const userFactory = new Factory<User>((faker) => ({
* email: faker.internet.email(),
* name: faker.person.fullName()
* })).withAdapter(mongooseAdapter);
*
* const user = await userFactory.create({ name: 'John' });
*
* // With adapter in options
* const user2 = await userFactory.create(
* { name: 'Jane' },
* { adapter: prismaAdapter }
* );
* ```
*/
create(kwargs?: Partial<T>, options?: CreateOptions<T>): Promise<T>;
/**
* Creates and persists multiple instances in a batch operation.
* Uses the configured persistence adapter if available.
*
* @param size Number of instances to create and persist
* @param kwargs Optional overrides for the instances
* @param options Options including an optional persistence adapter
* @returns Promise that resolves with the persisted instances
* @throws {ConfigurationError} If no persistence adapter is configured
*
* @example
* ```typescript
* // Create 5 users with default adapter
* const users = await userFactory.createMany(5);
*
* // With individual overrides
* const users2 = await userFactory.createMany(3, [
* { role: 'admin' },
* { role: 'user' },
* { role: 'guest' }
* ]);
*
* // With adapter in options
* const users3 = await userFactory.createMany(
* 10,
* undefined,
* { adapter: typeormAdapter }
* );
* ```
*/
createMany(size: number, kwargs?: Partial<T> | Partial<T>[], options?: CreateManyOptions<T>): Promise<T[]>;
/**
* Creates a new factory that inherits from this factory's schema with modifications.
* Unlike compose(), extend() provides access to the factory instance for dynamic property generation.
*
* @template U The extended type (must extend the base type T)
* @param factoryFn Function that returns properties to merge with the base schema
* @returns A new factory with inherited and extended properties
*/
extend<U extends T>(factoryFn: FactoryFunction<U>): Factory<U>;
/**
* Creates a generator that yields values from an iterable in sequential, cyclic order.
* Values must be explicitly requested via the generator's next() method. When all values
* have been yielded, the generator starts over from the beginning.
*
* @template T The type of elements in the iterable
* @param iterable An iterable containing values to cycle through (must not be empty)
* @returns A generator that yields values in order, restarting after the last element
* @throws {Error} If the iterable is empty
*/
iterate<U>(iterable: Iterable<U>): Generator<U, U, U>;
/**
* Creates a new factory where all properties are optional (Partial<T>).
* This is useful for creating test data where only specific fields need to be set.
*
* @returns A new factory that produces Partial<T> objects
*/
partial(): Factory<Partial<T>>;
/**
* Creates a generator that yields random values from an iterable without consecutive duplicates.
* Each value is randomly selected with replacement, but the generator ensures the same value
* is never returned twice in a row (unless the iterable contains only one element).
*
* @template T The type of elements in the iterable
* @param iterable An iterable containing values to sample from (must not be empty)
* @returns A generator that yields random values without consecutive repetition
* @throws {Error} If the iterable is empty
*/
sample<U>(iterable: Iterable<U>): Generator<U, U, U>;
/**
* Creates a reference to a function call for lazy evaluation within factory definitions.
* The function and its arguments are stored but not executed until the factory builds an object.
* Essential for creating relationships between factories without causing infinite recursion.
*
* @template C The function type
* @param handler The function to call during object generation
* @param args Arguments to pass to the function when called
* @returns A reference that will execute the function during build
*/
use<R, A extends unknown[]>(handler: (...args: A) => R, ...args: A): R;
/**
* Sets the default persistence adapter for this factory instance.
*
* @param adapter The persistence adapter to use as default
* @returns The current Factory instance for method chaining
*
* @example
* ```typescript
* import { MongooseAdapter } from './adapters/mongoose-adapter';
*
* const userFactory = new Factory<User>((faker) => ({
* id: faker.string.uuid(),
* email: faker.internet.email(),
* name: faker.person.fullName()
* }));
*
* // Set default adapter
* const factoryWithDb = userFactory.withAdapter(
* new MongooseAdapter(UserModel)
* );
*
* // Now all create/createMany calls will use this adapter
* const user = await factoryWithDb.create();
* const users = await factoryWithDb.createMany(5);
* ```
*/
withAdapter(adapter: PersistenceAdapter<T>): this;
protected buildWithFixture(filePath: string, kwargs: Partial<T> | undefined, _options: FactoryOptions & O): F extends FactoryFunction<T> ? T : Partial<T>;
protected buildWithFixtureAsync(filePath: string, kwargs: Partial<T> | undefined, _options: FactoryOptions & O): Promise<F extends FactoryFunction<T> ? T : Partial<T>>;
protected calculateSignature(config: Required<FixtureConfiguration>): string;
/* Excluded from this release type: createDepthLimitedProxy */
protected getDefaultFixturePath(): string;
protected getFixtureConfig(): Required<FixtureConfiguration>;
/* Excluded from this release type: isDepthExceeded */
protected parseFixturePath(filePath: string, config: Required<FixtureConfiguration>): {
fixturesDir: string;
fullPath: string;
};
protected readFixture(fullPath: string): FixtureMetadata | null;
protected validateFixture(metadata: FixtureMetadata, config: Required<FixtureConfiguration>): void;
protected writeFixture(parsedPath: {
fixturesDir: string;
fullPath: string;
}, data: unknown, config: Required<FixtureConfiguration>): void;
}
declare type FactoryComposition<T> = {
[K in keyof T]?: Factory<T[K]> | T[K];
};
declare type FactoryFunction<T> = (factory: Factory<T>, iteration: number, kwargs?: Partial<T>) => FactorySchema<T> | Promise<FactorySchema<T>>;
declare interface FactoryOptions {
/**
* Fixture configuration for caching generated data
*/
fixtures?: FixtureConfiguration;
/**
* Enable fixture generation/loading for this build.
* - true: Use default fixture path based on call stack
* - string: Use as fixture file path
*/
generateFixture?: boolean | string;
locale?: LocaleDefinition | LocaleDefinition[];
maxDepth?: number;
randomizer?: Randomizer;
}
declare type FactorySchema<T> = {
[K in keyof T]: Generator<T[K], T[K], T[K]> | Ref<T[K], (...args: unknown[]) => T[K]> | T[K];
};
declare interface FixtureConfiguration {
/**
* Base directory for storing fixtures. Defaults to process.cwd()
*/
basePath?: string;
/**
* Custom directory name for fixtures. Defaults to '__fixtures__'
* Set to empty string to store fixtures in the same directory as the file path
*/
directory?: string;
/**
* Whether to include the factory function source in signature calculation. Defaults to true
*/
includeSource?: boolean;
/**
* Whether to use subdirectories for fixtures. Defaults to true
* When true: fixtures are stored in a subdirectory (e.g., /path/__fixtures__/file.json)
* When false: fixtures are stored directly in the path (e.g., /path/file.json)
*/
useSubdirectory?: boolean;
/**
* Whether to validate factory signature changes. Defaults to true
*/
validateSignature?: boolean;
}
declare interface FixtureMetadata {
/**
* ISO 8601 timestamp when the fixture was created
*/
createdAt: string;
/**
* The actual fixture data
*/
data: unknown;
/**
* SHA-256 hash of the factory configuration
*/
signature: string;
/**
* Fixture version for future compatibility
*/
version: number;
}
declare type PartialFactoryFunction<T> = (factory: Factory<T>, iteration: number) => Partial<FactorySchema<T>> | Promise<Partial<FactorySchema<T>>>;
/**
* Interface for a persistence adapter.
* Adapters are responsible for interacting with the underlying data store
* (e.g., database, API) to save generated objects.
*/
declare interface PersistenceAdapter<T, R = T> {
/**
* Persists a single generated object to the data store.
*
* @param data The object to persist.
* @returns A promise that resolves with the persisted object,
* including any properties assigned by the database (e.g., auto-generated IDs).
*/
create(data: T): Promise<R>;
/**
* Persists multiple generated objects to the data store in a batch.
*
* @param data An array of objects to persist.
* @returns A promise that resolves with an array of the persisted objects,
* including any properties assigned by the database.
*/
createMany(data: T[]): Promise<R[]>;
}
/**
* Encapsulates a function and its arguments for deferred execution within factories.
* This enables lazy evaluation of nested factory calls, preventing infinite recursion
* and allowing complex object relationships to be defined declaratively.
*
* @template T - The return type of the encapsulated function
* @template C - The function type that returns T
*/
declare class Ref<T, C extends (...args: never[]) => T> {
private readonly args;
private readonly handler;
constructor({ args, handler }: {
args: Parameters<C>;
handler: C;
});
callHandler(): T;
}
/**
* A factory class for generating type-safe mock data from Zod schemas.
*
* ZodFactory extends the base Factory class to provide automatic generation of mock data
* that conforms to Zod schema definitions. It supports all major Zod types including
* primitives, objects, arrays, unions, enums, and more.
*
* Note: This class requires the `zod` package to be installed and uses Zod v4 API.
*
* @example
* Basic usage with a simple schema:
* ```typescript
* import { z } from 'zod/v4';
* import { ZodFactory } from 'interface-forge/zod';
*
* const UserSchema = z.object({
* id: z.string().uuid(),
* name: z.string().min(1).max(100),
* email: z.string().email(),
* age: z.number().int().min(18).max(120)
* });
*
* const factory = new ZodFactory(UserSchema);
* const user = factory.build();
* ```
*
* @example
* Using partial factory functions for custom generation:
* ```typescript
* const factory = new ZodFactory(UserSchema, (faker) => ({
* name: faker.person.fullName(),
* }));
* ```
*
* @template T - Must be a ZodObject type that defines the schema shape
* @template O - Factory options extending ZodFactoryOptions
*
* @see {@link https://github.com/goldziher/interface-forge/blob/main/examples/07-zod-basic.ts | Basic Example}
* @see {@link https://github.com/goldziher/interface-forge/blob/main/examples/07-zod-integration.ts | Advanced Example}
* @see {@link https://github.com/goldziher/interface-forge/blob/main/examples/07-zod-testing.ts | Testing Example}
*/
export declare class ZodFactory<T extends ZodObject<any, any>, O extends ZodFactoryOptions = ZodFactoryOptions> extends Factory<z.output<T>, O> {
private readonly generator;
private readonly schema;
/**
* Creates a new ZodFactory instance.
*
* @param schema - The Zod object schema to generate data from
* @param optionsOrFactory - Either factory options or a partial factory function
* @param options - Factory options (when second parameter is a factory function)
*
* @example
* Simple schema-based generation:
* ```typescript
* const factory = new ZodFactory(UserSchema);
* ```
*
* @example
* With partial factory function:
* ```typescript
* const factory = new ZodFactory(UserSchema, (faker) => ({
* name: faker.person.fullName(),
* // Other fields auto-generated
* }));
* ```
*
* @example
* With options:
* ```typescript
* const factory = new ZodFactory(UserSchema, {
* maxDepth: 3,
* generators: { userId: () => 'custom-id' }
* });
* ```
*/
constructor(schema: T, optionsOrFactory?: O | PartialFactoryFunction<z.output<T>>, options?: O);
/**
* Generates a batch of instances that conform to the Zod schema.
*
* @param size - Number of instances to generate
* @param kwargs - Optional overrides for each instance
* @returns Array of generated instances
*/
batch: (size: number, kwargs?: Partial<z.output<T>> | Partial<z.output<T>>[]) => z.output<T>[];
/**
* Builds a single instance that conforms to the Zod schema.
*
* The build process:
* 1. Generates data from the Zod schema constraints
* 2. Applies any factory function customizations
* 3. Applies the provided overrides
* 4. Validates the result against the schema
*
* @param kwargs - Optional property overrides
* @param options - Optional build options including fixture generation
* @returns A generated instance conforming to the schema
*
* @example
* ```typescript
* const user = factory.build({
* role: 'admin',
* isActive: true
* });
* ```
*
* @example
* With fixture generation:
* ```typescript
* const user = factory.build(undefined, { generateFixture: 'user-fixture' });
* ```
*/
build: (kwargs?: Partial<z.output<T>>, options?: Partial<O>) => z.output<T>;
/**
* Register a custom handler for a specific Zod type.
* Allows customization of how specific Zod types are generated.
*
* @param typeName - The Zod type name (e.g., 'ZodCustom', 'ZodBigInt')
* @param handler - Function that generates values for this type
* @returns The factory instance for method chaining
*
* @example
* const factory = new ZodFactory(schema)
* .withTypeHandler('ZodCustom', (schema, generator) => ({
* customField: generator.factory.lorem.word()
* }))
* .withTypeHandler('ZodBigInt', () => BigInt(42));
*/
withTypeHandler(typeName: string, handler: ZodTypeHandler): this;
/**
* Register multiple type handlers at once.
*
* @param handlers - Object mapping type names to handler functions
* @returns The factory instance for method chaining
*
* @example
* const factory = new ZodFactory(schema).withTypeHandlers({
* ZodCustom: (schema, generator) => ({ id: generator.factory.string.uuid() }),
* ZodBigInt: () => BigInt(Math.floor(Math.random() * 1000))
* });
*/
withTypeHandlers(handlers: Record<string, ZodTypeHandler>): this;
/**
* Override buildWithFixture to handle Zod-specific generation
*
* @param filePath The fixture file path
* @param kwargs Optional property overrides
* @param _options Build options
* @returns The generated Zod object
*/
protected buildWithFixture(filePath: string, kwargs?: Partial<z.output<T>>, _options?: Partial<O>): z.output<T>;
/**
* Override signature calculation to include Zod schema information
*
* @param config Fixture configuration
* @returns SHA-256 hash of the factory signature
*/
protected calculateSignature(config: Required<FixtureConfiguration>): string;
}
/**
* Options for configuring ZodFactory behavior.
*
*/
export declare interface ZodFactoryOptions extends FactoryOptions {
/**
* Custom generator functions for specific field types.
*
* Generators are matched by the field's description metadata.
*
* @example
* ```typescript
* const factory = new ZodFactory(schema, {
* generators: {
* // Schema field must have .describe('userId')
* userId: () => `USR_${Date.now()}`,
* customEmail: () => `test_${Date.now()}@example.com`
* }
* });
* ```
*/
generators?: Record<string, () => unknown>;
}
/**
* Schema generator class that handles recursive generation of values from any ZodType.
* This class is responsible for all the complex schema traversal and generation logic.
*/
declare class ZodSchemaGenerator {
readonly factory: ZodFactory<never>;
private dateFormatGenerators;
private readonly NOT_FOUND;
private stringFormatGenerators;
private readonly typeHandlers;
constructor(factory: ZodFactory<never>);
/**
* Generate a value from any ZodType schema.
*
* @param schema
* @param currentDepth
*
* @returns The generated value
*/
generateFromSchema(schema: ZodType, currentDepth?: number): unknown;
getDepthLimitFallback(schema: ZodType): unknown;
/**
* Register a custom handler for a specific Zod type.
*
* @param typeName
* @param handler
*
* @returns The factory instance
*/
registerTypeHandler(typeName: string, handler: ZodTypeHandler): this;
/**
* Register multiple type handlers at once.
*
* @param handlers
*
* @returns The factory instance
*/
registerTypeHandlers(handlers: Record<string, ZodTypeHandler>): this;
private calculateArrayLength;
private calculateTargetLength;
private extractSizeConstraints;
private generateArray;
private generateDate;
private generateFromRegex;
private generateFromZodType;
private generateMap;
private generateNumber;
private generateObject;
private generateRecord;
private generateSet;
private generateString;
private generateStringWithConstraints;
private generateTuple;
private initializeBuiltinHandlers;
private tryGenerateFromMapping;
private tryGenerateFromMetadata;
private tryGenerateFromTypeHandler;
}
declare type ZodTypeHandler = (schema: ZodType, generator: ZodSchemaGenerator, currentDepth: number) => unknown;
export { }