/* eslint-disable @typescript-eslint/no-explicit-any */ import { IContainer, IFactory, IRegistry, IResolver, InstanceProvider, InterfaceSymbol, Key, Resolved, inject } from './di'; import { createContainer } from './di.container'; import { ErrorNames, createMappedError } from './errors'; import { Constructable } from './interfaces'; import { isFunction, objectAssign, safeString } from './utilities'; export type ICallableResolver = IResolver & ((...args: unknown[]) => any); /** * ! Semi private API to avoid repetitive work creating resolvers. * * Naming isn't entirely correct, but it's good enough for internal usage. */ export function createResolver(getter: (key: T, handler: IContainer, requestor: IContainer) => any): ((key: T) => ICallableResolver) { return function (key: any) { function Resolver(target: any, context: DecoratorContext): void { inject(Resolver)(target, context); } Resolver.$isResolver = true; Resolver.resolve = function (handler: IContainer, requestor: IContainer): any { return getter(key, handler, requestor); }; return Resolver as ICallableResolver; }; } /** * Create a resolver that will resolve all values of a key from resolving container */ export const all = (key: T, searchAncestors: boolean = false): IAllResolver => { function resolver(decorated: unknown, context: DecoratorContext): void { inject(resolver)(decorated, context); } resolver.$isResolver = true; resolver.resolve = (handler: IContainer, requestor: IContainer) => requestor.getAll(key, searchAncestors); return resolver as IAllResolver; }; export type IAllResolver = IResolver[]> & // any for decorator ((decorated: unknown, context: DecoratorContext) => any); /** * Create a resolver that will resolve the last instance of a key from the resolving container * * - @param key [[`Key`]] */ export const last = (key: T): IResolver => ({ $isResolver: true, resolve: handler => { const allInstances = handler.getAll(key); return allInstances.length > 0 ? allInstances[allInstances.length - 1] : undefined; } }); /** * Lazily inject a dependency depending on whether the [[`Key`]] is present at the time of function call. * * You need to make your argument a function that returns the type, for example * ```ts * class Foo { * constructor( @lazy('random') public random: () => number ) * } * const foo = container.get(Foo); // instanceof Foo * foo.random(); // throws * ``` * would throw an exception because you haven't registered `'random'` before calling the method. This, would give you a * new [['Math.random()']] number each time. * ```ts * class Foo { * constructor( @lazy('random') public random: () => random ) * } * container.register(Registration.callback('random', Math.random )); * container.get(Foo).random(); // some random number * container.get(Foo).random(); // another random number * ``` * `@lazy` does not manage the lifecycle of the underlying key. If you want a singleton, you have to register as a * `singleton`, `transient` would also behave as you would expect, providing you a new instance each time. * * - @param key [[`Key`]] * see { @link DI.createInterface } on interactions with interfaces */ export const lazy = /*@__PURE__*/createResolver((key: Key, handler: IContainer, requestor: IContainer) => { return () => requestor.get(key); }) as (key: K) => ILazyResolver; export type ILazyResolver = IResolver<() => K> // type only hack & { __isLazy: undefined } // any is needed for decorator usages & ((...args: unknown[]) => any); export type IResolvedLazy = () => Resolved; /** * Allows you to optionally inject a dependency depending on whether the [[`Key`]] is present, for example * ```ts * class Foo { * constructor( @inject('mystring') public str: string = 'somestring' ) * } * container.get(Foo); // throws * ``` * would fail * ```ts * class Foo { * constructor( @optional('mystring') public str: string = 'somestring' ) * } * container.get(Foo).str // somestring * ``` * if you use it without a default it will inject `undefined`, so rember to mark your input type as * possibly `undefined`! * * - @param key: [[`Key`]] * * see { @link DI.createInterface } on interactions with interfaces */ export const optional = /*@__PURE__*/createResolver((key: Key, handler: IContainer, requestor: IContainer) => { if (requestor.has(key, true)) { return requestor.get(key); } else { return undefined; } }) as (key: K) => IOptionalResolver; export type IOptionalResolver = IResolver & { // type only hack __isOptional: undefined; // any is needed for decorator usages (...args: unknown[]): any; }; /** * ignore tells the container not to try to inject a dependency */ export const ignore: IResolver = /*@__PURE__*/objectAssign((decorated: unknown, context: DecoratorContext): void => { inject(ignore)(decorated, context); }, {$isResolver: true, resolve: () => void 0} as const); /** * Inject a function that will return a resolved instance of the [[key]] given. * Also supports passing extra parameters to the invocation of the resolved constructor of [[key]] * * For typings, it's a function that take 0 or more arguments and return an instance. Example: * ```ts * class Foo { * constructor( @factory(MyService) public createService: (...args: unknown[]) => MyService) * } * const foo = container.get(Foo); // instanceof Foo * const myService_1 = foo.createService('user service') * const myService_2 = foo.createService('content service') * ``` * * ```ts * class Foo { * constructor( @factory('random') public createRandomizer: () => Randomizer) * } * container.get(Foo).createRandomizer(); // create a randomizer * ``` * would throw an exception because you haven't registered `'random'` before calling the method. This, would give you a * new instance of Randomizer each time. * * `@factory` does not manage the lifecycle of the underlying key. If you want a singleton, you have to register as a * `singleton`, `transient` would also behave as you would expect, providing you a new instance each time. * * - @param key [[`Key`]] * see { @link DI.createInterface } on interactions with interfaces */ export const factory = /*@__PURE__*/createResolver((key: any, handler: IContainer, requestor: IContainer) => { return (...args: unknown[]) => handler.getFactory(key).construct(requestor, args); }) as (key: K) => IFactoryResolver; export type IFactoryResolver = IResolver // type only hack & { __isFactory: undefined } // any is needed for decorator usage & ((...args: unknown[]) => any); export type IResolvedFactory = (...args: unknown[]) => Resolved; /** * Create a resolver that will only resolve if the requesting container has the key pre-registered */ export const own = /*@__PURE__*/createResolver((key: any, handler: IContainer, requestor: IContainer) => { return requestor.has(key, false) ? requestor.get(key) : void 0; }) as (key: T) => IOptionalResolver; /** * Create a resolver that will resolve a key based on resource semantic (leaf + root + ignore middle layer container) * Will resolve at the root level if the key is not registered in the requestor container */ export const resource = /*@__PURE__*/createResolver((key, handler, requestor) => requestor.has(key, false) ? requestor.get(key) : requestor.root.get(key) ) as (key: K) => ICallableResolver; /** * Create a resolver that will resolve a key based on resource semantic (leaf + root + ignore middle layer container) * only if the key is registered either in the requestor container or in the root container * * Returns `undefined` if the key is not registered in either container */ export const optionalResource = /*@__PURE__*/createResolver((key, handler, requestor) => requestor.has(key, false) ? requestor.get(key) : requestor.root.has(key, false) ? requestor.root.get(key) : void 0 ) as (key: K) => IOptionalResolver; /** * Create a resolver for resolving all registrations of a key with resource semantic (leaf + root + ignore middle layer container) */ export const allResources = /*@__PURE__*/createResolver((key, handler, requestor) => // prevent duplicate retrieval requestor === requestor.root ? requestor.getAll(key, false) : requestor.has(key, false) ? requestor.getAll(key, false).concat(requestor.root.getAll(key, false)) : requestor.root.getAll(key, false) ) as (key: T) => IAllResolver; /** * Create a resolver that will resolve a new instance of a key, and register the newly created instance with the requestor container */ export const newInstanceForScope = /*@__PURE__*/createResolver( (key: any, handler: IContainer, requestor: IContainer) => { const instance = createNewInstance(key, handler, requestor); const instanceProvider = new InstanceProvider<{}>(safeString(key), instance); /** * By default the new instances for scope are disposable. * If need be, we can always enhance the `createNewInstance` to support a 'injection' context, to make a non/disposable registration here. */ requestor.registerResolver(key, instanceProvider, true); return instance; }) as (key: K) => INewInstanceResolver; /** * Create a resolver that will resolve a new instance of a key */ export const newInstanceOf = /*@__PURE__*/createResolver( (key: any, handler: IContainer, requestor: IContainer) => createNewInstance(key, handler, requestor) ) as (key: K) => INewInstanceResolver; export type INewInstanceResolver = IResolver & { // type only hack __newInstance: undefined; // any is needed for decorator usage (...args: unknown[]): any; }; const createNewInstance = (key: any, handler: IContainer, requestor: IContainer) => { // 1. if there's a factory registration for the key if (handler.hasFactory(key)) { return handler.getFactory(key).construct(requestor); } // 2. if key is an interface if (isInterface(key)) { const hasDefault = isFunction((key as unknown as IRegistry).register); const resolver = handler.getResolver(key, false) as IResolver>; let factory: IFactory | null | undefined; if (resolver == null) { if (hasDefault) { // creating a new container as we do not want to pollute the resolver registry factory = (newInstanceContainer ??= createContainer()).getResolver(key, true)?.getFactory?.(handler); } newInstanceContainer.dispose(); } else { factory = resolver.getFactory?.(handler); } // 2.1 and has resolvable factory if (factory != null) { return factory.construct(requestor); } // 2.2 cannot instantiate a dummy interface throw createMappedError(ErrorNames.invalid_new_instance_on_interface, key); } // 3. jit factory, in case of newInstanceOf(SomeClass) return handler.getFactory(key).construct(requestor); }; const isInterface = (key: any): key is InterfaceSymbol => (key as {$isInterface?: boolean})?.$isInterface === true; let newInstanceContainer: IContainer;