navi-di 1.1.1 → 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 +144 -12
- package/dist/container/container.d.ts +40 -87
- package/dist/container/container.js +348 -147
- package/dist/container/registry.d.ts +1 -40
- package/dist/container/registry.js +14 -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/errors/container-disposed-error.d.ts +11 -0
- package/dist/errors/container-disposed-error.js +12 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/types/container.d.ts +54 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- 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,8 +59,12 @@ Runtime exports:
|
|
|
53
59
|
- `Container`
|
|
54
60
|
- `Service`
|
|
55
61
|
- `Inject`
|
|
62
|
+
- `InjectMany`
|
|
56
63
|
- `Token`
|
|
57
64
|
|
|
65
|
+
`Container` exposes the runtime API for resolution and low-level registration,
|
|
66
|
+
including `of()`, `ofChild()`, `get()`, `getMany()`, `set()`, `add()`, `has()`, `remove()`, and `reset()`.
|
|
67
|
+
|
|
58
68
|
Type-only exports:
|
|
59
69
|
|
|
60
70
|
- `Constructable` / `AbstractConstructable`
|
|
@@ -99,8 +109,8 @@ How resolution works:
|
|
|
99
109
|
The default scope. One instance is cached per container.
|
|
100
110
|
|
|
101
111
|
- repeated `get()` calls in the same container reuse the same instance;
|
|
102
|
-
-
|
|
103
|
-
-
|
|
112
|
+
- child containers receive their own isolated instance;
|
|
113
|
+
- child containers lazily clone ancestor registrations on first access.
|
|
104
114
|
|
|
105
115
|
### `singleton`
|
|
106
116
|
|
|
@@ -113,22 +123,28 @@ One shared instance across all containers.
|
|
|
113
123
|
|
|
114
124
|
A new instance is created on every `get()` call.
|
|
115
125
|
|
|
116
|
-
##
|
|
126
|
+
## Hierarchical containers
|
|
117
127
|
|
|
118
|
-
Use
|
|
128
|
+
Use containers when you want isolated request, job, or unit-of-work state.
|
|
119
129
|
|
|
120
130
|
```ts
|
|
121
131
|
const requestA = Container.of('request-a');
|
|
122
132
|
const requestB = Container.of('request-b');
|
|
133
|
+
|
|
134
|
+
const tenant = Container.of('tenant-a');
|
|
135
|
+
const request = tenant.ofChild('tenant-a-request');
|
|
123
136
|
```
|
|
124
137
|
|
|
125
138
|
Current behavior:
|
|
126
139
|
|
|
127
140
|
- `Container.of()` and `Container.of('default')` return the same default container;
|
|
128
141
|
- `Container.of('name')` reuses the same named container for the same id;
|
|
129
|
-
-
|
|
130
|
-
- `
|
|
131
|
-
-
|
|
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.
|
|
132
148
|
|
|
133
149
|
## Decorators
|
|
134
150
|
|
|
@@ -146,6 +162,7 @@ Options supported today:
|
|
|
146
162
|
|
|
147
163
|
- `id?: ServiceIdentifier`
|
|
148
164
|
- `scope?: 'singleton' | 'container' | 'transient'`
|
|
165
|
+
- `multiple?: boolean`
|
|
149
166
|
|
|
150
167
|
Example with a custom id:
|
|
151
168
|
|
|
@@ -174,6 +191,16 @@ Current characteristics:
|
|
|
174
191
|
- injected fields are defined as writable and configurable own properties on the created instance;
|
|
175
192
|
- injected fields are assigned after construction, so they are not available inside constructors or field initializers.
|
|
176
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
|
+
|
|
177
204
|
Token example:
|
|
178
205
|
|
|
179
206
|
```ts
|
|
@@ -209,6 +236,18 @@ handler.logger.log('hello from token injection');
|
|
|
209
236
|
|
|
210
237
|
Returns the default container or a named container.
|
|
211
238
|
|
|
239
|
+
Calling `Container.of(id)` with the same identifier reuses the same container
|
|
240
|
+
instance until that instance is disposed.
|
|
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
|
+
|
|
212
251
|
### `container.get(id)`
|
|
213
252
|
|
|
214
253
|
Resolves a service by class or service identifier.
|
|
@@ -218,6 +257,24 @@ Throws:
|
|
|
218
257
|
- `ServiceNotFoundError` when no registration exists;
|
|
219
258
|
- `CircularDependencyError` when the current resolution path loops back to an in-progress dependency.
|
|
220
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
|
+
|
|
269
|
+
### `container.tryGet(id)`
|
|
270
|
+
|
|
271
|
+
Resolves a service by class or service identifier and returns `undefined` when
|
|
272
|
+
no registration exists.
|
|
273
|
+
|
|
274
|
+
Unlike `get()`, this only returns `undefined` when the requested identifier is
|
|
275
|
+
not registered anywhere in the current resolution path. Other failures, such as
|
|
276
|
+
circular dependencies or missing nested dependencies, still throw.
|
|
277
|
+
|
|
221
278
|
### `container.has(id)`
|
|
222
279
|
|
|
223
280
|
Checks whether the current container has a local registration.
|
|
@@ -235,12 +292,87 @@ Supported strategies:
|
|
|
235
292
|
|
|
236
293
|
This is especially useful in tests.
|
|
237
294
|
|
|
238
|
-
### `container.
|
|
295
|
+
### `await container.dispose()`
|
|
296
|
+
|
|
297
|
+
Disposes the current container instance explicitly.
|
|
298
|
+
|
|
299
|
+
- clears local registrations, bindings, and cached service instances;
|
|
300
|
+
- awaits any bound value or cached service instance that exposes a `dispose()` method;
|
|
301
|
+
- makes the disposed container instance unusable for future operations.
|
|
302
|
+
|
|
303
|
+
After disposal, calling `Container.of(id)` again creates a fresh container for
|
|
304
|
+
that identifier. The same applies to the default container: after disposal, the
|
|
305
|
+
next `Container.of()` or named-container fallback access recreates a fresh
|
|
306
|
+
default container with no previous registrations or cached instances.
|
|
307
|
+
|
|
308
|
+
### `container.set(id, valueOrProvider)`
|
|
309
|
+
|
|
310
|
+
Registers or replaces a bound value or provider for a service identifier.
|
|
311
|
+
|
|
312
|
+
Use `set()` when you want manual registration without decorating a class.
|
|
313
|
+
This is the supported low-level API for values, classes, and factories.
|
|
314
|
+
|
|
315
|
+
Supported forms today:
|
|
316
|
+
|
|
317
|
+
- `container.set(id, value)`
|
|
318
|
+
- `container.set(id, { useValue: value })`
|
|
319
|
+
- `container.set(id, { useClass: Class, scope?, injections? })`
|
|
320
|
+
- `container.set(id, { useFactory: (container) => value, scope? })`
|
|
321
|
+
|
|
322
|
+
This is the low-level API for manual value, class, and factory registration.
|
|
323
|
+
For decorator-driven classes, prefer `@Service()`.
|
|
324
|
+
|
|
325
|
+
### `container.add(id, valueOrProvider)`
|
|
239
326
|
|
|
240
|
-
|
|
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
|
+
|
|
332
|
+
Value example:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
const REQUEST_ID = 'request-id';
|
|
336
|
+
|
|
337
|
+
Container.of().set(REQUEST_ID, { useValue: crypto.randomUUID() });
|
|
338
|
+
|
|
339
|
+
const requestId = Container.of().get<string>(REQUEST_ID);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Class example:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
interface Logger {
|
|
346
|
+
log(message: string): void;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
class ConsoleLogger implements Logger {
|
|
350
|
+
public log(message: string) {
|
|
351
|
+
console.log(message);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
Container.of().set<Logger>('logger', { useClass: ConsoleLogger, scope: 'singleton' });
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Factory example:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
class Connection {
|
|
362
|
+
constructor(public readonly requestId: string) {}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
Container.of().set('request-id', { useValue: 'request-1' });
|
|
366
|
+
Container.of().set(Connection, {
|
|
367
|
+
useFactory: (container) => new Connection(container.get('request-id')),
|
|
368
|
+
scope: 'container',
|
|
369
|
+
});
|
|
370
|
+
```
|
|
241
371
|
|
|
242
|
-
|
|
243
|
-
|
|
372
|
+
For named containers, values bound in the default container are available through
|
|
373
|
+
fallback. Provider objects are detected structurally, so plain object values that
|
|
374
|
+
contain `useValue`, `useClass`, or `useFactory` should be wrapped with
|
|
375
|
+
`{ useValue: value }` if you want them treated as values.
|
|
244
376
|
|
|
245
377
|
## Internal architecture
|
|
246
378
|
|
|
@@ -1,103 +1,56 @@
|
|
|
1
|
-
import type { ContainerIdentifier, Metadata, ServiceIdentifier } 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
|
-
*/
|
|
1
|
+
import type { ContainerIdentifier, Metadata, ServiceIdentifier, ServiceProvider } from '../types';
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
13
|
+
private disposed;
|
|
14
|
+
constructor(id: ContainerIdentifier, parent?: Container);
|
|
15
|
+
get parent(): Container | undefined;
|
|
16
|
+
private ensureNotDisposed;
|
|
17
|
+
private isDisposable;
|
|
18
|
+
private isValueProvider;
|
|
19
|
+
private isClassProvider;
|
|
20
|
+
private isFactoryProvider;
|
|
21
|
+
private isRoot;
|
|
22
|
+
private getRoot;
|
|
23
|
+
private getLineage;
|
|
24
|
+
private cloneMetadata;
|
|
25
|
+
private findBindingOwner;
|
|
26
|
+
private findMetadataOwner;
|
|
27
|
+
private canResolve;
|
|
28
|
+
private getClassProviderMetadata;
|
|
29
|
+
private getFactoryProviderMetadata;
|
|
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;
|
|
30
39
|
static of(id?: ContainerIdentifier): Container;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* This is a low-level API for manual registration when you are not using
|
|
35
|
-
* `@Service()`. Registering a `singleton` on a named container stores it in
|
|
36
|
-
* the default container so it can be shared across containers.
|
|
37
|
-
*
|
|
38
|
-
* @param metadata The service metadata to register.
|
|
39
|
-
* @returns The current container.
|
|
40
|
-
*/
|
|
40
|
+
static ofChild(id: ContainerIdentifier, parent?: Container | ContainerIdentifier): Container;
|
|
41
|
+
ofChild(id: ContainerIdentifier): Container;
|
|
41
42
|
register<T>(metadata: Metadata<T>): this;
|
|
42
|
-
/**
|
|
43
|
-
* Returns whether this container has a local registration for an identifier.
|
|
44
|
-
*
|
|
45
|
-
* This only checks registrations stored on the current container and does
|
|
46
|
-
* not indicate whether the identifier can be resolved through fallback.
|
|
47
|
-
*
|
|
48
|
-
* @param id The service identifier to check.
|
|
49
|
-
* @returns `true` when the current container has a local registration.
|
|
50
|
-
*/
|
|
51
43
|
has(id: ServiceIdentifier): boolean;
|
|
52
|
-
/**
|
|
53
|
-
* Binds a concrete value to this container.
|
|
54
|
-
*
|
|
55
|
-
* Bound values are returned as-is when the same identifier is requested from
|
|
56
|
-
* this container.
|
|
57
|
-
*
|
|
58
|
-
* @param id The service identifier to bind.
|
|
59
|
-
* @param value The value to return for that identifier.
|
|
60
|
-
* @returns The current container.
|
|
61
|
-
*/
|
|
62
44
|
set<T>(id: ServiceIdentifier<T>, value: T): this;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* @param id The service identifier to remove.
|
|
67
|
-
* @returns The current container.
|
|
68
|
-
*/
|
|
45
|
+
set<T>(id: ServiceIdentifier<T>, provider: ServiceProvider<T>): this;
|
|
46
|
+
add<T>(id: ServiceIdentifier<T>, value: T): this;
|
|
47
|
+
add<T>(id: ServiceIdentifier<T>, provider: ServiceProvider<T>): this;
|
|
69
48
|
remove(id: ServiceIdentifier): this;
|
|
70
|
-
/**
|
|
71
|
-
* Resets services stored in this container.
|
|
72
|
-
*
|
|
73
|
-
* Use `'value'` to clear cached instances while keeping registrations, or
|
|
74
|
-
* `'service'` to remove local registrations and bindings entirely.
|
|
75
|
-
*
|
|
76
|
-
* @param strategy The reset strategy to apply.
|
|
77
|
-
* @returns The current container.
|
|
78
|
-
*/
|
|
79
49
|
reset(strategy?: 'value' | 'service'): this;
|
|
80
|
-
/**
|
|
81
|
-
* Resolves a service from this container.
|
|
82
|
-
*
|
|
83
|
-
* If the current container does not have a local registration, named
|
|
84
|
-
* containers can continue resolution through the default container according
|
|
85
|
-
* to the service scope. Services are instantiated with `new Class()` and do
|
|
86
|
-
* not support constructor arguments.
|
|
87
|
-
*
|
|
88
|
-
* @param id The service identifier to resolve.
|
|
89
|
-
* @returns The resolved service instance or bound value.
|
|
90
|
-
* @throws {ServiceNotFoundError} If no service is registered for the identifier.
|
|
91
|
-
* @throws {CircularDependencyError} If the dependency graph contains a cycle.
|
|
92
|
-
*/
|
|
93
50
|
get<T>(id: ServiceIdentifier<T>): T;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
* @param containerId The container to reset.
|
|
99
|
-
* @param options Reset options.
|
|
100
|
-
*/
|
|
51
|
+
getMany<T>(id: ServiceIdentifier<T>): T[];
|
|
52
|
+
tryGet<T>(id: ServiceIdentifier<T>): T | undefined;
|
|
53
|
+
dispose(): Promise<void>;
|
|
101
54
|
static reset(containerId: ContainerIdentifier, options?: {
|
|
102
55
|
strategy?: 'value' | 'service';
|
|
103
56
|
}): void;
|
|
@@ -1,36 +1,208 @@
|
|
|
1
|
-
import { CircularDependencyError, ServiceNotFoundError } from '../errors';
|
|
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;
|
|
16
|
+
constructor(id, parent) {
|
|
23
17
|
this.id = id;
|
|
18
|
+
this.parentContainer = parent;
|
|
19
|
+
parent?.children.add(this);
|
|
20
|
+
}
|
|
21
|
+
get parent() {
|
|
22
|
+
return this.parentContainer;
|
|
23
|
+
}
|
|
24
|
+
ensureNotDisposed() {
|
|
25
|
+
if (this.disposed) {
|
|
26
|
+
throw new ContainerDisposedError(this.id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
isDisposable(value) {
|
|
30
|
+
return typeof value === 'object' && value !== null && 'dispose' in value && typeof value.dispose === 'function';
|
|
31
|
+
}
|
|
32
|
+
isValueProvider(provider) {
|
|
33
|
+
return typeof provider === 'object' && provider !== null && 'useValue' in provider;
|
|
34
|
+
}
|
|
35
|
+
isClassProvider(provider) {
|
|
36
|
+
return typeof provider === 'object' && provider !== null && 'useClass' in provider;
|
|
37
|
+
}
|
|
38
|
+
isFactoryProvider(provider) {
|
|
39
|
+
return typeof provider === 'object' && provider !== null && 'useFactory' in provider;
|
|
40
|
+
}
|
|
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);
|
|
74
|
+
const inheritedInjections = existingMetadata?.Class === provider.useClass ? [...existingMetadata.injections] : [];
|
|
75
|
+
const inheritedScope = existingMetadata?.Class === provider.useClass ? existingMetadata.scope : 'container';
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
Class: provider.useClass,
|
|
79
|
+
name: provider.useClass.name || String(id),
|
|
80
|
+
injections: provider.injections ?? inheritedInjections,
|
|
81
|
+
scope: provider.scope ?? inheritedScope,
|
|
82
|
+
value: EMPTY_VALUE,
|
|
83
|
+
multiple,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
getFactoryProviderMetadata(id, provider, multiple = false) {
|
|
87
|
+
return {
|
|
88
|
+
id,
|
|
89
|
+
name: typeof id === 'function' ? id.name : String(id),
|
|
90
|
+
injections: [],
|
|
91
|
+
scope: provider.scope ?? 'container',
|
|
92
|
+
value: EMPTY_VALUE,
|
|
93
|
+
factory: provider.useFactory,
|
|
94
|
+
multiple,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
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;
|
|
24
205
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Returns the default container or a named container.
|
|
27
|
-
*
|
|
28
|
-
* Calling this method with the same identifier always returns the same
|
|
29
|
-
* container instance.
|
|
30
|
-
*
|
|
31
|
-
* @param id The container identifier. Omit this to use the default container.
|
|
32
|
-
* @returns The matching container instance.
|
|
33
|
-
*/
|
|
34
206
|
static of(id = 'default') {
|
|
35
207
|
if (id === 'default') {
|
|
36
208
|
return ContainerRegistry.defaultContainer;
|
|
@@ -38,29 +210,41 @@ export class Container {
|
|
|
38
210
|
if (ContainerRegistry.hasContainer(id)) {
|
|
39
211
|
return ContainerRegistry.getContainer(id);
|
|
40
212
|
}
|
|
41
|
-
const container = new Container(id);
|
|
213
|
+
const container = new Container(id, ContainerRegistry.defaultContainer);
|
|
214
|
+
ContainerRegistry.registerContainer(container);
|
|
215
|
+
return container;
|
|
216
|
+
}
|
|
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);
|
|
42
230
|
ContainerRegistry.registerContainer(container);
|
|
43
231
|
return container;
|
|
44
232
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* This is a low-level API for manual registration when you are not using
|
|
49
|
-
* `@Service()`. Registering a `singleton` on a named container stores it in
|
|
50
|
-
* the default container so it can be shared across containers.
|
|
51
|
-
*
|
|
52
|
-
* @param metadata The service metadata to register.
|
|
53
|
-
* @returns The current container.
|
|
54
|
-
*/
|
|
233
|
+
ofChild(id) {
|
|
234
|
+
return Container.ofChild(id, this);
|
|
235
|
+
}
|
|
55
236
|
register(metadata) {
|
|
56
|
-
|
|
57
|
-
|
|
237
|
+
this.ensureNotDisposed();
|
|
238
|
+
if (metadata.scope === 'singleton' && !this.isRoot()) {
|
|
239
|
+
this.getRoot().register(metadata);
|
|
58
240
|
this.metadataMap.delete(metadata.id);
|
|
59
241
|
return this;
|
|
60
242
|
}
|
|
61
|
-
const newMetadata =
|
|
62
|
-
|
|
63
|
-
|
|
243
|
+
const newMetadata = this.cloneMetadata(metadata);
|
|
244
|
+
if (newMetadata.multiple) {
|
|
245
|
+
this.pushMultiMetadata(newMetadata.id, newMetadata);
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
64
248
|
const existingMetadata = this.metadataMap.get(newMetadata.id);
|
|
65
249
|
if (existingMetadata) {
|
|
66
250
|
Object.assign(existingMetadata, newMetadata);
|
|
@@ -70,142 +254,159 @@ export class Container {
|
|
|
70
254
|
}
|
|
71
255
|
return this;
|
|
72
256
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Returns whether this container has a local registration for an identifier.
|
|
75
|
-
*
|
|
76
|
-
* This only checks registrations stored on the current container and does
|
|
77
|
-
* not indicate whether the identifier can be resolved through fallback.
|
|
78
|
-
*
|
|
79
|
-
* @param id The service identifier to check.
|
|
80
|
-
* @returns `true` when the current container has a local registration.
|
|
81
|
-
*/
|
|
82
257
|
has(id) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
258
|
+
this.ensureNotDisposed();
|
|
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);
|
|
264
|
+
}
|
|
265
|
+
set(id, valueOrProvider) {
|
|
266
|
+
this.ensureNotDisposed();
|
|
267
|
+
if (this.isValueProvider(valueOrProvider)) {
|
|
268
|
+
this.bindingMap.set(id, valueOrProvider.useValue);
|
|
269
|
+
this.metadataMap.delete(id);
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
if (this.isClassProvider(valueOrProvider)) {
|
|
273
|
+
this.bindingMap.delete(id);
|
|
274
|
+
return this.register(this.getClassProviderMetadata(id, valueOrProvider));
|
|
275
|
+
}
|
|
276
|
+
if (this.isFactoryProvider(valueOrProvider)) {
|
|
277
|
+
this.bindingMap.delete(id);
|
|
278
|
+
return this.register(this.getFactoryProviderMetadata(id, valueOrProvider));
|
|
279
|
+
}
|
|
280
|
+
this.bindingMap.set(id, valueOrProvider);
|
|
281
|
+
this.metadataMap.delete(id);
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
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);
|
|
97
297
|
return this;
|
|
98
298
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Removes a local binding or registration from this container.
|
|
101
|
-
*
|
|
102
|
-
* @param id The service identifier to remove.
|
|
103
|
-
* @returns The current container.
|
|
104
|
-
*/
|
|
105
299
|
remove(id) {
|
|
300
|
+
this.ensureNotDisposed();
|
|
106
301
|
this.bindingMap.delete(id);
|
|
107
302
|
this.metadataMap.delete(id);
|
|
303
|
+
this.multiBindingMap.delete(id);
|
|
304
|
+
this.multiMetadataMap.delete(id);
|
|
305
|
+
this.inheritedMultiMetadataMap.delete(id);
|
|
108
306
|
return this;
|
|
109
307
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Resets services stored in this container.
|
|
112
|
-
*
|
|
113
|
-
* Use `'value'` to clear cached instances while keeping registrations, or
|
|
114
|
-
* `'service'` to remove local registrations and bindings entirely.
|
|
115
|
-
*
|
|
116
|
-
* @param strategy The reset strategy to apply.
|
|
117
|
-
* @returns The current container.
|
|
118
|
-
*/
|
|
119
308
|
reset(strategy = 'value') {
|
|
309
|
+
this.ensureNotDisposed();
|
|
120
310
|
if (strategy === 'value') {
|
|
121
|
-
this.
|
|
122
|
-
metadata.value = EMPTY_VALUE;
|
|
123
|
-
});
|
|
124
|
-
return this;
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
this.bindingMap.clear();
|
|
128
|
-
this.metadataMap.clear();
|
|
311
|
+
this.resetMetadataValues();
|
|
129
312
|
return this;
|
|
130
313
|
}
|
|
314
|
+
this.bindingMap.clear();
|
|
315
|
+
this.metadataMap.clear();
|
|
316
|
+
this.multiBindingMap.clear();
|
|
317
|
+
this.multiMetadataMap.clear();
|
|
318
|
+
this.inheritedMultiMetadataMap.clear();
|
|
319
|
+
return this;
|
|
131
320
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Resolves a service from this container.
|
|
134
|
-
*
|
|
135
|
-
* If the current container does not have a local registration, named
|
|
136
|
-
* containers can continue resolution through the default container according
|
|
137
|
-
* to the service scope. Services are instantiated with `new Class()` and do
|
|
138
|
-
* not support constructor arguments.
|
|
139
|
-
*
|
|
140
|
-
* @param id The service identifier to resolve.
|
|
141
|
-
* @returns The resolved service instance or bound value.
|
|
142
|
-
* @throws {ServiceNotFoundError} If no service is registered for the identifier.
|
|
143
|
-
* @throws {CircularDependencyError} If the dependency graph contains a cycle.
|
|
144
|
-
*/
|
|
145
321
|
get(id) {
|
|
322
|
+
this.ensureNotDisposed();
|
|
146
323
|
if (this.bindingMap.has(id)) {
|
|
147
324
|
return this.bindingMap.get(id);
|
|
148
325
|
}
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return ContainerRegistry.defaultContainer.get(id);
|
|
157
|
-
}
|
|
158
|
-
if (defaultMetadata.scope === 'container') {
|
|
159
|
-
metadata = {
|
|
160
|
-
...defaultMetadata,
|
|
161
|
-
injections: [...defaultMetadata.injections],
|
|
162
|
-
value: EMPTY_VALUE,
|
|
163
|
-
};
|
|
164
|
-
this.metadataMap.set(id, metadata);
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
metadata = defaultMetadata;
|
|
168
|
-
}
|
|
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);
|
|
169
333
|
}
|
|
170
|
-
|
|
334
|
+
const metadataOwner = this.parentContainer?.findMetadataOwner(id);
|
|
335
|
+
if (!metadataOwner) {
|
|
171
336
|
throw new ServiceNotFoundError(id);
|
|
172
337
|
}
|
|
173
|
-
|
|
174
|
-
|
|
338
|
+
const metadata = metadataOwner.metadataMap.get(id);
|
|
339
|
+
if (metadata.scope === 'singleton') {
|
|
340
|
+
return metadataOwner.resolveMetadata(metadata, metadataOwner);
|
|
175
341
|
}
|
|
176
|
-
if (
|
|
177
|
-
|
|
342
|
+
if (metadata.scope === 'container') {
|
|
343
|
+
const localizedMetadata = this.cloneMetadata(metadata);
|
|
344
|
+
this.metadataMap.set(id, localizedMetadata);
|
|
345
|
+
return this.resolveMetadata(localizedMetadata, this);
|
|
178
346
|
}
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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;
|
|
189
358
|
}
|
|
190
|
-
|
|
191
|
-
|
|
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));
|
|
192
374
|
}
|
|
193
|
-
return instance;
|
|
194
375
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
376
|
+
return resolved;
|
|
377
|
+
}
|
|
378
|
+
tryGet(id) {
|
|
379
|
+
this.ensureNotDisposed();
|
|
380
|
+
if (!this.canResolve(id)) {
|
|
381
|
+
return undefined;
|
|
198
382
|
}
|
|
383
|
+
return this.get(id);
|
|
199
384
|
}
|
|
200
|
-
|
|
201
|
-
|
|
385
|
+
async dispose() {
|
|
386
|
+
if (this.disposed) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const childContainers = [...this.children];
|
|
390
|
+
for (const child of childContainers) {
|
|
391
|
+
await child.dispose();
|
|
392
|
+
}
|
|
393
|
+
const ownedValues = this.collectOwnedValues();
|
|
394
|
+
this.disposed = true;
|
|
395
|
+
ContainerRegistry.disposeContainer(this);
|
|
396
|
+
this.bindingMap.clear();
|
|
397
|
+
this.metadataMap.clear();
|
|
398
|
+
this.multiBindingMap.clear();
|
|
399
|
+
this.multiMetadataMap.clear();
|
|
400
|
+
this.inheritedMultiMetadataMap.clear();
|
|
401
|
+
this.children.clear();
|
|
402
|
+
this.resolving.clear();
|
|
403
|
+
this.resolvingPath = [];
|
|
404
|
+
for (const value of ownedValues) {
|
|
405
|
+
if (this.isDisposable(value)) {
|
|
406
|
+
await value.dispose();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
202
409
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Resets a container by its identifier.
|
|
205
|
-
*
|
|
206
|
-
* @param containerId The container to reset.
|
|
207
|
-
* @param options Reset options.
|
|
208
|
-
*/
|
|
209
410
|
static reset(containerId, options) {
|
|
210
411
|
const container = ContainerRegistry.getContainer(containerId);
|
|
211
412
|
container?.reset(options?.strategy);
|
|
@@ -1,51 +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;
|
|
11
|
+
static disposeContainer(container: Container): void;
|
|
51
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,42 +16,36 @@ 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
|
}
|
|
39
|
+
static disposeContainer(container) {
|
|
40
|
+
container.unlinkParent();
|
|
41
|
+
if (container.id === 'default') {
|
|
42
|
+
if (this.defaultContainerInstance === container) {
|
|
43
|
+
this.defaultContainerInstance = undefined;
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (this.containerMap.get(container.id) === container) {
|
|
48
|
+
this.containerMap.delete(container.id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
77
51
|
}
|
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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ContainerIdentifier } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an operation is attempted on a disposed container instance.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ContainerDisposedError extends Error {
|
|
6
|
+
name: string;
|
|
7
|
+
/**
|
|
8
|
+
* @param id The identifier of the disposed container.
|
|
9
|
+
*/
|
|
10
|
+
constructor(id: ContainerIdentifier);
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when an operation is attempted on a disposed container instance.
|
|
3
|
+
*/
|
|
4
|
+
export class ContainerDisposedError extends Error {
|
|
5
|
+
name = 'ContainerDisposedError';
|
|
6
|
+
/**
|
|
7
|
+
* @param id The identifier of the disposed container.
|
|
8
|
+
*/
|
|
9
|
+
constructor(id) {
|
|
10
|
+
super(`Container has been disposed: ${String(id)}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { CircularDependencyError } from './circular-dependency-error';
|
|
2
2
|
export { ContainerDuplicatedError } from './container-duplicated-error';
|
|
3
|
+
export { ContainerDisposedError } from './container-disposed-error';
|
|
3
4
|
export { DefaultContainerIdError } from './default-container-id-error';
|
|
4
5
|
export { ServiceNotFoundError } from './service-not-found-error';
|
package/dist/errors/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { CircularDependencyError } from './circular-dependency-error';
|
|
2
2
|
export { ContainerDuplicatedError } from './container-duplicated-error';
|
|
3
|
+
export { ContainerDisposedError } from './container-disposed-error';
|
|
3
4
|
export { DefaultContainerIdError } from './default-container-id-error';
|
|
4
5
|
export { ServiceNotFoundError } from './service-not-found-error';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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';
|
|
6
|
+
export type { ClassProvider, FactoryProvider, ServiceFactory, ServiceProvider, ValueProvider, } from './types/container.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Constructable } from './constructable';
|
|
2
2
|
import type { InjectionMetadata } from './injection';
|
|
3
3
|
import type { ServiceIdentifier, ServiceScope } from './service';
|
|
4
|
+
import type { Container } from '../container';
|
|
4
5
|
/**
|
|
5
6
|
* A container identifier used to select the default container or a named container.
|
|
6
7
|
*/
|
|
@@ -9,6 +10,53 @@ export type ContainerIdentifier = string | symbol;
|
|
|
9
10
|
* Sentinel value used internally to mark services that have not been instantiated yet.
|
|
10
11
|
*/
|
|
11
12
|
export declare const EMPTY_VALUE: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* A factory function that creates a service using the current container.
|
|
15
|
+
*/
|
|
16
|
+
export type ServiceFactory<T> = (container: Container) => T;
|
|
17
|
+
/**
|
|
18
|
+
* A provider that always resolves to the same explicit value.
|
|
19
|
+
*/
|
|
20
|
+
export interface ValueProvider<T> {
|
|
21
|
+
/**
|
|
22
|
+
* The value returned for the registered identifier.
|
|
23
|
+
*/
|
|
24
|
+
useValue: T;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A provider that resolves by instantiating a class.
|
|
28
|
+
*/
|
|
29
|
+
export interface ClassProvider<T> {
|
|
30
|
+
/**
|
|
31
|
+
* The class instantiated when the service is resolved.
|
|
32
|
+
*/
|
|
33
|
+
useClass: Constructable<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Optional property injection definitions for the registered class.
|
|
36
|
+
*/
|
|
37
|
+
injections?: InjectionMetadata[];
|
|
38
|
+
/**
|
|
39
|
+
* The lifetime used when the service is resolved.
|
|
40
|
+
*/
|
|
41
|
+
scope?: ServiceScope;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A provider that resolves by calling a factory function.
|
|
45
|
+
*/
|
|
46
|
+
export interface FactoryProvider<T> {
|
|
47
|
+
/**
|
|
48
|
+
* The factory used to create the resolved value.
|
|
49
|
+
*/
|
|
50
|
+
useFactory: ServiceFactory<T>;
|
|
51
|
+
/**
|
|
52
|
+
* The lifetime used when the service is resolved.
|
|
53
|
+
*/
|
|
54
|
+
scope?: ServiceScope;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A provider object accepted by low-level container registration APIs.
|
|
58
|
+
*/
|
|
59
|
+
export type ServiceProvider<T> = ValueProvider<T> | ClassProvider<T> | FactoryProvider<T>;
|
|
12
60
|
/**
|
|
13
61
|
* Service registration metadata stored by a container.
|
|
14
62
|
*
|
|
@@ -22,7 +70,7 @@ export interface Metadata<T = unknown> {
|
|
|
22
70
|
/**
|
|
23
71
|
* The class instantiated for this service.
|
|
24
72
|
*/
|
|
25
|
-
Class
|
|
73
|
+
Class?: Constructable<T>;
|
|
26
74
|
/**
|
|
27
75
|
* The original class or member name, when available.
|
|
28
76
|
*/
|
|
@@ -39,4 +87,9 @@ export interface Metadata<T = unknown> {
|
|
|
39
87
|
* The cached service instance, or `EMPTY_VALUE` when no instance is cached.
|
|
40
88
|
*/
|
|
41
89
|
value: T | typeof EMPTY_VALUE;
|
|
90
|
+
multiple?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* An optional factory used to create the resolved value.
|
|
93
|
+
*/
|
|
94
|
+
factory?: ServiceFactory<T>;
|
|
42
95
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type { AbstractConstructable, Constructable } from './constructable';
|
|
2
2
|
export type { ServiceIdentifier, ServiceOption } from './service';
|
|
3
|
-
export { type ContainerIdentifier, type Metadata,
|
|
3
|
+
export { type ClassProvider, type ContainerIdentifier, EMPTY_VALUE, type FactoryProvider, type Metadata, type ServiceFactory, type ServiceProvider, type ValueProvider, } from './container';
|
|
4
4
|
export { type InjectionMetadata, INJECTION_KEY } from './injection';
|
package/dist/types/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { EMPTY_VALUE } from './container';
|
|
1
|
+
export { EMPTY_VALUE, } from './container';
|
|
2
2
|
export { INJECTION_KEY } from './injection';
|
package/dist/types/service.d.ts
CHANGED