@codefast/di 0.3.13 → 0.3.14-canary.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/CHANGELOG.md +20 -0
- package/README.md +262 -235
- package/dist/binding-select.d.mts +17 -6
- package/dist/binding-select.mjs +17 -6
- package/dist/binding.d.mts +148 -23
- package/dist/binding.mjs +103 -14
- package/dist/constraints.d.mts +18 -3
- package/dist/constraints.mjs +18 -3
- package/dist/container.d.mts +81 -26
- package/dist/container.mjs +91 -3
- package/dist/decorators/inject.d.mts +40 -9
- package/dist/decorators/inject.mjs +50 -11
- package/dist/decorators/injectable.d.mts +2 -1
- package/dist/decorators/injectable.mjs +14 -2
- package/dist/decorators/lifecycle-decorators.d.mts +16 -4
- package/dist/decorators/lifecycle-decorators.mjs +16 -4
- package/dist/dependency-graph.d.mts +31 -8
- package/dist/dependency-graph.mjs +42 -8
- package/dist/errors.d.mts +124 -13
- package/dist/errors.mjs +126 -18
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/inspector.d.mts +38 -14
- package/dist/inspector.mjs +36 -15
- package/dist/lifecycle.d.mts +28 -6
- package/dist/lifecycle.mjs +29 -10
- package/dist/metadata/metadata-keys.d.mts +17 -6
- package/dist/metadata/metadata-keys.mjs +17 -6
- package/dist/metadata/metadata-types.d.mts +29 -5
- package/dist/metadata/param-registry.mjs +6 -0
- package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
- package/dist/metadata/symbol-metadata-reader.mjs +23 -4
- package/dist/module.d.mts +34 -2
- package/dist/module.mjs +19 -0
- package/dist/registry.d.mts +39 -8
- package/dist/registry.mjs +39 -8
- package/dist/resolver.d.mts +107 -12
- package/dist/resolver.mjs +134 -37
- package/dist/scope-validation.d.mts +3 -2
- package/dist/scope-validation.mjs +3 -2
- package/dist/scope.d.mts +34 -6
- package/dist/scope.mjs +38 -13
- package/dist/token.d.mts +9 -2
- package/dist/token.mjs +7 -1
- package/package.json +2 -2
package/dist/constraints.mjs
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
//#region src/constraints.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Constraint predicate factory: matches when the direct parent on the materialization stack
|
|
4
|
+
* was registered under `registryKey`. Pass the result to {@link BindingBuilder.when}.
|
|
5
|
+
*
|
|
6
|
+
* @param registryKey - Token or constructor that the parent binding must be registered against.
|
|
7
|
+
* @returns A predicate compatible with {@link BindingBuilder.when}.
|
|
4
8
|
*/
|
|
5
9
|
function whenParentIs(registryKey) {
|
|
6
10
|
return (ctx) => ctx.parent?.registryKey === registryKey;
|
|
7
11
|
}
|
|
8
12
|
/**
|
|
9
|
-
*
|
|
13
|
+
* Constraint predicate factory: matches when *any* ancestor on the materialization stack
|
|
14
|
+
* (not just the immediate parent) was registered under `registryKey`.
|
|
15
|
+
* Pass the result to {@link BindingBuilder.when}.
|
|
16
|
+
*
|
|
17
|
+
* @param registryKey - Token or constructor to search for across the full construction chain.
|
|
18
|
+
* @returns A predicate compatible with {@link BindingBuilder.when}.
|
|
10
19
|
*/
|
|
11
20
|
function whenAnyAncestorIs(registryKey) {
|
|
12
21
|
return (ctx) => ctx.materializationStack.some((frame) => frame.registryKey === registryKey);
|
|
13
22
|
}
|
|
14
23
|
/**
|
|
15
|
-
*
|
|
24
|
+
* Constraint predicate factory: matches when the immediate parent binding carries a tag
|
|
25
|
+
* whose key is `tag` and whose value is reference-equal to `tagValue` (`Object.is`).
|
|
26
|
+
* Pass the result to {@link BindingBuilder.when}.
|
|
27
|
+
*
|
|
28
|
+
* @param tag - Tag key to check on the parent binding.
|
|
29
|
+
* @param tagValue - Expected value; compared via `Object.is`.
|
|
30
|
+
* @returns A predicate compatible with {@link BindingBuilder.when}.
|
|
16
31
|
*/
|
|
17
32
|
function whenTargetTagged(tag, tagValue) {
|
|
18
33
|
return (ctx) => {
|
package/dist/container.d.mts
CHANGED
|
@@ -5,6 +5,9 @@ import { ContainerGraphJson, ContainerSnapshot, DotGraphOptions } from "./inspec
|
|
|
5
5
|
import { AsyncModule, Module } from "./module.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/container.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Union of sync and async modules accepted by `loadAsync` / `unloadAsync`.
|
|
10
|
+
*/
|
|
8
11
|
type ModuleLike = Module | AsyncModule;
|
|
9
12
|
/**
|
|
10
13
|
* Public contract for an IoC container (registry, modules, resolution, lifecycle).
|
|
@@ -14,56 +17,102 @@ type ModuleLike = Module | AsyncModule;
|
|
|
14
17
|
* {@link Container.dispose} automatically at scope exit (TC39 Explicit Resource Management).
|
|
15
18
|
*/
|
|
16
19
|
interface Container extends AsyncDisposable {
|
|
17
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Starts a fluent binding builder for the given token or constructor.
|
|
22
|
+
*/
|
|
18
23
|
bind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
|
|
19
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Removes all existing bindings for the token (with sync deactivation) then starts a fresh builder.
|
|
26
|
+
*/
|
|
20
27
|
rebind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
|
|
21
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Removes all bindings for a token or a single binding by its {@link BindingIdentifier}; runs sync deactivation.
|
|
30
|
+
*/
|
|
22
31
|
unbind(tokenOrId: RegistryKey | BindingIdentifier): void;
|
|
23
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Same as {@link unbind} but awaits async `onDeactivation` handlers before removing.
|
|
34
|
+
*/
|
|
24
35
|
unbindAsync(tokenOrId: RegistryKey | BindingIdentifier): Promise<void>;
|
|
25
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Returns `true` if at least one binding exists for `token`, optionally filtered by `hint`.
|
|
38
|
+
*/
|
|
26
39
|
has(token: RegistryKey, hint?: ResolveHint): boolean;
|
|
27
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Resolves the token synchronously. Throws {@link AsyncResolutionError} if any binding in the chain is async.
|
|
42
|
+
*/
|
|
28
43
|
resolve<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value;
|
|
29
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the token, awaiting any async factory in the chain. Safe for both sync and async bindings.
|
|
46
|
+
*/
|
|
30
47
|
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value>;
|
|
31
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Resolves all bindings registered for the token (multi-binding). Throws {@link AsyncResolutionError} if any is async.
|
|
50
|
+
*/
|
|
32
51
|
resolveAll<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value[];
|
|
33
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Async variant of {@link resolveAll} — safe when the multi-binding set contains async factories.
|
|
54
|
+
*/
|
|
34
55
|
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value[]>;
|
|
35
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Resolves the token or returns `undefined` if no binding is registered (never throws on missing).
|
|
58
|
+
*/
|
|
36
59
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value | undefined;
|
|
37
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Registers bindings from one or more synchronous modules. Re-loading a module already present is a no-op.
|
|
62
|
+
*/
|
|
38
63
|
load(...modules: Module[]): void;
|
|
39
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Registers bindings from sync and/or async modules, awaiting each async setup in sequence.
|
|
66
|
+
*/
|
|
40
67
|
loadAsync(...modules: ModuleLike[]): Promise<void>;
|
|
41
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Removes all bindings contributed by the given modules; runs sync deactivation on released singletons.
|
|
70
|
+
*/
|
|
42
71
|
unload(...modules: ModuleLike[]): void;
|
|
43
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Same as {@link unload} but awaits async `onDeactivation` handlers.
|
|
74
|
+
*/
|
|
44
75
|
unloadAsync(...modules: ModuleLike[]): Promise<void>;
|
|
45
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Eagerly constructs every singleton binding so the first request is never cold.
|
|
78
|
+
*/
|
|
46
79
|
initializeAsync(): Promise<void>;
|
|
47
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Scans {@link getAutoRegistered} entries and binds each to its declared scope. Returns the count added.
|
|
82
|
+
*/
|
|
48
83
|
loadAutoRegistered(): number;
|
|
49
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Checks for scope violations (captive dependencies). Throws {@link ScopeViolationError} on the first violation found.
|
|
86
|
+
*/
|
|
50
87
|
validate(): void;
|
|
51
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Returns a debug snapshot of all registered bindings and their activation state.
|
|
90
|
+
*/
|
|
52
91
|
inspect(): ContainerSnapshot;
|
|
53
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Renders the dependency graph as a Graphviz DOT string (default) or a typed JSON object.
|
|
94
|
+
*/
|
|
54
95
|
generateDependencyGraph(options?: DotGraphOptions & {
|
|
55
96
|
format?: "dot";
|
|
56
97
|
}): string;
|
|
57
98
|
generateDependencyGraph(options: DotGraphOptions & {
|
|
58
99
|
format: "json";
|
|
59
100
|
}): ContainerGraphJson;
|
|
60
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Creates a child container that inherits bindings from this container without polluting its registry.
|
|
103
|
+
*/
|
|
61
104
|
createChild(): Container;
|
|
62
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* @throws Always — container disposal is async; use `await using` or `await container.dispose()`.
|
|
107
|
+
*/
|
|
63
108
|
[Symbol.dispose](): never;
|
|
64
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Returns the raw binding list for a token without triggering resolution. `undefined` means no binding.
|
|
111
|
+
*/
|
|
65
112
|
lookupBindings(token: RegistryKey): readonly Binding<unknown>[] | undefined;
|
|
66
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Runs all `onDeactivation` hooks on active singletons and releases all caches.
|
|
115
|
+
*/
|
|
67
116
|
dispose(): Promise<void>;
|
|
68
117
|
[Symbol.asyncDispose](): Promise<void>;
|
|
69
118
|
}
|
|
@@ -71,11 +120,17 @@ interface Container extends AsyncDisposable {
|
|
|
71
120
|
* Factory functions for {@link Container} instances (interface + namespace merge).
|
|
72
121
|
*/
|
|
73
122
|
declare namespace Container {
|
|
74
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Creates an empty container with no bindings.
|
|
125
|
+
*/
|
|
75
126
|
function create(): Container;
|
|
76
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Creates a container and immediately loads the given sync modules.
|
|
129
|
+
*/
|
|
77
130
|
function fromModules(...modules: Module[]): Container;
|
|
78
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Creates a container and awaits loading of sync and/or async modules.
|
|
133
|
+
*/
|
|
79
134
|
function fromModulesAsync(...modules: (Module | AsyncModule)[]): Promise<Container>;
|
|
80
135
|
}
|
|
81
136
|
//#endregion
|
package/dist/container.mjs
CHANGED
|
@@ -10,18 +10,48 @@ import { validateScopeRules } from "./scope-validation.mjs";
|
|
|
10
10
|
import { ScopeManager } from "./scope.mjs";
|
|
11
11
|
import { isDevelopmentOrTestEnvironment } from "./environment.mjs";
|
|
12
12
|
//#region src/container.ts
|
|
13
|
+
/**
|
|
14
|
+
* Derives a {@link ResolveHint} from a binding's name or first tag.
|
|
15
|
+
* Used by {@link DefaultContainer.initializeAsync} to re-resolve named/tagged singletons
|
|
16
|
+
* through the standard resolution path.
|
|
17
|
+
*/
|
|
13
18
|
function resolveHintForBinding(binding) {
|
|
14
19
|
if (binding.bindingName !== void 0) return { name: binding.bindingName };
|
|
15
20
|
for (const [tagKey, tagValue] of binding.tags) return { tag: [tagKey, tagValue] };
|
|
16
21
|
}
|
|
17
22
|
/**
|
|
23
|
+
* Module {@link ModuleBuilder.bind}: append when the binding is disambiguated **at first
|
|
24
|
+
* registration** (`whenNamed` / `whenTagged` / `when` before `to*()`). Otherwise replace
|
|
25
|
+
* all bindings for the token (last-wins). Chaining `.whenNamed()` after `.to*()` only updates
|
|
26
|
+
* in place and does not enable multi-binding for subsequent module lines — use hint-before-`to*()`
|
|
27
|
+
* in modules (same style as `container.bind(...).whenNamed("x").to*(...)` in the package README).
|
|
28
|
+
*/
|
|
29
|
+
function moduleBindingUsesMultiSlot(built) {
|
|
30
|
+
return built.bindingName !== void 0 || built.tags.size > 0 || built.constraint !== void 0;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
18
33
|
* Default IoC container: registry + scoped caches + synchronous / asynchronous resolution.
|
|
19
34
|
* @internal Implementation of {@link Container}; not part of the public package contract.
|
|
20
35
|
*/
|
|
21
36
|
var DefaultContainer = class DefaultContainer {
|
|
37
|
+
/**
|
|
38
|
+
* Stack guard for detecting circular sync module imports during {@link ensureSyncModuleLoaded}.
|
|
39
|
+
*/
|
|
22
40
|
syncModuleStack = [];
|
|
41
|
+
/**
|
|
42
|
+
* Stack guard for detecting circular async module imports during {@link ensureAsyncModuleLoaded}.
|
|
43
|
+
*/
|
|
23
44
|
asyncModuleStack = [];
|
|
45
|
+
/**
|
|
46
|
+
* Tracks loaded modules → their binding IDs so {@link unload} / {@link unloadAsync} can
|
|
47
|
+
* remove exactly the bindings contributed by each module.
|
|
48
|
+
* Also serves as a deduplication set: a module present as a key is considered loaded.
|
|
49
|
+
*/
|
|
24
50
|
loadedModules = /* @__PURE__ */ new Map();
|
|
51
|
+
/**
|
|
52
|
+
* True after the first dev/test one-shot scope validation has run for the current registry state.
|
|
53
|
+
* Reset to `false` by {@link invalidateDevValidationState} on every registry mutation.
|
|
54
|
+
*/
|
|
25
55
|
devValidationRan = false;
|
|
26
56
|
constructor(ownRegistry, ownScopeManager, parent, resolver, metadataReader) {
|
|
27
57
|
this.ownRegistry = ownRegistry;
|
|
@@ -30,6 +60,10 @@ var DefaultContainer = class DefaultContainer {
|
|
|
30
60
|
this.resolver = resolver;
|
|
31
61
|
this.metadataReader = metadataReader;
|
|
32
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates a root container with an empty registry and fresh singleton/scoped caches.
|
|
65
|
+
* Wires the circular container↔resolver reference via a mutable {@link ContainerRef} holder.
|
|
66
|
+
*/
|
|
33
67
|
static create() {
|
|
34
68
|
const ownRegistry = new BindingRegistry();
|
|
35
69
|
const ownScopeManager = ScopeManager.createRoot();
|
|
@@ -48,7 +82,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
48
82
|
return container;
|
|
49
83
|
}
|
|
50
84
|
/**
|
|
51
|
-
* Starts a fluent
|
|
85
|
+
* Starts a fluent {@link BindingBuilder} for the given token or constructor.
|
|
86
|
+
* The binding is registered into this container's registry immediately when a
|
|
87
|
+
* `to*()` strategy method is called on the returned builder.
|
|
52
88
|
*/
|
|
53
89
|
bind(token) {
|
|
54
90
|
return new BindingBuilder(token, void 0, {
|
|
@@ -162,7 +198,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
162
198
|
this.maybeRunDevValidationOnce();
|
|
163
199
|
}
|
|
164
200
|
}
|
|
165
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* Async variant of {@link resolve}; same dev/test validation and runtime scope enforcement.
|
|
203
|
+
*/
|
|
166
204
|
resolveAsync(key, hint) {
|
|
167
205
|
return this.resolver.resolveAsyncRoot(key, hint).finally(() => {
|
|
168
206
|
this.maybeRunDevValidationOnce();
|
|
@@ -205,9 +243,17 @@ var DefaultContainer = class DefaultContainer {
|
|
|
205
243
|
}
|
|
206
244
|
}
|
|
207
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Marks the dev/test one-shot validation as stale so the next resolve or load triggers it again.
|
|
248
|
+
* Called on every registry mutation (bind, unbind, rebind, load, unload).
|
|
249
|
+
*/
|
|
208
250
|
invalidateDevValidationState() {
|
|
209
251
|
this.devValidationRan = false;
|
|
210
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Runs scope validation at most once per registry epoch when `NODE_ENV` is not `"production"`.
|
|
255
|
+
* Guards against repeated validation on successive resolves without intervening mutations.
|
|
256
|
+
*/
|
|
211
257
|
maybeRunDevValidationOnce() {
|
|
212
258
|
if (!isDevelopmentOrTestEnvironment()) return;
|
|
213
259
|
if (this.devValidationRan) return;
|
|
@@ -257,6 +303,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
257
303
|
[Symbol.dispose]() {
|
|
258
304
|
throw new InternalError("Container disposal is async. Use `await using container = Container.create()` or call `await container.dispose()` instead of `using`.");
|
|
259
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Constructs a {@link ContainerInspector} wired to this container's full hierarchy.
|
|
308
|
+
*/
|
|
260
309
|
createInspector() {
|
|
261
310
|
return new ContainerInspector({
|
|
262
311
|
collectAllRegistryKeys: () => this.collectAllRegistryKeysInHierarchy(),
|
|
@@ -265,11 +314,18 @@ var DefaultContainer = class DefaultContainer {
|
|
|
265
314
|
metadataReader: this.metadataReader
|
|
266
315
|
});
|
|
267
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Collects the union of all registry keys from this container and every ancestor.
|
|
319
|
+
* Deduplicates by reference equality (tokens are objects).
|
|
320
|
+
*/
|
|
268
321
|
collectAllRegistryKeysInHierarchy() {
|
|
269
322
|
const keys = /* @__PURE__ */ new Set();
|
|
270
323
|
this.accumulateRegistryKeysFromHierarchy(keys, this);
|
|
271
324
|
return [...keys];
|
|
272
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Recursive helper: walks the parent chain bottom-up, adding each level's registry keys.
|
|
328
|
+
*/
|
|
273
329
|
accumulateRegistryKeysFromHierarchy(keys, container) {
|
|
274
330
|
if (container === void 0) return;
|
|
275
331
|
for (const entry of container.ownRegistry.listEntries()) keys.add(entry.key);
|
|
@@ -307,11 +363,22 @@ var DefaultContainer = class DefaultContainer {
|
|
|
307
363
|
[Symbol.asyncDispose]() {
|
|
308
364
|
return this.dispose();
|
|
309
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Returns a `bind` function scoped to a module. Registrations are tracked in
|
|
368
|
+
* {@link loadedModules} for {@link unload}.
|
|
369
|
+
*
|
|
370
|
+
* - **Last-wins** (replaces every binding for that token): `bind(token).to*(...)` with no
|
|
371
|
+
* `whenNamed` / `whenTagged` / `when` **before** the `to*()` call.
|
|
372
|
+
* - **Multi-binding** (append): call `whenNamed`, `whenTagged`, and/or `when` **before** `to*()`
|
|
373
|
+
* so the disambiguator exists at registration time — supports `resolveAll` and per-binding
|
|
374
|
+
* hints in {@link Container.initializeAsync}.
|
|
375
|
+
*/
|
|
310
376
|
bindForModule(owner) {
|
|
311
377
|
return (token) => new BindingBuilder(token, owner.name, {
|
|
312
378
|
register: (built) => {
|
|
313
379
|
this.invalidateDevValidationState();
|
|
314
|
-
this.ownRegistry.
|
|
380
|
+
if (moduleBindingUsesMultiSlot(built)) this.ownRegistry.add(token, built);
|
|
381
|
+
else this.ownRegistry.replaceKeyLastWins(token, built, (removed) => {
|
|
315
382
|
this.ownScopeManager.releaseBinding(removed);
|
|
316
383
|
});
|
|
317
384
|
this.recordBindingForModule(owner, built.id);
|
|
@@ -322,6 +389,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
322
389
|
}
|
|
323
390
|
});
|
|
324
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Appends a binding ID to the tracking list for `owner` so {@link unload} can remove it later.
|
|
394
|
+
*/
|
|
325
395
|
recordBindingForModule(owner, id) {
|
|
326
396
|
const list = this.loadedModules.get(owner);
|
|
327
397
|
if (list === void 0) {
|
|
@@ -330,6 +400,10 @@ var DefaultContainer = class DefaultContainer {
|
|
|
330
400
|
}
|
|
331
401
|
list.push(id);
|
|
332
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Creates the {@link ModuleBuilder} passed to a sync module's setup callback.
|
|
405
|
+
* The `import` method throws {@link InternalError} if an {@link AsyncModule} is passed.
|
|
406
|
+
*/
|
|
333
407
|
createSyncModuleBuilder(module) {
|
|
334
408
|
return {
|
|
335
409
|
import: (...deps) => {
|
|
@@ -341,6 +415,11 @@ var DefaultContainer = class DefaultContainer {
|
|
|
341
415
|
bind: this.bindForModule(module)
|
|
342
416
|
};
|
|
343
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Creates the {@link AsyncModuleBuilder} and a companion `awaitImports` thunk.
|
|
420
|
+
* Async sub-imports are collected into `pendingImports` and flushed after the module's
|
|
421
|
+
* own setup returns — this avoids interleaving setup code with dependency loading.
|
|
422
|
+
*/
|
|
344
423
|
createAsyncModuleBuilder(module) {
|
|
345
424
|
const pendingImports = [];
|
|
346
425
|
return {
|
|
@@ -357,6 +436,11 @@ var DefaultContainer = class DefaultContainer {
|
|
|
357
436
|
}
|
|
358
437
|
};
|
|
359
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Loads a sync module if not already loaded. Detects circular module imports via
|
|
441
|
+
* {@link syncModuleStack} and throws {@link CircularDependencyError} with the full cycle path.
|
|
442
|
+
* The module is marked as loaded *before* its setup runs so that re-entrant imports are deduped.
|
|
443
|
+
*/
|
|
360
444
|
ensureSyncModuleLoaded(module) {
|
|
361
445
|
if (this.loadedModules.has(module)) return;
|
|
362
446
|
if (this.syncModuleStack.includes(module)) throw new CircularDependencyError([...this.syncModuleStack.map((stackedModule) => stackedModule.name), module.name]);
|
|
@@ -369,6 +453,10 @@ var DefaultContainer = class DefaultContainer {
|
|
|
369
453
|
this.syncModuleStack.pop();
|
|
370
454
|
}
|
|
371
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Async counterpart of {@link ensureSyncModuleLoaded}: loads the module's async setup,
|
|
458
|
+
* then flushes any pending async sub-imports collected during setup.
|
|
459
|
+
*/
|
|
372
460
|
async ensureAsyncModuleLoaded(asyncModule) {
|
|
373
461
|
if (this.loadedModules.has(asyncModule)) return;
|
|
374
462
|
if (this.asyncModuleStack.includes(asyncModule)) throw new CircularDependencyError([...this.asyncModuleStack.map((stackedAsyncModule) => stackedAsyncModule.name), asyncModule.name]);
|
|
@@ -3,22 +3,53 @@ import { Constructor, ResolveHint } from "../binding.mjs";
|
|
|
3
3
|
import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/decorators/inject.d.ts
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Name/tag hint forwarded to the container when resolving an injected dependency.
|
|
8
|
+
* Alias for {@link ResolveHint}; used as the second parameter of {@link inject} and {@link optional}.
|
|
9
|
+
*/
|
|
7
10
|
type InjectOptions = ResolveHint;
|
|
8
11
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
12
|
+
* Dual-purpose injection helper:
|
|
13
|
+
*
|
|
14
|
+
* **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
|
|
15
|
+
* optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* @injectable([inject(Logger, { name: 'file' })])
|
|
19
|
+
* class UserService { constructor(log: Logger) {} }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
|
|
23
|
+
* `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
|
|
24
|
+
* injection after construction.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* @inject(Logger) accessor logger!: LoggerService;
|
|
28
|
+
* ```
|
|
11
29
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
30
|
+
* @param token - The injection key (token or constructor) to resolve.
|
|
31
|
+
* @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
|
|
32
|
+
* `ClassAccessorDecoratorContext` automatically supplied by the runtime.
|
|
14
33
|
*/
|
|
15
34
|
declare function inject<Value>(token: Token<Value> | Constructor<Value>, optionsOrContext?: InjectOptions | ClassAccessorDecoratorContext): InjectionDescriptor<Value>;
|
|
16
35
|
/**
|
|
17
|
-
* Same as {@link inject} but marks the dependency as optional
|
|
18
|
-
*
|
|
36
|
+
* Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
|
|
37
|
+
* During resolution, an unbound token resolves to `undefined` instead of throwing
|
|
38
|
+
* {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
|
|
19
39
|
*/
|
|
20
40
|
declare function optional<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
|
|
21
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
|
|
43
|
+
* (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
|
|
44
|
+
* Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
|
|
45
|
+
*
|
|
46
|
+
* Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
|
|
47
|
+
* callers omit options and register disambiguators on each binding instead).
|
|
48
|
+
*/
|
|
49
|
+
declare function injectAll<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
|
|
50
|
+
/**
|
|
51
|
+
* Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
|
|
52
|
+
*/
|
|
22
53
|
declare function isInjectionDescriptor(value: unknown): value is InjectionDescriptor;
|
|
23
54
|
//#endregion
|
|
24
|
-
export { InjectOptions, inject, isInjectionDescriptor, optional };
|
|
55
|
+
export { InjectOptions, inject, injectAll, isInjectionDescriptor, optional };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { InternalError } from "../errors.mjs";
|
|
2
2
|
import { CODEFAST_DI_ACCESSOR_INJECTIONS } from "../metadata/metadata-keys.mjs";
|
|
3
3
|
//#region src/decorators/inject.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Validates and normalises the `tag` option from {@link InjectOptions}; throws {@link InternalError} on bad input.
|
|
6
|
+
*/
|
|
5
7
|
function normalizeTag(tag) {
|
|
6
8
|
if (tag === void 0) return;
|
|
7
9
|
if (!Array.isArray(tag) || tag.length !== 2) throw new InternalError(`@inject tag must be a tuple [tagKey, value] with length 2; received ${String(tag)}`);
|
|
@@ -9,7 +11,9 @@ function normalizeTag(tag) {
|
|
|
9
11
|
if (typeof tagName !== "string") throw new InternalError(`@inject tag key must be a string; received ${typeof tagName}`);
|
|
10
12
|
return [tagName, value];
|
|
11
13
|
}
|
|
12
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Builds an {@link InjectionDescriptor} from a token, optional flag, and raw inject options.
|
|
16
|
+
*/
|
|
13
17
|
function toDescriptor(token, optional, options) {
|
|
14
18
|
const normalizedTag = normalizeTag(options?.tag);
|
|
15
19
|
if (options?.name !== void 0) return {
|
|
@@ -27,16 +31,34 @@ function toDescriptor(token, optional, options) {
|
|
|
27
31
|
optional
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Type guard — returns `true` when `value` is a TC39 `ClassAccessorDecoratorContext` (accessor field).
|
|
36
|
+
*/
|
|
31
37
|
function isAccessorDecoratorContext(value) {
|
|
32
38
|
return typeof value === "object" && value !== null && "kind" in value && value.kind === "accessor";
|
|
33
39
|
}
|
|
34
40
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
41
|
+
* Dual-purpose injection helper:
|
|
42
|
+
*
|
|
43
|
+
* **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
|
|
44
|
+
* optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* @injectable([inject(Logger, { name: 'file' })])
|
|
48
|
+
* class UserService { constructor(log: Logger) {} }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
|
|
52
|
+
* `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
|
|
53
|
+
* injection after construction.
|
|
37
54
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* @inject(Logger) accessor logger!: LoggerService;
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param token - The injection key (token or constructor) to resolve.
|
|
60
|
+
* @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
|
|
61
|
+
* `ClassAccessorDecoratorContext` automatically supplied by the runtime.
|
|
40
62
|
*/
|
|
41
63
|
function inject(token, optionsOrContext) {
|
|
42
64
|
if (isAccessorDecoratorContext(optionsOrContext)) {
|
|
@@ -54,16 +76,33 @@ function inject(token, optionsOrContext) {
|
|
|
54
76
|
return toDescriptor(token, false, optionsOrContext);
|
|
55
77
|
}
|
|
56
78
|
/**
|
|
57
|
-
* Same as {@link inject} but marks the dependency as optional
|
|
58
|
-
*
|
|
79
|
+
* Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
|
|
80
|
+
* During resolution, an unbound token resolves to `undefined` instead of throwing
|
|
81
|
+
* {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
|
|
59
82
|
*/
|
|
60
83
|
function optional(token, options) {
|
|
61
84
|
return toDescriptor(token, true, options);
|
|
62
85
|
}
|
|
63
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
|
|
88
|
+
* (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
|
|
89
|
+
* Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
|
|
90
|
+
*
|
|
91
|
+
* Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
|
|
92
|
+
* callers omit options and register disambiguators on each binding instead).
|
|
93
|
+
*/
|
|
94
|
+
function injectAll(token, options) {
|
|
95
|
+
return {
|
|
96
|
+
...toDescriptor(token, false, options),
|
|
97
|
+
all: true
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
|
|
102
|
+
*/
|
|
64
103
|
function isInjectionDescriptor(value) {
|
|
65
104
|
if (typeof value !== "object" || value === null) return false;
|
|
66
105
|
return "token" in value && "optional" in value;
|
|
67
106
|
}
|
|
68
107
|
//#endregion
|
|
69
|
-
export { inject, isInjectionDescriptor, optional };
|
|
108
|
+
export { inject, injectAll, isInjectionDescriptor, optional };
|
|
@@ -6,7 +6,8 @@ import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
|
|
|
6
6
|
/**
|
|
7
7
|
* A single entry in the `deps` array passed to `@injectable()`.
|
|
8
8
|
* Can be a plain token/constructor (resolved with no hint) or an {@link InjectionDescriptor}
|
|
9
|
-
* produced by
|
|
9
|
+
* produced by `inject` / `optional` / `injectAll` when name, tag, optional, or resolve-all
|
|
10
|
+
* semantics are needed.
|
|
10
11
|
*/
|
|
11
12
|
type InjectableDependency = Token<unknown> | Constructor<unknown> | InjectionDescriptor<unknown>;
|
|
12
13
|
/**
|
|
@@ -2,6 +2,11 @@ import { InternalError } from "../errors.mjs";
|
|
|
2
2
|
import { CODEFAST_DI_CONSTRUCTOR_METADATA } from "../metadata/metadata-keys.mjs";
|
|
3
3
|
import { isInjectionDescriptor } from "./inject.mjs";
|
|
4
4
|
//#region src/decorators/injectable.ts
|
|
5
|
+
/**
|
|
6
|
+
* Global mutable registry of classes decorated with `@injectable({ autoRegister: true })`.
|
|
7
|
+
* Populated at class-definition time (via `context.addInitializer`), drained by
|
|
8
|
+
* {@link Container.loadAutoRegistered}. Entries accumulate for the lifetime of the process.
|
|
9
|
+
*/
|
|
5
10
|
const AUTO_REGISTER_REGISTRY = [];
|
|
6
11
|
/**
|
|
7
12
|
* Returns all classes decorated with `@injectable({ autoRegister: true })`.
|
|
@@ -10,14 +15,21 @@ const AUTO_REGISTER_REGISTRY = [];
|
|
|
10
15
|
function getAutoRegistered() {
|
|
11
16
|
return AUTO_REGISTER_REGISTRY;
|
|
12
17
|
}
|
|
13
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Normalises a single `@injectable` deps-array entry into the uniform {@link ParamMetadata}
|
|
20
|
+
* shape used by the resolver's constructor-instantiation path.
|
|
21
|
+
*
|
|
22
|
+
* - {@link InjectionDescriptor} entries carry `optional`, `name`, and `tag` fields.
|
|
23
|
+
* - Plain token / constructor entries are wrapped with `optional: false` and no hint.
|
|
24
|
+
*/
|
|
14
25
|
function toParamMetadata(dependency, index) {
|
|
15
26
|
if (isInjectionDescriptor(dependency)) return {
|
|
16
27
|
index,
|
|
17
28
|
token: dependency.token,
|
|
18
29
|
optional: dependency.optional,
|
|
19
30
|
name: dependency.name,
|
|
20
|
-
tag: dependency.tag
|
|
31
|
+
tag: dependency.tag,
|
|
32
|
+
all: dependency.all === true ? true : void 0
|
|
21
33
|
};
|
|
22
34
|
return {
|
|
23
35
|
index,
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
//#region src/decorators/lifecycle-decorators.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Stage 3 method decorator: marks a method to be called after the class is instantiated
|
|
4
|
-
*
|
|
3
|
+
* Stage 3 method decorator: marks a method to be called after the class is instantiated
|
|
4
|
+
* by the container and before the `onActivation` hook runs.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle order: `new Class(…)` → **`@postConstruct()`** → `onActivation()` → scope cache.
|
|
7
|
+
*
|
|
8
|
+
* Only one method per class may carry this decorator; a second application throws.
|
|
9
|
+
* If the decorated method returns a `Promise` during synchronous resolution,
|
|
10
|
+
* {@link AsyncResolutionError} is thrown — use `Container.resolveAsync()` instead.
|
|
5
11
|
*/
|
|
6
12
|
declare function postConstruct(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
|
|
7
13
|
/**
|
|
8
|
-
* Stage 3 method decorator: marks a method to be called
|
|
9
|
-
*
|
|
14
|
+
* Stage 3 method decorator: marks a method to be called when the container disposes or
|
|
15
|
+
* unloads the owning binding.
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle order: `onDeactivation()` → **`@preDestroy()`**.
|
|
18
|
+
*
|
|
19
|
+
* Only one method per class may carry this decorator; a second application throws.
|
|
20
|
+
* If the decorated method returns a `Promise` during synchronous disposal,
|
|
21
|
+
* an error is thrown — use `Container.disposeAsync()` instead.
|
|
10
22
|
*/
|
|
11
23
|
declare function preDestroy(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
|
|
12
24
|
//#endregion
|