@evyweb/ioctopus
Version:
A simple IoC container for JavaScript and TypeScript for classes and functions.
206 lines (202 loc) • 7.16 kB
JavaScript
;
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
});