UNPKG

@rmk-labs/typescript-dependency-injector

Version:
532 lines (527 loc) 16.6 kB
// src/utils.ts function isProvider(value) { return value !== null && typeof value === "object" && PROVIDER_SYMBOL in value && value[PROVIDER_SYMBOL] === true; } function resolveProviders(value, seen = /* @__PURE__ */ new WeakSet(), resolved = /* @__PURE__ */ new WeakMap()) { if (value === null || value === void 0) { return value; } if (typeof value !== "object" && typeof value !== "function") { return value; } if (typeof value === "function") { return value; } if (isProvider(value)) { return value.provide(); } if (resolved.has(value)) { return resolved.get(value); } if (seen.has(value)) { return value; } seen.add(value); if (value instanceof Date) { return value; } if (value instanceof RegExp) { return value; } if (Array.isArray(value)) { const resolvedArray = []; resolved.set(value, resolvedArray); for (const item of value) { resolvedArray.push(resolveProviders(item, seen, resolved)); } seen.delete(value); return resolvedArray; } if (value instanceof Map) { const resolvedMap = /* @__PURE__ */ new Map(); resolved.set(value, resolvedMap); for (const [key, val] of value.entries()) { resolvedMap.set(key, resolveProviders(val, seen, resolved)); } seen.delete(value); return resolvedMap; } if (value instanceof Set) { const resolvedSet = /* @__PURE__ */ new Set(); resolved.set(value, resolvedSet); for (const item of value.values()) { resolvedSet.add(resolveProviders(item, seen, resolved)); } seen.delete(value); return resolvedSet; } const proto = Object.getPrototypeOf(value); const isPlainObject = proto === Object.prototype || proto === null; if (!isPlainObject) { return value; } const resolvedObj = {}; resolved.set(value, resolvedObj); for (const key of Object.keys(value)) { resolvedObj[key] = resolveProviders(value[key], seen, resolved); } seen.delete(value); return resolvedObj; } // src/extend.ts var EXTEND_SYMBOL = Symbol("extend"); var _a; _a = EXTEND_SYMBOL; var Extend = class { constructor(defaults) { this.defaults = defaults; this[_a] = true; } }; function isExtend(value) { return !!value && typeof value === "object" && EXTEND_SYMBOL in value && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access value[EXTEND_SYMBOL] === true; } // src/providers.ts var PROVIDER_SYMBOL = Symbol("@@Provider"); var _a2; _a2 = PROVIDER_SYMBOL; var BaseProvider = class { constructor() { this[_a2] = true; this._overridingProviders = []; } /** * Returns a readonly copy of the overriding providers stack. */ get overrides() { return [...this._overridingProviders]; } /** * Overrides the current provider with another provider. * When the provider is called, it will delegate to the last overriding provider. * @param provider - The provider to override with * @throws Error if the argument is not a provider */ override(provider) { if (!provider || typeof provider.provide !== "function") { throw new Error("Override argument must be a provider with a provide() method"); } this._overridingProviders.push(provider); } /** * Resets the last overriding provider and returns it. * @returns The provider that was removed from the override stack * @throws Error if there are no overriding providers */ resetLastOverriding() { if (this._overridingProviders.length === 0) { throw new Error("No overriding providers to reset"); } return this._overridingProviders.pop(); } /** * Resets all overriding providers at once and returns them. * @returns An array of all providers that were removed from the override stack */ resetOverride() { const removed = [...this._overridingProviders]; this._overridingProviders = []; return removed; } /** * Returns true if the provider is currently overridden, false otherwise. */ isOverridden() { return this._overridingProviders.length > 0; } /** * Returns a Delegate provider that wraps this provider. * The Delegate's provide() method returns this provider instance. */ get provider() { return new Delegate(this); } /** * Protected method for providers to delegate to the overriding provider if one exists. * Should be called at the beginning of each provider's provide() method. */ _delegateIfOverridden(...args) { if (this._overridingProviders.length > 0) { const overridingProvider = this._overridingProviders[this._overridingProviders.length - 1]; return overridingProvider.provide(...args); } return void 0; } }; var Factory = class extends BaseProvider { constructor(factory, ...injectedArgs) { super(); this.factory = factory; this.injectedArgs = injectedArgs; this.isConstructor = typeof factory === "function" && factory.prototype !== void 0 && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access factory.prototype.constructor === factory; } provide(...args) { const overridden = this._delegateIfOverridden(...args); if (overridden !== void 0) { return overridden; } const hasExtend = this.injectedArgs.some((arg) => isExtend(arg)); const resolvedArgs = this.injectedArgs.map((arg) => this._resolveArg(arg, args)); if (this.isConstructor) { if (hasExtend) { return new this.factory(...resolvedArgs); } return new this.factory(...args, ...resolvedArgs); } else { if (hasExtend) { return this.factory(...resolvedArgs); } return this.factory(...args, ...resolvedArgs); } } /** * Resolves an argument, handling Extend instances specially. * If the argument is an Extend instance and context args are provided, * merges the context with defaults (context takes priority). */ _resolveArg(arg, contextArgs) { if (isExtend(arg)) { const defaults = arg.defaults; const context = contextArgs.length > 0 ? contextArgs[0] : {}; const resolvedDefaults = {}; for (const key in defaults) { if (!(key in context)) { resolvedDefaults[key] = resolveProviders(defaults[key]); } } return { ...resolvedDefaults, ...context }; } return resolveProviders(arg); } }; var Singleton = class extends Factory { constructor() { super(...arguments); this.instance = null; } provide(...args) { const overridden = this._delegateIfOverridden(...args); if (overridden !== void 0) { return overridden; } if (this.instance === null) { this.instance = super.provide(...args); } return this.instance; } resetInstance() { this.instance = null; } }; var Delegate = class extends BaseProvider { constructor(delegatedProvider) { super(); this.delegatedProvider = delegatedProvider; } provide() { const overridden = this._delegateIfOverridden(); if (overridden !== void 0) { return overridden; } return this.delegatedProvider; } }; // src/container.ts var DeclarativeContainer = class { resetProviderOverrides() { for (const key of Reflect.ownKeys(this)) { const val = this[key]; if (val instanceof BaseProvider) { val.resetOverride(); } } } resetSingletonInstances() { for (const key of Reflect.ownKeys(this)) { const val = this[key]; if (val instanceof Singleton) { val.resetInstance(); } } } }; // src/inject.ts var CONTAINER_PROVIDER_TOKENS = /* @__PURE__ */ new WeakMap(); var CONTAINER_PROVIDER_AS_VALUE_TOKENS = /* @__PURE__ */ new WeakMap(); var PARAM_INJECT_IDS = /* @__PURE__ */ new WeakMap(); var DECORATION_SITES = /* @__PURE__ */ new WeakMap(); var CONSTRUCTOR_DECORATION_SITES = /* @__PURE__ */ new WeakMap(); function getTokenFor(containerCtor, key) { let byKey = CONTAINER_PROVIDER_TOKENS.get(containerCtor); if (!byKey) { byKey = /* @__PURE__ */ new Map(); CONTAINER_PROVIDER_TOKENS.set(containerCtor, byKey); } let token = byKey.get(key); if (!token) { token = Symbol(`di:token:${String(key)}`); byKey.set(key, token); } return token; } function getProviderTokenFor(containerCtor, key) { let byKey = CONTAINER_PROVIDER_AS_VALUE_TOKENS.get(containerCtor); if (!byKey) { byKey = /* @__PURE__ */ new Map(); CONTAINER_PROVIDER_AS_VALUE_TOKENS.set(containerCtor, byKey); } let token = byKey.get(key); if (!token) { token = Symbol(`di:provider-token:${String(key)}`); byKey.set(key, token); } return token; } function resolveDecoratedFunction(target, propertyKey) { if (propertyKey != null && target) { return target[propertyKey]; } if (typeof target === "function") { return target; } return void 0; } function isProviderLike(value) { return !!value && typeof value === "object" && PROVIDER_SYMBOL in value && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access value[PROVIDER_SYMBOL] === true; } function createInject(options) { const { containerClass } = options; const instance = new containerClass(); const markers = {}; const wiredContainers = /* @__PURE__ */ new Set(); const wrappedMethods = /* @__PURE__ */ new WeakMap(); for (const key of Reflect.ownKeys(instance)) { const val = instance[key]; if (!isProviderLike(val)) continue; const token = getTokenFor(containerClass, key); const providerToken = getProviderTokenFor(containerClass, key); const decorator = (_target, _propertyKey, parameterIndex) => { if (_propertyKey === void 0) { if (typeof _target === "function") { let constructorSites = CONSTRUCTOR_DECORATION_SITES.get(containerClass); if (!constructorSites) { constructorSites = []; CONSTRUCTOR_DECORATION_SITES.set(containerClass, constructorSites); } constructorSites.push({ constructor: _target, paramIndex: parameterIndex, token }); } return; } const fn = resolveDecoratedFunction(_target, _propertyKey); if (!fn) return; let paramMap = PARAM_INJECT_IDS.get(fn); if (!paramMap) { paramMap = /* @__PURE__ */ new Map(); PARAM_INJECT_IDS.set(fn, paramMap); } paramMap.set(parameterIndex, token); let sites = DECORATION_SITES.get(containerClass); if (!sites) { sites = []; DECORATION_SITES.set(containerClass, sites); } sites.push({ target: _target, propertyKey: _propertyKey, paramIndex: parameterIndex, token }); }; const providerDecorator = (_target, _propertyKey, parameterIndex) => { if (_propertyKey === void 0) { if (typeof _target === "function") { let constructorSites = CONSTRUCTOR_DECORATION_SITES.get(containerClass); if (!constructorSites) { constructorSites = []; CONSTRUCTOR_DECORATION_SITES.set(containerClass, constructorSites); } constructorSites.push({ constructor: _target, paramIndex: parameterIndex, token: providerToken }); } return; } const fn = resolveDecoratedFunction(_target, _propertyKey); if (!fn) return; let paramMap = PARAM_INJECT_IDS.get(fn); if (!paramMap) { paramMap = /* @__PURE__ */ new Map(); PARAM_INJECT_IDS.set(fn, paramMap); } paramMap.set(parameterIndex, providerToken); let sites = DECORATION_SITES.get(containerClass); if (!sites) { sites = []; DECORATION_SITES.set(containerClass, sites); } sites.push({ target: _target, propertyKey: _propertyKey, paramIndex: parameterIndex, token: providerToken }); }; Object.defineProperty(decorator, "provider", { value: providerDecorator, enumerable: true, configurable: false, writable: false }); Object.defineProperty(markers, key, { value: decorator, enumerable: true, configurable: false, writable: false }); } function findProviderByToken(container, token) { for (const key of Reflect.ownKeys(container)) { const containerToken = getTokenFor(containerClass, key); const providerContainerToken = getProviderTokenFor(containerClass, key); if (containerToken === token) { const provider = container[key]; if (isProviderLike(provider)) { return provider; } } else if (providerContainerToken === token) { const provider = container[key]; if (isProviderLike(provider)) { return provider.provider; } } } return void 0; } function wire(container) { wiredContainers.add(container); const sites = DECORATION_SITES.get(containerClass); if (!sites) return; const sitesByTargetAndKey = /* @__PURE__ */ new Map(); for (const site of sites) { let byKey = sitesByTargetAndKey.get(site.target); if (!byKey) { byKey = /* @__PURE__ */ new Map(); sitesByTargetAndKey.set(site.target, byKey); } let sitesForKey = byKey.get(site.propertyKey); if (!sitesForKey) { sitesForKey = []; byKey.set(site.propertyKey, sitesForKey); } sitesForKey.push(site); } for (const [target, byKey] of sitesByTargetAndKey.entries()) { let wrapped = wrappedMethods.get(target); if (!wrapped) { wrapped = /* @__PURE__ */ new Set(); wrappedMethods.set(target, wrapped); } for (const [propertyKey, sitesForKey] of byKey.entries()) { if (wrapped.has(propertyKey)) continue; const originalFn = target[propertyKey]; if (typeof originalFn !== "function") continue; const wrapper = function(...args) { const newArgs = [...args]; const activeContainer = wiredContainers.values().next().value; if (activeContainer) { const tokensByIndex = /* @__PURE__ */ new Map(); for (const site of sitesForKey) { tokensByIndex.set(site.paramIndex, site.token); } for (const [paramIndex, token] of tokensByIndex.entries()) { if (paramIndex >= newArgs.length || newArgs[paramIndex] === void 0) { const provider = findProviderByToken(activeContainer, token); if (provider) { newArgs[paramIndex] = provider.provide(); } } } } return originalFn.apply(this, newArgs); }; target[propertyKey] = wrapper; wrapped.add(propertyKey); } } } function unwire(container) { wiredContainers.delete(container); } function Injectable(target) { const constructorSites = CONSTRUCTOR_DECORATION_SITES.get(containerClass); if (!constructorSites) { return target; } const sitesForConstructor = constructorSites.filter((site) => site.constructor === target); if (sitesForConstructor.length === 0) { return target; } const tokensByIndex = /* @__PURE__ */ new Map(); for (const site of sitesForConstructor) { tokensByIndex.set(site.paramIndex, site.token); } const proxy = new Proxy(target, { construct(Target, args) { const newArgs = [...args]; const activeContainer = wiredContainers.values().next().value; if (activeContainer) { for (const [paramIndex, token] of tokensByIndex.entries()) { if (paramIndex >= newArgs.length || newArgs[paramIndex] === void 0) { const provider = findProviderByToken(activeContainer, token); if (provider) { newArgs[paramIndex] = provider.provide(); } } } } return new Target(...newArgs); } }); return proxy; } return { ...markers, wire, unwire, Injectable }; } function getInjectedParamIds(fn) { return PARAM_INJECT_IDS.get(fn); } function getMarkerFor(containerCtor, key) { return getTokenFor(containerCtor, key); } function InstanceOf(_type) { return void 0; } function ProviderOf(_type) { return void 0; } export { BaseProvider, DeclarativeContainer, Delegate, Extend, Factory, InstanceOf, ProviderOf, Singleton, createInject, getInjectedParamIds, getMarkerFor };