Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"modules": {
"@wessberg/di": ["dist/index"]
},
"preload": ["@wessberg/di"]
}
49 changes: 29 additions & 20 deletions src/di-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type {
GetOptions,
HasOptions,
ConstructInstanceOptions,
DIContainerOptions,
IDIContainerMaps,
Parent
} from "./type.js";
import {isClass, isCustomConstructableService} from "./util.js";
Expand All @@ -23,23 +25,25 @@ import {isClass, isCustomConstructableService} from "./util.js";
* @author Frederik Wessberg
*/
export class DIContainer implements IDIContainer {
get [Symbol.toStringTag]() {
return "DIContainer";
}
readonly #containerMaps: IDIContainerMaps;

/**
* A map between interface names and the services that should be dependency injected
*/
readonly #constructorArguments = new Map<string, ConstructorArgument[]>();
/**
* A Map between identifying names for services and their IRegistrationRecords.
* Constructs a new dependency-injection container, optionally using custom container maps (defaults to using Map objects).
*
* @param options - Optional object with options, currently including only `customContainerMaps` to override the
* default Map-based implementation of container maps.
*/
readonly #serviceRegistry = new Map<string, RegistrationRecord<unknown>>();
constructor(options?: DIContainerOptions) {
this.#containerMaps = options?.customContainerMaps ?? {
constructorArguments: new Map<string, ConstructorArgument[]>(),
serviceRegistry: new Map<string, RegistrationRecord<unknown>>(),
instances: new Map<string, unknown>()
};
}

/**
* A map between identifying names for services and concrete instances of their implementation.
*/
readonly #instances = new Map<string, unknown>();
get [Symbol.toStringTag]() {
return "DIContainer";
}

/**
* Registers a service that will be instantiated once in the application lifecycle. All requests
Expand Down Expand Up @@ -112,7 +116,7 @@ export class DIContainer implements IDIContainer {
if (options == null) {
throw new ReferenceError(`1 argument required, but only 0 present. ${DI_COMPILER_ERROR_HINT}`);
}
return this.#serviceRegistry.has(options.identifier);
return this.#containerMaps.serviceRegistry.get(options.identifier) != null;
}

/**
Expand All @@ -124,9 +128,14 @@ export class DIContainer implements IDIContainer {
// Take all of the constructor arguments for the implementation
const implementationArguments =
"implementation" in options && options.implementation?.[CONSTRUCTOR_ARGUMENTS_SYMBOL] != null ? options.implementation[CONSTRUCTOR_ARGUMENTS_SYMBOL] : [];
this.#constructorArguments.set(options.identifier, implementationArguments);
this.#containerMaps.constructorArguments.set(options.identifier, implementationArguments);

// Clear cached instance of re-registered singletons
if (this.#hasInstance(options.identifier)) {
this.#setInstance(options.identifier, undefined);
}

this.#serviceRegistry.set(
this.#containerMaps.serviceRegistry.set(
options.identifier,
"implementation" in options && options.implementation != null ? {...options, kind} : {...options, kind, newExpression: newExpression!}
);
Expand All @@ -143,22 +152,22 @@ export class DIContainer implements IDIContainer {
* Gets the cached instance, if any, associated with the given identifier.
*/
#getInstance<T>(identifier: string): T | null {
const instance = this.#instances.get(identifier);
const instance = this.#containerMaps.instances.get(identifier);
return instance == null ? null : (instance as T);
}

/**
* Gets an IRegistrationRecord associated with the given identifier.
*/
#getRegistrationRecord<T>(identifier: string): RegistrationRecord<T> | undefined {
return this.#serviceRegistry.get(identifier) as RegistrationRecord<T> | undefined;
return this.#containerMaps.serviceRegistry.get(identifier) as RegistrationRecord<T> | undefined;
}

/**
* Caches the given instance so that it can be retrieved in the future.
*/
#setInstance<T>(identifier: string, instance: T): T {
this.#instances.set(identifier, instance);
this.#containerMaps.instances.set(identifier, instance);
return instance;
}

Expand Down Expand Up @@ -197,7 +206,7 @@ export class DIContainer implements IDIContainer {

if (isClass<T>(implementation)) {
// Find the arguments for the identifier
const mappedArgs = this.#constructorArguments.get(identifier);
const mappedArgs = this.#containerMaps.constructorArguments.get(identifier);
if (mappedArgs == null) {
throw new InstantiationError(`Could not find constructor arguments. Have you registered it as a service?`, {identifier, parentChain});
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export type {IDIContainer} from "./type.js";
export type {IDIContainer, ConstructorArgument, IContainerMap, RegistrationRecord} from "./type.js";
export {DIContainer} from "./di-container.js";
export {CONSTRUCTOR_ARGUMENTS_SYMBOL, CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER} from "./constant.js";
26 changes: 26 additions & 0 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,29 @@ export interface IDIContainer {
// @ts-expect-error The 'T' type parameter is required for compile-time reflection, even though it is not part of the signature.
has<T>(options?: HasOptions): boolean;
}

export interface IContainerMap<K extends string, V> {
get(key: K): V | undefined;
set(key: K, value: V): void;
}

export interface IDIContainerMaps {
/**
* A map between interface names and the services that should be dependency injected
*/
constructorArguments: IContainerMap<string, ConstructorArgument[]>;

/**
* A Map between identifying names for services and their IRegistrationRecords.
*/
serviceRegistry: IContainerMap<string, RegistrationRecord<unknown>>;

/**
* A map between identifying names for services and concrete instances of their implementation.
*/
instances: IContainerMap<string, unknown>;
}

export interface DIContainerOptions {
customContainerMaps?: IDIContainerMaps;
}