@aurbi/tiny-composite-builder
Version:
Small framework for taking base objects and adding extensions using a builder pattern, but with full and strict compile-time type support.
72 lines (71 loc) • 2.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Builder = void 0;
/**
* A builder that should produce a base
* @typeparam T extends ExtensibleBase: what kind of extensible object are we creating?
*/
class Builder {
/**
* Create a new ExtensibleBase object with optional extensions, added via a builder function.
* @param builder Builder function that describes the desired extensions for the object
* @returns New instance of ExtensibleBase object with the requested extensions present
*/
static Create(base, builder, ...args) {
let instance = new Builder(base);
instance = builder(instance);
const built = instance.make(...args);
return built;
}
constructor(_base) {
this._base = _base;
this._extensions = [];
this._methodNameFilter = (m) => m.startsWith("$");
}
/**
* Add a compatible extension to be grafted onto the base.
* @param extClass Extension class (non-instantiated: use the class name as a value!)
* @param args Arguments, if any, to pass along to the extension constructor. Do not include the
* Base instance parameter, this is automatically provided to the extension constructor later.
* @returns The builder, now aware of this extension
*/
with(extClass, ...args) {
const newSelf = this;
newSelf._extensions.push([extClass, args]);
return newSelf;
}
/**
* Change the default method name filter for creating extension forwarding functions in the base.
*
* By default, this is `(m) => m.startsWith('$')`, meaning only public functions that start with
* a dollar sign ($) have method forwarders constructed on the base instance.
*
* You can disable method forwarding entirely by just setting this to `() => false`.
* @param f method name filter
* @returns The builder, with modified filter.
*/
withMethodNameFilter(f) {
this._methodNameFilter = f;
return this;
}
make(...args) {
const base = new this._base(...args);
const extensions = this._extensions
.map(([extClass, extraArgs]) => new extClass(base, ...extraArgs));
base.Extensions = extensions;
// provide the forwarding functions in the base
extensions.forEach((e, i) => {
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(e))
.filter(m => typeof e[m] === "function")
.filter(m => m !== "constructor")
.filter(this._methodNameFilter);
methods.forEach((m) => {
base[m.substring(1)] = (...args) => {
return extensions[i][m](...args);
};
});
});
return base;
}
}
exports.Builder = Builder;