UNPKG

interface-forge

Version:

A TypeScript library for creating strongly typed mock data factories using Faker.js for test data generation

409 lines (388 loc) 17.5 kB
import { Faker } from '@faker-js/faker'; import { LocaleDefinition } from '@faker-js/faker'; import { Randomizer } from '@faker-js/faker'; export declare type AfterBuildHook<T> = (obj: T) => Promise<T> | T; export declare type BeforeBuildHook<T> = (params: Partial<T>) => Partial<T> | Promise<Partial<T>>; export declare class CircularReferenceError extends Error { constructor(message?: string); } export declare class ConfigurationError extends Error { constructor(message: string); } export declare interface CreateManyOptions<T> { adapter?: PersistenceAdapter<T>; } export 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 */ export 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; } export declare type FactoryComposition<T> = { [K in keyof T]?: Factory<T[K]> | T[K]; }; export declare type FactoryFunction<T> = (factory: Factory<T>, iteration: number, kwargs?: Partial<T>) => FactorySchema<T> | Promise<FactorySchema<T>>; export 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; } export declare type FactorySchema<T> = { [K in keyof T]: Generator<T[K], T[K], T[K]> | Ref<T[K], (...args: unknown[]) => T[K]> | T[K]; }; export 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; } export declare class FixtureError extends Error { constructor(message: string); } export 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; } export declare class FixtureValidationError extends FixtureError { constructor(message: string); } export 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. */ export 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 */ export 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; } export declare class ValidationError extends Error { constructor(message: string); } export { }