UNPKG

@evyweb/ioctopus

Version:

A simple IoC container for JavaScript and TypeScript for classes and functions.

206 lines (202 loc) 7.16 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { createContainer: () => createContainer, createModule: () => createModule }); module.exports = __toCommonJS(index_exports); // src/module.ts function createModule() { const bindings = /* @__PURE__ */ new Map(); const resolveDependenciesArray = (dependencies, resolve) => dependencies.map(resolve); const resolveDependenciesObject = (dependencies, resolve) => { const entries = Object.entries(dependencies); return Object.fromEntries(entries.map(([key, dependency]) => [key, resolve(dependency)])); }; const isDependencyArray = (dependencies) => Array.isArray(dependencies); const isDependencyObject = (dependencies) => dependencies !== null && typeof dependencies === "object" && !Array.isArray(dependencies); const bind = (key) => { const toValue = (value) => { bindings.set(key, { factory: () => value, scope: "singleton" }); }; const toFunction = (fn) => { bindings.set(key, { factory: () => fn, scope: "singleton" }); }; const toHigherOrderFunction = (fn, dependencies, scope = "singleton") => { if (dependencies && !isDependencyArray(dependencies) && !isDependencyObject(dependencies)) { throw new Error("Invalid dependencies type"); } const factory = (resolve) => { if (!dependencies) { return fn(); } if (isDependencyArray(dependencies)) { return fn(...resolveDependenciesArray(dependencies, resolve)); } return fn({ ...resolveDependenciesObject(dependencies, resolve) }); }; bindings.set(key, { factory, scope }); }; const toCurry = toHigherOrderFunction; const toFactory = (factory, scope = "singleton") => { bindings.set(key, { factory: (resolve) => factory(resolve), scope }); }; const toClass = (AnyClass, dependencies, scope = "singleton") => { if (dependencies && !isDependencyArray(dependencies) && !isDependencyObject(dependencies)) { throw new Error("Invalid dependencies type"); } const factory = (resolve) => { if (!dependencies) { return new AnyClass(); } if (isDependencyArray(dependencies)) { const resolvedDeps = resolveDependenciesArray(dependencies, resolve); return new AnyClass(...resolvedDeps); } if (isDependencyObject(dependencies)) { const resolvedDeps = resolveDependenciesObject(dependencies, resolve); return new AnyClass({ ...resolvedDeps }); } }; bindings.set(key, { factory, scope }); }; return { toValue, toFunction, toFactory, toClass, toHigherOrderFunction, toCurry }; }; return { bind, bindings }; } // src/container.ts function createContainerCore() { const modules = /* @__PURE__ */ new Map(); const singletonInstances = /* @__PURE__ */ new Map(); const scopedInstances = /* @__PURE__ */ new Map(); const resolutionStack = []; let currentScopeId; const DEFAULT_MODULE_KEY = Symbol("DEFAULT"); const defaultModule = createModule(); modules.set(DEFAULT_MODULE_KEY, defaultModule); const load = (moduleKey, module2) => modules.set(moduleKey, module2); const unload = (moduleKey) => { singletonInstances.clear(); modules.delete(moduleKey); }; const findLastBinding = (key) => { const modulesArray = Array.from(modules.values()); for (let i = modulesArray.length - 1; i >= 0; i--) { const module2 = modulesArray[i]; const binding = module2.bindings.get(key); if (binding) { return binding; } } return null; }; const getLastBinding = (key) => { const binding = findLastBinding(key); if (!binding) { throw new Error(`No binding found for key: ${key.toString()}`); } return binding; }; const isCircularDependency = (key) => resolutionStack.includes(key); const buildCyclePath = (key) => [...resolutionStack, key].map((k) => k.toString()).join(" -> "); const startResolution = (dependencyKey) => resolutionStack.push(dependencyKey); const endResolution = () => resolutionStack.pop(); const resolveBinding = (dependencyKey) => { if (isCircularDependency(dependencyKey)) { const cycle = buildCyclePath(dependencyKey); throw new Error(`Circular dependency detected: ${cycle}`); } startResolution(dependencyKey); try { const binding = getLastBinding(dependencyKey); const { factory, scope } = binding; if (scope === "singleton") { if (!singletonInstances.has(dependencyKey)) { singletonInstances.set(dependencyKey, factory(resolveDependency)); } return singletonInstances.get(dependencyKey); } if (scope === "transient") { return factory(resolveDependency); } if (scope === "scoped") { if (!currentScopeId) { throw new Error(`Cannot resolve scoped binding outside of a scope: ${dependencyKey.toString()}`); } if (!scopedInstances.has(currentScopeId)) { scopedInstances.set(currentScopeId, /* @__PURE__ */ new Map()); } const scopeMap = scopedInstances.get(currentScopeId); if (!scopeMap.has(dependencyKey)) { scopeMap.set(dependencyKey, factory(resolveDependency)); } return scopeMap.get(dependencyKey); } throw new Error(`Unknown scope: ${scope}`); } finally { endResolution(); } }; const resolveDependency = (depKey) => { return resolveBinding(depKey); }; const runInScope = (callback) => { const previousScopeId = currentScopeId; currentScopeId = Symbol("scope"); try { return callback(); } finally { scopedInstances.delete(currentScopeId); currentScopeId = previousScopeId; } }; return { defaultModule, load, unload, runInScope, resolveBinding }; } function createContainer() { const core = createContainerCore(); const bind = (key) => core.defaultModule.bind(key); const get = (key) => core.resolveBinding(key); return { bind, load: core.load, get, unload: core.unload, runInScope: core.runInScope }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createContainer, createModule });