feathers-factory
Version:
Create data factories for your Feathers services
295 lines (285 loc) • 12 kB
TypeScript
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 };