@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
|
@@ -2,53 +2,77 @@ import { Token } from "../token.mjs";
|
|
|
2
2
|
import { Constructor } from "../binding.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/metadata/metadata-types.d.ts
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Metadata written per `accessor` field decorated with `@inject`; collected into `Symbol.metadata`.
|
|
7
|
+
*/
|
|
6
8
|
type AccessorInjectionMetadata = {
|
|
7
|
-
readonly name: string;
|
|
8
|
-
readonly token: Token<unknown> | Constructor<unknown>;
|
|
9
|
-
readonly optional: boolean;
|
|
9
|
+
/** Accessor property name to inject after construction. */readonly name: string; /** Token/constructor resolved for this accessor. */
|
|
10
|
+
readonly token: Token<unknown> | Constructor<unknown>; /** Whether missing binding resolves to `undefined` instead of throwing. */
|
|
11
|
+
readonly optional: boolean; /** Optional name/tag filter forwarded to binding selection. */
|
|
10
12
|
readonly resolveHint?: {
|
|
11
|
-
readonly name?: string;
|
|
13
|
+
/** Named-binding discriminator (`whenNamed`). */readonly name?: string; /** Tagged-binding discriminator (`whenTagged`). */
|
|
12
14
|
readonly tag?: readonly [tag: string, value: unknown];
|
|
13
15
|
};
|
|
14
16
|
};
|
|
15
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Lifecycle method names written by `@postConstruct()` / `@preDestroy()` into `Symbol.metadata`.
|
|
19
|
+
*/
|
|
16
20
|
type LifecycleMetadata = {
|
|
17
|
-
readonly postConstruct?: string;
|
|
21
|
+
/** Method name marked with `@postConstruct()`. */readonly postConstruct?: string; /** Method name marked with `@preDestroy()`. */
|
|
18
22
|
readonly preDestroy?: string;
|
|
19
|
-
readonly accessorInjections?: readonly AccessorInjectionMetadata[];
|
|
20
23
|
};
|
|
21
24
|
/**
|
|
22
25
|
* Per-parameter injection description collected by `@injectable()`.
|
|
23
26
|
*/
|
|
24
27
|
type ParamMetadata = {
|
|
25
|
-
readonly index: number;
|
|
26
|
-
readonly token: Token<unknown> | Constructor<unknown>;
|
|
27
|
-
readonly optional: boolean;
|
|
28
|
-
readonly name?: string;
|
|
28
|
+
/** Zero-based constructor parameter index. */readonly index: number; /** Token/constructor used to resolve this parameter. */
|
|
29
|
+
readonly token: Token<unknown> | Constructor<unknown>; /** Whether missing binding resolves to `undefined`. */
|
|
30
|
+
readonly optional: boolean; /** Optional named-binding discriminator. */
|
|
31
|
+
readonly name?: string; /** Optional tagged-binding discriminator. */
|
|
29
32
|
readonly tag?: readonly [tag: string, value: unknown];
|
|
33
|
+
/**
|
|
34
|
+
* When true, the parameter receives every binding for `token` as an array (same semantics as
|
|
35
|
+
* `Container.resolveAll` / `ResolutionContext.resolveAll`), using `name` / `tag` as a filter when set.
|
|
36
|
+
*/
|
|
37
|
+
readonly all?: boolean;
|
|
30
38
|
};
|
|
31
39
|
/**
|
|
32
40
|
* Resolved form of an `inject()` / `optional()` call: token + optional flag + optional resolve hint.
|
|
33
41
|
* Used both as a deps-array entry in `@injectable()` and as accessor-field injection metadata.
|
|
34
42
|
*/
|
|
35
43
|
type InjectionDescriptor<Value = unknown> = {
|
|
36
|
-
readonly token: Token<Value> | Constructor<Value>;
|
|
37
|
-
readonly optional: boolean;
|
|
38
|
-
readonly name?: string;
|
|
39
|
-
readonly tag?: readonly [tag: string, value: unknown];
|
|
44
|
+
/** Token/constructor to resolve. */readonly token: Token<Value> | Constructor<Value>; /** Whether unbound token should resolve as `undefined`. */
|
|
45
|
+
readonly optional: boolean; /** Optional named-binding discriminator. */
|
|
46
|
+
readonly name?: string; /** Optional tagged-binding discriminator. */
|
|
47
|
+
readonly tag?: readonly [tag: string, value: unknown]; /** When true, resolve every binding for {@link InjectionDescriptor.token} into an array. */
|
|
48
|
+
readonly all?: boolean;
|
|
40
49
|
};
|
|
41
50
|
/**
|
|
42
51
|
* Constructor injection shape stored on the class `Symbol.metadata` object.
|
|
43
52
|
*/
|
|
44
53
|
type ConstructorMetadata = {
|
|
45
|
-
readonly params: readonly ParamMetadata[];
|
|
54
|
+
/** Ordered constructor dependency descriptors. */readonly params: readonly ParamMetadata[];
|
|
46
55
|
};
|
|
47
56
|
/**
|
|
48
|
-
* Abstraction for reading DI metadata
|
|
57
|
+
* Abstraction for reading DI metadata without tying callers to `Symbol.metadata` directly.
|
|
58
|
+
* The {@link DependencyResolver} uses this to instantiate `class` bindings and read lifecycle hooks.
|
|
59
|
+
*
|
|
60
|
+
* The default implementation is {@link SymbolMetadataReader}; consumers can supply a custom
|
|
61
|
+
* reader (e.g. backed by a static config object) via `ResolverDependencies.metadataReader`.
|
|
49
62
|
*/
|
|
50
63
|
type MetadataReader = {
|
|
64
|
+
/**
|
|
65
|
+
* Returns constructor parameter injection metadata for `implementationClass`, or `undefined`
|
|
66
|
+
* if the class has no own `@injectable()` metadata. When a {@link MetadataReader} is
|
|
67
|
+
* configured on the container, `undefined` here combined with `arity > 0` on the class
|
|
68
|
+
* causes {@link MissingMetadataError} during resolution; when no reader is configured, the
|
|
69
|
+
* resolver instantiates with zero arguments instead (see `DependencyResolver` class binding path).
|
|
70
|
+
*/
|
|
51
71
|
getConstructorMetadata(implementationClass: Constructor<unknown>): ConstructorMetadata | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Returns lifecycle method names (`@postConstruct` / `@preDestroy`), or `undefined` if none.
|
|
74
|
+
* Optional: when absent the resolver skips lifecycle hooks entirely.
|
|
75
|
+
*/
|
|
52
76
|
getLifecycleMetadata?(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
|
|
53
77
|
};
|
|
54
78
|
//#endregion
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
//#region src/metadata/param-registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* WeakMap keyed by constructor → pending parameter metadata.
|
|
4
|
+
* Entries are populated by legacy parameter decorators that fire *before* the
|
|
5
|
+
* `@injectable()` class decorator runs, and consumed (via {@link takePendingMap})
|
|
6
|
+
* by the class decorator to merge into the final {@link ConstructorMetadata}.
|
|
7
|
+
*/
|
|
2
8
|
const pendingByConstructor = /* @__PURE__ */ new WeakMap();
|
|
3
9
|
/**
|
|
4
10
|
* Returns the pending `ParamMetadata` map for `implementationClass`, creating it on first access.
|
|
@@ -3,12 +3,29 @@ import { ConstructorMetadata, LifecycleMetadata, MetadataReader } from "./metada
|
|
|
3
3
|
|
|
4
4
|
//#region src/metadata/symbol-metadata-reader.d.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Default {@link MetadataReader} implementation backed by TC39 `Symbol.metadata`.
|
|
7
|
+
*
|
|
8
|
+
* Constructor metadata (`@injectable()`) is read with `Object.hasOwn` to prevent
|
|
9
|
+
* a subclass from silently inheriting a parent's dependency list — each class must
|
|
10
|
+
* declare its own `@injectable()` decorator or be constructed with zero arguments.
|
|
11
|
+
*
|
|
12
|
+
* Lifecycle metadata (`@postConstruct` / `@preDestroy`) *is* inherited through the
|
|
13
|
+
* prototype chain, matching the intent that a parent's lifecycle hook applies to children.
|
|
7
14
|
*/
|
|
8
15
|
declare class SymbolMetadataReader implements MetadataReader {
|
|
9
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Reads constructor param metadata written by `@injectable()`.
|
|
18
|
+
* Returns `undefined` if no own metadata is present on the class.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Uses `Object.hasOwn` intentionally — TC39 `Symbol.metadata` prototype-chains from
|
|
22
|
+
* parent to child, so without this guard a subclass without `@injectable()` would
|
|
23
|
+
* silently inherit the parent's parameter list and inject the wrong dependency count.
|
|
24
|
+
*/
|
|
10
25
|
getConstructorMetadata(implementationClass: Constructor<unknown>): ConstructorMetadata | undefined;
|
|
11
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes.
|
|
28
|
+
*/
|
|
12
29
|
getLifecycleMetadata(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
|
|
13
30
|
}
|
|
14
31
|
//#endregion
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import { CODEFAST_DI_CONSTRUCTOR_METADATA, CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol } from "./metadata-keys.mjs";
|
|
2
2
|
//#region src/metadata/symbol-metadata-reader.ts
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Type guard — returns `true` when `value` has the shape of a {@link ConstructorMetadata} object.
|
|
5
|
+
*/
|
|
4
6
|
function isConstructorMetadata(value) {
|
|
5
7
|
if (typeof value !== "object" || value === null || !("params" in value)) return false;
|
|
6
8
|
return Array.isArray(value.params);
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* Default {@link MetadataReader} implementation backed by TC39 `Symbol.metadata`.
|
|
12
|
+
*
|
|
13
|
+
* Constructor metadata (`@injectable()`) is read with `Object.hasOwn` to prevent
|
|
14
|
+
* a subclass from silently inheriting a parent's dependency list — each class must
|
|
15
|
+
* declare its own `@injectable()` decorator or be constructed with zero arguments.
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle metadata (`@postConstruct` / `@preDestroy`) *is* inherited through the
|
|
18
|
+
* prototype chain, matching the intent that a parent's lifecycle hook applies to children.
|
|
10
19
|
*/
|
|
11
20
|
var SymbolMetadataReader = class {
|
|
12
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Reads constructor param metadata written by `@injectable()`.
|
|
23
|
+
* Returns `undefined` if no own metadata is present on the class.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* Uses `Object.hasOwn` intentionally — TC39 `Symbol.metadata` prototype-chains from
|
|
27
|
+
* parent to child, so without this guard a subclass without `@injectable()` would
|
|
28
|
+
* silently inherit the parent's parameter list and inject the wrong dependency count.
|
|
29
|
+
*/
|
|
13
30
|
getConstructorMetadata(implementationClass) {
|
|
14
31
|
const rawMetadata = implementationClass[decoratorMetadataObjectSymbol()];
|
|
15
32
|
if (typeof rawMetadata !== "object" || rawMetadata === null) return;
|
|
@@ -19,7 +36,9 @@ var SymbolMetadataReader = class {
|
|
|
19
36
|
if (!isConstructorMetadata(raw)) return;
|
|
20
37
|
return raw;
|
|
21
38
|
}
|
|
22
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes.
|
|
41
|
+
*/
|
|
23
42
|
getLifecycleMetadata(implementationClass) {
|
|
24
43
|
const metadataObject = implementationClass[decoratorMetadataObjectSymbol()];
|
|
25
44
|
if (typeof metadataObject !== "object" || metadataObject === null) return;
|
package/dist/module.d.mts
CHANGED
|
@@ -3,17 +3,42 @@ import { BindingBuilder, Constructor } from "./binding.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/module.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Builder passed to
|
|
6
|
+
* Builder passed to the setup callback of a synchronous {@link Module}.
|
|
7
|
+
* Use `import()` to declare module dependencies (sync modules only —
|
|
8
|
+
* passing an {@link AsyncModule} throws {@link InternalError}) and `bind()` to register tokens.
|
|
9
|
+
*
|
|
10
|
+
* **Single slot (last-wins)** — `bind(key).to*(...)` with no `whenNamed` / `whenTagged` / `when`
|
|
11
|
+
* *before* the `to*()` call replaces all prior bindings for `key` from this module pass.
|
|
12
|
+
*
|
|
13
|
+
* **Multi-binding** — put at least one disambiguator *before* `to*()` (e.g.
|
|
14
|
+
* `bind(key).whenNamed("a").to*(...)`, `whenTagged` before `to*()`, or `when` before `to*()`).
|
|
15
|
+
* Each such line **appends** another binding so {@link Container.resolveAll} can return every
|
|
16
|
+
* implementation. Use this order in modules; chaining `.to*(...).whenNamed()` only updates that
|
|
17
|
+
* binding in place and does not stack multiple registrations across lines.
|
|
7
18
|
*/
|
|
8
19
|
type ModuleBuilder = {
|
|
20
|
+
/**
|
|
21
|
+
* Declares synchronous module dependencies to load before/alongside current setup.
|
|
22
|
+
*/
|
|
9
23
|
readonly import: (...modules: Module[]) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Starts binding registration for a token/constructor within this module setup pass.
|
|
26
|
+
*/
|
|
10
27
|
readonly bind: <Value>(key: Token<Value> | Constructor<Value>) => BindingBuilder<Value>;
|
|
11
28
|
};
|
|
12
29
|
/**
|
|
13
|
-
* Builder passed to
|
|
30
|
+
* Builder passed to the setup callback of an {@link AsyncModule}.
|
|
31
|
+
* Unlike {@link ModuleBuilder}, `import()` accepts both sync and async modules.
|
|
32
|
+
* Async sub-imports are collected and awaited **after** the setup callback returns.
|
|
14
33
|
*/
|
|
15
34
|
type AsyncModuleBuilder = {
|
|
35
|
+
/**
|
|
36
|
+
* Declares sync/async module dependencies to be loaded by the async module loader.
|
|
37
|
+
*/
|
|
16
38
|
readonly import: (...modules: (Module | AsyncModule)[]) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Starts binding registration for a token/constructor within this async module setup pass.
|
|
41
|
+
*/
|
|
17
42
|
readonly bind: <Value>(key: Token<Value> | Constructor<Value>) => BindingBuilder<Value>;
|
|
18
43
|
};
|
|
19
44
|
/**
|
|
@@ -24,8 +49,17 @@ type AsyncModuleBuilder = {
|
|
|
24
49
|
* binding ids each module produced; the module itself never sees a container reference.
|
|
25
50
|
*/
|
|
26
51
|
declare class Module {
|
|
52
|
+
/**
|
|
53
|
+
* Human-readable label used in error messages, graph output, and module-cycle diagnostics.
|
|
54
|
+
*/
|
|
27
55
|
readonly name: string;
|
|
56
|
+
/**
|
|
57
|
+
* The user-supplied setup callback; invoked exactly once per `load()` call.
|
|
58
|
+
*/
|
|
28
59
|
private readonly syncSetup;
|
|
60
|
+
/**
|
|
61
|
+
* @internal Use {@link Module.create} instead.
|
|
62
|
+
*/
|
|
29
63
|
private constructor();
|
|
30
64
|
/**
|
|
31
65
|
* Defines a synchronous module.
|
|
@@ -46,9 +80,19 @@ declare class Module {
|
|
|
46
80
|
/**
|
|
47
81
|
* An async module whose setup callback may `await` before registering bindings.
|
|
48
82
|
* Prefer {@link Module.createAsync} over constructing this class directly.
|
|
83
|
+
*
|
|
84
|
+
* Load via `Container.loadAsync()` or `Container.fromModulesAsync()`;
|
|
85
|
+
* passing an `AsyncModule` to the synchronous `Container.load()` throws
|
|
86
|
+
* {@link AsyncModuleLoadError}.
|
|
49
87
|
*/
|
|
50
88
|
declare class AsyncModule {
|
|
89
|
+
/**
|
|
90
|
+
* Human-readable label used in error messages and graph output.
|
|
91
|
+
*/
|
|
51
92
|
readonly name: string;
|
|
93
|
+
/**
|
|
94
|
+
* The user-supplied async setup callback; invoked exactly once per `loadAsync()` call.
|
|
95
|
+
*/
|
|
52
96
|
private readonly asyncSetup;
|
|
53
97
|
constructor(name: string, asyncSetup: (builder: AsyncModuleBuilder) => Promise<void>);
|
|
54
98
|
/**
|
package/dist/module.mjs
CHANGED
|
@@ -7,8 +7,17 @@
|
|
|
7
7
|
* binding ids each module produced; the module itself never sees a container reference.
|
|
8
8
|
*/
|
|
9
9
|
var Module = class Module {
|
|
10
|
+
/**
|
|
11
|
+
* Human-readable label used in error messages, graph output, and module-cycle diagnostics.
|
|
12
|
+
*/
|
|
10
13
|
name;
|
|
14
|
+
/**
|
|
15
|
+
* The user-supplied setup callback; invoked exactly once per `load()` call.
|
|
16
|
+
*/
|
|
11
17
|
syncSetup;
|
|
18
|
+
/**
|
|
19
|
+
* @internal Use {@link Module.create} instead.
|
|
20
|
+
*/
|
|
12
21
|
constructor(name, syncSetup) {
|
|
13
22
|
this.name = name;
|
|
14
23
|
this.syncSetup = syncSetup;
|
|
@@ -38,9 +47,19 @@ var Module = class Module {
|
|
|
38
47
|
/**
|
|
39
48
|
* An async module whose setup callback may `await` before registering bindings.
|
|
40
49
|
* Prefer {@link Module.createAsync} over constructing this class directly.
|
|
50
|
+
*
|
|
51
|
+
* Load via `Container.loadAsync()` or `Container.fromModulesAsync()`;
|
|
52
|
+
* passing an `AsyncModule` to the synchronous `Container.load()` throws
|
|
53
|
+
* {@link AsyncModuleLoadError}.
|
|
41
54
|
*/
|
|
42
55
|
var AsyncModule = class {
|
|
56
|
+
/**
|
|
57
|
+
* Human-readable label used in error messages and graph output.
|
|
58
|
+
*/
|
|
43
59
|
name;
|
|
60
|
+
/**
|
|
61
|
+
* The user-supplied async setup callback; invoked exactly once per `loadAsync()` call.
|
|
62
|
+
*/
|
|
44
63
|
asyncSetup;
|
|
45
64
|
constructor(name, asyncSetup) {
|
|
46
65
|
this.name = name;
|
package/dist/registry.d.mts
CHANGED
|
@@ -7,15 +7,39 @@ import { Binding, BindingIdentifier, Constructor } from "./binding.mjs";
|
|
|
7
7
|
*/
|
|
8
8
|
type RegistryKey = Token<unknown> | Constructor<unknown>;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Flat, in-memory storage for {@link Binding} entries keyed by {@link RegistryKey}.
|
|
11
|
+
* Each key maps to an ordered list of bindings (multi-binding support).
|
|
12
|
+
*
|
|
13
|
+
* The registry is a "dumb" store — it does not perform selection, scope caching, or
|
|
14
|
+
* lifecycle management. Those concerns live in `DependencyResolver` and `ScopeManager`.
|
|
15
|
+
*
|
|
16
|
+
* Mutation styles:
|
|
17
|
+
* - `add` — append-only; never removes existing entries.
|
|
18
|
+
* - `replaceById` — swaps a single binding in place by ID; no removal callback.
|
|
19
|
+
* - `remove` / `removeById` — delete entries **without** notifying callers; the caller
|
|
20
|
+
* is responsible for draining the scope cache before calling these.
|
|
21
|
+
* - `replaceKeyLastWins` — replaces all bindings for a key with a single new one **and**
|
|
22
|
+
* invokes the `onReplaced` callback for each evicted binding, giving the caller
|
|
23
|
+
* (typically the container or scope manager) a chance to run deactivation.
|
|
11
24
|
*/
|
|
12
25
|
declare class BindingRegistry {
|
|
26
|
+
/**
|
|
27
|
+
* Primary index: registry key → ordered binding list.
|
|
28
|
+
* Reference equality on the key (i.e. the same {@link Token} or {@link Constructor} object).
|
|
29
|
+
*/
|
|
13
30
|
private readonly bindingsByKey;
|
|
14
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Appends `binding` to the list for `key` (multi-binding: each call adds an entry).
|
|
33
|
+
*/
|
|
15
34
|
add<Value>(key: Token<Value> | Constructor<Value>, binding: Binding<Value>): void;
|
|
16
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Returns all bindings registered for `key`, or `undefined` if none exist.
|
|
37
|
+
*/
|
|
17
38
|
get<Value>(key: Token<Value> | Constructor<Value>): readonly Binding<Value>[] | undefined;
|
|
18
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Removes all bindings for `key`. Does **not** invoke any callback — the caller must
|
|
41
|
+
* drain the scope cache (run deactivation) for the affected bindings before calling this.
|
|
42
|
+
*/
|
|
19
43
|
remove(key: RegistryKey): void;
|
|
20
44
|
/**
|
|
21
45
|
* Returns owned registry rows (does not include parent containers).
|
|
@@ -24,13 +48,20 @@ declare class BindingRegistry {
|
|
|
24
48
|
key: RegistryKey;
|
|
25
49
|
bindings: readonly Binding<unknown>[];
|
|
26
50
|
}[];
|
|
27
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Removes the single binding whose `id` matches, scanning all keys.
|
|
53
|
+
* Like {@link remove}, does **not** invoke a removal callback.
|
|
54
|
+
*/
|
|
28
55
|
removeById(id: BindingIdentifier): void;
|
|
29
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Swaps the binding with the given `id` in place, preserving its position in the list.
|
|
58
|
+
*/
|
|
30
59
|
replaceById(id: BindingIdentifier, next: Binding<unknown>): void;
|
|
31
60
|
/**
|
|
32
|
-
* Replaces all bindings for `key` with a single binding (module "last-wins" semantics).
|
|
33
|
-
*
|
|
61
|
+
* Replaces all bindings for `key` with a single new binding (module "last-wins" semantics).
|
|
62
|
+
* Unlike {@link remove} / {@link removeById}, this method invokes `onReplaced` for every
|
|
63
|
+
* evicted binding **before** inserting the replacement, giving the caller (e.g. the
|
|
64
|
+
* container's scope manager) a chance to release cached instances and run deactivation hooks.
|
|
34
65
|
*/
|
|
35
66
|
replaceKeyLastWins<Value>(key: Token<Value> | Constructor<Value>, binding: Binding<Value>, onReplaced: (removed: Binding<unknown>) => void): void;
|
|
36
67
|
}
|
package/dist/registry.mjs
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
//#region src/registry.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Flat, in-memory storage for {@link Binding} entries keyed by {@link RegistryKey}.
|
|
4
|
+
* Each key maps to an ordered list of bindings (multi-binding support).
|
|
5
|
+
*
|
|
6
|
+
* The registry is a "dumb" store — it does not perform selection, scope caching, or
|
|
7
|
+
* lifecycle management. Those concerns live in `DependencyResolver` and `ScopeManager`.
|
|
8
|
+
*
|
|
9
|
+
* Mutation styles:
|
|
10
|
+
* - `add` — append-only; never removes existing entries.
|
|
11
|
+
* - `replaceById` — swaps a single binding in place by ID; no removal callback.
|
|
12
|
+
* - `remove` / `removeById` — delete entries **without** notifying callers; the caller
|
|
13
|
+
* is responsible for draining the scope cache before calling these.
|
|
14
|
+
* - `replaceKeyLastWins` — replaces all bindings for a key with a single new one **and**
|
|
15
|
+
* invokes the `onReplaced` callback for each evicted binding, giving the caller
|
|
16
|
+
* (typically the container or scope manager) a chance to run deactivation.
|
|
4
17
|
*/
|
|
5
18
|
var BindingRegistry = class {
|
|
19
|
+
/**
|
|
20
|
+
* Primary index: registry key → ordered binding list.
|
|
21
|
+
* Reference equality on the key (i.e. the same {@link Token} or {@link Constructor} object).
|
|
22
|
+
*/
|
|
6
23
|
bindingsByKey = /* @__PURE__ */ new Map();
|
|
7
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Appends `binding` to the list for `key` (multi-binding: each call adds an entry).
|
|
26
|
+
*/
|
|
8
27
|
add(key, binding) {
|
|
9
28
|
const registryKey = key;
|
|
10
29
|
const nextBinding = binding;
|
|
@@ -12,11 +31,16 @@ var BindingRegistry = class {
|
|
|
12
31
|
const merged = existing === void 0 ? [nextBinding] : [...existing, nextBinding];
|
|
13
32
|
this.bindingsByKey.set(registryKey, merged);
|
|
14
33
|
}
|
|
15
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Returns all bindings registered for `key`, or `undefined` if none exist.
|
|
36
|
+
*/
|
|
16
37
|
get(key) {
|
|
17
38
|
return this.bindingsByKey.get(key);
|
|
18
39
|
}
|
|
19
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Removes all bindings for `key`. Does **not** invoke any callback — the caller must
|
|
42
|
+
* drain the scope cache (run deactivation) for the affected bindings before calling this.
|
|
43
|
+
*/
|
|
20
44
|
remove(key) {
|
|
21
45
|
this.bindingsByKey.delete(key);
|
|
22
46
|
}
|
|
@@ -29,7 +53,10 @@ var BindingRegistry = class {
|
|
|
29
53
|
bindings
|
|
30
54
|
}));
|
|
31
55
|
}
|
|
32
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Removes the single binding whose `id` matches, scanning all keys.
|
|
58
|
+
* Like {@link remove}, does **not** invoke a removal callback.
|
|
59
|
+
*/
|
|
33
60
|
removeById(id) {
|
|
34
61
|
for (const [registryKey, list] of [...this.bindingsByKey.entries()]) {
|
|
35
62
|
const filtered = list.filter((binding) => binding.id !== id);
|
|
@@ -38,7 +65,9 @@ var BindingRegistry = class {
|
|
|
38
65
|
else this.bindingsByKey.set(registryKey, filtered);
|
|
39
66
|
}
|
|
40
67
|
}
|
|
41
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Swaps the binding with the given `id` in place, preserving its position in the list.
|
|
70
|
+
*/
|
|
42
71
|
replaceById(id, next) {
|
|
43
72
|
for (const [registryKey, list] of this.bindingsByKey.entries()) {
|
|
44
73
|
const index = list.findIndex((binding) => binding.id === id);
|
|
@@ -50,8 +79,10 @@ var BindingRegistry = class {
|
|
|
50
79
|
}
|
|
51
80
|
}
|
|
52
81
|
/**
|
|
53
|
-
* Replaces all bindings for `key` with a single binding (module "last-wins" semantics).
|
|
54
|
-
*
|
|
82
|
+
* Replaces all bindings for `key` with a single new binding (module "last-wins" semantics).
|
|
83
|
+
* Unlike {@link remove} / {@link removeById}, this method invokes `onReplaced` for every
|
|
84
|
+
* evicted binding **before** inserting the replacement, giving the caller (e.g. the
|
|
85
|
+
* container's scope manager) a chance to release cached instances and run deactivation hooks.
|
|
55
86
|
*/
|
|
56
87
|
replaceKeyLastWins(key, binding, onReplaced) {
|
|
57
88
|
const registryKey = key;
|
package/dist/resolver.d.mts
CHANGED
|
@@ -5,29 +5,114 @@ import { MetadataReader } from "./metadata/metadata-types.mjs";
|
|
|
5
5
|
import { ScopeManager } from "./scope.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/resolver.d.ts
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Dependencies injected into {@link DependencyResolver} at construction time.
|
|
10
|
+
*/
|
|
9
11
|
type ResolverDependencies = {
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Looks up all bindings registered for a given registry key (own + parent containers).
|
|
14
|
+
*/
|
|
15
|
+
readonly lookup: (key: RegistryKey) => readonly Binding<unknown>[] | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Manages singleton/scoped instance caches and deactivation.
|
|
18
|
+
*/
|
|
19
|
+
readonly scopeManager: ScopeManager;
|
|
20
|
+
/**
|
|
21
|
+
* Reads `@injectable()` and lifecycle metadata from constructors. Omit to disable decorator support.
|
|
22
|
+
*/
|
|
12
23
|
readonly metadataReader?: MetadataReader;
|
|
13
24
|
};
|
|
14
25
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
26
|
+
* Stateless graph walker: selects a binding, checks for cycles and scope violations,
|
|
27
|
+
* delegates instance caching to `ScopeManager`, and runs lifecycle hooks.
|
|
28
|
+
*
|
|
29
|
+
* Resolution algorithm (per token):
|
|
30
|
+
* 1. Lookup all bindings for the registry key.
|
|
31
|
+
* 2. Apply name/tag hint and `when()` constraint filtering.
|
|
32
|
+
* 3. Circular-dependency check via a mutable `visiting` set (per call tree).
|
|
33
|
+
* 4. Captive-dependency check via the `materializationStack`.
|
|
34
|
+
* 5. Scope-cache hit → return cached instance.
|
|
35
|
+
* 6. Scope-cache miss → `materialize` → `@postConstruct` → `onActivation` → cache.
|
|
36
|
+
*
|
|
37
|
+
* Used exclusively by `DefaultContainer`; not part of the public API.
|
|
17
38
|
*/
|
|
18
39
|
declare class DependencyResolver {
|
|
19
40
|
private readonly deps;
|
|
20
41
|
constructor(deps: ResolverDependencies);
|
|
21
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Entry point for synchronous single-binding resolution.
|
|
44
|
+
*
|
|
45
|
+
* @throws {@link TokenNotBoundError} — no binding registered for `key`, or a nested dependency is unbound.
|
|
46
|
+
* @throws {@link NoMatchingBindingError} — a name/tag `hint` was given for `key` but no binding matched it.
|
|
47
|
+
* @throws {@link InternalError} — multiple bindings matched for `key` after applying the hint (ambiguous).
|
|
48
|
+
* @throws {@link CircularDependencyError} — `key` or a nested token appears twice on the resolution stack.
|
|
49
|
+
* @throws {@link AsyncResolutionError} — an `async-dynamic` binding or async lifecycle/activation on the sync path.
|
|
50
|
+
* @throws {@link ScopeViolationError} — captive dependency (singleton → scoped/transient).
|
|
51
|
+
* @throws {@link MissingMetadataError} — `class` binding lacks injectable metadata when the reader requires it.
|
|
52
|
+
*/
|
|
22
53
|
resolveRoot<Value>(key: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value;
|
|
23
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Entry point for async single-binding resolution. Awaits `async-dynamic` factories
|
|
56
|
+
* and async lifecycle hooks that would cause {@link AsyncResolutionError} on the sync path.
|
|
57
|
+
*
|
|
58
|
+
* @throws {@link TokenNotBoundError} — no binding registered for `key`, or a nested dependency is unbound.
|
|
59
|
+
* @throws {@link NoMatchingBindingError} — a name/tag `hint` was given for `key` but no binding matched it.
|
|
60
|
+
* @throws {@link InternalError} — multiple bindings matched for `key` after applying the hint (ambiguous).
|
|
61
|
+
* @throws {@link CircularDependencyError} — `key` or a nested token appears twice on the resolution stack.
|
|
62
|
+
* @throws {@link ScopeViolationError} — captive dependency (singleton → scoped/transient).
|
|
63
|
+
* @throws {@link MissingMetadataError} — `class` binding lacks injectable metadata when the reader requires it.
|
|
64
|
+
*/
|
|
24
65
|
resolveAsyncRoot<Value>(key: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value>;
|
|
25
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Optional resolution for the **requested key only**: returns `undefined` when that key has no
|
|
68
|
+
* bindings or every candidate is filtered out **without** a name/tag hint — without throwing
|
|
69
|
+
* {@link TokenNotBoundError} for those cases. Instantiating the selected binding still runs the
|
|
70
|
+
* normal sync resolution path for nested dependencies; an unregistered transitive dependency
|
|
71
|
+
* throws {@link TokenNotBoundError} as usual.
|
|
72
|
+
*
|
|
73
|
+
* Behavioral rules:
|
|
74
|
+
* 1. If the registry key is completely unbound → returns `undefined`.
|
|
75
|
+
* 2. If no candidate survives constraint filtering (without a hint) → returns `undefined`.
|
|
76
|
+
* 3. If a name/tag hint was provided but no binding matches it → throws {@link NoMatchingBindingError}.
|
|
77
|
+
* 4. Still throws on: circular dependencies, async operations on the sync path,
|
|
78
|
+
* scope violations, ambiguous multi-binding matches, and {@link TokenNotBoundError} when a
|
|
79
|
+
* required nested dependency is unbound.
|
|
80
|
+
*/
|
|
26
81
|
resolveOptionalRoot<Value>(key: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value | undefined;
|
|
27
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Resolves every matching binding for `key` synchronously into an array.
|
|
84
|
+
* Returns an empty array when no bindings exist (does not throw).
|
|
85
|
+
*
|
|
86
|
+
* @throws {@link AsyncResolutionError} — any candidate is `async-dynamic`.
|
|
87
|
+
* @throws {@link NoMatchingBindingError} — hint was specified but no binding matched it.
|
|
88
|
+
* @throws {@link TokenNotBoundError} — nested dependency unbound (same as {@link resolveRoot}).
|
|
89
|
+
* @throws {@link CircularDependencyError} — cycle while materializing a candidate.
|
|
90
|
+
* @throws {@link ScopeViolationError} — captive dependency during materialization.
|
|
91
|
+
* @throws {@link MissingMetadataError} — class binding lacks injectable metadata when required.
|
|
92
|
+
*/
|
|
28
93
|
resolveAllRoot<Value>(key: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value[];
|
|
29
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Async counterpart of {@link resolveAllRoot}: resolves every matching binding for `key`,
|
|
96
|
+
* awaiting `async-dynamic` factories. Returns an empty array when no bindings exist.
|
|
97
|
+
*
|
|
98
|
+
* @throws {@link NoMatchingBindingError} — hint was specified but no binding matched it.
|
|
99
|
+
* @throws {@link TokenNotBoundError} — nested dependency unbound (same as {@link resolveAsyncRoot}).
|
|
100
|
+
* @throws {@link CircularDependencyError} — cycle while materializing a candidate.
|
|
101
|
+
* @throws {@link ScopeViolationError} — captive dependency during materialization.
|
|
102
|
+
* @throws {@link MissingMetadataError} — class binding lacks injectable metadata when required.
|
|
103
|
+
*/
|
|
30
104
|
resolveAllAsyncRoot<Value>(key: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Context-aware sync `resolveAll`: extends the current resolution path and preserves
|
|
107
|
+
* visiting/materialization stacks so nested multi-resolution participates in cycle and
|
|
108
|
+
* captive-dependency checks exactly like single-value {@link resolve}.
|
|
109
|
+
*/
|
|
110
|
+
private resolveAll;
|
|
111
|
+
/**
|
|
112
|
+
* Async counterpart of {@link resolveAll}: keeps the current path and materialization stack
|
|
113
|
+
* so `when()` constraints and scope validation behave consistently for nested multi-resolution.
|
|
114
|
+
*/
|
|
115
|
+
private resolveAllAsync;
|
|
31
116
|
/**
|
|
32
117
|
* Assembles the read-only {@link ConstraintContext} snapshot passed to `when()` predicates.
|
|
33
118
|
* Extracts the top-of-stack frame as `parent` and the rest as `ancestors`.
|
|
@@ -46,6 +131,9 @@ declare class DependencyResolver {
|
|
|
46
131
|
*/
|
|
47
132
|
private createContext;
|
|
48
133
|
/**
|
|
134
|
+
* Core synchronous resolution: lookup → filter → cycle check → scope check → instantiate.
|
|
135
|
+
* Called recursively when a binding's dependencies need resolution.
|
|
136
|
+
*
|
|
49
137
|
* @param key - Token or constructor being resolved.
|
|
50
138
|
* @param hint - Optional name/tag filter for multi-binding selection.
|
|
51
139
|
* @param pathLabels - Mutable label path accumulated during graph walk; extended in place.
|
|
@@ -54,6 +142,9 @@ declare class DependencyResolver {
|
|
|
54
142
|
*/
|
|
55
143
|
private resolve;
|
|
56
144
|
/**
|
|
145
|
+
* Core async resolution: same pipeline as {@link resolve} but awaits `async-dynamic`
|
|
146
|
+
* factories and async lifecycle hooks instead of throwing {@link AsyncResolutionError}.
|
|
147
|
+
*
|
|
57
148
|
* @param key - Token or constructor being resolved.
|
|
58
149
|
* @param hint - Optional name/tag filter for multi-binding selection.
|
|
59
150
|
* @param pathLabels - Mutable label path accumulated during graph walk; extended in place.
|
|
@@ -77,8 +168,12 @@ declare class DependencyResolver {
|
|
|
77
168
|
*/
|
|
78
169
|
private assertCaptiveDependencyFromMaterializationStack;
|
|
79
170
|
/**
|
|
80
|
-
* Synchronously produces the raw instance for a binding without touching the scope cache
|
|
81
|
-
*
|
|
171
|
+
* Synchronously produces the raw instance for a binding **without** touching the scope cache
|
|
172
|
+
* or running lifecycle hooks. The caller ({@link instantiateBinding}) wraps this with
|
|
173
|
+
* cache logic and post-construction hooks.
|
|
174
|
+
*
|
|
175
|
+
* Dispatches on `binding.kind`; throws {@link AsyncResolutionError} if the binding is
|
|
176
|
+
* `async-dynamic` or if a `dynamic` / `resolved` factory returns a Promise.
|
|
82
177
|
*/
|
|
83
178
|
private materialize;
|
|
84
179
|
/**
|