@codefast/di 0.3.13 → 0.3.14-canary.1
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 +30 -0
- package/README.md +270 -234
- package/dist/binding-select.d.mts +17 -6
- package/dist/binding-select.mjs +17 -6
- package/dist/binding.d.mts +167 -34
- package/dist/binding.mjs +111 -14
- package/dist/constraints.d.mts +18 -3
- package/dist/constraints.mjs +18 -3
- package/dist/container.d.mts +85 -35
- package/dist/container.mjs +140 -6
- 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 +36 -13
- package/dist/dependency-graph.mjs +42 -8
- package/dist/errors.d.mts +132 -21
- package/dist/errors.mjs +126 -18
- package/dist/graph-adapters/cytoscape.d.mts +10 -0
- package/dist/graph-adapters/cytoscape.mjs +40 -0
- package/dist/graph-adapters/dot.d.mts +9 -0
- package/dist/graph-adapters/dot.mjs +97 -0
- package/dist/graph-adapters/reactflow.d.mts +10 -0
- package/dist/graph-adapters/reactflow.mjs +80 -0
- package/dist/graph-adapters/types.d.mts +91 -0
- package/dist/graph-adapters/types.mjs +1 -0
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +2 -2
- package/dist/inspector.d.mts +42 -40
- package/dist/inspector.mjs +18 -169
- 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 +42 -18
- 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 +46 -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 +38 -6
- package/dist/scope.mjs +42 -13
- package/dist/token.d.mts +9 -2
- package/dist/token.mjs +7 -1
- package/package.json +18 -2
package/dist/constraints.d.mts
CHANGED
|
@@ -3,15 +3,30 @@ import { ConstraintContext, Constructor } from "./binding.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/constraints.d.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Constraint predicate factory: matches when the direct parent on the materialization stack
|
|
7
|
+
* was registered under `registryKey`. Pass the result to {@link BindingBuilder.when}.
|
|
8
|
+
*
|
|
9
|
+
* @param registryKey - Token or constructor that the parent binding must be registered against.
|
|
10
|
+
* @returns A predicate compatible with {@link BindingBuilder.when}.
|
|
7
11
|
*/
|
|
8
12
|
declare function whenParentIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
|
|
9
13
|
/**
|
|
10
|
-
*
|
|
14
|
+
* Constraint predicate factory: matches when *any* ancestor on the materialization stack
|
|
15
|
+
* (not just the immediate parent) was registered under `registryKey`.
|
|
16
|
+
* Pass the result to {@link BindingBuilder.when}.
|
|
17
|
+
*
|
|
18
|
+
* @param registryKey - Token or constructor to search for across the full construction chain.
|
|
19
|
+
* @returns A predicate compatible with {@link BindingBuilder.when}.
|
|
11
20
|
*/
|
|
12
21
|
declare function whenAnyAncestorIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
23
|
+
* Constraint predicate factory: matches when the immediate parent binding carries a tag
|
|
24
|
+
* whose key is `tag` and whose value is reference-equal to `tagValue` (`Object.is`).
|
|
25
|
+
* Pass the result to {@link BindingBuilder.when}.
|
|
26
|
+
*
|
|
27
|
+
* @param tag - Tag key to check on the parent binding.
|
|
28
|
+
* @param tagValue - Expected value; compared via `Object.is`.
|
|
29
|
+
* @returns A predicate compatible with {@link BindingBuilder.when}.
|
|
15
30
|
*/
|
|
16
31
|
declare function whenTargetTagged(tag: string, tagValue: unknown): (ctx: ConstraintContext) => boolean;
|
|
17
32
|
//#endregion
|
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
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Token } from "./token.mjs";
|
|
2
2
|
import { RegistryKey } from "./registry.mjs";
|
|
3
|
-
import { Binding, BindingBuilder, BindingIdentifier, Constructor, ResolveHint
|
|
4
|
-
import { ContainerGraphJson, ContainerSnapshot,
|
|
3
|
+
import { Binding, BindingBuilder, BindingIdentifier, Constructor, ResolveHint } from "./binding.mjs";
|
|
4
|
+
import { ContainerGraphJson, ContainerSnapshot, GraphOptions } from "./inspector.mjs";
|
|
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,97 @@ 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
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/** Creates a child container that inherits bindings from this container without polluting its registry. */
|
|
92
|
+
/**
|
|
93
|
+
* Returns the canonical dependency graph as typed JSON (`nodes` + `edges`).
|
|
94
|
+
*/
|
|
95
|
+
generateDependencyGraph(options?: GraphOptions): ContainerGraphJson;
|
|
96
|
+
/**
|
|
97
|
+
* Creates a child container that inherits bindings from this container without polluting its registry.
|
|
98
|
+
*/
|
|
61
99
|
createChild(): Container;
|
|
62
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* @throws Always — container disposal is async; use `await using` or `await container.dispose()`.
|
|
102
|
+
*/
|
|
63
103
|
[Symbol.dispose](): never;
|
|
64
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* Returns the raw binding list for a token without triggering resolution. `undefined` means no binding.
|
|
106
|
+
*/
|
|
65
107
|
lookupBindings(token: RegistryKey): readonly Binding<unknown>[] | undefined;
|
|
66
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Runs all `onDeactivation` hooks on active singletons and releases all caches.
|
|
110
|
+
*/
|
|
67
111
|
dispose(): Promise<void>;
|
|
68
112
|
[Symbol.asyncDispose](): Promise<void>;
|
|
69
113
|
}
|
|
@@ -71,12 +115,18 @@ interface Container extends AsyncDisposable {
|
|
|
71
115
|
* Factory functions for {@link Container} instances (interface + namespace merge).
|
|
72
116
|
*/
|
|
73
117
|
declare namespace Container {
|
|
74
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Creates an empty container with no bindings.
|
|
120
|
+
*/
|
|
75
121
|
function create(): Container;
|
|
76
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Creates a container and immediately loads the given sync modules.
|
|
124
|
+
*/
|
|
77
125
|
function fromModules(...modules: Module[]): Container;
|
|
78
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Creates a container and awaits loading of sync and/or async modules.
|
|
128
|
+
*/
|
|
79
129
|
function fromModulesAsync(...modules: (Module | AsyncModule)[]): Promise<Container>;
|
|
80
130
|
}
|
|
81
131
|
//#endregion
|
|
82
|
-
export {
|
|
132
|
+
export { Container };
|
package/dist/container.mjs
CHANGED
|
@@ -10,19 +10,53 @@ 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;
|
|
56
|
+
/**
|
|
57
|
+
* Internal constructor for root/child instances.
|
|
58
|
+
* Use {@link Container.create}, {@link Container.fromModules}, or {@link createChild}.
|
|
59
|
+
*/
|
|
26
60
|
constructor(ownRegistry, ownScopeManager, parent, resolver, metadataReader) {
|
|
27
61
|
this.ownRegistry = ownRegistry;
|
|
28
62
|
this.ownScopeManager = ownScopeManager;
|
|
@@ -30,6 +64,10 @@ var DefaultContainer = class DefaultContainer {
|
|
|
30
64
|
this.resolver = resolver;
|
|
31
65
|
this.metadataReader = metadataReader;
|
|
32
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Creates a root container with an empty registry and fresh singleton/scoped caches.
|
|
69
|
+
* Wires the circular container↔resolver reference via a mutable {@link ContainerRef} holder.
|
|
70
|
+
*/
|
|
33
71
|
static create() {
|
|
34
72
|
const ownRegistry = new BindingRegistry();
|
|
35
73
|
const ownScopeManager = ScopeManager.createRoot();
|
|
@@ -48,7 +86,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
48
86
|
return container;
|
|
49
87
|
}
|
|
50
88
|
/**
|
|
51
|
-
* Starts a fluent
|
|
89
|
+
* Starts a fluent {@link BindingBuilder} for the given token or constructor.
|
|
90
|
+
* The binding is registered into this container's registry immediately when a
|
|
91
|
+
* `to*()` strategy method is called on the returned builder.
|
|
52
92
|
*/
|
|
53
93
|
bind(token) {
|
|
54
94
|
return new BindingBuilder(token, void 0, {
|
|
@@ -62,6 +102,12 @@ var DefaultContainer = class DefaultContainer {
|
|
|
62
102
|
}
|
|
63
103
|
});
|
|
64
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Fast registry presence check without instantiation.
|
|
107
|
+
*
|
|
108
|
+
* When `hint` is provided, this only verifies that at least one binding matches
|
|
109
|
+
* the name/tag discriminator; it does not evaluate runtime `when()` predicates.
|
|
110
|
+
*/
|
|
65
111
|
has(token, hint) {
|
|
66
112
|
const list = this.lookupBindings(token);
|
|
67
113
|
if (list === void 0 || list.length === 0) return false;
|
|
@@ -75,6 +121,12 @@ var DefaultContainer = class DefaultContainer {
|
|
|
75
121
|
return true;
|
|
76
122
|
});
|
|
77
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Removes bindings by token or by binding id and synchronously releases cached instances.
|
|
126
|
+
*
|
|
127
|
+
* - `binding id` path removes one binding and its cache entry.
|
|
128
|
+
* - `token` path removes all owned bindings for that key at once.
|
|
129
|
+
*/
|
|
78
130
|
unbind(tokenOrId) {
|
|
79
131
|
this.invalidateDevValidationState();
|
|
80
132
|
if (typeof tokenOrId === "string") {
|
|
@@ -86,6 +138,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
86
138
|
if (owned !== void 0) for (const binding of owned) this.ownScopeManager.releaseBinding(binding);
|
|
87
139
|
this.ownRegistry.remove(tokenOrId);
|
|
88
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Async counterpart of {@link unbind}; awaits deactivation hooks before registry removal.
|
|
143
|
+
*/
|
|
89
144
|
async unbindAsync(tokenOrId) {
|
|
90
145
|
this.invalidateDevValidationState();
|
|
91
146
|
if (typeof tokenOrId === "string") {
|
|
@@ -97,6 +152,11 @@ var DefaultContainer = class DefaultContainer {
|
|
|
97
152
|
if (owned !== void 0) for (const binding of owned) await this.ownScopeManager.releaseBindingAsync(binding);
|
|
98
153
|
this.ownRegistry.remove(tokenOrId);
|
|
99
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Replaces all owned bindings for `token` and returns a fresh builder.
|
|
157
|
+
*
|
|
158
|
+
* Existing cached instances for the removed bindings are synchronously released first.
|
|
159
|
+
*/
|
|
100
160
|
rebind(token) {
|
|
101
161
|
this.invalidateDevValidationState();
|
|
102
162
|
const owned = this.ownRegistry.get(token);
|
|
@@ -162,12 +222,18 @@ var DefaultContainer = class DefaultContainer {
|
|
|
162
222
|
this.maybeRunDevValidationOnce();
|
|
163
223
|
}
|
|
164
224
|
}
|
|
165
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* Async variant of {@link resolve}; same dev/test validation and runtime scope enforcement.
|
|
227
|
+
*/
|
|
166
228
|
resolveAsync(key, hint) {
|
|
167
229
|
return this.resolver.resolveAsyncRoot(key, hint).finally(() => {
|
|
168
230
|
this.maybeRunDevValidationOnce();
|
|
169
231
|
});
|
|
170
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Optional root resolution: returns `undefined` when the requested key is absent (or filtered out
|
|
235
|
+
* without a name/tag hint), while preserving normal errors for nested required dependencies.
|
|
236
|
+
*/
|
|
171
237
|
resolveOptional(key, hint) {
|
|
172
238
|
try {
|
|
173
239
|
return this.resolver.resolveOptionalRoot(key, hint);
|
|
@@ -175,6 +241,10 @@ var DefaultContainer = class DefaultContainer {
|
|
|
175
241
|
this.maybeRunDevValidationOnce();
|
|
176
242
|
}
|
|
177
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Synchronously resolves every matching binding for a key.
|
|
246
|
+
* Returns an empty array when no binding exists.
|
|
247
|
+
*/
|
|
178
248
|
resolveAll(key, hint) {
|
|
179
249
|
try {
|
|
180
250
|
return this.resolver.resolveAllRoot(key, hint);
|
|
@@ -205,9 +275,17 @@ var DefaultContainer = class DefaultContainer {
|
|
|
205
275
|
}
|
|
206
276
|
}
|
|
207
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Marks the dev/test one-shot validation as stale so the next resolve or load triggers it again.
|
|
280
|
+
* Called on every registry mutation (bind, unbind, rebind, load, unload).
|
|
281
|
+
*/
|
|
208
282
|
invalidateDevValidationState() {
|
|
209
283
|
this.devValidationRan = false;
|
|
210
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Runs scope validation at most once per registry epoch when `NODE_ENV` is not `"production"`.
|
|
287
|
+
* Guards against repeated validation on successive resolves without intervening mutations.
|
|
288
|
+
*/
|
|
211
289
|
maybeRunDevValidationOnce() {
|
|
212
290
|
if (!isDevelopmentOrTestEnvironment()) return;
|
|
213
291
|
if (this.devValidationRan) return;
|
|
@@ -231,11 +309,16 @@ var DefaultContainer = class DefaultContainer {
|
|
|
231
309
|
inspect() {
|
|
232
310
|
return this.createInspector().getSnapshot();
|
|
233
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Delegates canonical dependency-graph generation to {@link ContainerInspector}.
|
|
314
|
+
*/
|
|
234
315
|
generateDependencyGraph(options) {
|
|
235
|
-
|
|
236
|
-
if (options?.format === "json") return inspector.generateDependencyGraph(options);
|
|
237
|
-
return inspector.generateDotGraph(options);
|
|
316
|
+
return this.createInspector().generateDependencyGraph(options);
|
|
238
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Registers every class collected by `@injectable({ autoRegister: true })`.
|
|
320
|
+
* Returns how many entries were processed.
|
|
321
|
+
*/
|
|
239
322
|
loadAutoRegistered() {
|
|
240
323
|
const entries = getAutoRegistered();
|
|
241
324
|
let count = 0;
|
|
@@ -257,6 +340,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
257
340
|
[Symbol.dispose]() {
|
|
258
341
|
throw new InternalError("Container disposal is async. Use `await using container = Container.create()` or call `await container.dispose()` instead of `using`.");
|
|
259
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Constructs a {@link ContainerInspector} wired to this container's full hierarchy.
|
|
345
|
+
*/
|
|
260
346
|
createInspector() {
|
|
261
347
|
return new ContainerInspector({
|
|
262
348
|
collectAllRegistryKeys: () => this.collectAllRegistryKeysInHierarchy(),
|
|
@@ -265,11 +351,18 @@ var DefaultContainer = class DefaultContainer {
|
|
|
265
351
|
metadataReader: this.metadataReader
|
|
266
352
|
});
|
|
267
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Collects the union of all registry keys from this container and every ancestor.
|
|
356
|
+
* Deduplicates by reference equality (tokens are objects).
|
|
357
|
+
*/
|
|
268
358
|
collectAllRegistryKeysInHierarchy() {
|
|
269
359
|
const keys = /* @__PURE__ */ new Set();
|
|
270
360
|
this.accumulateRegistryKeysFromHierarchy(keys, this);
|
|
271
361
|
return [...keys];
|
|
272
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Recursive helper: walks the parent chain bottom-up, adding each level's registry keys.
|
|
365
|
+
*/
|
|
273
366
|
accumulateRegistryKeysFromHierarchy(keys, container) {
|
|
274
367
|
if (container === void 0) return;
|
|
275
368
|
for (const entry of container.ownRegistry.listEntries()) keys.add(entry.key);
|
|
@@ -296,22 +389,42 @@ var DefaultContainer = class DefaultContainer {
|
|
|
296
389
|
holder.current = child;
|
|
297
390
|
return child;
|
|
298
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Lookup helper with parent fallback: own bindings take precedence over parent bindings.
|
|
394
|
+
*/
|
|
299
395
|
lookupBindings(token) {
|
|
300
396
|
const own = this.ownRegistry.get(token);
|
|
301
397
|
if (own !== void 0 && own.length > 0) return own;
|
|
302
398
|
return this.parent?.lookupBindings(token);
|
|
303
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Disposes this container's scope manager and runs async deactivation hooks.
|
|
402
|
+
*/
|
|
304
403
|
async dispose() {
|
|
305
404
|
await this.ownScopeManager.disposeAsync();
|
|
306
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Async-dispose protocol hook used by `await using`.
|
|
408
|
+
*/
|
|
307
409
|
[Symbol.asyncDispose]() {
|
|
308
410
|
return this.dispose();
|
|
309
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Returns a `bind` function scoped to a module. Registrations are tracked in
|
|
414
|
+
* {@link loadedModules} for {@link unload}.
|
|
415
|
+
*
|
|
416
|
+
* - **Last-wins** (replaces every binding for that token): `bind(token).to*(...)` with no
|
|
417
|
+
* `whenNamed` / `whenTagged` / `when` **before** the `to*()` call.
|
|
418
|
+
* - **Multi-binding** (append): call `whenNamed`, `whenTagged`, and/or `when` **before** `to*()`
|
|
419
|
+
* so the disambiguator exists at registration time — supports `resolveAll` and per-binding
|
|
420
|
+
* hints in {@link Container.initializeAsync}.
|
|
421
|
+
*/
|
|
310
422
|
bindForModule(owner) {
|
|
311
423
|
return (token) => new BindingBuilder(token, owner.name, {
|
|
312
424
|
register: (built) => {
|
|
313
425
|
this.invalidateDevValidationState();
|
|
314
|
-
this.ownRegistry.
|
|
426
|
+
if (moduleBindingUsesMultiSlot(built)) this.ownRegistry.add(token, built);
|
|
427
|
+
else this.ownRegistry.replaceKeyLastWins(token, built, (removed) => {
|
|
315
428
|
this.ownScopeManager.releaseBinding(removed);
|
|
316
429
|
});
|
|
317
430
|
this.recordBindingForModule(owner, built.id);
|
|
@@ -322,6 +435,9 @@ var DefaultContainer = class DefaultContainer {
|
|
|
322
435
|
}
|
|
323
436
|
});
|
|
324
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Appends a binding ID to the tracking list for `owner` so {@link unload} can remove it later.
|
|
440
|
+
*/
|
|
325
441
|
recordBindingForModule(owner, id) {
|
|
326
442
|
const list = this.loadedModules.get(owner);
|
|
327
443
|
if (list === void 0) {
|
|
@@ -330,6 +446,10 @@ var DefaultContainer = class DefaultContainer {
|
|
|
330
446
|
}
|
|
331
447
|
list.push(id);
|
|
332
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Creates the {@link ModuleBuilder} passed to a sync module's setup callback.
|
|
451
|
+
* The `import` method throws {@link InternalError} if an {@link AsyncModule} is passed.
|
|
452
|
+
*/
|
|
333
453
|
createSyncModuleBuilder(module) {
|
|
334
454
|
return {
|
|
335
455
|
import: (...deps) => {
|
|
@@ -341,6 +461,11 @@ var DefaultContainer = class DefaultContainer {
|
|
|
341
461
|
bind: this.bindForModule(module)
|
|
342
462
|
};
|
|
343
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Creates the {@link AsyncModuleBuilder} and a companion `awaitImports` thunk.
|
|
466
|
+
* Async sub-imports are collected into `pendingImports` and flushed after the module's
|
|
467
|
+
* own setup returns — this avoids interleaving setup code with dependency loading.
|
|
468
|
+
*/
|
|
344
469
|
createAsyncModuleBuilder(module) {
|
|
345
470
|
const pendingImports = [];
|
|
346
471
|
return {
|
|
@@ -357,6 +482,11 @@ var DefaultContainer = class DefaultContainer {
|
|
|
357
482
|
}
|
|
358
483
|
};
|
|
359
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Loads a sync module if not already loaded. Detects circular module imports via
|
|
487
|
+
* {@link syncModuleStack} and throws {@link CircularDependencyError} with the full cycle path.
|
|
488
|
+
* The module is marked as loaded *before* its setup runs so that re-entrant imports are deduped.
|
|
489
|
+
*/
|
|
360
490
|
ensureSyncModuleLoaded(module) {
|
|
361
491
|
if (this.loadedModules.has(module)) return;
|
|
362
492
|
if (this.syncModuleStack.includes(module)) throw new CircularDependencyError([...this.syncModuleStack.map((stackedModule) => stackedModule.name), module.name]);
|
|
@@ -369,6 +499,10 @@ var DefaultContainer = class DefaultContainer {
|
|
|
369
499
|
this.syncModuleStack.pop();
|
|
370
500
|
}
|
|
371
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* Async counterpart of {@link ensureSyncModuleLoaded}: loads the module's async setup,
|
|
504
|
+
* then flushes any pending async sub-imports collected during setup.
|
|
505
|
+
*/
|
|
372
506
|
async ensureAsyncModuleLoaded(asyncModule) {
|
|
373
507
|
if (this.loadedModules.has(asyncModule)) return;
|
|
374
508
|
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 };
|