@codefast/di 0.3.13-canary.4 → 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 +78 -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/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, DiError, InternalError, MissingMetadataError, NoMatchingBindingError, ScopeViolationError, TokenNotBoundError } from "./errors.mjs";
|
|
2
|
-
import { inject, isInjectionDescriptor, optional } from "./decorators/inject.mjs";
|
|
2
|
+
import { inject, injectAll, isInjectionDescriptor, optional } from "./decorators/inject.mjs";
|
|
3
3
|
import { getAutoRegistered, injectable } from "./decorators/injectable.mjs";
|
|
4
4
|
import { AsyncModule, Module } from "./module.mjs";
|
|
5
5
|
import { Container } from "./container.mjs";
|
|
6
6
|
import { token } from "./token.mjs";
|
|
7
7
|
import { postConstruct, preDestroy } from "./decorators/lifecycle-decorators.mjs";
|
|
8
|
-
export { AsyncModule, AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, Container, DiError, InternalError, MissingMetadataError, Module, NoMatchingBindingError, ScopeViolationError, TokenNotBoundError, getAutoRegistered, inject, injectable, isInjectionDescriptor, optional, postConstruct, preDestroy, token };
|
|
8
|
+
export { AsyncModule, AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, Container, DiError, InternalError, MissingMetadataError, Module, NoMatchingBindingError, ScopeViolationError, TokenNotBoundError, getAutoRegistered, inject, injectAll, injectable, isInjectionDescriptor, optional, postConstruct, preDestroy, token };
|
package/dist/inspector.d.mts
CHANGED
|
@@ -4,35 +4,50 @@ import { MetadataReader } from "./metadata/metadata-types.mjs";
|
|
|
4
4
|
import { collectStaticDependencyEdges } from "./dependency-graph.mjs";
|
|
5
5
|
|
|
6
6
|
//#region src/inspector.d.ts
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Whether a singleton/scoped binding's instance is currently held in the scope cache.
|
|
9
|
+
*/
|
|
8
10
|
type BindingActivationStatus = "cached" | "not-cached" | "transient";
|
|
9
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Per-binding row inside a {@link ContainerSnapshot}.
|
|
13
|
+
*/
|
|
10
14
|
type ContainerBindingSnapshot = {
|
|
11
15
|
readonly registryKeyLabel: string;
|
|
12
16
|
readonly bindingId: BindingIdentifier;
|
|
13
17
|
readonly kind: Binding<unknown>["kind"];
|
|
14
18
|
readonly scope: BindingScope;
|
|
15
|
-
readonly activationStatus: BindingActivationStatus;
|
|
19
|
+
readonly activationStatus: BindingActivationStatus;
|
|
20
|
+
/**
|
|
21
|
+
* True when {@link BindingBuilder.when} was used (runtime predicate; static graph may still show edges).
|
|
22
|
+
*/
|
|
16
23
|
readonly hasConditionalConstraint: boolean;
|
|
17
24
|
readonly moduleId?: string;
|
|
18
25
|
};
|
|
19
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Full debug snapshot returned by {@link Container.inspect}.
|
|
28
|
+
*/
|
|
20
29
|
type ContainerSnapshot = {
|
|
21
30
|
readonly bindings: readonly ContainerBindingSnapshot[];
|
|
22
31
|
};
|
|
23
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Structured dependency graph returned by {@link Container.generateDependencyGraph} with `format: "json"`.
|
|
34
|
+
*/
|
|
24
35
|
type ContainerGraphJson = {
|
|
25
36
|
nodes: ContainerBindingSnapshot[];
|
|
26
37
|
edges: ReturnType<typeof collectStaticDependencyEdges>[number][];
|
|
27
38
|
};
|
|
28
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Read-only view of the container internals exposed to {@link ContainerInspector}.
|
|
41
|
+
*/
|
|
29
42
|
type ContainerInspectorContext = {
|
|
30
43
|
collectAllRegistryKeys(): readonly RegistryKey[];
|
|
31
44
|
lookupBindings(key: RegistryKey): readonly Binding<unknown>[] | undefined;
|
|
32
45
|
isBindingCached(binding: Binding<unknown>): boolean;
|
|
33
46
|
metadataReader: MetadataReader | undefined;
|
|
34
47
|
};
|
|
35
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Options for {@link Container.generateDependencyGraph}.
|
|
50
|
+
*/
|
|
36
51
|
type DotGraphOptions = {
|
|
37
52
|
/**
|
|
38
53
|
* When true, omit registry keys whose label starts with `CODEFAST_DI_` (framework-style tokens)
|
|
@@ -41,17 +56,18 @@ type DotGraphOptions = {
|
|
|
41
56
|
readonly hideInternals?: boolean;
|
|
42
57
|
};
|
|
43
58
|
/**
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the `@codefast/di/inspector` subpath export.
|
|
59
|
+
* Reads the container's registry and scope-cache state to produce debug snapshots
|
|
60
|
+
* and dependency-graph output (Graphviz DOT / typed JSON).
|
|
61
|
+
*
|
|
62
|
+
* Constructed internally by the container; advanced consumers can also construct it
|
|
63
|
+
* directly via the `@codefast/di/inspector` subpath export.
|
|
50
64
|
*/
|
|
51
65
|
declare class ContainerInspector {
|
|
52
66
|
private readonly ctx;
|
|
53
67
|
constructor(ctx: ContainerInspectorContext);
|
|
54
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Collects all registered bindings into a flat, serialisable snapshot.
|
|
70
|
+
*/
|
|
55
71
|
getSnapshot(): ContainerSnapshot;
|
|
56
72
|
/**
|
|
57
73
|
* Produces a Graphviz `digraph` string with HTML-label nodes and styled edges.
|
|
@@ -59,12 +75,20 @@ declare class ContainerInspector {
|
|
|
59
75
|
* Pass `hideInternals: true` to suppress `CODEFAST_DI_`-prefixed tokens.
|
|
60
76
|
*/
|
|
61
77
|
generateDotGraph(options?: DotGraphOptions): string;
|
|
78
|
+
/**
|
|
79
|
+
* Overloaded entry point: returns DOT format by default, or a typed {@link ContainerGraphJson}
|
|
80
|
+
* when `format: "json"` is specified.
|
|
81
|
+
*/
|
|
62
82
|
generateDependencyGraph(options?: DotGraphOptions & {
|
|
63
83
|
format?: "dot";
|
|
64
84
|
}): string;
|
|
65
85
|
generateDependencyGraph(options: DotGraphOptions & {
|
|
66
86
|
format: "json";
|
|
67
87
|
}): ContainerGraphJson;
|
|
88
|
+
/**
|
|
89
|
+
* Builds the typed JSON graph (nodes + edges) used by the `"json"` format path.
|
|
90
|
+
* Applies the same `hideInternals` / deduplication logic as {@link generateDotGraph}.
|
|
91
|
+
*/
|
|
68
92
|
generateDependencyGraphJsonTyped(options?: DotGraphOptions): ContainerGraphJson;
|
|
69
93
|
/**
|
|
70
94
|
* Produces a JSON graph representation with `nodes` and `edges`.
|
package/dist/inspector.mjs
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { registryKeyLabel } from "./binding-select.mjs";
|
|
2
2
|
import { collectStaticDependencyEdges } from "./dependency-graph.mjs";
|
|
3
3
|
//#region src/inspector.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Maps a binding's scope and cache state to a {@link BindingActivationStatus} label.
|
|
6
|
+
*/
|
|
5
7
|
function activationStatusFor(binding, isCached) {
|
|
6
8
|
if (binding.scope === "transient") return "transient";
|
|
7
9
|
return isCached(binding) ? "cached" : "not-cached";
|
|
8
10
|
}
|
|
9
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Escapes a string for use as a DOT `label="..."` attribute value.
|
|
13
|
+
*/
|
|
10
14
|
function dotEscapeLabel(text) {
|
|
11
15
|
return text.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n");
|
|
12
16
|
}
|
|
13
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Escapes a string for safe embedding inside a DOT HTML-label (`<...>`) table cell.
|
|
19
|
+
*/
|
|
14
20
|
function dotEscapeHtml(text) {
|
|
15
21
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
16
22
|
}
|
|
17
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Returns the Graphviz node shape name for a given binding kind.
|
|
25
|
+
*/
|
|
18
26
|
function nodeShapeForKind(kind) {
|
|
19
27
|
switch (kind) {
|
|
20
28
|
case "constant": return "ellipse";
|
|
@@ -26,7 +34,9 @@ function nodeShapeForKind(kind) {
|
|
|
26
34
|
default: return kind;
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Returns DOT fill-color and style attributes that visually distinguish binding scopes.
|
|
39
|
+
*/
|
|
30
40
|
function scopeVisualAttributes(scope) {
|
|
31
41
|
switch (scope) {
|
|
32
42
|
case "singleton": return "style=\"filled\", fillcolor=\"#FFD700\", penwidth=2";
|
|
@@ -35,31 +45,38 @@ function scopeVisualAttributes(scope) {
|
|
|
35
45
|
default: return scope;
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Strips non-alphanumeric characters from a module name to produce a valid DOT subgraph identifier.
|
|
50
|
+
*/
|
|
39
51
|
function sanitizeClusterId(moduleName) {
|
|
40
52
|
return moduleName.replace(/[^0-9a-zA-Z_]/g, "_");
|
|
41
53
|
}
|
|
42
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Returns `true` when the label string belongs to a framework-internal registry key.
|
|
56
|
+
*/
|
|
43
57
|
function registryKeyLabelIsInternal(label) {
|
|
44
58
|
return label.startsWith("CODEFAST_DI_");
|
|
45
59
|
}
|
|
46
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Returns `true` when the registry key resolves to an internal framework label.
|
|
62
|
+
*/
|
|
47
63
|
function isInternalRegistryKey(key) {
|
|
48
64
|
return registryKeyLabelIsInternal(registryKeyLabel(key));
|
|
49
65
|
}
|
|
50
66
|
/**
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* the `@codefast/di/inspector` subpath export.
|
|
67
|
+
* Reads the container's registry and scope-cache state to produce debug snapshots
|
|
68
|
+
* and dependency-graph output (Graphviz DOT / typed JSON).
|
|
69
|
+
*
|
|
70
|
+
* Constructed internally by the container; advanced consumers can also construct it
|
|
71
|
+
* directly via the `@codefast/di/inspector` subpath export.
|
|
57
72
|
*/
|
|
58
73
|
var ContainerInspector = class {
|
|
59
74
|
constructor(ctx) {
|
|
60
75
|
this.ctx = ctx;
|
|
61
76
|
}
|
|
62
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Collects all registered bindings into a flat, serialisable snapshot.
|
|
79
|
+
*/
|
|
63
80
|
getSnapshot() {
|
|
64
81
|
const bindings = [];
|
|
65
82
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -182,6 +199,10 @@ var ContainerInspector = class {
|
|
|
182
199
|
if (options?.format === "json") return this.generateDependencyGraphJsonTyped(options);
|
|
183
200
|
return this.generateDotGraph(options);
|
|
184
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Builds the typed JSON graph (nodes + edges) used by the `"json"` format path.
|
|
204
|
+
* Applies the same `hideInternals` / deduplication logic as {@link generateDotGraph}.
|
|
205
|
+
*/
|
|
185
206
|
generateDependencyGraphJsonTyped(options) {
|
|
186
207
|
const hideInternals = options?.hideInternals === true;
|
|
187
208
|
const snapshot = this.getSnapshot();
|
package/dist/lifecycle.d.mts
CHANGED
|
@@ -3,15 +3,30 @@ import { LifecycleMetadata } from "./metadata/metadata-types.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/lifecycle.d.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Duck-typed Promise check: returns `true` when `value` has a `then` method.
|
|
7
|
+
* Used throughout the lifecycle layer to guard against async return values on sync resolution paths.
|
|
8
|
+
*/
|
|
9
|
+
declare function isPromiseLike(value: unknown): value is Promise<unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Runs `onActivation` synchronously on a newly constructed instance.
|
|
12
|
+
* If the handler returns a Promise during synchronous resolution, throws
|
|
13
|
+
* {@link AsyncResolutionError} — callers must use `resolveAsync` for async activation handlers.
|
|
14
|
+
*
|
|
15
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** → cache.
|
|
7
16
|
*/
|
|
8
17
|
declare function runActivation(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, pathLabels: readonly string[]): unknown;
|
|
9
18
|
/**
|
|
10
|
-
* Reads lifecycle metadata directly from a constructor's
|
|
19
|
+
* Reads lifecycle metadata ({@link LifecycleMetadata}) directly from a constructor's
|
|
20
|
+
* `Symbol.metadata` object. Bypasses the {@link MetadataReader} abstraction —
|
|
21
|
+
* used by the scope manager during deactivation when the reader is not available.
|
|
11
22
|
*/
|
|
12
23
|
declare function readLifecycleMetadataFromCtor(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
|
|
13
24
|
/**
|
|
14
|
-
* Runs the `@postConstruct()` method synchronously if present.
|
|
25
|
+
* Runs the `@postConstruct()` method synchronously if present.
|
|
26
|
+
* Throws {@link AsyncResolutionError} if the method returns a Promise — async lifecycle
|
|
27
|
+
* methods require `resolveAsync`.
|
|
28
|
+
*
|
|
29
|
+
* Lifecycle ordering: `construct` → **`@postConstruct`** → `onActivation` → cache.
|
|
15
30
|
*/
|
|
16
31
|
declare function runPostConstruct(implementationClass: Constructor<unknown>, instance: unknown, pathLabels?: string[]): void;
|
|
17
32
|
/**
|
|
@@ -19,16 +34,23 @@ declare function runPostConstruct(implementationClass: Constructor<unknown>, ins
|
|
|
19
34
|
*/
|
|
20
35
|
declare function runPostConstructAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
|
|
21
36
|
/**
|
|
22
|
-
* Runs the `@preDestroy()` method synchronously if present.
|
|
37
|
+
* Runs the `@preDestroy()` method synchronously if present.
|
|
38
|
+
* Throws if the method returns a Promise — use `disposeAsync()` / `unloadAsync()` for async teardown.
|
|
39
|
+
*
|
|
40
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`**.
|
|
23
41
|
*/
|
|
24
42
|
declare function runPreDestroy(implementationClass: Constructor<unknown>, instance: unknown): void;
|
|
25
43
|
/**
|
|
26
44
|
* Runs the `@preDestroy()` method, awaiting if it returns a Promise.
|
|
45
|
+
*
|
|
46
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`** (async variant).
|
|
27
47
|
*/
|
|
28
48
|
declare function runPreDestroyAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
|
|
29
49
|
/**
|
|
30
|
-
* Runs `onActivation`, awaiting
|
|
50
|
+
* Runs `onActivation`, awaiting if the handler returns a Promise.
|
|
51
|
+
*
|
|
52
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** (async variant) → cache.
|
|
31
53
|
*/
|
|
32
54
|
declare function runActivationAsync(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, _pathLabels: readonly string[]): Promise<unknown>;
|
|
33
55
|
//#endregion
|
|
34
|
-
export { readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
|
56
|
+
export { isPromiseLike, readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
package/dist/lifecycle.mjs
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { AsyncResolutionError } from "./errors.mjs";
|
|
2
2
|
import { CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol } from "./metadata/metadata-keys.mjs";
|
|
3
3
|
//#region src/lifecycle.ts
|
|
4
|
+
/**
|
|
5
|
+
* Duck-typed Promise check: returns `true` when `value` has a `then` method.
|
|
6
|
+
* Used throughout the lifecycle layer to guard against async return values on sync resolution paths.
|
|
7
|
+
*/
|
|
4
8
|
function isPromiseLike(value) {
|
|
5
9
|
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
6
10
|
}
|
|
7
11
|
/**
|
|
8
|
-
* Runs `onActivation` synchronously
|
|
12
|
+
* Runs `onActivation` synchronously on a newly constructed instance.
|
|
13
|
+
* If the handler returns a Promise during synchronous resolution, throws
|
|
14
|
+
* {@link AsyncResolutionError} — callers must use `resolveAsync` for async activation handlers.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** → cache.
|
|
9
17
|
*/
|
|
10
18
|
function runActivation(binding, instance, ctx, pathLabels) {
|
|
11
19
|
const handler = binding.onActivation;
|
|
@@ -16,7 +24,9 @@ function runActivation(binding, instance, ctx, pathLabels) {
|
|
|
16
24
|
return activationResult;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
|
-
* Reads lifecycle metadata directly from a constructor's
|
|
27
|
+
* Reads lifecycle metadata ({@link LifecycleMetadata}) directly from a constructor's
|
|
28
|
+
* `Symbol.metadata` object. Bypasses the {@link MetadataReader} abstraction —
|
|
29
|
+
* used by the scope manager during deactivation when the reader is not available.
|
|
20
30
|
*/
|
|
21
31
|
function readLifecycleMetadataFromCtor(implementationClass) {
|
|
22
32
|
const metadataObject = implementationClass[decoratorMetadataObjectSymbol()];
|
|
@@ -25,7 +35,11 @@ function readLifecycleMetadataFromCtor(implementationClass) {
|
|
|
25
35
|
return typeof raw === "object" && raw !== null ? raw : void 0;
|
|
26
36
|
}
|
|
27
37
|
/**
|
|
28
|
-
* Runs the `@postConstruct()` method synchronously if present.
|
|
38
|
+
* Runs the `@postConstruct()` method synchronously if present.
|
|
39
|
+
* Throws {@link AsyncResolutionError} if the method returns a Promise — async lifecycle
|
|
40
|
+
* methods require `resolveAsync`.
|
|
41
|
+
*
|
|
42
|
+
* Lifecycle ordering: `construct` → **`@postConstruct`** → `onActivation` → cache.
|
|
29
43
|
*/
|
|
30
44
|
function runPostConstruct(implementationClass, instance, pathLabels) {
|
|
31
45
|
const meta = readLifecycleMetadataFromCtor(implementationClass);
|
|
@@ -33,8 +47,7 @@ function runPostConstruct(implementationClass, instance, pathLabels) {
|
|
|
33
47
|
const methodName = meta.postConstruct;
|
|
34
48
|
const lifecycleMethod = instance[methodName];
|
|
35
49
|
if (typeof lifecycleMethod !== "function") return;
|
|
36
|
-
|
|
37
|
-
if (typeof postConstructResult === "object" && postConstructResult !== null && "then" in postConstructResult && typeof postConstructResult.then === "function") {
|
|
50
|
+
if (isPromiseLike(lifecycleMethod.call(instance))) {
|
|
38
51
|
const labels = pathLabels ?? [];
|
|
39
52
|
throw new AsyncResolutionError(labels[labels.length - 1] ?? "(unknown)", labels, `@postConstruct() "${methodName}" returned a Promise during synchronous resolution`);
|
|
40
53
|
}
|
|
@@ -50,7 +63,10 @@ async function runPostConstructAsync(implementationClass, instance) {
|
|
|
50
63
|
await lifecycleMethod.call(instance);
|
|
51
64
|
}
|
|
52
65
|
/**
|
|
53
|
-
* Runs the `@preDestroy()` method synchronously if present.
|
|
66
|
+
* Runs the `@preDestroy()` method synchronously if present.
|
|
67
|
+
* Throws if the method returns a Promise — use `disposeAsync()` / `unloadAsync()` for async teardown.
|
|
68
|
+
*
|
|
69
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`**.
|
|
54
70
|
*/
|
|
55
71
|
function runPreDestroy(implementationClass, instance) {
|
|
56
72
|
const meta = readLifecycleMetadataFromCtor(implementationClass);
|
|
@@ -58,11 +74,12 @@ function runPreDestroy(implementationClass, instance) {
|
|
|
58
74
|
const methodName = meta.preDestroy;
|
|
59
75
|
const lifecycleMethod = instance[methodName];
|
|
60
76
|
if (typeof lifecycleMethod !== "function") return;
|
|
61
|
-
|
|
62
|
-
if (typeof preDestroyResult === "object" && preDestroyResult !== null && "then" in preDestroyResult && typeof preDestroyResult.then === "function") throw new Error(`@preDestroy() "${methodName}" returned a Promise during synchronous disposal; use disposeAsync() / unloadAsync().`);
|
|
77
|
+
if (isPromiseLike(lifecycleMethod.call(instance))) throw new Error(`@preDestroy() "${methodName}" returned a Promise during synchronous disposal; use disposeAsync() / unloadAsync().`);
|
|
63
78
|
}
|
|
64
79
|
/**
|
|
65
80
|
* Runs the `@preDestroy()` method, awaiting if it returns a Promise.
|
|
81
|
+
*
|
|
82
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`** (async variant).
|
|
66
83
|
*/
|
|
67
84
|
async function runPreDestroyAsync(implementationClass, instance) {
|
|
68
85
|
const meta = readLifecycleMetadataFromCtor(implementationClass);
|
|
@@ -72,7 +89,9 @@ async function runPreDestroyAsync(implementationClass, instance) {
|
|
|
72
89
|
await lifecycleMethod.call(instance);
|
|
73
90
|
}
|
|
74
91
|
/**
|
|
75
|
-
* Runs `onActivation`, awaiting
|
|
92
|
+
* Runs `onActivation`, awaiting if the handler returns a Promise.
|
|
93
|
+
*
|
|
94
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** (async variant) → cache.
|
|
76
95
|
*/
|
|
77
96
|
async function runActivationAsync(binding, instance, ctx, _pathLabels) {
|
|
78
97
|
const handler = binding.onActivation;
|
|
@@ -80,4 +99,4 @@ async function runActivationAsync(binding, instance, ctx, _pathLabels) {
|
|
|
80
99
|
return await handler(ctx, instance);
|
|
81
100
|
}
|
|
82
101
|
//#endregion
|
|
83
|
-
export { readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
|
102
|
+
export { isPromiseLike, readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
//#region src/metadata/metadata-keys.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Well-known key for
|
|
3
|
+
* Well-known property key for constructor injection metadata on `Symbol.metadata`.
|
|
4
|
+
* Written by `@injectable()`, read by `SymbolMetadataReader.getConstructorMetadata()`.
|
|
5
|
+
* The `:v1` suffix is a schema version — bump only when the `ConstructorMetadata` shape changes.
|
|
4
6
|
*/
|
|
5
7
|
declare const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Well-known property key for accessor-field injection metadata on `Symbol.metadata`.
|
|
10
|
+
* Written by `@inject()` when used as an accessor decorator; read during post-construction
|
|
11
|
+
* property injection by the resolver.
|
|
12
|
+
*/
|
|
7
13
|
declare const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
|
|
8
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`.
|
|
16
|
+
*/
|
|
9
17
|
declare const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
|
|
10
18
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `Symbol.metadata`
|
|
19
|
+
* Returns the runtime symbol used to access TC39 decorator metadata on a class.
|
|
20
|
+
*
|
|
21
|
+
* Prefers the native `Symbol.metadata` when available (Stage 3 decorators); falls back to
|
|
22
|
+
* `Symbol.for("Symbol.metadata")` for Node/runtime versions that polyfill or partially
|
|
23
|
+
* implement the proposal. The fallback key is the de facto convention used by TypeScript
|
|
24
|
+
* and polyfill libraries.
|
|
14
25
|
*/
|
|
15
26
|
declare function decoratorMetadataObjectSymbol(): symbol;
|
|
16
27
|
//#endregion
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
//#region src/metadata/metadata-keys.ts
|
|
2
2
|
/**
|
|
3
|
-
* Well-known key for
|
|
3
|
+
* Well-known property key for constructor injection metadata on `Symbol.metadata`.
|
|
4
|
+
* Written by `@injectable()`, read by `SymbolMetadataReader.getConstructorMetadata()`.
|
|
5
|
+
* The `:v1` suffix is a schema version — bump only when the `ConstructorMetadata` shape changes.
|
|
4
6
|
*/
|
|
5
7
|
const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Well-known property key for accessor-field injection metadata on `Symbol.metadata`.
|
|
10
|
+
* Written by `@inject()` when used as an accessor decorator; read during post-construction
|
|
11
|
+
* property injection by the resolver.
|
|
12
|
+
*/
|
|
7
13
|
const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
|
|
8
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`.
|
|
16
|
+
*/
|
|
9
17
|
const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
|
|
10
18
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `Symbol.metadata`
|
|
19
|
+
* Returns the runtime symbol used to access TC39 decorator metadata on a class.
|
|
20
|
+
*
|
|
21
|
+
* Prefers the native `Symbol.metadata` when available (Stage 3 decorators); falls back to
|
|
22
|
+
* `Symbol.for("Symbol.metadata")` for Node/runtime versions that polyfill or partially
|
|
23
|
+
* implement the proposal. The fallback key is the de facto convention used by TypeScript
|
|
24
|
+
* and polyfill libraries.
|
|
14
25
|
*/
|
|
15
26
|
function decoratorMetadataObjectSymbol() {
|
|
16
27
|
return typeof Symbol.metadata === "symbol" ? Symbol.metadata : Symbol.for("Symbol.metadata");
|
|
@@ -2,7 +2,9 @@ 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
9
|
readonly name: string;
|
|
8
10
|
readonly token: Token<unknown> | Constructor<unknown>;
|
|
@@ -12,11 +14,12 @@ type AccessorInjectionMetadata = {
|
|
|
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
21
|
readonly postConstruct?: string;
|
|
18
22
|
readonly preDestroy?: string;
|
|
19
|
-
readonly accessorInjections?: readonly AccessorInjectionMetadata[];
|
|
20
23
|
};
|
|
21
24
|
/**
|
|
22
25
|
* Per-parameter injection description collected by `@injectable()`.
|
|
@@ -27,6 +30,11 @@ type ParamMetadata = {
|
|
|
27
30
|
readonly optional: boolean;
|
|
28
31
|
readonly name?: string;
|
|
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.
|
|
@@ -36,7 +44,8 @@ type InjectionDescriptor<Value = unknown> = {
|
|
|
36
44
|
readonly token: Token<Value> | Constructor<Value>;
|
|
37
45
|
readonly optional: boolean;
|
|
38
46
|
readonly name?: string;
|
|
39
|
-
readonly tag?: readonly [tag: string, value: unknown];
|
|
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.
|
|
@@ -45,10 +54,25 @@ type ConstructorMetadata = {
|
|
|
45
54
|
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;
|