UNPKG

@daisugi/kado

Version:

Kado is a minimal and unobtrusive inversion of control container.

150 lines 5.11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Kado = exports.Container = void 0; const ayamari_1 = require("@daisugi/ayamari"); const kintsugi_1 = require("@daisugi/kintsugi"); const { errFn } = new ayamari_1.Ayamari(); class Container { #tokenToContainerItem; constructor() { this.#tokenToContainerItem = new Map(); } async resolve(token) { const containerItem = this.#tokenToContainerItem.get(token); if (containerItem === undefined) { throw errFn.NotFound(`Attempted to resolve unregistered dependency token: "${token.toString()}".`); } const manifestItem = containerItem.manifestItem; if (manifestItem.useValue !== undefined) { return manifestItem.useValue; } if (containerItem.instance) { return containerItem.instance; } let resolve; if (manifestItem.scope !== Kado.scope.Transient) { containerItem.instance = new Promise((_resolve) => { resolve = _resolve; }); } let paramsInstances = null; if (manifestItem.params) { this.#checkForCircularDep(containerItem); paramsInstances = await Promise.all(manifestItem.params.map(this.#resolveParam.bind(this))); } let instance; if (manifestItem.useFn) { instance = paramsInstances ? manifestItem.useFn(...paramsInstances) : manifestItem.useFn(); } else if (manifestItem.useFnByContainer) { instance = manifestItem.useFnByContainer(this); } else if (manifestItem.useClass) { instance = paramsInstances ? new manifestItem.useClass(...paramsInstances) : new manifestItem.useClass(); } if (manifestItem.scope === Kado.scope.Transient) { return instance; } // biome-ignore lint/style/noNonNullAssertion: We know that `resolve` is defined if the scope is not transient. resolve(instance); return containerItem.instance; } async #resolveParam(param) { const token = typeof param === 'object' ? this.#registerItem(param) : param; return this.resolve(token); } register(manifestItems) { for (const manifestItem of manifestItems) { this.#registerItem(manifestItem); } } #registerItem(manifestItem) { const token = manifestItem.token || (0, kintsugi_1.urandom)(); this.#tokenToContainerItem.set(token, { manifestItem: Object.assign(manifestItem, { token }), checkedForCircularDep: false, instance: null, }); return token; } list() { return Array.from(this.#tokenToContainerItem.values()).map((containerItem) => containerItem.manifestItem); } get(token) { const containerItem = this.#tokenToContainerItem.get(token); if (containerItem === undefined) { throw errFn.NotFound(`Attempted to get unregistered dependency token: "${token.toString()}".`); } return containerItem.manifestItem; } #checkForCircularDep(containerItem, tokens = []) { if (containerItem.checkedForCircularDep) { return; } const token = containerItem.manifestItem.token; if (!token) { return; } if (tokens.includes(token)) { const chainOfTokens = tokens .map((token) => `"${token.toString()}"`) .join(' ➡️ '); throw errFn.CircularDependencyDetected(`Attempted to resolve circular dependency: ${chainOfTokens} 🔄 "${token.toString()}".`); } if (containerItem.manifestItem.params) { for (const param of containerItem.manifestItem .params) { if (typeof param === 'object') { continue; } const paramContainerItem = this.#tokenToContainerItem.get(param); if (!paramContainerItem) { continue; } this.#checkForCircularDep(paramContainerItem, [ ...tokens, token, ]); paramContainerItem.checkedForCircularDep = true; } } } } exports.Container = Container; class Kado { static scope = { Transient: 'Transient', Singleton: 'Singleton', }; container; constructor() { this.container = new Container(); } static value(value) { return { useValue: value }; } static map(params) { return { useFn(...args) { return args; }, params, }; } static flatMap(params) { return { useFn(...args) { return args.flat(); }, params, }; } } exports.Kado = Kado; //# sourceMappingURL=kado.js.map