UNPKG

class-injector

Version:

Crazy simple dependency injection for TypeScript

213 lines (212 loc) 8.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isContext = exports.getContext = exports.provideRawInstance = exports.provideInstance = exports.ProvideInstance = exports.provide = exports.Provide = exports.Inject = exports.Context = exports.createContext = exports.createPartialForContext = exports.__InstanceRegistry = exports.__TypeRegistry = exports.__ContextSymbol = void 0; require("reflect-metadata"); exports.__ContextSymbol = Symbol.for('@@class-injector-context@@'); class PartialContextImpl extends Map { } class ContextImpl { constructor() { this.contextPartials = []; } get(type) { for (const partial of this.contextPartials) { if (partial.has(type)) { return partial.get(type); } } return null; } set(type, instance) { if (this.contextPartials.length === 0) { throw new Error([ 'Failed to set an instance in the context because no context partials are bound to the context.', 'Unless you are manipulating the context directly, this is a bug.', 'Please report this issue at https://github.com/bcheidemann/class-injector/issues/new' ].join('\n')); } this.contextPartials[0].set(type, instance); } has(type) { return this.contextPartials.some(partial => partial.has(type)); } instantiate(type) { let instance = this.get(type); if (!instance) { // Get existing context if one exists const existingContext = type.prototype[exports.__ContextSymbol]; if (existingContext) { // Extend the existing context existingContext.contextPartials.push(...this.contextPartials); // Instantiate the type. instance = new type(); // Remove the context partials we added existingContext.contextPartials.splice(existingContext.contextPartials.length - this.contextPartials.length, this.contextPartials.length); } else { // Bind the context to the types prototype type.prototype[exports.__ContextSymbol] = this; // Instantiate the type. instance = new type(); // Unbind the prototype from the context. type.prototype[exports.__ContextSymbol] = undefined; } // Bind the instance to the context. this.bind(instance); // Store the instance in the context. this.set(type, instance); } return instance; } ; bind(instance) { if (typeof instance !== 'object') { return; } const existingContext = instance[exports.__ContextSymbol]; if (existingContext) { // Extend the existing context existingContext.contextPartials.push(...this.contextPartials); return; } // Bind this context to the instance Object.defineProperty(instance, exports.__ContextSymbol, { value: this, }); } } exports.__TypeRegistry = new Map(); exports.__InstanceRegistry = new Map(); function createPartialForContext(context, options = {}) { // Create a new context const partial = new PartialContextImpl(); // Provide the context to the constructor const provide = options.provide || []; for (const dependency of provide) { let type; let instance; if (Array.isArray(dependency)) { type = dependency[0]; instance = dependency[1]; } else { type = dependency.constructor; instance = dependency; } // Add the instance to the context partial.set(type, instance); // Add the context to the instance if (typeof instance === 'object') { Object.defineProperty(instance, exports.__ContextSymbol, { value: context, }); } } return partial; } exports.createPartialForContext = createPartialForContext; function createContext(options = {}) { // Create a new context context const context = new ContextImpl(); // Create a new context const partial = createPartialForContext(context, options); // Bind the context to the context context.contextPartials.unshift(partial); return context; } exports.createContext = createContext; const Context = (options = {}) => (target) => { // Get the context context from the target if it exists let context = target.prototype[exports.__ContextSymbol]; // Create a new context context if it doesn't exist and bind it to the target if (!context) { context = new ContextImpl(); target.prototype[exports.__ContextSymbol] = context; } // Create a new context partial const partial = createPartialForContext(context, options); // Bind the context partial to the context context.contextPartials.unshift(partial); }; exports.Context = Context; const Inject = (type) => (target, propertyKey) => { // Get the type of the dependency to inject const _type = (type === undefined) ? Reflect.getMetadata("design:type", target, propertyKey) : type; // Lazy bind the dependency to the instance via a getter Object.defineProperty(target, propertyKey, { get() { // Get the context let context = this[exports.__ContextSymbol]; if (!context) { this[exports.__ContextSymbol] = context = createContext(); } // Return the context if the @Inject(Context) pattern is used if (_type === exports.Context) { return context; } // Try to get the instance from the context if (context.has(_type)) { return this[exports.__ContextSymbol].get(_type); } // Get the type from the symbol registry if (exports.__InstanceRegistry.has(_type)) { const instance = exports.__InstanceRegistry.get(_type); context.set(_type, instance); // NOTE: The instance is not bound to the context since it could be reused across many // contexts ProvideInstance and provideInstance should not be used if the instance requires // access to the parent context. Provide and provide should be used instead, as these will // create a new instance for each context and bind the context to that instance. return instance; } if (exports.__TypeRegistry.has(_type)) { const type = exports.__TypeRegistry.get(_type); return context.instantiate(type); } // Ensure the dependency can be instantiated as a constructor type if (typeof _type !== 'function') { throw new Error('Cannot instantiate a symbol type. Please use a class type instead or provide an instance for the type in the context.'); } return context.instantiate(_type); }, }); }; exports.Inject = Inject; const Provide = (symbol) => (target) => { const _symbol = symbol === undefined ? target : symbol; exports.__TypeRegistry.set(_symbol, target); }; exports.Provide = Provide; function provide(symbolOrType, type) { const _type = type === undefined ? symbolOrType : type; exports.__TypeRegistry.set(symbolOrType, _type); } exports.provide = provide; const ProvideInstance = (symbol) => (target) => { const instance = new target(); exports.__InstanceRegistry.set(symbol, instance); }; exports.ProvideInstance = ProvideInstance; function provideInstance(symbolOrConstructor, constructor) { const _constructor = constructor === undefined ? symbolOrConstructor : constructor; const instance = new _constructor(); exports.__InstanceRegistry.set(symbolOrConstructor, instance); return instance; } exports.provideInstance = provideInstance; function provideRawInstance(symbolOrInstance, instance) { const _symbol = instance === undefined ? symbolOrInstance.constructor : symbolOrInstance; const _instance = instance === undefined ? symbolOrInstance : instance; exports.__InstanceRegistry.set(_symbol, _instance); return _instance; } exports.provideRawInstance = provideRawInstance; const getContext = (instance) => { return instance[exports.__ContextSymbol]; }; exports.getContext = getContext; const isContext = (instance) => { return instance instanceof ContextImpl; }; exports.isContext = isContext;