navi-di 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -9
- package/dist/container/container.d.ts +28 -104
- package/dist/container/container.js +266 -179
- package/dist/container/registry.d.ts +0 -40
- package/dist/container/registry.js +3 -40
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +1 -0
- package/dist/decorators/inject-many.d.ts +2 -0
- package/dist/decorators/inject-many.js +11 -0
- package/dist/decorators/service.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types/container.d.ts +1 -0
- package/dist/types/injection.d.ts +1 -0
- package/dist/types/service.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,9 @@ The current implementation focuses on a compact core:
|
|
|
17
17
|
|
|
18
18
|
- `@Service()` registers classes in the default container.
|
|
19
19
|
- `@Inject()` wires decorated class fields from the active container.
|
|
20
|
+
- `@InjectMany()` wires all matching bindings into a decorated class field.
|
|
20
21
|
- `Container.of()` resolves services from the default container or from named containers.
|
|
22
|
+
- `container.ofChild()` creates hierarchical child containers.
|
|
21
23
|
- `singleton`, `container`, and `transient` scopes control instance lifetime.
|
|
22
24
|
- Circular graphs and missing services fail with explicit runtime errors.
|
|
23
25
|
|
|
@@ -27,11 +29,15 @@ The repository is no longer just scaffolding. The source and tests currently cov
|
|
|
27
29
|
|
|
28
30
|
- class registration through `@Service()`;
|
|
29
31
|
- field injection through `@Inject()`;
|
|
32
|
+
- multi-field injection through `@InjectMany()`;
|
|
30
33
|
- named containers via `Container.of(id)`;
|
|
31
|
-
-
|
|
34
|
+
- hierarchical child containers via `container.ofChild(id)` and `Container.ofChild(id, parent)`;
|
|
35
|
+
- ancestor lookup across container hierarchies;
|
|
32
36
|
- per-container caching for `container` scope;
|
|
33
37
|
- shared instances for `singleton` scope;
|
|
34
38
|
- fresh instances for `transient` scope;
|
|
39
|
+
- multi-binding registration through `container.add()` and `@Service({ multiple: true })`;
|
|
40
|
+
- multi-binding resolution through `container.getMany()`;
|
|
35
41
|
- cache reset and registration reset through `container.reset()`;
|
|
36
42
|
- error handling for circular dependencies, missing services, and invalid container operations.
|
|
37
43
|
|
|
@@ -53,10 +59,11 @@ Runtime exports:
|
|
|
53
59
|
- `Container`
|
|
54
60
|
- `Service`
|
|
55
61
|
- `Inject`
|
|
62
|
+
- `InjectMany`
|
|
56
63
|
- `Token`
|
|
57
64
|
|
|
58
65
|
`Container` exposes the runtime API for resolution and low-level registration,
|
|
59
|
-
including `of()`, `get()`, `set()`, `has()`, `remove()`, and `reset()`.
|
|
66
|
+
including `of()`, `ofChild()`, `get()`, `getMany()`, `set()`, `add()`, `has()`, `remove()`, and `reset()`.
|
|
60
67
|
|
|
61
68
|
Type-only exports:
|
|
62
69
|
|
|
@@ -102,8 +109,8 @@ How resolution works:
|
|
|
102
109
|
The default scope. One instance is cached per container.
|
|
103
110
|
|
|
104
111
|
- repeated `get()` calls in the same container reuse the same instance;
|
|
105
|
-
-
|
|
106
|
-
-
|
|
112
|
+
- child containers receive their own isolated instance;
|
|
113
|
+
- child containers lazily clone ancestor registrations on first access.
|
|
107
114
|
|
|
108
115
|
### `singleton`
|
|
109
116
|
|
|
@@ -116,22 +123,28 @@ One shared instance across all containers.
|
|
|
116
123
|
|
|
117
124
|
A new instance is created on every `get()` call.
|
|
118
125
|
|
|
119
|
-
##
|
|
126
|
+
## Hierarchical containers
|
|
120
127
|
|
|
121
|
-
Use
|
|
128
|
+
Use containers when you want isolated request, job, or unit-of-work state.
|
|
122
129
|
|
|
123
130
|
```ts
|
|
124
131
|
const requestA = Container.of('request-a');
|
|
125
132
|
const requestB = Container.of('request-b');
|
|
133
|
+
|
|
134
|
+
const tenant = Container.of('tenant-a');
|
|
135
|
+
const request = tenant.ofChild('tenant-a-request');
|
|
126
136
|
```
|
|
127
137
|
|
|
128
138
|
Current behavior:
|
|
129
139
|
|
|
130
140
|
- `Container.of()` and `Container.of('default')` return the same default container;
|
|
131
141
|
- `Container.of('name')` reuses the same named container for the same id;
|
|
132
|
-
-
|
|
133
|
-
- `
|
|
134
|
-
-
|
|
142
|
+
- `Container.of('name')` creates a child of the default container when the container does not exist yet;
|
|
143
|
+
- `Container.ofChild(id, parent)` and `parent.ofChild(id)` create deeper hierarchies;
|
|
144
|
+
- containers resolve single bindings from the nearest ancestor that owns them;
|
|
145
|
+
- `container` scope becomes container-local after first resolution in a descendant container;
|
|
146
|
+
- `singleton` scope stays shared from the root container;
|
|
147
|
+
- disposing a parent container also disposes its descendants.
|
|
135
148
|
|
|
136
149
|
## Decorators
|
|
137
150
|
|
|
@@ -149,6 +162,7 @@ Options supported today:
|
|
|
149
162
|
|
|
150
163
|
- `id?: ServiceIdentifier`
|
|
151
164
|
- `scope?: 'singleton' | 'container' | 'transient'`
|
|
165
|
+
- `multiple?: boolean`
|
|
152
166
|
|
|
153
167
|
Example with a custom id:
|
|
154
168
|
|
|
@@ -177,6 +191,16 @@ Current characteristics:
|
|
|
177
191
|
- injected fields are defined as writable and configurable own properties on the created instance;
|
|
178
192
|
- injected fields are assigned after construction, so they are not available inside constructors or field initializers.
|
|
179
193
|
|
|
194
|
+
### `@InjectMany(dependency)`
|
|
195
|
+
|
|
196
|
+
Marks a decorated class field for multi-binding injection.
|
|
197
|
+
|
|
198
|
+
Current characteristics:
|
|
199
|
+
|
|
200
|
+
- resolves every matching multi-binding for the identifier;
|
|
201
|
+
- returns values in ancestor-first, local-last order;
|
|
202
|
+
- uses the same active container as the service being created.
|
|
203
|
+
|
|
180
204
|
Token example:
|
|
181
205
|
|
|
182
206
|
```ts
|
|
@@ -215,6 +239,15 @@ Returns the default container or a named container.
|
|
|
215
239
|
Calling `Container.of(id)` with the same identifier reuses the same container
|
|
216
240
|
instance until that instance is disposed.
|
|
217
241
|
|
|
242
|
+
When a named container is first created through `Container.of(id)`, its parent is
|
|
243
|
+
the default container.
|
|
244
|
+
|
|
245
|
+
### `Container.ofChild(id, parent?)`
|
|
246
|
+
|
|
247
|
+
Creates or reuses a child container under the provided parent container.
|
|
248
|
+
|
|
249
|
+
You can also call `parent.ofChild(id)` from an existing container instance.
|
|
250
|
+
|
|
218
251
|
### `container.get(id)`
|
|
219
252
|
|
|
220
253
|
Resolves a service by class or service identifier.
|
|
@@ -224,6 +257,15 @@ Throws:
|
|
|
224
257
|
- `ServiceNotFoundError` when no registration exists;
|
|
225
258
|
- `CircularDependencyError` when the current resolution path loops back to an in-progress dependency.
|
|
226
259
|
|
|
260
|
+
### `container.getMany(id)`
|
|
261
|
+
|
|
262
|
+
Resolves all multi-bindings registered for a service identifier.
|
|
263
|
+
|
|
264
|
+
- multi-bindings are aggregated from the root of the hierarchy down to the current container;
|
|
265
|
+
- container-scoped registrations are cached per resolving container;
|
|
266
|
+
- singleton registrations are shared from the root container;
|
|
267
|
+
- returns an empty array when no multi-binding exists.
|
|
268
|
+
|
|
227
269
|
### `container.tryGet(id)`
|
|
228
270
|
|
|
229
271
|
Resolves a service by class or service identifier and returns `undefined` when
|
|
@@ -280,6 +322,13 @@ Supported forms today:
|
|
|
280
322
|
This is the low-level API for manual value, class, and factory registration.
|
|
281
323
|
For decorator-driven classes, prefer `@Service()`.
|
|
282
324
|
|
|
325
|
+
### `container.add(id, valueOrProvider)`
|
|
326
|
+
|
|
327
|
+
Appends a value or provider to a multi-binding group.
|
|
328
|
+
|
|
329
|
+
Supported forms are the same as `container.set(...)`, but registrations are
|
|
330
|
+
collected instead of replaced.
|
|
331
|
+
|
|
283
332
|
Value example:
|
|
284
333
|
|
|
285
334
|
```ts
|
|
@@ -1,132 +1,56 @@
|
|
|
1
1
|
import type { ContainerIdentifier, Metadata, ServiceIdentifier, ServiceProvider } from '../types';
|
|
2
|
-
/**
|
|
3
|
-
* Resolves services and stores container-local bindings.
|
|
4
|
-
*
|
|
5
|
-
* Containers let you resolve registered services, override values for the
|
|
6
|
-
* current context, and isolate container-scoped services by container.
|
|
7
|
-
*/
|
|
8
2
|
export declare class Container {
|
|
9
|
-
/**
|
|
10
|
-
* The identifier of this container.
|
|
11
|
-
*
|
|
12
|
-
* The default container uses `'default'`, and named containers use the
|
|
13
|
-
* identifier they were created with.
|
|
14
|
-
*/
|
|
15
3
|
readonly id: ContainerIdentifier;
|
|
4
|
+
private parentContainer?;
|
|
5
|
+
private readonly children;
|
|
16
6
|
private metadataMap;
|
|
17
7
|
private bindingMap;
|
|
8
|
+
private multiMetadataMap;
|
|
9
|
+
private multiBindingMap;
|
|
10
|
+
private inheritedMultiMetadataMap;
|
|
18
11
|
private resolving;
|
|
19
12
|
private resolvingPath;
|
|
20
13
|
private disposed;
|
|
21
|
-
constructor(id: ContainerIdentifier);
|
|
14
|
+
constructor(id: ContainerIdentifier, parent?: Container);
|
|
15
|
+
get parent(): Container | undefined;
|
|
22
16
|
private ensureNotDisposed;
|
|
23
17
|
private isDisposable;
|
|
24
|
-
private canResolveLocally;
|
|
25
|
-
private canResolve;
|
|
26
18
|
private isValueProvider;
|
|
27
19
|
private isClassProvider;
|
|
28
20
|
private isFactoryProvider;
|
|
21
|
+
private isRoot;
|
|
22
|
+
private getRoot;
|
|
23
|
+
private getLineage;
|
|
24
|
+
private cloneMetadata;
|
|
25
|
+
private findBindingOwner;
|
|
26
|
+
private findMetadataOwner;
|
|
27
|
+
private canResolve;
|
|
29
28
|
private getClassProviderMetadata;
|
|
30
29
|
private getFactoryProviderMetadata;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
private pushMultiBinding;
|
|
31
|
+
private pushMultiMetadata;
|
|
32
|
+
private getOrCreateInheritedMultiMetadata;
|
|
33
|
+
private defineInjection;
|
|
34
|
+
private resolveMetadata;
|
|
35
|
+
private resetMetadataValues;
|
|
36
|
+
private collectOwnedValues;
|
|
37
|
+
unlinkParent(): void;
|
|
38
|
+
hasParent(parent: Container | undefined): boolean;
|
|
40
39
|
static of(id?: ContainerIdentifier): Container;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
* This is a low-level API for manual registration when you are not using
|
|
45
|
-
* `@Service()`. Registering a `singleton` on a named container stores it in
|
|
46
|
-
* the default container so it can be shared across containers.
|
|
47
|
-
*
|
|
48
|
-
* @param metadata The service metadata to register.
|
|
49
|
-
* @returns The current container.
|
|
50
|
-
*/
|
|
40
|
+
static ofChild(id: ContainerIdentifier, parent?: Container | ContainerIdentifier): Container;
|
|
41
|
+
ofChild(id: ContainerIdentifier): Container;
|
|
51
42
|
register<T>(metadata: Metadata<T>): this;
|
|
52
|
-
/**
|
|
53
|
-
* Returns whether this container has a local registration for an identifier.
|
|
54
|
-
*
|
|
55
|
-
* This only checks registrations stored on the current container and does
|
|
56
|
-
* not indicate whether the identifier can be resolved through fallback.
|
|
57
|
-
*
|
|
58
|
-
* @param id The service identifier to check.
|
|
59
|
-
* @returns `true` when the current container has a local registration.
|
|
60
|
-
*/
|
|
61
43
|
has(id: ServiceIdentifier): boolean;
|
|
62
|
-
/**
|
|
63
|
-
* Registers a value or provider for a service identifier.
|
|
64
|
-
*
|
|
65
|
-
* Use a plain value to bind an explicit instance, or a provider object to
|
|
66
|
-
* resolve by class or factory.
|
|
67
|
-
*
|
|
68
|
-
* @param id The service identifier to register.
|
|
69
|
-
* @param valueOrProvider The bound value or provider definition.
|
|
70
|
-
* @returns The current container.
|
|
71
|
-
*/
|
|
72
44
|
set<T>(id: ServiceIdentifier<T>, value: T): this;
|
|
73
45
|
set<T>(id: ServiceIdentifier<T>, provider: ServiceProvider<T>): this;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
* @param id The service identifier to remove.
|
|
78
|
-
* @returns The current container.
|
|
79
|
-
*/
|
|
46
|
+
add<T>(id: ServiceIdentifier<T>, value: T): this;
|
|
47
|
+
add<T>(id: ServiceIdentifier<T>, provider: ServiceProvider<T>): this;
|
|
80
48
|
remove(id: ServiceIdentifier): this;
|
|
81
|
-
/**
|
|
82
|
-
* Resets services stored in this container.
|
|
83
|
-
*
|
|
84
|
-
* Use `'value'` to clear cached instances while keeping registrations, or
|
|
85
|
-
* `'service'` to remove local registrations and bindings entirely.
|
|
86
|
-
*
|
|
87
|
-
* @param strategy The reset strategy to apply.
|
|
88
|
-
* @returns The current container.
|
|
89
|
-
*/
|
|
90
49
|
reset(strategy?: 'value' | 'service'): this;
|
|
91
|
-
/**
|
|
92
|
-
* Resolves a service from this container.
|
|
93
|
-
*
|
|
94
|
-
* If the current container does not have a local registration, named
|
|
95
|
-
* containers can continue resolution through the default container according
|
|
96
|
-
* to the service scope. Services are instantiated with `new Class()` and do
|
|
97
|
-
* not support constructor arguments.
|
|
98
|
-
*
|
|
99
|
-
* @param id The service identifier to resolve.
|
|
100
|
-
* @returns The resolved service instance or bound value.
|
|
101
|
-
* @throws {ServiceNotFoundError} If no service is registered for the identifier.
|
|
102
|
-
* @throws {CircularDependencyError} If the dependency graph contains a cycle.
|
|
103
|
-
*/
|
|
104
50
|
get<T>(id: ServiceIdentifier<T>): T;
|
|
105
|
-
|
|
106
|
-
* Resolves a service if it exists, otherwise returns `undefined`.
|
|
107
|
-
*
|
|
108
|
-
* Unlike `get()`, this only suppresses `ServiceNotFoundError`. Other errors,
|
|
109
|
-
* such as circular dependencies or disposed-container access, still surface.
|
|
110
|
-
*
|
|
111
|
-
* @param id The service identifier to resolve.
|
|
112
|
-
* @returns The resolved value, or `undefined` when the service is missing.
|
|
113
|
-
*/
|
|
51
|
+
getMany<T>(id: ServiceIdentifier<T>): T[];
|
|
114
52
|
tryGet<T>(id: ServiceIdentifier<T>): T | undefined;
|
|
115
|
-
/**
|
|
116
|
-
* Disposes this container instance and clears all local registrations.
|
|
117
|
-
*
|
|
118
|
-
* The container becomes unusable after disposal. Cached service instances and
|
|
119
|
-
* bound values that expose an async or sync `dispose()` method are awaited in
|
|
120
|
-
* the order they were discovered.
|
|
121
|
-
*/
|
|
122
53
|
dispose(): Promise<void>;
|
|
123
|
-
private isDefault;
|
|
124
|
-
/**
|
|
125
|
-
* Resets a container by its identifier.
|
|
126
|
-
*
|
|
127
|
-
* @param containerId The container to reset.
|
|
128
|
-
* @param options Reset options.
|
|
129
|
-
*/
|
|
130
54
|
static reset(containerId: ContainerIdentifier, options?: {
|
|
131
55
|
strategy?: 'value' | 'service';
|
|
132
56
|
}): void;
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { CircularDependencyError, ContainerDisposedError, ServiceNotFoundError } from '../errors';
|
|
2
2
|
import { EMPTY_VALUE } from '../types';
|
|
3
3
|
import { ContainerRegistry } from './registry';
|
|
4
|
-
/**
|
|
5
|
-
* Resolves services and stores container-local bindings.
|
|
6
|
-
*
|
|
7
|
-
* Containers let you resolve registered services, override values for the
|
|
8
|
-
* current context, and isolate container-scoped services by container.
|
|
9
|
-
*/
|
|
10
4
|
export class Container {
|
|
11
|
-
/**
|
|
12
|
-
* The identifier of this container.
|
|
13
|
-
*
|
|
14
|
-
* The default container uses `'default'`, and named containers use the
|
|
15
|
-
* identifier they were created with.
|
|
16
|
-
*/
|
|
17
5
|
id;
|
|
6
|
+
parentContainer;
|
|
7
|
+
children = new Set();
|
|
18
8
|
metadataMap = new Map();
|
|
19
9
|
bindingMap = new Map();
|
|
10
|
+
multiMetadataMap = new Map();
|
|
11
|
+
multiBindingMap = new Map();
|
|
12
|
+
inheritedMultiMetadataMap = new Map();
|
|
20
13
|
resolving = new Set();
|
|
21
14
|
resolvingPath = [];
|
|
22
15
|
disposed = false;
|
|
23
|
-
constructor(id) {
|
|
16
|
+
constructor(id, parent) {
|
|
24
17
|
this.id = id;
|
|
18
|
+
this.parentContainer = parent;
|
|
19
|
+
parent?.children.add(this);
|
|
20
|
+
}
|
|
21
|
+
get parent() {
|
|
22
|
+
return this.parentContainer;
|
|
25
23
|
}
|
|
26
24
|
ensureNotDisposed() {
|
|
27
25
|
if (this.disposed) {
|
|
@@ -31,18 +29,6 @@ export class Container {
|
|
|
31
29
|
isDisposable(value) {
|
|
32
30
|
return typeof value === 'object' && value !== null && 'dispose' in value && typeof value.dispose === 'function';
|
|
33
31
|
}
|
|
34
|
-
canResolveLocally(id) {
|
|
35
|
-
return this.bindingMap.has(id) || this.metadataMap.has(id);
|
|
36
|
-
}
|
|
37
|
-
canResolve(id) {
|
|
38
|
-
if (this.canResolveLocally(id)) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
if (this.isDefault()) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
return ContainerRegistry.defaultContainer.canResolveLocally(id);
|
|
45
|
-
}
|
|
46
32
|
isValueProvider(provider) {
|
|
47
33
|
return typeof provider === 'object' && provider !== null && 'useValue' in provider;
|
|
48
34
|
}
|
|
@@ -52,8 +38,39 @@ export class Container {
|
|
|
52
38
|
isFactoryProvider(provider) {
|
|
53
39
|
return typeof provider === 'object' && provider !== null && 'useFactory' in provider;
|
|
54
40
|
}
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
isRoot() {
|
|
42
|
+
return this.parentContainer === undefined;
|
|
43
|
+
}
|
|
44
|
+
getRoot() {
|
|
45
|
+
return this.parentContainer?.getRoot() ?? this;
|
|
46
|
+
}
|
|
47
|
+
getLineage() {
|
|
48
|
+
return this.parentContainer ? [...this.parentContainer.getLineage(), this] : [this];
|
|
49
|
+
}
|
|
50
|
+
cloneMetadata(metadata) {
|
|
51
|
+
return {
|
|
52
|
+
...metadata,
|
|
53
|
+
injections: [...metadata.injections],
|
|
54
|
+
value: EMPTY_VALUE,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
findBindingOwner(id) {
|
|
58
|
+
if (this.bindingMap.has(id)) {
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
return this.parentContainer?.findBindingOwner(id);
|
|
62
|
+
}
|
|
63
|
+
findMetadataOwner(id) {
|
|
64
|
+
if (this.metadataMap.has(id)) {
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
return this.parentContainer?.findMetadataOwner(id);
|
|
68
|
+
}
|
|
69
|
+
canResolve(id) {
|
|
70
|
+
return this.findBindingOwner(id) !== undefined || this.findMetadataOwner(id) !== undefined;
|
|
71
|
+
}
|
|
72
|
+
getClassProviderMetadata(id, provider, multiple = false) {
|
|
73
|
+
const existingMetadata = this.findMetadataOwner(provider.useClass)?.metadataMap.get(provider.useClass);
|
|
57
74
|
const inheritedInjections = existingMetadata?.Class === provider.useClass ? [...existingMetadata.injections] : [];
|
|
58
75
|
const inheritedScope = existingMetadata?.Class === provider.useClass ? existingMetadata.scope : 'container';
|
|
59
76
|
return {
|
|
@@ -63,9 +80,10 @@ export class Container {
|
|
|
63
80
|
injections: provider.injections ?? inheritedInjections,
|
|
64
81
|
scope: provider.scope ?? inheritedScope,
|
|
65
82
|
value: EMPTY_VALUE,
|
|
83
|
+
multiple,
|
|
66
84
|
};
|
|
67
85
|
}
|
|
68
|
-
getFactoryProviderMetadata(id, provider) {
|
|
86
|
+
getFactoryProviderMetadata(id, provider, multiple = false) {
|
|
69
87
|
return {
|
|
70
88
|
id,
|
|
71
89
|
name: typeof id === 'function' ? id.name : String(id),
|
|
@@ -73,17 +91,118 @@ export class Container {
|
|
|
73
91
|
scope: provider.scope ?? 'container',
|
|
74
92
|
value: EMPTY_VALUE,
|
|
75
93
|
factory: provider.useFactory,
|
|
94
|
+
multiple,
|
|
76
95
|
};
|
|
77
96
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
pushMultiBinding(id, value) {
|
|
98
|
+
const bindings = this.multiBindingMap.get(id) ?? [];
|
|
99
|
+
bindings.push(value);
|
|
100
|
+
this.multiBindingMap.set(id, bindings);
|
|
101
|
+
}
|
|
102
|
+
pushMultiMetadata(id, metadata) {
|
|
103
|
+
const registrations = this.multiMetadataMap.get(id) ?? [];
|
|
104
|
+
registrations.push(metadata);
|
|
105
|
+
this.multiMetadataMap.set(id, registrations);
|
|
106
|
+
}
|
|
107
|
+
getOrCreateInheritedMultiMetadata(id, sourceMetadata) {
|
|
108
|
+
let inheritedRegistrations = this.inheritedMultiMetadataMap.get(id);
|
|
109
|
+
if (!inheritedRegistrations) {
|
|
110
|
+
inheritedRegistrations = new Map();
|
|
111
|
+
this.inheritedMultiMetadataMap.set(id, inheritedRegistrations);
|
|
112
|
+
}
|
|
113
|
+
const existingMetadata = inheritedRegistrations.get(sourceMetadata);
|
|
114
|
+
if (existingMetadata) {
|
|
115
|
+
return existingMetadata;
|
|
116
|
+
}
|
|
117
|
+
const metadata = this.cloneMetadata(sourceMetadata);
|
|
118
|
+
inheritedRegistrations.set(sourceMetadata, metadata);
|
|
119
|
+
return metadata;
|
|
120
|
+
}
|
|
121
|
+
defineInjection(instance, injection) {
|
|
122
|
+
const value = injection.multiple ? this.getMany(injection.id) : this.get(injection.id);
|
|
123
|
+
Object.defineProperty(instance, injection.name, {
|
|
124
|
+
value,
|
|
125
|
+
writable: true,
|
|
126
|
+
configurable: true,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
resolveMetadata(metadata, resolutionContainer) {
|
|
130
|
+
if (metadata.scope !== 'transient' && metadata.value !== EMPTY_VALUE) {
|
|
131
|
+
return metadata.value;
|
|
132
|
+
}
|
|
133
|
+
if (resolutionContainer.resolving.has(metadata)) {
|
|
134
|
+
throw new CircularDependencyError([...resolutionContainer.resolvingPath, metadata.id]);
|
|
135
|
+
}
|
|
136
|
+
resolutionContainer.resolving.add(metadata);
|
|
137
|
+
resolutionContainer.resolvingPath.push(metadata.id);
|
|
138
|
+
try {
|
|
139
|
+
const instance = metadata.factory ? metadata.factory(resolutionContainer) : new metadata.Class();
|
|
140
|
+
for (const injection of metadata.injections) {
|
|
141
|
+
this.defineInjection(instance, injection);
|
|
142
|
+
}
|
|
143
|
+
if (metadata.scope !== 'transient') {
|
|
144
|
+
metadata.value = instance;
|
|
145
|
+
}
|
|
146
|
+
return instance;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
resolutionContainer.resolving.delete(metadata);
|
|
150
|
+
resolutionContainer.resolvingPath.pop();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
resetMetadataValues() {
|
|
154
|
+
this.metadataMap.forEach((metadata) => {
|
|
155
|
+
metadata.value = EMPTY_VALUE;
|
|
156
|
+
});
|
|
157
|
+
this.multiMetadataMap.forEach((registrations) => {
|
|
158
|
+
registrations.forEach((metadata) => {
|
|
159
|
+
metadata.value = EMPTY_VALUE;
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
this.inheritedMultiMetadataMap.forEach((registrations) => {
|
|
163
|
+
registrations.forEach((metadata) => {
|
|
164
|
+
metadata.value = EMPTY_VALUE;
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
collectOwnedValues() {
|
|
169
|
+
const ownedValues = new Set();
|
|
170
|
+
this.bindingMap.forEach((value) => {
|
|
171
|
+
ownedValues.add(value);
|
|
172
|
+
});
|
|
173
|
+
this.multiBindingMap.forEach((values) => {
|
|
174
|
+
values.forEach((value) => {
|
|
175
|
+
ownedValues.add(value);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
this.metadataMap.forEach((metadata) => {
|
|
179
|
+
if (metadata.value !== EMPTY_VALUE) {
|
|
180
|
+
ownedValues.add(metadata.value);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
this.multiMetadataMap.forEach((registrations) => {
|
|
184
|
+
registrations.forEach((metadata) => {
|
|
185
|
+
if (metadata.value !== EMPTY_VALUE) {
|
|
186
|
+
ownedValues.add(metadata.value);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
this.inheritedMultiMetadataMap.forEach((registrations) => {
|
|
191
|
+
registrations.forEach((metadata) => {
|
|
192
|
+
if (metadata.value !== EMPTY_VALUE) {
|
|
193
|
+
ownedValues.add(metadata.value);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
return ownedValues;
|
|
198
|
+
}
|
|
199
|
+
unlinkParent() {
|
|
200
|
+
this.parentContainer?.children.delete(this);
|
|
201
|
+
this.parentContainer = undefined;
|
|
202
|
+
}
|
|
203
|
+
hasParent(parent) {
|
|
204
|
+
return this.parentContainer === parent;
|
|
205
|
+
}
|
|
87
206
|
static of(id = 'default') {
|
|
88
207
|
if (id === 'default') {
|
|
89
208
|
return ContainerRegistry.defaultContainer;
|
|
@@ -91,30 +210,41 @@ export class Container {
|
|
|
91
210
|
if (ContainerRegistry.hasContainer(id)) {
|
|
92
211
|
return ContainerRegistry.getContainer(id);
|
|
93
212
|
}
|
|
94
|
-
const container = new Container(id);
|
|
213
|
+
const container = new Container(id, ContainerRegistry.defaultContainer);
|
|
95
214
|
ContainerRegistry.registerContainer(container);
|
|
96
215
|
return container;
|
|
97
216
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
217
|
+
static ofChild(id, parent = 'default') {
|
|
218
|
+
const parentContainer = parent instanceof Container ? parent : Container.of(parent);
|
|
219
|
+
if (id === 'default') {
|
|
220
|
+
return ContainerRegistry.defaultContainer;
|
|
221
|
+
}
|
|
222
|
+
const existingContainer = ContainerRegistry.getContainer(id);
|
|
223
|
+
if (existingContainer) {
|
|
224
|
+
if (!existingContainer.hasParent(parentContainer)) {
|
|
225
|
+
throw new Error(`Container already exists with a different parent: ${String(id)}`);
|
|
226
|
+
}
|
|
227
|
+
return existingContainer;
|
|
228
|
+
}
|
|
229
|
+
const container = new Container(id, parentContainer);
|
|
230
|
+
ContainerRegistry.registerContainer(container);
|
|
231
|
+
return container;
|
|
232
|
+
}
|
|
233
|
+
ofChild(id) {
|
|
234
|
+
return Container.ofChild(id, this);
|
|
235
|
+
}
|
|
108
236
|
register(metadata) {
|
|
109
237
|
this.ensureNotDisposed();
|
|
110
|
-
if (metadata.scope === 'singleton' && !this.
|
|
111
|
-
|
|
238
|
+
if (metadata.scope === 'singleton' && !this.isRoot()) {
|
|
239
|
+
this.getRoot().register(metadata);
|
|
112
240
|
this.metadataMap.delete(metadata.id);
|
|
113
241
|
return this;
|
|
114
242
|
}
|
|
115
|
-
const newMetadata =
|
|
116
|
-
|
|
117
|
-
|
|
243
|
+
const newMetadata = this.cloneMetadata(metadata);
|
|
244
|
+
if (newMetadata.multiple) {
|
|
245
|
+
this.pushMultiMetadata(newMetadata.id, newMetadata);
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
118
248
|
const existingMetadata = this.metadataMap.get(newMetadata.id);
|
|
119
249
|
if (existingMetadata) {
|
|
120
250
|
Object.assign(existingMetadata, newMetadata);
|
|
@@ -124,18 +254,13 @@ export class Container {
|
|
|
124
254
|
}
|
|
125
255
|
return this;
|
|
126
256
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Returns whether this container has a local registration for an identifier.
|
|
129
|
-
*
|
|
130
|
-
* This only checks registrations stored on the current container and does
|
|
131
|
-
* not indicate whether the identifier can be resolved through fallback.
|
|
132
|
-
*
|
|
133
|
-
* @param id The service identifier to check.
|
|
134
|
-
* @returns `true` when the current container has a local registration.
|
|
135
|
-
*/
|
|
136
257
|
has(id) {
|
|
137
258
|
this.ensureNotDisposed();
|
|
138
|
-
return this.bindingMap.has(id) ||
|
|
259
|
+
return (this.bindingMap.has(id) ||
|
|
260
|
+
this.metadataMap.has(id) ||
|
|
261
|
+
(this.multiBindingMap.get(id)?.length ?? 0) > 0 ||
|
|
262
|
+
(this.multiMetadataMap.get(id)?.length ?? 0) > 0 ||
|
|
263
|
+
(this.inheritedMultiMetadataMap.get(id)?.size ?? 0) > 0);
|
|
139
264
|
}
|
|
140
265
|
set(id, valueOrProvider) {
|
|
141
266
|
this.ensureNotDisposed();
|
|
@@ -156,122 +281,100 @@ export class Container {
|
|
|
156
281
|
this.metadataMap.delete(id);
|
|
157
282
|
return this;
|
|
158
283
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
284
|
+
add(id, valueOrProvider) {
|
|
285
|
+
this.ensureNotDisposed();
|
|
286
|
+
if (this.isValueProvider(valueOrProvider)) {
|
|
287
|
+
this.pushMultiBinding(id, valueOrProvider.useValue);
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
if (this.isClassProvider(valueOrProvider)) {
|
|
291
|
+
return this.register(this.getClassProviderMetadata(id, valueOrProvider, true));
|
|
292
|
+
}
|
|
293
|
+
if (this.isFactoryProvider(valueOrProvider)) {
|
|
294
|
+
return this.register(this.getFactoryProviderMetadata(id, valueOrProvider, true));
|
|
295
|
+
}
|
|
296
|
+
this.pushMultiBinding(id, valueOrProvider);
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
165
299
|
remove(id) {
|
|
166
300
|
this.ensureNotDisposed();
|
|
167
301
|
this.bindingMap.delete(id);
|
|
168
302
|
this.metadataMap.delete(id);
|
|
303
|
+
this.multiBindingMap.delete(id);
|
|
304
|
+
this.multiMetadataMap.delete(id);
|
|
305
|
+
this.inheritedMultiMetadataMap.delete(id);
|
|
169
306
|
return this;
|
|
170
307
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Resets services stored in this container.
|
|
173
|
-
*
|
|
174
|
-
* Use `'value'` to clear cached instances while keeping registrations, or
|
|
175
|
-
* `'service'` to remove local registrations and bindings entirely.
|
|
176
|
-
*
|
|
177
|
-
* @param strategy The reset strategy to apply.
|
|
178
|
-
* @returns The current container.
|
|
179
|
-
*/
|
|
180
308
|
reset(strategy = 'value') {
|
|
181
309
|
this.ensureNotDisposed();
|
|
182
310
|
if (strategy === 'value') {
|
|
183
|
-
this.
|
|
184
|
-
metadata.value = EMPTY_VALUE;
|
|
185
|
-
});
|
|
186
|
-
return this;
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
this.bindingMap.clear();
|
|
190
|
-
this.metadataMap.clear();
|
|
311
|
+
this.resetMetadataValues();
|
|
191
312
|
return this;
|
|
192
313
|
}
|
|
314
|
+
this.bindingMap.clear();
|
|
315
|
+
this.metadataMap.clear();
|
|
316
|
+
this.multiBindingMap.clear();
|
|
317
|
+
this.multiMetadataMap.clear();
|
|
318
|
+
this.inheritedMultiMetadataMap.clear();
|
|
319
|
+
return this;
|
|
193
320
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Resolves a service from this container.
|
|
196
|
-
*
|
|
197
|
-
* If the current container does not have a local registration, named
|
|
198
|
-
* containers can continue resolution through the default container according
|
|
199
|
-
* to the service scope. Services are instantiated with `new Class()` and do
|
|
200
|
-
* not support constructor arguments.
|
|
201
|
-
*
|
|
202
|
-
* @param id The service identifier to resolve.
|
|
203
|
-
* @returns The resolved service instance or bound value.
|
|
204
|
-
* @throws {ServiceNotFoundError} If no service is registered for the identifier.
|
|
205
|
-
* @throws {CircularDependencyError} If the dependency graph contains a cycle.
|
|
206
|
-
*/
|
|
207
321
|
get(id) {
|
|
208
322
|
this.ensureNotDisposed();
|
|
209
323
|
if (this.bindingMap.has(id)) {
|
|
210
324
|
return this.bindingMap.get(id);
|
|
211
325
|
}
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
throw new ServiceNotFoundError(id);
|
|
220
|
-
}
|
|
221
|
-
if (defaultMetadata.scope === 'singleton') {
|
|
222
|
-
return ContainerRegistry.defaultContainer.get(id);
|
|
223
|
-
}
|
|
224
|
-
if (defaultMetadata.scope === 'container') {
|
|
225
|
-
metadata = {
|
|
226
|
-
...defaultMetadata,
|
|
227
|
-
injections: [...defaultMetadata.injections],
|
|
228
|
-
value: EMPTY_VALUE,
|
|
229
|
-
};
|
|
230
|
-
this.metadataMap.set(id, metadata);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
metadata = defaultMetadata;
|
|
234
|
-
}
|
|
326
|
+
const localMetadata = this.metadataMap.get(id);
|
|
327
|
+
if (localMetadata) {
|
|
328
|
+
return this.resolveMetadata(localMetadata, this);
|
|
329
|
+
}
|
|
330
|
+
const bindingOwner = this.parentContainer?.findBindingOwner(id);
|
|
331
|
+
if (bindingOwner) {
|
|
332
|
+
return bindingOwner.bindingMap.get(id);
|
|
235
333
|
}
|
|
236
|
-
|
|
334
|
+
const metadataOwner = this.parentContainer?.findMetadataOwner(id);
|
|
335
|
+
if (!metadataOwner) {
|
|
237
336
|
throw new ServiceNotFoundError(id);
|
|
238
337
|
}
|
|
239
|
-
|
|
240
|
-
|
|
338
|
+
const metadata = metadataOwner.metadataMap.get(id);
|
|
339
|
+
if (metadata.scope === 'singleton') {
|
|
340
|
+
return metadataOwner.resolveMetadata(metadata, metadataOwner);
|
|
241
341
|
}
|
|
242
|
-
if (
|
|
243
|
-
|
|
342
|
+
if (metadata.scope === 'container') {
|
|
343
|
+
const localizedMetadata = this.cloneMetadata(metadata);
|
|
344
|
+
this.metadataMap.set(id, localizedMetadata);
|
|
345
|
+
return this.resolveMetadata(localizedMetadata, this);
|
|
244
346
|
}
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
347
|
+
return this.resolveMetadata(metadata, this);
|
|
348
|
+
}
|
|
349
|
+
getMany(id) {
|
|
350
|
+
this.ensureNotDisposed();
|
|
351
|
+
const resolved = [];
|
|
352
|
+
for (const container of this.getLineage()) {
|
|
353
|
+
const bindings = container.multiBindingMap.get(id) ?? [];
|
|
354
|
+
resolved.push(...bindings);
|
|
355
|
+
const registrations = container.multiMetadataMap.get(id);
|
|
356
|
+
if (!registrations) {
|
|
357
|
+
continue;
|
|
255
358
|
}
|
|
256
|
-
|
|
257
|
-
|
|
359
|
+
for (const metadata of registrations) {
|
|
360
|
+
if (container === this) {
|
|
361
|
+
resolved.push(this.resolveMetadata(metadata, this));
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (metadata.scope === 'singleton') {
|
|
365
|
+
resolved.push(container.resolveMetadata(metadata, container));
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (metadata.scope === 'container') {
|
|
369
|
+
const localizedMetadata = this.getOrCreateInheritedMultiMetadata(id, metadata);
|
|
370
|
+
resolved.push(this.resolveMetadata(localizedMetadata, this));
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
resolved.push(this.resolveMetadata(metadata, this));
|
|
258
374
|
}
|
|
259
|
-
return instance;
|
|
260
375
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.resolvingPath.pop();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Resolves a service if it exists, otherwise returns `undefined`.
|
|
268
|
-
*
|
|
269
|
-
* Unlike `get()`, this only suppresses `ServiceNotFoundError`. Other errors,
|
|
270
|
-
* such as circular dependencies or disposed-container access, still surface.
|
|
271
|
-
*
|
|
272
|
-
* @param id The service identifier to resolve.
|
|
273
|
-
* @returns The resolved value, or `undefined` when the service is missing.
|
|
274
|
-
*/
|
|
376
|
+
return resolved;
|
|
377
|
+
}
|
|
275
378
|
tryGet(id) {
|
|
276
379
|
this.ensureNotDisposed();
|
|
277
380
|
if (!this.canResolve(id)) {
|
|
@@ -279,30 +382,23 @@ export class Container {
|
|
|
279
382
|
}
|
|
280
383
|
return this.get(id);
|
|
281
384
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Disposes this container instance and clears all local registrations.
|
|
284
|
-
*
|
|
285
|
-
* The container becomes unusable after disposal. Cached service instances and
|
|
286
|
-
* bound values that expose an async or sync `dispose()` method are awaited in
|
|
287
|
-
* the order they were discovered.
|
|
288
|
-
*/
|
|
289
385
|
async dispose() {
|
|
290
386
|
if (this.disposed) {
|
|
291
387
|
return;
|
|
292
388
|
}
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
this.
|
|
298
|
-
if (metadata.value !== EMPTY_VALUE) {
|
|
299
|
-
ownedValues.add(metadata.value);
|
|
300
|
-
}
|
|
301
|
-
});
|
|
389
|
+
const childContainers = [...this.children];
|
|
390
|
+
for (const child of childContainers) {
|
|
391
|
+
await child.dispose();
|
|
392
|
+
}
|
|
393
|
+
const ownedValues = this.collectOwnedValues();
|
|
302
394
|
this.disposed = true;
|
|
303
395
|
ContainerRegistry.disposeContainer(this);
|
|
304
396
|
this.bindingMap.clear();
|
|
305
397
|
this.metadataMap.clear();
|
|
398
|
+
this.multiBindingMap.clear();
|
|
399
|
+
this.multiMetadataMap.clear();
|
|
400
|
+
this.inheritedMultiMetadataMap.clear();
|
|
401
|
+
this.children.clear();
|
|
306
402
|
this.resolving.clear();
|
|
307
403
|
this.resolvingPath = [];
|
|
308
404
|
for (const value of ownedValues) {
|
|
@@ -311,15 +407,6 @@ export class Container {
|
|
|
311
407
|
}
|
|
312
408
|
}
|
|
313
409
|
}
|
|
314
|
-
isDefault() {
|
|
315
|
-
return this === ContainerRegistry.defaultContainer;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Resets a container by its identifier.
|
|
319
|
-
*
|
|
320
|
-
* @param containerId The container to reset.
|
|
321
|
-
* @param options Reset options.
|
|
322
|
-
*/
|
|
323
410
|
static reset(containerId, options) {
|
|
324
411
|
const container = ContainerRegistry.getContainer(containerId);
|
|
325
412
|
container?.reset(options?.strategy);
|
|
@@ -1,52 +1,12 @@
|
|
|
1
1
|
import type { ContainerIdentifier } from '../types';
|
|
2
2
|
import { Container } from './container';
|
|
3
|
-
/**
|
|
4
|
-
* Stores the default container and all named containers.
|
|
5
|
-
*
|
|
6
|
-
* This registry is the shared source of truth behind `Container.of()` and
|
|
7
|
-
* decorator-based service registration.
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*/
|
|
11
3
|
export declare class ContainerRegistry {
|
|
12
4
|
private static defaultContainerInstance?;
|
|
13
5
|
private static readonly containerMap;
|
|
14
|
-
/**
|
|
15
|
-
* Returns the canonical default container instance.
|
|
16
|
-
*
|
|
17
|
-
* The default container is created lazily the first time it is requested.
|
|
18
|
-
*/
|
|
19
6
|
static get defaultContainer(): Container;
|
|
20
|
-
/**
|
|
21
|
-
* Registers a named container in the shared registry.
|
|
22
|
-
*
|
|
23
|
-
* @param container The container to register.
|
|
24
|
-
* @throws {DefaultContainerIdError} If the container uses the reserved `default` id.
|
|
25
|
-
* @throws {ContainerDuplicatedError} If another container already uses the same id.
|
|
26
|
-
*/
|
|
27
7
|
static registerContainer(container: Container): void;
|
|
28
|
-
/**
|
|
29
|
-
* Returns the default container or a named container by identifier.
|
|
30
|
-
*
|
|
31
|
-
* @param id The container identifier to resolve.
|
|
32
|
-
* @returns The default container for `'default'`, or the matching named container if one exists.
|
|
33
|
-
*/
|
|
34
8
|
static getContainer(id: ContainerIdentifier): Container | undefined;
|
|
35
|
-
/**
|
|
36
|
-
* Returns whether a container exists for the given identifier.
|
|
37
|
-
*
|
|
38
|
-
* The reserved `default` identifier is always treated as present.
|
|
39
|
-
*
|
|
40
|
-
* @param id The container identifier to check.
|
|
41
|
-
* @returns `true` when the container exists in the registry.
|
|
42
|
-
*/
|
|
43
9
|
static hasContainer(id: ContainerIdentifier): boolean;
|
|
44
|
-
/**
|
|
45
|
-
* Removes a named container from the registry.
|
|
46
|
-
*
|
|
47
|
-
* @param id The named container identifier to remove.
|
|
48
|
-
* @throws {DefaultContainerIdError} If the reserved `default` id is used.
|
|
49
|
-
*/
|
|
50
10
|
static removeContainer(id: ContainerIdentifier): void;
|
|
51
11
|
static disposeContainer(container: Container): void;
|
|
52
12
|
}
|
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
import { ContainerDuplicatedError, DefaultContainerIdError } from '../errors';
|
|
2
2
|
import { Container } from './container';
|
|
3
|
-
/**
|
|
4
|
-
* Stores the default container and all named containers.
|
|
5
|
-
*
|
|
6
|
-
* This registry is the shared source of truth behind `Container.of()` and
|
|
7
|
-
* decorator-based service registration.
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*/
|
|
11
3
|
export class ContainerRegistry {
|
|
12
4
|
static defaultContainerInstance;
|
|
13
5
|
static containerMap = new Map();
|
|
14
|
-
/**
|
|
15
|
-
* Returns the canonical default container instance.
|
|
16
|
-
*
|
|
17
|
-
* The default container is created lazily the first time it is requested.
|
|
18
|
-
*/
|
|
19
6
|
static get defaultContainer() {
|
|
20
7
|
this.defaultContainerInstance ??= new Container('default');
|
|
21
8
|
return this.defaultContainerInstance;
|
|
22
9
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Registers a named container in the shared registry.
|
|
25
|
-
*
|
|
26
|
-
* @param container The container to register.
|
|
27
|
-
* @throws {DefaultContainerIdError} If the container uses the reserved `default` id.
|
|
28
|
-
* @throws {ContainerDuplicatedError} If another container already uses the same id.
|
|
29
|
-
*/
|
|
30
10
|
static registerContainer(container) {
|
|
31
11
|
if (container.id === 'default') {
|
|
32
12
|
throw new DefaultContainerIdError();
|
|
@@ -36,45 +16,28 @@ export class ContainerRegistry {
|
|
|
36
16
|
}
|
|
37
17
|
this.containerMap.set(container.id, container);
|
|
38
18
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Returns the default container or a named container by identifier.
|
|
41
|
-
*
|
|
42
|
-
* @param id The container identifier to resolve.
|
|
43
|
-
* @returns The default container for `'default'`, or the matching named container if one exists.
|
|
44
|
-
*/
|
|
45
19
|
static getContainer(id) {
|
|
46
20
|
if (id === 'default') {
|
|
47
21
|
return this.defaultContainer;
|
|
48
22
|
}
|
|
49
23
|
return this.containerMap.get(id);
|
|
50
24
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Returns whether a container exists for the given identifier.
|
|
53
|
-
*
|
|
54
|
-
* The reserved `default` identifier is always treated as present.
|
|
55
|
-
*
|
|
56
|
-
* @param id The container identifier to check.
|
|
57
|
-
* @returns `true` when the container exists in the registry.
|
|
58
|
-
*/
|
|
59
25
|
static hasContainer(id) {
|
|
60
26
|
if (id === 'default') {
|
|
61
27
|
return true;
|
|
62
28
|
}
|
|
63
29
|
return this.containerMap.has(id);
|
|
64
30
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Removes a named container from the registry.
|
|
67
|
-
*
|
|
68
|
-
* @param id The named container identifier to remove.
|
|
69
|
-
* @throws {DefaultContainerIdError} If the reserved `default` id is used.
|
|
70
|
-
*/
|
|
71
31
|
static removeContainer(id) {
|
|
72
32
|
if (id === 'default') {
|
|
73
33
|
throw new DefaultContainerIdError();
|
|
74
34
|
}
|
|
35
|
+
const container = this.containerMap.get(id);
|
|
36
|
+
container?.unlinkParent();
|
|
75
37
|
this.containerMap.delete(id);
|
|
76
38
|
}
|
|
77
39
|
static disposeContainer(container) {
|
|
40
|
+
container.unlinkParent();
|
|
78
41
|
if (container.id === 'default') {
|
|
79
42
|
if (this.defaultContainerInstance === container) {
|
|
80
43
|
this.defaultContainerInstance = undefined;
|
package/dist/decorators/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { INJECTION_KEY } from '../types';
|
|
2
|
+
export function InjectMany(dependency) {
|
|
3
|
+
return function (_, context) {
|
|
4
|
+
const injections = (context.metadata[INJECTION_KEY] ??= []);
|
|
5
|
+
injections.push({
|
|
6
|
+
id: dependency,
|
|
7
|
+
name: context.name,
|
|
8
|
+
multiple: true,
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Container } from './container';
|
|
2
|
-
export { Service, Inject } from './decorators';
|
|
2
|
+
export { Service, Inject, InjectMany } from './decorators';
|
|
3
3
|
export { Token } from './tokens';
|
|
4
4
|
export type { AbstractConstructable, Constructable } from './types/constructable.ts';
|
|
5
5
|
export type { ServiceIdentifier } from './types/service.ts';
|
package/dist/index.js
CHANGED
package/dist/types/service.d.ts
CHANGED