UNPKG

feathers-factory

Version:

Create data factories for your Feathers services

295 lines (285 loc) 12 kB
import { Paths, Get } from 'type-fest'; import { Params } from '@feathersjs/feathers'; /** * Factory Template context. * Provides access to the current generation context. * * The `this` type within your template's generator functions. */ declare class TemplateContext<TTemplate, TContext extends TemplateContext<TTemplate> = any> { protected readonly template: FactoryTemplate<TTemplate, TContext>; /** * Internal state for the getter machine. * The structure of this field can be unexpected unless explicitly accessed * through the {@link this.get} method. * @private */ readonly _state: ContextState<TTemplate>; constructor(template: FactoryTemplate<TTemplate, TContext>); /** * Resolve the value of a template field within the current generator * context. Fields are only resolved once per generator context. * * This ensures that you can safely reference the same field multiple times * within the same generation context and from different fields. * * @example * template = ({ * firstName: () => faker.person.firstName(), * lastName: () => faker.person.lastName(), * * fullName: () => `${this.get('firstName')} ${this.get('lastName')}`, * // -> John Doe * * // Functions are only called once, then cached to ensure consistent * // results within the same generation context. * email: () => `${this.get('firstName')}.${this.get('lastName')}@example.com` * // -> John.Doe@example.com, * }) * */ get<TKey extends Paths<TTemplate> & string>(key: TKey): ContextFieldOutcome<Get<TTemplate, TKey>>; /** * Run the generator function for a given field. This will not cache the * result within the current context. Meaning you can call it multiple times * within the same generation context and it will always return a new value. * * This is useful if you want to extend the result of a field from within * another field. Do keep in mind that you might want to use it sparingly * in case the field has side-effects. E.g. creating new records in the * database. * * @example * template = ({ * firstName: () => faker.person.firstName(), * lastName: () => faker.person.lastName(), * * fullName: () => `${this.get('firstName')} ${this.get('lastName')}`, * // -> John Doe * * family: () => [ * this.call('fullName'), // -> <New random name> * this.call('fullName'), // -> <New random name> * * this.get('fullName'), // -> John Doe * ] */ call<TKey extends Paths<TTemplate> & string>(key: TKey): ContextFieldOutcome<Get<TTemplate, TKey>>; /** * Wrap any template functions around an array to indicate to Clues.js * what parameters are expected. Which is just this class instance. * * Enables use of the context parameter in arrow functions. */ protected wrapTemplateField(field: unknown): unknown; /** * Check whether the provided field is a function we should wrap to help * Clues.js resolve input types. */ protected shouldWrap(field: unknown): field is Function; /** * Attempt to resolve the current context state. * Used primarily for testing. The internal state does change during * resolve and could yield unexpected results. * @private */ _resolveState(): Promise<any>; } /** * Resolver state. May contain some partially resolved template fields. */ type ContextState<TTemplate> = { [key in keyof TTemplate]: ContextField<TTemplate[key]>; }; /** * Contextualized FactoryTemplate fields. * When resolving some fields will get converted to promises, * some immediately to their resulting value, etc. * * Todo: Attempt to infer types that will never get converted to a promise or * function. (sync functions, static values, etc.) */ type ContextField<TType> = TType | Promise<TType> | (() => Promise<TType> | TType); /** * After resolving a field, it'll either be it's final type or a promise * in cases where there's peer dependencies or the field function actually * is async. */ type ContextFieldOutcome<TType> = TType extends ContextField<infer T> ? Promise<T> | T : never; /** * Factory boilerplate template. * Defines the fields that will be generated when factories are called. */ declare class FactoryTemplate<TTemplate, TContext extends TemplateContext<TTemplate> = TemplateContext<TTemplate>> { readonly _schema: TemplateSchema<TTemplate, TContext>; constructor(_schema: TemplateSchema<TTemplate, TContext>); /** * Run all factory functions in the template and return final result to be * stored in the database. */ resolve(overrides?: TemplateOverrides<TTemplate, TContext>): Promise<TemplateResult<TTemplate>>; extend<TOverrides>(overrides: ExtendSchema<TTemplate, TOverrides>): ExtendTemplate<TTemplate, TOverrides>; } /** * Factory Template definition. * Defines the fields that will be generated when the factory is called. */ type TemplateSchema<TTemplate, TContext = TemplateContext<TTemplate>> = { [key in keyof TTemplate]: TemplateField<TTemplate[key], TContext>; } & ThisType<TContext>; /** * Factory Template field. * Specifies a function to run every time the factory is called. Or a static * value that will always remain the same. */ type TemplateField<TValue = unknown, TContext = unknown> = TValue | Promise<TValue> | ((context: TContext) => TValue | Promise<TValue>); /** * Factory Template result. * The raw output of type of the template after resolving all fields. */ type TemplateResult<TTemplate> = { [key in keyof TTemplate]: InferFieldType<TTemplate[key]>; }; /** * Infer the resulting data type of the provided factory template. * @example * const userTemplate = new FactoryTemplate({ * userId: () => 123, * createdAt: () => new Date() * }) * * const data: InferOutput<typeof userTemplate> * // -> { userId: number, createdAt: Date } */ type InferOutput<TTemplate> = TTemplate extends FactoryTemplate<infer T> ? T : never; /** * Template overrides. * Defines the fields that can be overridden before resolving the final * template. */ type TemplateOverrides<TTemplate, TContext = {}> = { [key in keyof TTemplate]?: TemplateField<InferFieldType<TTemplate[key]>, TContext>; } & ThisType<TContext>; /** * Infer the resolved output type of a given template field. */ type InferFieldType<T> = T extends TemplateField<infer T> ? T : never; /** * Merge two template definitions to create a new template using one as a base. */ type ExtendTemplate<TTemplate, TOverrides, TResult = TTemplate & TOverrides, TMerged = { [key in keyof TResult]: key extends keyof TOverrides ? TOverrides[key] : TResult[key]; }> = FactoryTemplate<TMerged>; /** * Combine a target schema with a baseline schema. */ type ExtendSchema<TTemplate, TOverrides, TResult = TTemplate & TOverrides, TMerged = { [key in keyof TResult]: key extends keyof TOverrides ? TOverrides[key] : TResult[key]; }> = TOverrides & ThisType<TemplateContext<TMerged>>; /** * Utility type for any Feathers or class that implements * an interface compatible with Feathers Factory. * @param TSchema The expected input type for the service create() method. * @param TResult The return type for the provided service class */ type FactoryService<TSchema = unknown, TResult = TSchema, TParams = Params> = FactoryCompatibleService<TSchema, TResult, TParams>; interface FactoryCompatibleService<TSchema, TResult = TSchema, TParams = Params, TData = TSchema | TSchema[], TReturn = TResult | TResult[]> { create(data: TData, params?: TParams): TReturn | Promise<TReturn>; } declare class Factory<TSchema, TResult = TSchema, TParams = Params> { private readonly service; protected readonly data: FactoryTemplate<TSchema>; protected readonly params: FactoryTemplate<Params>; /** * Factory constructor. */ constructor(service: FactoryCompatibleService<TSchema, TResult, TParams>, data: TemplateSchema<TSchema> | FactoryTemplate<TSchema>, defaultParams?: TemplateSchema<TParams>); /** * Store generated data to the Feathers service. */ create(data?: TemplateOverrides<TSchema>, params?: TemplateOverrides<TParams>): Promise<TResult>; /** * Quickly populate the database running the factory a number of times. */ createMany(quantity: number, overrides?: TemplateOverrides<TSchema>, params?: TemplateOverrides<TParams>): Promise<TResult[]>; /** * Just resolve a predefined factory template without inserting it into * the underlying service. * * @param overrides */ get(overrides?: TemplateOverrides<TSchema>): Promise<TemplateResult<TSchema>>; } type TemplateTypes_ExtendSchema<TTemplate, TOverrides, TResult = TTemplate & TOverrides, TMerged = { [key in keyof TResult]: key extends keyof TOverrides ? TOverrides[key] : TResult[key]; }> = ExtendSchema<TTemplate, TOverrides, TResult, TMerged>; type TemplateTypes_InferFieldType<T> = InferFieldType<T>; type TemplateTypes_InferOutput<TTemplate> = InferOutput<TTemplate>; declare namespace TemplateTypes { export type { ExtendTemplate as Extend, TemplateTypes_ExtendSchema as ExtendSchema, TemplateTypes_InferFieldType as InferFieldType, TemplateTypes_InferOutput as InferOutput, TemplateOverrides as Overrides, TemplateResult as Result, TemplateSchema as Schema }; } type HasBeenAugmented<T> = [keyof T] extends [never] ? false : true; /** * We are intentionally leaving this empty here so you can add type * declarations for globally accessible factories. * * @example * const MyUserFactory = new Factory(...); * * declare module 'feathers-factory' { * interface GlobalFactories { * 'user-factory': typeof MyUserFactory * } * } */ interface GlobalFactories { } declare const _default: { /** * Defined factories. */ factories: FactoryRegistry; /** * Define a new factory. */ define<TSchema, TResult = TSchema>(factoryName: FactoryName, factory: Factory<TSchema, TResult, Params>): void; /** * Retrieve a factory name as defined in the define() method. * * @param name */ getFactory<TName extends FactoryName>(name: TName): GlobalFactory<TName>; /** * Run factory, creating entry in Feathers service. * * @param factoryName * @param overrides * @param params */ create<TName extends FactoryName>(factoryName: TName, overrides?: GlobalFactoryOverrides<TName> | undefined, params?: Params): Promise<any>; /** * Run a number of factories, creating entries in Feathers service. * * @param quantity * @param factoryName * @param overrides * @param params */ createMany<TName extends FactoryName>(quantity: number, factoryName: TName, overrides?: TemplateOverrides<any>, params?: Params): Promise<any[]>; /** * Run factory without creating entry in Feathers service. * Returns resolved data object. * * @param factoryName * @param overrides */ get<TName extends FactoryName>(factoryName: TName, overrides?: TemplateOverrides<TName> | undefined): Promise<TemplateResult<any>>; }; type FactoryName = keyof FactoryRegistry; type GlobalFactory<TName extends FactoryName> = FactoryRegistry[TName]; type GlobalFactoryOverrides<TName extends FactoryName> = GlobalFactory<TName> extends Factory<infer TSchema, infer TResult> ? TemplateOverrides<TSchema> : never; type FactoryRegistry = HasBeenAugmented<GlobalFactories> extends true ? GlobalFactories : DefaultFactoryRegistry; type DefaultFactoryRegistry = { [key: string]: Factory<any, any>; }; export { Factory, type FactoryService, FactoryTemplate, _default as GlobalFactories, TemplateTypes as Template, TemplateContext };