UNPKG

@thaitype/ioctopus

Version:

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

237 lines (232 loc) 8.36 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 src_exports = {}; __export(src_exports, { ServiceRegistry: () => ServiceRegistry, createContainer: () => createContainer, createModule: () => createModule, createServiceRegistry: () => createServiceRegistry }); module.exports = __toCommonJS(src_exports); // src/module.ts function createModule(serviceRegistry) { 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 resovledKey = serviceRegistry.get(key); const toValue = (value) => { bindings.set(resovledKey, { factory: () => value, scope: "singleton" }); }; const toFunction = (fn) => { bindings.set(resovledKey, { factory: () => fn, scope: "singleton" }); }; const toHigherOrderFunction = (fn, dependencies, scope = "singleton") => { if (dependencies && !isDependencyArray(dependencies) && !isDependencyObject(dependencies)) { throw new Error("Invalid dependencies type"); } const arrayDependencies = []; if (!isDependencyObject(dependencies) && isDependencyArray(dependencies)) { for (const dependency of dependencies) { arrayDependencies.push(serviceRegistry.get(dependency)); } } const factory = (resolve) => { if (!dependencies) { return fn(); } if (isDependencyArray(dependencies)) { return fn(...resolveDependenciesArray(arrayDependencies, resolve)); } return fn({ ...resolveDependenciesObject(dependencies, resolve) }); }; bindings.set(resovledKey, { factory, scope }); }; const toCurry = toHigherOrderFunction; const toFactory = (factory, scope = "singleton") => { bindings.set(resovledKey, { 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(resovledKey, { factory, scope }); }; return { toValue, toFunction, toFactory, toClass, toHigherOrderFunction, toCurry }; }; return { bind, bindings }; } // src/container.ts function createContainer(serviceRegistry) { 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(serviceRegistry); modules.set(DEFAULT_MODULE_KEY, defaultModule); const bind = (key) => defaultModule.bind(key); 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 buildCycleOf = (key) => [...resolutionStack, key].map((k) => k.toString()).join(" -> "); const startCircularDependencyDetectionFor = (dependencyKey) => resolutionStack.push(dependencyKey); const endCircularDependencyDetection = () => resolutionStack.pop(); const resolveDependencyKey = (dependency) => { let dependencyKey; if (typeof dependency === "symbol") { dependencyKey = dependency; } else { dependencyKey = serviceRegistry.get(dependency); } return dependencyKey; }; const get = (dependency) => { const dependencyKey = resolveDependencyKey(dependency); if (!dependencyKey) { throw new Error(`No key found for dependency: ${dependency.toString()}`); } if (isCircularDependency(dependencyKey)) { const cycle = buildCycleOf(dependencyKey); throw new Error(`Circular dependency detected: ${cycle}`); } startCircularDependencyDetectionFor(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 { endCircularDependencyDetection(); } }; const resolveDependency = (depKey) => { return get(depKey); }; const runInScope = (callback) => { const previousScopeId = currentScopeId; currentScopeId = Symbol("scope"); try { return callback(); } finally { scopedInstances.delete(currentScopeId); currentScopeId = previousScopeId; } }; return { bind, load, get, unload, runInScope }; } // src/service-registry.ts function createServiceRegistry() { return new ServiceRegistry(); } var ServiceRegistry = class { keyMap = {}; define(key, _identifier) { this.keyMap[key] = Symbol.for(key); return new ServiceBinder(this, key); } get(key) { return this.keyMap[key]; } }; var ServiceBinder = class { constructor(serviceRegistry, key) { this.serviceRegistry = serviceRegistry; this.key = key; } mapTo() { return this.serviceRegistry; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ServiceRegistry, createContainer, createModule, createServiceRegistry });