// https://github.com/Vincent-Pang/builder-pattern export type IBuilder = { [k in keyof T]-?: ((arg: T[k]) => IBuilder) & (() => T[k]); } & { build(): T; }; type Clazz = new (...args: unknown[]) => T; /** * Create a Builder for a class. Returned objects will be of the class type. * * e.g. let obj: MyClass = Builder(MyClass).setA(5).setB("str").build(); * * @param type the name of the class to instantiate. * @param template optional class partial which the builder will derive initial params from. * @param override optional class partial which the builder will override params from when calling build(). */ export function Builder( type: Clazz, template?: Partial | null, override?: Partial | null, ): IBuilder; /** * Create a Builder for an interface. Returned objects will be untyped. * * e.g. let obj: Interface = Builder().setA(5).setB("str").build(); * * @param template optional partial object which the builder will derive initial params from. * @param override optional partial object which the builder will override params from when calling build(). */ export function Builder( template?: Partial | null, override?: Partial | null, ): IBuilder; export function Builder( typeOrTemplate?: Clazz | Partial | null, templateOrOverride?: Partial | null, override?: Partial | null, ): IBuilder { let type: Clazz | undefined; let template: Partial | null | undefined; let overrideValues: Partial | null | undefined; if (typeOrTemplate instanceof Function) { type = typeOrTemplate; template = templateOrOverride; overrideValues = override; } else { template = typeOrTemplate; overrideValues = templateOrOverride; } const built: Record = template ? Object.assign({}, template) : {}; const builder = new Proxy( {}, { get(target, prop) { if ('build' === prop) { if (overrideValues) { Object.assign(built, overrideValues); } if (type) { // A class name (identified by the constructor) was passed. Instantiate it with props. const obj: T = new type(); return () => Object.assign(obj as T & Record, { ...built }); } else { // No type information - just return the object. return () => built; } } return (...args: unknown[]): unknown => { // If no arguments passed return current value. if (0 === args.length) { return built[prop.toString()]; } built[prop.toString()] = args[0]; return builder; }; }, }, ); return builder as IBuilder; }