mango-v4/ts/client/src/builder.ts

96 lines
2.7 KiB
TypeScript

// https://github.com/Vincent-Pang/builder-pattern
export type IBuilder<T> = {
[k in keyof T]-?: ((arg: T[k]) => IBuilder<T>) & (() => T[k]);
} & {
build(): T;
};
type Clazz<T> = 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<T>(
type: Clazz<T>,
template?: Partial<T> | null,
override?: Partial<T> | null,
): IBuilder<T>;
/**
* Create a Builder for an interface. Returned objects will be untyped.
*
* e.g. let obj: Interface = Builder<Interface>().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<T>(
template?: Partial<T> | null,
override?: Partial<T> | null,
): IBuilder<T>;
export function Builder<T>(
typeOrTemplate?: Clazz<T> | Partial<T> | null,
templateOrOverride?: Partial<T> | null,
override?: Partial<T> | null,
): IBuilder<T> {
let type: Clazz<T> | undefined;
let template: Partial<T> | null | undefined;
let overrideValues: Partial<T> | null | undefined;
if (typeOrTemplate instanceof Function) {
type = typeOrTemplate;
template = templateOrOverride;
overrideValues = override;
} else {
template = typeOrTemplate;
overrideValues = templateOrOverride;
}
const built: Record<string, unknown> = 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<string, unknown>, { ...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<T>;
}