From 9b63c86f80096038a25b2309fb19642aeb8aa334 Mon Sep 17 00:00:00 2001 From: cmidgley Date: Tue, 11 Jun 2024 18:36:08 -0400 Subject: [PATCH 1/6] Support for Moddable XS - During moddable preload, container registration occurs and is placed into the maps in flash that are frozen - Container maps are refactored to use an interface for containing all maps, and clones all container maps, if and only if the are frozen, so they can become writable in RAM - Change only affects preloaded frozen maps, no meaningful change to Node environments aside from one level of indirection on accessing maps - Added moddable.json file, which is similar to package.json for Moddable XS --- moddable.json | 9 ++++ src/di-container/di-container.ts | 66 ++++++++++++++++++------------ src/di-container/i-di-container.ts | 26 ++++++++++++ 3 files changed, 75 insertions(+), 26 deletions(-) create mode 100644 moddable.json diff --git a/moddable.json b/moddable.json new file mode 100644 index 0000000..dae59a4 --- /dev/null +++ b/moddable.json @@ -0,0 +1,9 @@ +{ + "modules": { + "@wessberg/di": ["dist/esm/index"], + "@wessberg/di.d": ["dist/esm/index.d"] + }, + "preload": [ + "@wessberg/di" + ] +} \ No newline at end of file diff --git a/src/di-container/di-container.ts b/src/di-container/di-container.ts index e62134a..1c2afc1 100644 --- a/src/di-container/di-container.ts +++ b/src/di-container/di-container.ts @@ -1,6 +1,5 @@ import { IConstructInstanceOptions } from "../construct-instance-options/i-construct-instance-options"; import { IParent } from "../construct-instance-options/i-parent"; -import { ConstructorArgument } from "../constructor-arguments/constructor-argument"; import { CONSTRUCTOR_ARGUMENTS_SYMBOL } from "../constructor-arguments/constructor-arguments-identifier"; import { IGetOptions } from "../get-options/i-get-options"; import { IHasOptions } from "../has-options/i-has-options"; @@ -11,7 +10,7 @@ import { RegisterOptions, } from "../register-options/i-register-options"; import { RegistrationKind } from "../registration-kind/registration-kind"; -import { IDIContainer } from "./i-di-container"; +import { IDIContainer, IDIContainerMaps } from "./i-di-container"; import { RegistrationRecord } from "../registration-record/i-registration-record"; import { ImplementationInstance } from "../implementation/implementation"; @@ -21,22 +20,6 @@ import { ImplementationInstance } from "../implementation/implementation"; * @author Frederik Wessberg */ export class DIContainer implements IDIContainer { - /** - * A map between interface names and the services that should be dependency injected - */ - private readonly constructorArguments: Map = - new Map(); - /** - * A Map between identifying names for services and their IRegistrationRecords. - */ - private readonly serviceRegistry: Map> = - new Map(); - - /** - * A map between identifying names for services and concrete instances of their implementation. - */ - private readonly instances: Map = new Map(); - /** * Registers a service that will be instantiated once in the application lifecycle. All requests * for the service will retrieve the same instance of it. @@ -143,9 +126,8 @@ export class DIContainer implements IDIContainer { `1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}` ); } - return this.serviceRegistry.has(options.identifier); + return this.diContainerMaps.serviceRegistry.has(options.identifier); } - /** * Registers a service */ @@ -171,9 +153,9 @@ export class DIContainer implements IDIContainer { options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] != null ? options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL]! : []; - this.constructorArguments.set(options.identifier, implementationArguments); + this.diContainerMaps.constructorArguments.set(options.identifier, implementationArguments); - this.serviceRegistry.set( + this.diContainerMaps.serviceRegistry.set( options.identifier, "implementation" in options && options.implementation != null ? { ...options, kind } @@ -192,7 +174,7 @@ export class DIContainer implements IDIContainer { * Gets the cached instance, if any, associated with the given identifier. */ private getInstance(identifier: string): T | null { - const instance = this.instances.get(identifier); + const instance = this.diContainerMaps.instances.get(identifier); return instance == null ? null : instance; } @@ -203,7 +185,7 @@ export class DIContainer implements IDIContainer { identifier, parentChain, }: IConstructInstanceOptions): RegistrationRecord { - const record = this.serviceRegistry.get(identifier); + const record = this.diContainerMaps.serviceRegistry.get(identifier); if (record == null) { throw new ReferenceError( `${ @@ -224,7 +206,7 @@ export class DIContainer implements IDIContainer { * Caches the given instance so that it can be retrieved in the future. */ private setInstance(identifier: string, instance: T): T { - this.instances.set(identifier, instance); + this.diContainerMaps.instances.set(identifier, instance); return instance; } @@ -282,7 +264,7 @@ export class DIContainer implements IDIContainer { } } else { // Find the arguments for the identifier - const mappedArgs = this.constructorArguments.get(identifier); + const mappedArgs = this.diContainerMaps.constructorArguments.get(identifier); if (mappedArgs == null) { throw new ReferenceError( `${this.constructor.name} could not find constructor arguments for the service: '${identifier}'. Have you registered it as a service?` @@ -324,6 +306,38 @@ export class DIContainer implements IDIContainer { ? this.setInstance(identifier, instance) : instance; } + + /** + * Maps that may get defined during Moddable pre-load, and then frozen into flash memory. See the getter + * `diContainerMaps` which handles the transition from flash to runtime for the maps. + */ + private readonly writableDiContainerMaps: IDIContainerMaps = { + constructorArguments: new Map(), + serviceRegistry: new Map(), + instances: new Map() + }; + + /** + * Getter that provides access to the various maps. Handles cloning the maps from the read-only preload condition + * to a writable runtime version to support Moddable preloads. + */ + private get diContainerMaps(): IDIContainerMaps { + // if a map is frozen, it has been preloaded, so we need to clone the map. This happens because registrations + // occur during preload, but then also need to work at runtime. + if (Object.isFrozen(this.writableDiContainerMaps.constructorArguments)) { + this.writableDiContainerMaps.constructorArguments = new Map( + this.writableDiContainerMaps.constructorArguments + ); + this.writableDiContainerMaps.instances = new Map( + this.writableDiContainerMaps.instances + ); + this.writableDiContainerMaps.serviceRegistry = new Map( + this.writableDiContainerMaps.serviceRegistry + ); + } + + return this.writableDiContainerMaps; + } } const DI_COMPILER_ERROR_HINT = `Note: You must use DI-Compiler (https://github.com/wessberg/di-compiler) for this library to work correctly. Please consult the readme for instructions on how to install and configure it for your project.`; diff --git a/src/di-container/i-di-container.ts b/src/di-container/i-di-container.ts index 5d25dd1..58fe016 100644 --- a/src/di-container/i-di-container.ts +++ b/src/di-container/i-di-container.ts @@ -6,6 +6,8 @@ import { import { IGetOptions } from "../get-options/i-get-options"; import { IHasOptions } from "../has-options/i-has-options"; import { ImplementationInstance } from "../implementation/implementation"; +import { ConstructorArgument } from '../constructor-arguments/constructor-argument'; +import { RegistrationRecord } from '../registration-record/i-registration-record'; export interface IDIContainer { registerSingleton( @@ -36,3 +38,27 @@ export interface IDIContainer { // eslint-disable-next-line @typescript-eslint/no-unused-vars has(options?: IHasOptions): boolean; } + +/** + * Contains all maps in a object, so that Moddable can "preload" the object into flash memory. See + * https://github.com/Moddable-OpenSource/moddable/blob/83dadd3def6d2e7e75fc003a5ab409aa81275dd8/documentation/xs/preload.md + * for information on Moddable preloading, but the basic concept is code that the startup code is + * executed during the linker so that the resulting slots (variables) can be placed into flash ROM to + * reduce the memory footprint. By moving these maps into an object, Moddable will freeze the object + * but allow the members (the maps) to be writable at runtime. + */ +export interface IDIContainerMaps { + /** + * A map between interface names and the services that should be dependency injected + */ + constructorArguments: Map; + /** + * A Map between identifying names for services and their IRegistrationRecords. + */ + serviceRegistry: Map>; + + /** + * A map between identifying names for services and concrete instances of their implementation. + */ + instances: Map; +} \ No newline at end of file From 2633e729544305733cace848da78f298fd983ac7 Mon Sep 17 00:00:00 2001 From: cmidgley Date: Tue, 11 Jun 2024 19:02:42 -0400 Subject: [PATCH 2/6] Fix to use forrect manifest.json file (not moddable.json) --- moddable.json => manifest.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename moddable.json => manifest.json (100%) diff --git a/moddable.json b/manifest.json similarity index 100% rename from moddable.json rename to manifest.json From cedf01941afe8e3ca6f549f83d6b963d91804ce1 Mon Sep 17 00:00:00 2001 From: cmidgley Date: Tue, 11 Jun 2024 20:43:11 -0400 Subject: [PATCH 3/6] Added manifest.json to distribution files in package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1be6d38..59d79c5 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ }, "license": "MIT", "files": [ - "dist/**/*.*" + "dist/**/*.*", + "manifest.json" ] } From 4e6c9ade51742e7d63d97028c7220eaa645046cf Mon Sep 17 00:00:00 2001 From: cmidgley Date: Wed, 12 Jun 2024 07:47:07 -0400 Subject: [PATCH 4/6] package.json: adjust build process for git-based npm installs - remove build from preversion - add prepare script to do build - This allows a git-based project dependency to build and get dist artifacts --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 59d79c5..c03fdb2 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,9 @@ "prewatch": "pnpm run clean", "watch": "pnpm run prewatch && pnpm run rollup -- --watch", "rollup": "rollup -c rollup.config.js", - "preversion": "pnpm run lint && pnpm run build", + "preversion": "pnpm run lint", "version": "pnpm run preversion && pnpm run generate:all && git add .", + "prepare": "pnpm run build", "release": "np --no-cleanup --no-yarn --no-tests", "update:check": "pnpx npm-check-updates -x typescript-* --dep dev,prod", "update:commit": "pnpx npm-check-updates -u -x typescript-* --dep dev,prod && pnpm update && pnpm install" From 03d315c2ba6546976b8815b8f92a9a10561ac949 Mon Sep 17 00:00:00 2001 From: cmidgley Date: Tue, 16 Jul 2024 20:30:30 -0400 Subject: [PATCH 5/6] Remove unnecessary .d.ts reference --- manifest.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index dae59a4..cc3281a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,6 @@ { "modules": { - "@wessberg/di": ["dist/esm/index"], - "@wessberg/di.d": ["dist/esm/index.d"] + "@wessberg/di": ["dist/esm/index"] }, "preload": [ "@wessberg/di" From 51e0c0dd7563600caeaaac1e2e2660f3d31794b7 Mon Sep 17 00:00:00 2001 From: cmidgley Date: Thu, 19 Sep 2024 15:09:53 -0400 Subject: [PATCH 6/6] Revert package.json preversion/prepare due to verion conflicts when building on npm i. removed /dist/ from gitignore to use that instead. --- .gitignore | 1 - dist/cjs/index.d.ts | 120 ++++++++++++++++++++++++ dist/cjs/index.js | 212 ++++++++++++++++++++++++++++++++++++++++++ dist/cjs/index.js.map | 1 + dist/esm/index.d.ts | 120 ++++++++++++++++++++++++ dist/esm/index.js | 206 ++++++++++++++++++++++++++++++++++++++++ dist/esm/index.js.map | 1 + package.json | 3 +- 8 files changed, 661 insertions(+), 3 deletions(-) create mode 100644 dist/cjs/index.d.ts create mode 100644 dist/cjs/index.js create mode 100644 dist/cjs/index.js.map create mode 100644 dist/esm/index.d.ts create mode 100644 dist/esm/index.js create mode 100644 dist/esm/index.js.map diff --git a/.gitignore b/.gitignore index 22246df..7524e52 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,4 @@ Thumbs.db *.crt *.csr /node_modules/ -/dist/ package-lock.json \ No newline at end of file diff --git a/dist/cjs/index.d.ts b/dist/cjs/index.d.ts new file mode 100644 index 0000000..da7a12c --- /dev/null +++ b/dist/cjs/index.d.ts @@ -0,0 +1,120 @@ +declare const CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = "___CTOR_ARGS___"; +declare const CONSTRUCTOR_ARGUMENTS_SYMBOL: unique symbol; +type ConstructorArgument = string | undefined; +interface IWithConstructorArgumentsSymbol { + [CONSTRUCTOR_ARGUMENTS_SYMBOL]?: ConstructorArgument[]; +} +interface IContainerIdentifierable { + identifier: string; +} +interface IGetOptions extends IContainerIdentifierable { +} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type NewableService = new (...args: any[]) => T; +/* eslint-disable @typescript-eslint/no-explicit-any */ +type CustomConstructableService = (...args: any[]) => T; +type Implementation = NewableService & IWithConstructorArgumentsSymbol; +type ImplementationInstance = CustomConstructableService & IWithConstructorArgumentsSymbol; +interface IRegisterOptionsBase extends IContainerIdentifierable { +} +interface IRegisterOptionsWithImplementation extends IRegisterOptionsBase { + implementation: Implementation | null; +} +interface IRegisterOptionsWithoutImplementation extends IRegisterOptionsBase { +} +type RegisterOptions = IRegisterOptionsWithImplementation | IRegisterOptionsWithoutImplementation; +interface IHasOptions extends IContainerIdentifierable { +} +interface IDIContainer { + registerSingleton(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + registerSingleton(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerSingleton(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + registerTransient(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + registerTransient(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerTransient(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + get(options?: IGetOptions): T; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + has(options?: IHasOptions): boolean; +} +/** + * A Dependency-Injection container that holds services and can produce instances of them as required. + * It mimics reflection by parsing the app at compile-time and supporting the generic-reflection syntax. + * @author Frederik Wessberg + */ +declare class DIContainer implements IDIContainer { + /** + * Registers a service that will be instantiated once in the application lifecycle. All requests + * for the service will retrieve the same instance of it. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + registerSingleton(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerSingleton(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + registerSingleton(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + /** + * Registers a service that will be instantiated every time it is requested throughout the application lifecycle. + * This means that every call to get() will return a unique instance of the service. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + registerTransient(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerTransient(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + registerTransient(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + /** + * Gets an instance of the service matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + get(options?: IGetOptions): T; + /** + * Returns true if a service has been registered matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + has(options?: IHasOptions): boolean; + /** + * Registers a service + */ + private register; + /** + * Returns true if an instance exists that matches the given identifier. + */ + private hasInstance; + /** + * Gets the cached instance, if any, associated with the given identifier. + */ + private getInstance; + /** + * Gets an IRegistrationRecord associated with the given identifier. + */ + private getRegistrationRecord; + /** + * Caches the given instance so that it can be retrieved in the future. + */ + private setInstance; + /** + * Gets a lazy reference to another service + */ + private getLazyIdentifier; + /** + * Constructs a new instance of the given identifier and returns it. + * It checks the constructor arguments and injects any services it might depend on recursively. + */ + private constructInstance; + /** + * Maps that may get defined during Moddable pre-load, and then frozen into flash memory. See the getter + * `diContainerMaps` which handles the transition from flash to runtime for the maps. + */ + private readonly writableDiContainerMaps; + /** + * Getter that provides access to the various maps. Handles cloning the maps from the read-only preload condition + * to a writable runtime version to support Moddable preloads. + */ + private get diContainerMaps(); +} +export { ConstructorArgument, IGetOptions, RegisterOptions, IHasOptions, DIContainer, IDIContainer, CONSTRUCTOR_ARGUMENTS_SYMBOL, CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER }; diff --git a/dist/cjs/index.js b/dist/cjs/index.js new file mode 100644 index 0000000..d430b3f --- /dev/null +++ b/dist/cjs/index.js @@ -0,0 +1,212 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +const CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = `___CTOR_ARGS___`; +const CONSTRUCTOR_ARGUMENTS_SYMBOL = Symbol.for(CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER); + +/** + * A Dependency-Injection container that holds services and can produce instances of them as required. + * It mimics reflection by parsing the app at compile-time and supporting the generic-reflection syntax. + * @author Frederik Wessberg + */ +class DIContainer { + registerSingleton(newExpression, options) { + if (options == null) { + throw new ReferenceError(`2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + if (newExpression == null) { + return this.register("SINGLETON", newExpression, options); + } + else { + return this.register("SINGLETON", newExpression, options); + } + } + registerTransient(newExpression, options) { + if (options == null) { + throw new ReferenceError(`2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + if (newExpression == null) { + return this.register("TRANSIENT", newExpression, options); + } + else { + return this.register("TRANSIENT", newExpression, options); + } + } + /** + * Gets an instance of the service matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + get(options) { + if (options == null) { + throw new ReferenceError(`1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + return this.constructInstance(options); + } + /** + * Returns true if a service has been registered matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + has(options) { + if (options == null) { + throw new ReferenceError(`1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + return this.diContainerMaps.serviceRegistry.has(options.identifier); + } + register(kind, newExpression, options) { + // Take all of the constructor arguments for the implementation + const implementationArguments = "implementation" in options && + options.implementation != null && + options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] != null + ? options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] + : []; + this.diContainerMaps.constructorArguments.set(options.identifier, implementationArguments); + this.diContainerMaps.serviceRegistry.set(options.identifier, "implementation" in options && options.implementation != null + ? { ...options, kind } + : { ...options, kind, newExpression: newExpression }); + } + /** + * Returns true if an instance exists that matches the given identifier. + */ + hasInstance(identifier) { + return this.getInstance(identifier) != null; + } + /** + * Gets the cached instance, if any, associated with the given identifier. + */ + getInstance(identifier) { + const instance = this.diContainerMaps.instances.get(identifier); + return instance == null ? null : instance; + } + /** + * Gets an IRegistrationRecord associated with the given identifier. + */ + getRegistrationRecord({ identifier, parentChain, }) { + const record = this.diContainerMaps.serviceRegistry.get(identifier); + if (record == null) { + throw new ReferenceError(`${this.constructor.name} could not find a service for identifier: "${identifier}". ${parentChain == null || parentChain.length < 1 + ? "" + : `It is required by the service: '${parentChain + .map((parent) => parent.identifier) + .join(" -> ")}'.`} Remember to register it as a service!`); + } + return record; + } + /** + * Caches the given instance so that it can be retrieved in the future. + */ + setInstance(identifier, instance) { + this.diContainerMaps.instances.set(identifier, instance); + return instance; + } + /** + * Gets a lazy reference to another service + */ + getLazyIdentifier(lazyPointer) { + return (new Proxy({}, { get: (_, key) => lazyPointer()[key] })); + } + /** + * Constructs a new instance of the given identifier and returns it. + * It checks the constructor arguments and injects any services it might depend on recursively. + */ + constructInstance({ identifier, parentChain = [], }) { + const registrationRecord = this.getRegistrationRecord({ + identifier, + parentChain, + }); + // If an instance already exists (and it is a singleton), return that one + if (this.hasInstance(identifier) && + registrationRecord.kind === "SINGLETON") { + return this.getInstance(identifier); + } + // Otherwise, instantiate a new one + let instance; + const me = { + identifier, + ref: this.getLazyIdentifier(() => instance), + }; + // If a user-provided new-expression has been provided, invoke that to get an instance. + if ("newExpression" in registrationRecord) { + if (typeof registrationRecord.newExpression !== "function") { + throw new TypeError(`Could not instantiate the service with the identifier: '${registrationRecord.identifier}': You provided a custom instantiation argument, but it wasn't of type function. It has to be a function that returns whatever should be used as an instance of the Service!`); + } + try { + instance = registrationRecord.newExpression(); + } + catch (ex) { + throw new Error(`Could not instantiate the service with the identifier: '${registrationRecord.identifier}': When you registered the service, you provided a custom instantiation function, but it threw an exception when it was run!`); + } + } + else { + // Find the arguments for the identifier + const mappedArgs = this.diContainerMaps.constructorArguments.get(identifier); + if (mappedArgs == null) { + throw new ReferenceError(`${this.constructor.name} could not find constructor arguments for the service: '${identifier}'. Have you registered it as a service?`); + } + // Instantiate all of the argument services (or re-use them if they were registered as singletons) + const instanceArgs = mappedArgs.map((dep) => { + if (dep === undefined) + return undefined; + const matchedParent = parentChain.find((parent) => parent.identifier === dep); + if (matchedParent != null) + return matchedParent.ref; + return this.constructInstance({ + identifier: dep, + parentChain: [...parentChain, me], + }); + }); + try { + // Try to construct an instance with 'new' and if it fails, call the implementation directly. + const newable = registrationRecord.implementation; + instance = new newable(...instanceArgs); + } + catch (ex) { + if (registrationRecord.implementation == null) { + throw new ReferenceError(`${this.constructor.name} could not construct a new service of kind: ${identifier}. Reason: No implementation was given!`); + } + const constructable = registrationRecord.implementation; + // Try without 'new' and call the implementation as a function. + instance = constructable(...instanceArgs); + } + } + return registrationRecord.kind === "SINGLETON" + ? this.setInstance(identifier, instance) + : instance; + } + /** + * Maps that may get defined during Moddable pre-load, and then frozen into flash memory. See the getter + * `diContainerMaps` which handles the transition from flash to runtime for the maps. + */ + writableDiContainerMaps = { + constructorArguments: new Map(), + serviceRegistry: new Map(), + instances: new Map() + }; + /** + * Getter that provides access to the various maps. Handles cloning the maps from the read-only preload condition + * to a writable runtime version to support Moddable preloads. + */ + get diContainerMaps() { + // if a map is frozen, it has been preloaded, so we need to clone the map. This happens because registrations + // occur during preload, but then also need to work at runtime. + if (Object.isFrozen(this.writableDiContainerMaps.constructorArguments)) { + this.writableDiContainerMaps.constructorArguments = new Map(this.writableDiContainerMaps.constructorArguments); + this.writableDiContainerMaps.instances = new Map(this.writableDiContainerMaps.instances); + this.writableDiContainerMaps.serviceRegistry = new Map(this.writableDiContainerMaps.serviceRegistry); + } + return this.writableDiContainerMaps; + } +} +const DI_COMPILER_ERROR_HINT = `Note: You must use DI-Compiler (https://github.com/wessberg/di-compiler) for this library to work correctly. Please consult the readme for instructions on how to install and configure it for your project.`; + +exports.CONSTRUCTOR_ARGUMENTS_SYMBOL = CONSTRUCTOR_ARGUMENTS_SYMBOL; +exports.CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER; +exports.DIContainer = DIContainer; +//# sourceMappingURL=index.js.map diff --git a/dist/cjs/index.js.map b/dist/cjs/index.js.map new file mode 100644 index 0000000..3e43ed0 --- /dev/null +++ b/dist/cjs/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sources":["../../src/constructor-arguments/constructor-arguments-identifier.ts","../../src/di-container/di-container.ts"],"sourcesContent":["export const CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = `___CTOR_ARGS___`;\nexport const CONSTRUCTOR_ARGUMENTS_SYMBOL: unique symbol = Symbol.for(\n CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER\n);\n","import { IConstructInstanceOptions } from \"../construct-instance-options/i-construct-instance-options\";\nimport { IParent } from \"../construct-instance-options/i-parent\";\nimport { CONSTRUCTOR_ARGUMENTS_SYMBOL } from \"../constructor-arguments/constructor-arguments-identifier\";\nimport { IGetOptions } from \"../get-options/i-get-options\";\nimport { IHasOptions } from \"../has-options/i-has-options\";\nimport { NewableService } from \"../newable-service/newable-service\";\nimport {\n IRegisterOptionsWithImplementation,\n IRegisterOptionsWithoutImplementation,\n RegisterOptions,\n} from \"../register-options/i-register-options\";\nimport { RegistrationKind } from \"../registration-kind/registration-kind\";\nimport { IDIContainer, IDIContainerMaps } from \"./i-di-container\";\nimport { RegistrationRecord } from \"../registration-record/i-registration-record\";\nimport { ImplementationInstance } from \"../implementation/implementation\";\n\n/**\n * A Dependency-Injection container that holds services and can produce instances of them as required.\n * It mimics reflection by parsing the app at compile-time and supporting the generic-reflection syntax.\n * @author Frederik Wessberg\n */\nexport class DIContainer implements IDIContainer {\n /**\n * Registers a service that will be instantiated once in the application lifecycle. All requests\n * for the service will retrieve the same instance of it.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n registerSingleton(\n newExpression: ImplementationInstance,\n options: IRegisterOptionsWithoutImplementation\n ): void;\n registerSingleton(\n newExpression: undefined,\n options: IRegisterOptionsWithImplementation\n ): void;\n registerSingleton(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void;\n registerSingleton(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void {\n if (options == null) {\n throw new ReferenceError(\n `2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n if (newExpression == null) {\n return this.register(\n \"SINGLETON\",\n newExpression,\n >options\n );\n } else {\n return this.register(\"SINGLETON\", newExpression, options);\n }\n }\n\n /**\n * Registers a service that will be instantiated every time it is requested throughout the application lifecycle.\n * This means that every call to get() will return a unique instance of the service.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n registerTransient(\n newExpression: ImplementationInstance,\n options: IRegisterOptionsWithoutImplementation\n ): void;\n registerTransient(\n newExpression: undefined,\n options: IRegisterOptionsWithImplementation\n ): void;\n registerTransient(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void;\n registerTransient(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void {\n if (options == null) {\n throw new ReferenceError(\n `2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n if (newExpression == null) {\n return this.register(\n \"TRANSIENT\",\n newExpression,\n >options\n );\n } else {\n return this.register(\"TRANSIENT\", newExpression, options);\n }\n }\n\n /**\n * Gets an instance of the service matching the interface given as a generic type parameter.\n * For example, 'container.get()' returns a concrete instance of the implementation associated with the\n * generic interface name.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n get(options?: IGetOptions): T {\n if (options == null) {\n throw new ReferenceError(\n `1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n return this.constructInstance(options);\n }\n\n /**\n * Returns true if a service has been registered matching the interface given as a generic type parameter.\n * For example, 'container.get()' returns a concrete instance of the implementation associated with the\n * generic interface name.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n has(options?: IHasOptions): boolean {\n if (options == null) {\n throw new ReferenceError(\n `1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n return this.diContainerMaps.serviceRegistry.has(options.identifier);\n }\n /**\n * Registers a service\n */\n private register(\n kind: RegistrationKind,\n newExpression: ImplementationInstance,\n options: IRegisterOptionsWithoutImplementation\n ): void;\n private register(\n kind: RegistrationKind,\n newExpression: undefined,\n options: IRegisterOptionsWithImplementation\n ): void;\n private register(\n kind: RegistrationKind,\n newExpression: ImplementationInstance | undefined,\n options: RegisterOptions\n ): void {\n // Take all of the constructor arguments for the implementation\n const implementationArguments =\n \"implementation\" in options &&\n options.implementation != null &&\n options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] != null\n ? options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL]!\n : [];\n this.diContainerMaps.constructorArguments.set(options.identifier, implementationArguments);\n\n this.diContainerMaps.serviceRegistry.set(\n options.identifier,\n \"implementation\" in options && options.implementation != null\n ? { ...options, kind }\n : { ...options, kind, newExpression: newExpression! }\n );\n }\n\n /**\n * Returns true if an instance exists that matches the given identifier.\n */\n private hasInstance(identifier: string): boolean {\n return this.getInstance(identifier) != null;\n }\n\n /**\n * Gets the cached instance, if any, associated with the given identifier.\n */\n private getInstance(identifier: string): T | null {\n const instance = this.diContainerMaps.instances.get(identifier);\n return instance == null ? null : instance;\n }\n\n /**\n * Gets an IRegistrationRecord associated with the given identifier.\n */\n private getRegistrationRecord({\n identifier,\n parentChain,\n }: IConstructInstanceOptions): RegistrationRecord {\n const record = this.diContainerMaps.serviceRegistry.get(identifier);\n if (record == null) {\n throw new ReferenceError(\n `${\n this.constructor.name\n } could not find a service for identifier: \"${identifier}\". ${\n parentChain == null || parentChain.length < 1\n ? \"\"\n : `It is required by the service: '${parentChain\n .map((parent) => parent.identifier)\n .join(\" -> \")}'.`\n } Remember to register it as a service!`\n );\n }\n return >record;\n }\n\n /**\n * Caches the given instance so that it can be retrieved in the future.\n */\n private setInstance(identifier: string, instance: T): T {\n this.diContainerMaps.instances.set(identifier, instance);\n return instance;\n }\n\n /**\n * Gets a lazy reference to another service\n */\n private getLazyIdentifier(lazyPointer: () => T): T {\n return (\n new Proxy({}, { get: (_, key: keyof T & string) => lazyPointer()[key] })\n );\n }\n\n /**\n * Constructs a new instance of the given identifier and returns it.\n * It checks the constructor arguments and injects any services it might depend on recursively.\n */\n private constructInstance({\n identifier,\n parentChain = [],\n }: IConstructInstanceOptions): T {\n const registrationRecord = this.getRegistrationRecord({\n identifier,\n parentChain,\n });\n\n // If an instance already exists (and it is a singleton), return that one\n if (\n this.hasInstance(identifier) &&\n registrationRecord.kind === \"SINGLETON\"\n ) {\n return this.getInstance(identifier);\n }\n\n // Otherwise, instantiate a new one\n let instance: T;\n\n const me: IParent = {\n identifier,\n ref: this.getLazyIdentifier(() => instance),\n };\n\n // If a user-provided new-expression has been provided, invoke that to get an instance.\n if (\"newExpression\" in registrationRecord) {\n if (typeof registrationRecord.newExpression !== \"function\") {\n throw new TypeError(\n `Could not instantiate the service with the identifier: '${registrationRecord.identifier}': You provided a custom instantiation argument, but it wasn't of type function. It has to be a function that returns whatever should be used as an instance of the Service!`\n );\n }\n try {\n instance = registrationRecord.newExpression() as T;\n } catch (ex) {\n throw new Error(\n `Could not instantiate the service with the identifier: '${registrationRecord.identifier}': When you registered the service, you provided a custom instantiation function, but it threw an exception when it was run!`\n );\n }\n } else {\n // Find the arguments for the identifier\n const mappedArgs = this.diContainerMaps.constructorArguments.get(identifier);\n if (mappedArgs == null) {\n throw new ReferenceError(\n `${this.constructor.name} could not find constructor arguments for the service: '${identifier}'. Have you registered it as a service?`\n );\n }\n\n // Instantiate all of the argument services (or re-use them if they were registered as singletons)\n const instanceArgs = mappedArgs.map((dep) => {\n if (dep === undefined) return undefined;\n const matchedParent = parentChain.find(\n (parent) => parent.identifier === dep\n );\n if (matchedParent != null) return matchedParent.ref;\n return this.constructInstance({\n identifier: dep,\n parentChain: [...parentChain, me],\n });\n });\n\n try {\n // Try to construct an instance with 'new' and if it fails, call the implementation directly.\n const newable = registrationRecord.implementation as NewableService;\n instance = new newable(...instanceArgs);\n } catch (ex) {\n if (registrationRecord.implementation == null) {\n throw new ReferenceError(\n `${this.constructor.name} could not construct a new service of kind: ${identifier}. Reason: No implementation was given!`\n );\n }\n const constructable = registrationRecord.implementation;\n // Try without 'new' and call the implementation as a function.\n instance = (constructable as unknown as CallableFunction)(\n ...instanceArgs\n );\n }\n }\n\n return registrationRecord.kind === \"SINGLETON\"\n ? this.setInstance(identifier, instance)\n : instance;\n }\n\n /**\n * Maps that may get defined during Moddable pre-load, and then frozen into flash memory. See the getter\n * `diContainerMaps` which handles the transition from flash to runtime for the maps.\n */\n private readonly writableDiContainerMaps: IDIContainerMaps = {\n constructorArguments: new Map(),\n serviceRegistry: new Map(),\n instances: new Map()\n };\n\n /**\n * Getter that provides access to the various maps. Handles cloning the maps from the read-only preload condition\n * to a writable runtime version to support Moddable preloads.\n */\n private get diContainerMaps(): IDIContainerMaps {\n // if a map is frozen, it has been preloaded, so we need to clone the map. This happens because registrations\n // occur during preload, but then also need to work at runtime.\n if (Object.isFrozen(this.writableDiContainerMaps.constructorArguments)) {\n this.writableDiContainerMaps.constructorArguments = new Map(\n this.writableDiContainerMaps.constructorArguments\n );\n this.writableDiContainerMaps.instances = new Map(\n this.writableDiContainerMaps.instances\n );\n this.writableDiContainerMaps.serviceRegistry = new Map(\n this.writableDiContainerMaps.serviceRegistry\n );\n }\n\n return this.writableDiContainerMaps;\n }\n}\n\nconst DI_COMPILER_ERROR_HINT = `Note: You must use DI-Compiler (https://github.com/wessberg/di-compiler) for this library to work correctly. Please consult the readme for instructions on how to install and configure it for your project.`;\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/index.d.ts b/dist/esm/index.d.ts new file mode 100644 index 0000000..da7a12c --- /dev/null +++ b/dist/esm/index.d.ts @@ -0,0 +1,120 @@ +declare const CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = "___CTOR_ARGS___"; +declare const CONSTRUCTOR_ARGUMENTS_SYMBOL: unique symbol; +type ConstructorArgument = string | undefined; +interface IWithConstructorArgumentsSymbol { + [CONSTRUCTOR_ARGUMENTS_SYMBOL]?: ConstructorArgument[]; +} +interface IContainerIdentifierable { + identifier: string; +} +interface IGetOptions extends IContainerIdentifierable { +} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type NewableService = new (...args: any[]) => T; +/* eslint-disable @typescript-eslint/no-explicit-any */ +type CustomConstructableService = (...args: any[]) => T; +type Implementation = NewableService & IWithConstructorArgumentsSymbol; +type ImplementationInstance = CustomConstructableService & IWithConstructorArgumentsSymbol; +interface IRegisterOptionsBase extends IContainerIdentifierable { +} +interface IRegisterOptionsWithImplementation extends IRegisterOptionsBase { + implementation: Implementation | null; +} +interface IRegisterOptionsWithoutImplementation extends IRegisterOptionsBase { +} +type RegisterOptions = IRegisterOptionsWithImplementation | IRegisterOptionsWithoutImplementation; +interface IHasOptions extends IContainerIdentifierable { +} +interface IDIContainer { + registerSingleton(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + registerSingleton(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerSingleton(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + registerTransient(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + registerTransient(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerTransient(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + get(options?: IGetOptions): T; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + has(options?: IHasOptions): boolean; +} +/** + * A Dependency-Injection container that holds services and can produce instances of them as required. + * It mimics reflection by parsing the app at compile-time and supporting the generic-reflection syntax. + * @author Frederik Wessberg + */ +declare class DIContainer implements IDIContainer { + /** + * Registers a service that will be instantiated once in the application lifecycle. All requests + * for the service will retrieve the same instance of it. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + registerSingleton(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerSingleton(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + registerSingleton(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + /** + * Registers a service that will be instantiated every time it is requested throughout the application lifecycle. + * This means that every call to get() will return a unique instance of the service. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + registerTransient(newExpression: ImplementationInstance, options: IRegisterOptionsWithoutImplementation): void; + registerTransient(newExpression: undefined, options: IRegisterOptionsWithImplementation): void; + registerTransient(newExpression?: ImplementationInstance | undefined, options?: RegisterOptions): void; + /** + * Gets an instance of the service matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + get(options?: IGetOptions): T; + /** + * Returns true if a service has been registered matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + has(options?: IHasOptions): boolean; + /** + * Registers a service + */ + private register; + /** + * Returns true if an instance exists that matches the given identifier. + */ + private hasInstance; + /** + * Gets the cached instance, if any, associated with the given identifier. + */ + private getInstance; + /** + * Gets an IRegistrationRecord associated with the given identifier. + */ + private getRegistrationRecord; + /** + * Caches the given instance so that it can be retrieved in the future. + */ + private setInstance; + /** + * Gets a lazy reference to another service + */ + private getLazyIdentifier; + /** + * Constructs a new instance of the given identifier and returns it. + * It checks the constructor arguments and injects any services it might depend on recursively. + */ + private constructInstance; + /** + * Maps that may get defined during Moddable pre-load, and then frozen into flash memory. See the getter + * `diContainerMaps` which handles the transition from flash to runtime for the maps. + */ + private readonly writableDiContainerMaps; + /** + * Getter that provides access to the various maps. Handles cloning the maps from the read-only preload condition + * to a writable runtime version to support Moddable preloads. + */ + private get diContainerMaps(); +} +export { ConstructorArgument, IGetOptions, RegisterOptions, IHasOptions, DIContainer, IDIContainer, CONSTRUCTOR_ARGUMENTS_SYMBOL, CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER }; diff --git a/dist/esm/index.js b/dist/esm/index.js new file mode 100644 index 0000000..b376352 --- /dev/null +++ b/dist/esm/index.js @@ -0,0 +1,206 @@ +const CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = `___CTOR_ARGS___`; +const CONSTRUCTOR_ARGUMENTS_SYMBOL = Symbol.for(CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER); + +/** + * A Dependency-Injection container that holds services and can produce instances of them as required. + * It mimics reflection by parsing the app at compile-time and supporting the generic-reflection syntax. + * @author Frederik Wessberg + */ +class DIContainer { + registerSingleton(newExpression, options) { + if (options == null) { + throw new ReferenceError(`2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + if (newExpression == null) { + return this.register("SINGLETON", newExpression, options); + } + else { + return this.register("SINGLETON", newExpression, options); + } + } + registerTransient(newExpression, options) { + if (options == null) { + throw new ReferenceError(`2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + if (newExpression == null) { + return this.register("TRANSIENT", newExpression, options); + } + else { + return this.register("TRANSIENT", newExpression, options); + } + } + /** + * Gets an instance of the service matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + get(options) { + if (options == null) { + throw new ReferenceError(`1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + return this.constructInstance(options); + } + /** + * Returns true if a service has been registered matching the interface given as a generic type parameter. + * For example, 'container.get()' returns a concrete instance of the implementation associated with the + * generic interface name. + * + * You should not pass any options to the method if using the compiler. It will do that automatically. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + has(options) { + if (options == null) { + throw new ReferenceError(`1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`); + } + return this.diContainerMaps.serviceRegistry.has(options.identifier); + } + register(kind, newExpression, options) { + // Take all of the constructor arguments for the implementation + const implementationArguments = "implementation" in options && + options.implementation != null && + options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] != null + ? options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] + : []; + this.diContainerMaps.constructorArguments.set(options.identifier, implementationArguments); + this.diContainerMaps.serviceRegistry.set(options.identifier, "implementation" in options && options.implementation != null + ? { ...options, kind } + : { ...options, kind, newExpression: newExpression }); + } + /** + * Returns true if an instance exists that matches the given identifier. + */ + hasInstance(identifier) { + return this.getInstance(identifier) != null; + } + /** + * Gets the cached instance, if any, associated with the given identifier. + */ + getInstance(identifier) { + const instance = this.diContainerMaps.instances.get(identifier); + return instance == null ? null : instance; + } + /** + * Gets an IRegistrationRecord associated with the given identifier. + */ + getRegistrationRecord({ identifier, parentChain, }) { + const record = this.diContainerMaps.serviceRegistry.get(identifier); + if (record == null) { + throw new ReferenceError(`${this.constructor.name} could not find a service for identifier: "${identifier}". ${parentChain == null || parentChain.length < 1 + ? "" + : `It is required by the service: '${parentChain + .map((parent) => parent.identifier) + .join(" -> ")}'.`} Remember to register it as a service!`); + } + return record; + } + /** + * Caches the given instance so that it can be retrieved in the future. + */ + setInstance(identifier, instance) { + this.diContainerMaps.instances.set(identifier, instance); + return instance; + } + /** + * Gets a lazy reference to another service + */ + getLazyIdentifier(lazyPointer) { + return (new Proxy({}, { get: (_, key) => lazyPointer()[key] })); + } + /** + * Constructs a new instance of the given identifier and returns it. + * It checks the constructor arguments and injects any services it might depend on recursively. + */ + constructInstance({ identifier, parentChain = [], }) { + const registrationRecord = this.getRegistrationRecord({ + identifier, + parentChain, + }); + // If an instance already exists (and it is a singleton), return that one + if (this.hasInstance(identifier) && + registrationRecord.kind === "SINGLETON") { + return this.getInstance(identifier); + } + // Otherwise, instantiate a new one + let instance; + const me = { + identifier, + ref: this.getLazyIdentifier(() => instance), + }; + // If a user-provided new-expression has been provided, invoke that to get an instance. + if ("newExpression" in registrationRecord) { + if (typeof registrationRecord.newExpression !== "function") { + throw new TypeError(`Could not instantiate the service with the identifier: '${registrationRecord.identifier}': You provided a custom instantiation argument, but it wasn't of type function. It has to be a function that returns whatever should be used as an instance of the Service!`); + } + try { + instance = registrationRecord.newExpression(); + } + catch (ex) { + throw new Error(`Could not instantiate the service with the identifier: '${registrationRecord.identifier}': When you registered the service, you provided a custom instantiation function, but it threw an exception when it was run!`); + } + } + else { + // Find the arguments for the identifier + const mappedArgs = this.diContainerMaps.constructorArguments.get(identifier); + if (mappedArgs == null) { + throw new ReferenceError(`${this.constructor.name} could not find constructor arguments for the service: '${identifier}'. Have you registered it as a service?`); + } + // Instantiate all of the argument services (or re-use them if they were registered as singletons) + const instanceArgs = mappedArgs.map((dep) => { + if (dep === undefined) + return undefined; + const matchedParent = parentChain.find((parent) => parent.identifier === dep); + if (matchedParent != null) + return matchedParent.ref; + return this.constructInstance({ + identifier: dep, + parentChain: [...parentChain, me], + }); + }); + try { + // Try to construct an instance with 'new' and if it fails, call the implementation directly. + const newable = registrationRecord.implementation; + instance = new newable(...instanceArgs); + } + catch (ex) { + if (registrationRecord.implementation == null) { + throw new ReferenceError(`${this.constructor.name} could not construct a new service of kind: ${identifier}. Reason: No implementation was given!`); + } + const constructable = registrationRecord.implementation; + // Try without 'new' and call the implementation as a function. + instance = constructable(...instanceArgs); + } + } + return registrationRecord.kind === "SINGLETON" + ? this.setInstance(identifier, instance) + : instance; + } + /** + * Maps that may get defined during Moddable pre-load, and then frozen into flash memory. See the getter + * `diContainerMaps` which handles the transition from flash to runtime for the maps. + */ + writableDiContainerMaps = { + constructorArguments: new Map(), + serviceRegistry: new Map(), + instances: new Map() + }; + /** + * Getter that provides access to the various maps. Handles cloning the maps from the read-only preload condition + * to a writable runtime version to support Moddable preloads. + */ + get diContainerMaps() { + // if a map is frozen, it has been preloaded, so we need to clone the map. This happens because registrations + // occur during preload, but then also need to work at runtime. + if (Object.isFrozen(this.writableDiContainerMaps.constructorArguments)) { + this.writableDiContainerMaps.constructorArguments = new Map(this.writableDiContainerMaps.constructorArguments); + this.writableDiContainerMaps.instances = new Map(this.writableDiContainerMaps.instances); + this.writableDiContainerMaps.serviceRegistry = new Map(this.writableDiContainerMaps.serviceRegistry); + } + return this.writableDiContainerMaps; + } +} +const DI_COMPILER_ERROR_HINT = `Note: You must use DI-Compiler (https://github.com/wessberg/di-compiler) for this library to work correctly. Please consult the readme for instructions on how to install and configure it for your project.`; + +export { CONSTRUCTOR_ARGUMENTS_SYMBOL, CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER, DIContainer }; +//# sourceMappingURL=index.js.map diff --git a/dist/esm/index.js.map b/dist/esm/index.js.map new file mode 100644 index 0000000..1952e59 --- /dev/null +++ b/dist/esm/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sources":["../../src/constructor-arguments/constructor-arguments-identifier.ts","../../src/di-container/di-container.ts"],"sourcesContent":["export const CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = `___CTOR_ARGS___`;\nexport const CONSTRUCTOR_ARGUMENTS_SYMBOL: unique symbol = Symbol.for(\n CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER\n);\n","import { IConstructInstanceOptions } from \"../construct-instance-options/i-construct-instance-options\";\nimport { IParent } from \"../construct-instance-options/i-parent\";\nimport { CONSTRUCTOR_ARGUMENTS_SYMBOL } from \"../constructor-arguments/constructor-arguments-identifier\";\nimport { IGetOptions } from \"../get-options/i-get-options\";\nimport { IHasOptions } from \"../has-options/i-has-options\";\nimport { NewableService } from \"../newable-service/newable-service\";\nimport {\n IRegisterOptionsWithImplementation,\n IRegisterOptionsWithoutImplementation,\n RegisterOptions,\n} from \"../register-options/i-register-options\";\nimport { RegistrationKind } from \"../registration-kind/registration-kind\";\nimport { IDIContainer, IDIContainerMaps } from \"./i-di-container\";\nimport { RegistrationRecord } from \"../registration-record/i-registration-record\";\nimport { ImplementationInstance } from \"../implementation/implementation\";\n\n/**\n * A Dependency-Injection container that holds services and can produce instances of them as required.\n * It mimics reflection by parsing the app at compile-time and supporting the generic-reflection syntax.\n * @author Frederik Wessberg\n */\nexport class DIContainer implements IDIContainer {\n /**\n * Registers a service that will be instantiated once in the application lifecycle. All requests\n * for the service will retrieve the same instance of it.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n registerSingleton(\n newExpression: ImplementationInstance,\n options: IRegisterOptionsWithoutImplementation\n ): void;\n registerSingleton(\n newExpression: undefined,\n options: IRegisterOptionsWithImplementation\n ): void;\n registerSingleton(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void;\n registerSingleton(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void {\n if (options == null) {\n throw new ReferenceError(\n `2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n if (newExpression == null) {\n return this.register(\n \"SINGLETON\",\n newExpression,\n >options\n );\n } else {\n return this.register(\"SINGLETON\", newExpression, options);\n }\n }\n\n /**\n * Registers a service that will be instantiated every time it is requested throughout the application lifecycle.\n * This means that every call to get() will return a unique instance of the service.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n registerTransient(\n newExpression: ImplementationInstance,\n options: IRegisterOptionsWithoutImplementation\n ): void;\n registerTransient(\n newExpression: undefined,\n options: IRegisterOptionsWithImplementation\n ): void;\n registerTransient(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void;\n registerTransient(\n newExpression?: ImplementationInstance | undefined,\n options?: RegisterOptions\n ): void {\n if (options == null) {\n throw new ReferenceError(\n `2 arguments required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n if (newExpression == null) {\n return this.register(\n \"TRANSIENT\",\n newExpression,\n >options\n );\n } else {\n return this.register(\"TRANSIENT\", newExpression, options);\n }\n }\n\n /**\n * Gets an instance of the service matching the interface given as a generic type parameter.\n * For example, 'container.get()' returns a concrete instance of the implementation associated with the\n * generic interface name.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n get(options?: IGetOptions): T {\n if (options == null) {\n throw new ReferenceError(\n `1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n return this.constructInstance(options);\n }\n\n /**\n * Returns true if a service has been registered matching the interface given as a generic type parameter.\n * For example, 'container.get()' returns a concrete instance of the implementation associated with the\n * generic interface name.\n *\n * You should not pass any options to the method if using the compiler. It will do that automatically.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n has(options?: IHasOptions): boolean {\n if (options == null) {\n throw new ReferenceError(\n `1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`\n );\n }\n return this.diContainerMaps.serviceRegistry.has(options.identifier);\n }\n /**\n * Registers a service\n */\n private register(\n kind: RegistrationKind,\n newExpression: ImplementationInstance,\n options: IRegisterOptionsWithoutImplementation\n ): void;\n private register(\n kind: RegistrationKind,\n newExpression: undefined,\n options: IRegisterOptionsWithImplementation\n ): void;\n private register(\n kind: RegistrationKind,\n newExpression: ImplementationInstance | undefined,\n options: RegisterOptions\n ): void {\n // Take all of the constructor arguments for the implementation\n const implementationArguments =\n \"implementation\" in options &&\n options.implementation != null &&\n options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] != null\n ? options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL]!\n : [];\n this.diContainerMaps.constructorArguments.set(options.identifier, implementationArguments);\n\n this.diContainerMaps.serviceRegistry.set(\n options.identifier,\n \"implementation\" in options && options.implementation != null\n ? { ...options, kind }\n : { ...options, kind, newExpression: newExpression! }\n );\n }\n\n /**\n * Returns true if an instance exists that matches the given identifier.\n */\n private hasInstance(identifier: string): boolean {\n return this.getInstance(identifier) != null;\n }\n\n /**\n * Gets the cached instance, if any, associated with the given identifier.\n */\n private getInstance(identifier: string): T | null {\n const instance = this.diContainerMaps.instances.get(identifier);\n return instance == null ? null : instance;\n }\n\n /**\n * Gets an IRegistrationRecord associated with the given identifier.\n */\n private getRegistrationRecord({\n identifier,\n parentChain,\n }: IConstructInstanceOptions): RegistrationRecord {\n const record = this.diContainerMaps.serviceRegistry.get(identifier);\n if (record == null) {\n throw new ReferenceError(\n `${\n this.constructor.name\n } could not find a service for identifier: \"${identifier}\". ${\n parentChain == null || parentChain.length < 1\n ? \"\"\n : `It is required by the service: '${parentChain\n .map((parent) => parent.identifier)\n .join(\" -> \")}'.`\n } Remember to register it as a service!`\n );\n }\n return >record;\n }\n\n /**\n * Caches the given instance so that it can be retrieved in the future.\n */\n private setInstance(identifier: string, instance: T): T {\n this.diContainerMaps.instances.set(identifier, instance);\n return instance;\n }\n\n /**\n * Gets a lazy reference to another service\n */\n private getLazyIdentifier(lazyPointer: () => T): T {\n return (\n new Proxy({}, { get: (_, key: keyof T & string) => lazyPointer()[key] })\n );\n }\n\n /**\n * Constructs a new instance of the given identifier and returns it.\n * It checks the constructor arguments and injects any services it might depend on recursively.\n */\n private constructInstance({\n identifier,\n parentChain = [],\n }: IConstructInstanceOptions): T {\n const registrationRecord = this.getRegistrationRecord({\n identifier,\n parentChain,\n });\n\n // If an instance already exists (and it is a singleton), return that one\n if (\n this.hasInstance(identifier) &&\n registrationRecord.kind === \"SINGLETON\"\n ) {\n return this.getInstance(identifier);\n }\n\n // Otherwise, instantiate a new one\n let instance: T;\n\n const me: IParent = {\n identifier,\n ref: this.getLazyIdentifier(() => instance),\n };\n\n // If a user-provided new-expression has been provided, invoke that to get an instance.\n if (\"newExpression\" in registrationRecord) {\n if (typeof registrationRecord.newExpression !== \"function\") {\n throw new TypeError(\n `Could not instantiate the service with the identifier: '${registrationRecord.identifier}': You provided a custom instantiation argument, but it wasn't of type function. It has to be a function that returns whatever should be used as an instance of the Service!`\n );\n }\n try {\n instance = registrationRecord.newExpression() as T;\n } catch (ex) {\n throw new Error(\n `Could not instantiate the service with the identifier: '${registrationRecord.identifier}': When you registered the service, you provided a custom instantiation function, but it threw an exception when it was run!`\n );\n }\n } else {\n // Find the arguments for the identifier\n const mappedArgs = this.diContainerMaps.constructorArguments.get(identifier);\n if (mappedArgs == null) {\n throw new ReferenceError(\n `${this.constructor.name} could not find constructor arguments for the service: '${identifier}'. Have you registered it as a service?`\n );\n }\n\n // Instantiate all of the argument services (or re-use them if they were registered as singletons)\n const instanceArgs = mappedArgs.map((dep) => {\n if (dep === undefined) return undefined;\n const matchedParent = parentChain.find(\n (parent) => parent.identifier === dep\n );\n if (matchedParent != null) return matchedParent.ref;\n return this.constructInstance({\n identifier: dep,\n parentChain: [...parentChain, me],\n });\n });\n\n try {\n // Try to construct an instance with 'new' and if it fails, call the implementation directly.\n const newable = registrationRecord.implementation as NewableService;\n instance = new newable(...instanceArgs);\n } catch (ex) {\n if (registrationRecord.implementation == null) {\n throw new ReferenceError(\n `${this.constructor.name} could not construct a new service of kind: ${identifier}. Reason: No implementation was given!`\n );\n }\n const constructable = registrationRecord.implementation;\n // Try without 'new' and call the implementation as a function.\n instance = (constructable as unknown as CallableFunction)(\n ...instanceArgs\n );\n }\n }\n\n return registrationRecord.kind === \"SINGLETON\"\n ? this.setInstance(identifier, instance)\n : instance;\n }\n\n /**\n * Maps that may get defined during Moddable pre-load, and then frozen into flash memory. See the getter\n * `diContainerMaps` which handles the transition from flash to runtime for the maps.\n */\n private readonly writableDiContainerMaps: IDIContainerMaps = {\n constructorArguments: new Map(),\n serviceRegistry: new Map(),\n instances: new Map()\n };\n\n /**\n * Getter that provides access to the various maps. Handles cloning the maps from the read-only preload condition\n * to a writable runtime version to support Moddable preloads.\n */\n private get diContainerMaps(): IDIContainerMaps {\n // if a map is frozen, it has been preloaded, so we need to clone the map. This happens because registrations\n // occur during preload, but then also need to work at runtime.\n if (Object.isFrozen(this.writableDiContainerMaps.constructorArguments)) {\n this.writableDiContainerMaps.constructorArguments = new Map(\n this.writableDiContainerMaps.constructorArguments\n );\n this.writableDiContainerMaps.instances = new Map(\n this.writableDiContainerMaps.instances\n );\n this.writableDiContainerMaps.serviceRegistry = new Map(\n this.writableDiContainerMaps.serviceRegistry\n );\n }\n\n return this.writableDiContainerMaps;\n }\n}\n\nconst DI_COMPILER_ERROR_HINT = `Note: You must use DI-Compiler (https://github.com/wessberg/di-compiler) for this library to work correctly. Please consult the readme for instructions on how to install and configure it for your project.`;\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index c03fdb2..59d79c5 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,8 @@ "prewatch": "pnpm run clean", "watch": "pnpm run prewatch && pnpm run rollup -- --watch", "rollup": "rollup -c rollup.config.js", - "preversion": "pnpm run lint", + "preversion": "pnpm run lint && pnpm run build", "version": "pnpm run preversion && pnpm run generate:all && git add .", - "prepare": "pnpm run build", "release": "np --no-cleanup --no-yarn --no-tests", "update:check": "pnpx npm-check-updates -x typescript-* --dep dev,prod", "update:commit": "pnpx npm-check-updates -u -x typescript-* --dep dev,prod && pnpm update && pnpm install"