@codefast/di 0.3.13-canary.4

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +21 -0
  3. package/README.md +572 -0
  4. package/dist/binding-select.d.mts +22 -0
  5. package/dist/binding-select.mjs +50 -0
  6. package/dist/binding.d.mts +219 -0
  7. package/dist/binding.mjs +240 -0
  8. package/dist/constraints.d.mts +18 -0
  9. package/dist/constraints.mjs +24 -0
  10. package/dist/container.d.mts +82 -0
  11. package/dist/container.mjs +406 -0
  12. package/dist/decorators/inject.d.mts +24 -0
  13. package/dist/decorators/inject.mjs +69 -0
  14. package/dist/decorators/injectable.d.mts +40 -0
  15. package/dist/decorators/injectable.mjs +62 -0
  16. package/dist/decorators/lifecycle-decorators.d.mts +13 -0
  17. package/dist/decorators/lifecycle-decorators.mjs +34 -0
  18. package/dist/dependency-graph.d.mts +35 -0
  19. package/dist/dependency-graph.mjs +126 -0
  20. package/dist/environment.d.mts +14 -0
  21. package/dist/environment.mjs +20 -0
  22. package/dist/errors.d.mts +100 -0
  23. package/dist/errors.mjs +152 -0
  24. package/dist/index.d.mts +10 -0
  25. package/dist/index.mjs +8 -0
  26. package/dist/inspector.d.mts +76 -0
  27. package/dist/inspector.mjs +247 -0
  28. package/dist/lifecycle.d.mts +34 -0
  29. package/dist/lifecycle.mjs +83 -0
  30. package/dist/metadata/metadata-keys.d.mts +17 -0
  31. package/dist/metadata/metadata-keys.mjs +19 -0
  32. package/dist/metadata/metadata-types.d.mts +55 -0
  33. package/dist/metadata/metadata-types.mjs +1 -0
  34. package/dist/metadata/param-registry.d.mts +16 -0
  35. package/dist/metadata/param-registry.mjs +25 -0
  36. package/dist/metadata/symbol-metadata-reader.d.mts +15 -0
  37. package/dist/metadata/symbol-metadata-reader.mjs +32 -0
  38. package/dist/module.d.mts +60 -0
  39. package/dist/module.mjs +57 -0
  40. package/dist/registry.d.mts +38 -0
  41. package/dist/registry.mjs +65 -0
  42. package/dist/resolver.d.mts +102 -0
  43. package/dist/resolver.mjs +361 -0
  44. package/dist/scope-validation.d.mts +20 -0
  45. package/dist/scope-validation.mjs +34 -0
  46. package/dist/scope.d.mts +80 -0
  47. package/dist/scope.mjs +185 -0
  48. package/dist/token.d.mts +20 -0
  49. package/dist/token.mjs +9 -0
  50. package/package.json +157 -0
@@ -0,0 +1,247 @@
1
+ import { registryKeyLabel } from "./binding-select.mjs";
2
+ import { collectStaticDependencyEdges } from "./dependency-graph.mjs";
3
+ //#region src/inspector.ts
4
+ /** Maps a binding's scope and cache state to a {@link BindingActivationStatus} label. */
5
+ function activationStatusFor(binding, isCached) {
6
+ if (binding.scope === "transient") return "transient";
7
+ return isCached(binding) ? "cached" : "not-cached";
8
+ }
9
+ /** Escapes a string for use as a DOT `label="..."` attribute value. */
10
+ function dotEscapeLabel(text) {
11
+ return text.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n");
12
+ }
13
+ /** Escapes a string for safe embedding inside a DOT HTML-label (`<...>`) table cell. */
14
+ function dotEscapeHtml(text) {
15
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
16
+ }
17
+ /** Returns the Graphviz node shape name for a given binding kind. */
18
+ function nodeShapeForKind(kind) {
19
+ switch (kind) {
20
+ case "constant": return "ellipse";
21
+ case "class": return "box";
22
+ case "dynamic":
23
+ case "async-dynamic":
24
+ case "resolved": return "diamond";
25
+ case "alias": return "octagon";
26
+ default: return kind;
27
+ }
28
+ }
29
+ /** Returns DOT fill-color and style attributes that visually distinguish binding scopes. */
30
+ function scopeVisualAttributes(scope) {
31
+ switch (scope) {
32
+ case "singleton": return "style=\"filled\", fillcolor=\"#FFD700\", penwidth=2";
33
+ case "scoped": return "style=\"filled\", fillcolor=\"#ADD8E6\"";
34
+ case "transient": return "style=\"dashed\"";
35
+ default: return scope;
36
+ }
37
+ }
38
+ /** Strips non-alphanumeric characters from a module name to produce a valid DOT subgraph identifier. */
39
+ function sanitizeClusterId(moduleName) {
40
+ return moduleName.replace(/[^0-9a-zA-Z_]/g, "_");
41
+ }
42
+ /** Returns `true` when the label string belongs to a framework-internal registry key. */
43
+ function registryKeyLabelIsInternal(label) {
44
+ return label.startsWith("CODEFAST_DI_");
45
+ }
46
+ /** Returns `true` when the registry key resolves to an internal framework label. */
47
+ function isInternalRegistryKey(key) {
48
+ return registryKeyLabelIsInternal(registryKeyLabel(key));
49
+ }
50
+ /**
51
+ * Read-only introspection and Graphviz DOT export for a container graph.
52
+ */
53
+ /**
54
+ * Reads the registry and scope-cache state to produce debug snapshots and dependency graphs.
55
+ * Instantiated internally by the container; advanced consumers can construct it directly via
56
+ * the `@codefast/di/inspector` subpath export.
57
+ */
58
+ var ContainerInspector = class {
59
+ constructor(ctx) {
60
+ this.ctx = ctx;
61
+ }
62
+ /** Collects all registered bindings into a flat, serialisable snapshot. */
63
+ getSnapshot() {
64
+ const bindings = [];
65
+ const seen = /* @__PURE__ */ new Set();
66
+ for (const registryKey of this.ctx.collectAllRegistryKeys()) {
67
+ const list = this.ctx.lookupBindings(registryKey);
68
+ if (list === void 0 || list.length === 0) continue;
69
+ const registryLabel = registryKeyLabel(registryKey);
70
+ for (const binding of list) {
71
+ if (seen.has(binding.id)) continue;
72
+ seen.add(binding.id);
73
+ const row = {
74
+ registryKeyLabel: registryLabel,
75
+ bindingId: binding.id,
76
+ kind: binding.kind,
77
+ scope: binding.scope,
78
+ activationStatus: activationStatusFor(binding, (bindingArg) => this.ctx.isBindingCached(bindingArg)),
79
+ hasConditionalConstraint: binding.constraint !== void 0
80
+ };
81
+ bindings.push(binding.moduleId === void 0 ? row : {
82
+ ...row,
83
+ moduleId: binding.moduleId
84
+ });
85
+ }
86
+ }
87
+ return { bindings };
88
+ }
89
+ /**
90
+ * Produces a Graphviz `digraph` string with HTML-label nodes and styled edges.
91
+ * Cycles are represented as ordinary edges (Graphviz renders them correctly).
92
+ * Pass `hideInternals: true` to suppress `CODEFAST_DI_`-prefixed tokens.
93
+ */
94
+ generateDotGraph(options) {
95
+ const hideInternals = options?.hideInternals === true;
96
+ const fullSnapshot = this.getSnapshot();
97
+ const visibleRows = hideInternals ? fullSnapshot.bindings.filter((row) => !registryKeyLabelIsInternal(row.registryKeyLabel)) : fullSnapshot.bindings;
98
+ const allowedBindingIds = new Set(visibleRows.map((row) => row.bindingId));
99
+ const lines = [
100
+ "digraph codefast_di {",
101
+ " rankdir=LR;",
102
+ " graph [fontname=\"Arial\", fontsize=12, nodesep=0.8, ranksep=1.2];",
103
+ " node [fontname=\"Arial\", fontsize=12, shape=box, style=\"filled,rounded\", fillcolor=\"#F5F5F5\"];",
104
+ " edge [fontname=\"Arial\", fontsize=10];"
105
+ ];
106
+ const byModule = /* @__PURE__ */ new Map();
107
+ for (const row of visibleRows) {
108
+ const key = row.moduleId;
109
+ const moduleGroup = byModule.get(key);
110
+ if (moduleGroup === void 0) byModule.set(key, [row]);
111
+ else moduleGroup.push(row);
112
+ }
113
+ const clusteredEntries = [...byModule.entries()].filter((entry) => entry[0] !== void 0);
114
+ const unclustered = byModule.get(void 0) ?? [];
115
+ const nodeAttributeLine = (row, indent) => {
116
+ const shape = nodeShapeForKind(row.kind);
117
+ const scopeAttrs = scopeVisualAttributes(row.scope);
118
+ const kindText = dotEscapeHtml(row.kind);
119
+ const nameText = dotEscapeHtml(row.registryKeyLabel);
120
+ const scopeText = dotEscapeHtml(`scope=${row.scope}`);
121
+ const whenRow = row.hasConditionalConstraint ? ` <TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9" COLOR="#666666">when(...)</FONT></TD></TR>\n` : "";
122
+ const htmlLabel = [
123
+ "<",
124
+ " <TABLE BORDER=\"0\" CELLPADDING=\"4\" CELLSPACING=\"0\">",
125
+ ` <TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9" COLOR="#666666">${kindText}</FONT></TD></TR>`,
126
+ ` <TR><TD ALIGN="LEFT"><B>${nameText}</B></TD></TR>`,
127
+ ` <TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9">${scopeText}</FONT></TD></TR>`,
128
+ whenRow.trimEnd(),
129
+ " </TABLE>",
130
+ " >"
131
+ ].filter((line) => line.length > 0).join("\n");
132
+ return `${indent}"${row.bindingId}" [shape=${shape}, ${scopeAttrs}, label=${htmlLabel}];`;
133
+ };
134
+ for (const [moduleName, rows] of clusteredEntries) {
135
+ const clusterId = sanitizeClusterId(moduleName);
136
+ lines.push(` subgraph cluster_${clusterId} {`);
137
+ lines.push(` label="${dotEscapeLabel(moduleName)}";`);
138
+ lines.push(` style=filled;`);
139
+ lines.push(` fillcolor=lightgray;`);
140
+ for (const row of rows) lines.push(nodeAttributeLine(row, " "));
141
+ lines.push(` }`);
142
+ }
143
+ for (const row of unclustered) lines.push(nodeAttributeLine(row, " "));
144
+ const emittedNodeIds = new Set(visibleRows.map((row) => row.bindingId));
145
+ const edgeSeen = /* @__PURE__ */ new Set();
146
+ for (const registryKey of this.ctx.collectAllRegistryKeys()) {
147
+ if (hideInternals && isInternalRegistryKey(registryKey)) continue;
148
+ const list = this.ctx.lookupBindings(registryKey);
149
+ if (list === void 0) continue;
150
+ const pathStart = [registryKeyLabel(registryKey)];
151
+ for (const consumerBinding of list) {
152
+ if (hideInternals && !allowedBindingIds.has(consumerBinding.id)) continue;
153
+ const edges = collectStaticDependencyEdges(consumerBinding, (dependencyKey) => this.ctx.lookupBindings(dependencyKey), this.ctx.metadataReader, pathStart);
154
+ for (const edge of edges) {
155
+ if (hideInternals && (!allowedBindingIds.has(edge.fromBindingId) || !allowedBindingIds.has(edge.toBindingId))) continue;
156
+ const edgeKey = `${edge.fromBindingId}->${edge.toBindingId}:${edge.edgeKind}:${edge.injectHintLabel ?? ""}`;
157
+ if (edgeSeen.has(edgeKey)) continue;
158
+ edgeSeen.add(edgeKey);
159
+ if (!emittedNodeIds.has(edge.fromBindingId)) {
160
+ emittedNodeIds.add(edge.fromBindingId);
161
+ lines.push(` "${edge.fromBindingId}" [shape=box, style=dashed, label="(unlisted ${edge.fromBindingId})"];`);
162
+ }
163
+ if (!emittedNodeIds.has(edge.toBindingId)) {
164
+ emittedNodeIds.add(edge.toBindingId);
165
+ lines.push(` "${edge.toBindingId}" [shape=box, style=dashed, label="(unlisted ${edge.toBindingId})"];`);
166
+ }
167
+ const labelParts = [];
168
+ if (edge.injectHintLabel !== void 0) labelParts.push(edge.injectHintLabel);
169
+ labelParts.push(edge.edgeKind);
170
+ if (edge.toBindingConditional) labelParts.push("conditional");
171
+ const edgeLabel = dotEscapeLabel(labelParts.join(" | "));
172
+ const pathLabel = dotEscapeLabel(edge.resolutionPath.join(" -> "));
173
+ const edgeStyle = edge.isAliasEdge ? ", style=dashed" : "";
174
+ lines.push(` "${edge.fromBindingId}" -> "${edge.toBindingId}" [label="${edgeLabel}", xlabel="${pathLabel}"${edgeStyle}];`);
175
+ }
176
+ }
177
+ }
178
+ lines.push("}");
179
+ return lines.join("\n");
180
+ }
181
+ generateDependencyGraph(options) {
182
+ if (options?.format === "json") return this.generateDependencyGraphJsonTyped(options);
183
+ return this.generateDotGraph(options);
184
+ }
185
+ generateDependencyGraphJsonTyped(options) {
186
+ const hideInternals = options?.hideInternals === true;
187
+ const snapshot = this.getSnapshot();
188
+ const visibleNodes = hideInternals ? snapshot.bindings.filter((row) => !registryKeyLabelIsInternal(row.registryKeyLabel)) : [...snapshot.bindings];
189
+ const allowedBindingIds = new Set(visibleNodes.map((row) => row.bindingId));
190
+ const edges = [];
191
+ const edgeSeen = /* @__PURE__ */ new Set();
192
+ for (const registryKey of this.ctx.collectAllRegistryKeys()) {
193
+ if (hideInternals && isInternalRegistryKey(registryKey)) continue;
194
+ const list = this.ctx.lookupBindings(registryKey);
195
+ if (list === void 0) continue;
196
+ const pathStart = [registryKeyLabel(registryKey)];
197
+ for (const consumerBinding of list) {
198
+ if (hideInternals && !allowedBindingIds.has(consumerBinding.id)) continue;
199
+ for (const edge of collectStaticDependencyEdges(consumerBinding, (dependencyKey) => this.ctx.lookupBindings(dependencyKey), this.ctx.metadataReader, pathStart)) {
200
+ if (hideInternals && (!allowedBindingIds.has(edge.fromBindingId) || !allowedBindingIds.has(edge.toBindingId))) continue;
201
+ const edgeKey = `${edge.fromBindingId}->${edge.toBindingId}:${edge.edgeKind}:${edge.injectHintLabel ?? ""}`;
202
+ if (edgeSeen.has(edgeKey)) continue;
203
+ edgeSeen.add(edgeKey);
204
+ edges.push(edge);
205
+ }
206
+ }
207
+ }
208
+ return {
209
+ nodes: visibleNodes,
210
+ edges
211
+ };
212
+ }
213
+ /**
214
+ * Produces a JSON graph representation with `nodes` and `edges`.
215
+ * @internal Use `generateDependencyGraph({ format: "json" })` for typed output.
216
+ */
217
+ generateDependencyGraphJson(options) {
218
+ const hideInternals = options?.hideInternals === true;
219
+ const snapshot = this.getSnapshot();
220
+ const visibleNodes = hideInternals ? snapshot.bindings.filter((row) => !registryKeyLabelIsInternal(row.registryKeyLabel)) : snapshot.bindings;
221
+ const allowedBindingIds = new Set(visibleNodes.map((row) => row.bindingId));
222
+ const edges = [];
223
+ const edgeSeen = /* @__PURE__ */ new Set();
224
+ for (const registryKey of this.ctx.collectAllRegistryKeys()) {
225
+ if (hideInternals && isInternalRegistryKey(registryKey)) continue;
226
+ const list = this.ctx.lookupBindings(registryKey);
227
+ if (list === void 0) continue;
228
+ const pathStart = [registryKeyLabel(registryKey)];
229
+ for (const consumerBinding of list) {
230
+ if (hideInternals && !allowedBindingIds.has(consumerBinding.id)) continue;
231
+ for (const edge of collectStaticDependencyEdges(consumerBinding, (dependencyKey) => this.ctx.lookupBindings(dependencyKey), this.ctx.metadataReader, pathStart)) {
232
+ if (hideInternals && (!allowedBindingIds.has(edge.fromBindingId) || !allowedBindingIds.has(edge.toBindingId))) continue;
233
+ const edgeKey = `${edge.fromBindingId}->${edge.toBindingId}:${edge.edgeKind}:${edge.injectHintLabel ?? ""}`;
234
+ if (edgeSeen.has(edgeKey)) continue;
235
+ edgeSeen.add(edgeKey);
236
+ edges.push(edge);
237
+ }
238
+ }
239
+ }
240
+ return JSON.stringify({
241
+ nodes: visibleNodes,
242
+ edges
243
+ });
244
+ }
245
+ };
246
+ //#endregion
247
+ export { ContainerInspector };
@@ -0,0 +1,34 @@
1
+ import { Binding, Constructor, ResolutionContext } from "./binding.mjs";
2
+ import { LifecycleMetadata } from "./metadata/metadata-types.mjs";
3
+
4
+ //#region src/lifecycle.d.ts
5
+ /**
6
+ * Runs `onActivation` synchronously; rejects async activations on sync resolution paths.
7
+ */
8
+ declare function runActivation(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, pathLabels: readonly string[]): unknown;
9
+ /**
10
+ * Reads lifecycle metadata directly from a constructor's Symbol.metadata.
11
+ */
12
+ declare function readLifecycleMetadataFromCtor(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
13
+ /**
14
+ * Runs the `@postConstruct()` method synchronously if present. Throws if it returns a Promise.
15
+ */
16
+ declare function runPostConstruct(implementationClass: Constructor<unknown>, instance: unknown, pathLabels?: string[]): void;
17
+ /**
18
+ * Runs the `@postConstruct()` method, awaiting if it returns a Promise.
19
+ */
20
+ declare function runPostConstructAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
21
+ /**
22
+ * Runs the `@preDestroy()` method synchronously if present. Throws if it returns a Promise.
23
+ */
24
+ declare function runPreDestroy(implementationClass: Constructor<unknown>, instance: unknown): void;
25
+ /**
26
+ * Runs the `@preDestroy()` method, awaiting if it returns a Promise.
27
+ */
28
+ declare function runPreDestroyAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
29
+ /**
30
+ * Runs `onActivation`, awaiting promises returned by the handler.
31
+ */
32
+ declare function runActivationAsync(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, _pathLabels: readonly string[]): Promise<unknown>;
33
+ //#endregion
34
+ export { readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
@@ -0,0 +1,83 @@
1
+ import { AsyncResolutionError } from "./errors.mjs";
2
+ import { CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol } from "./metadata/metadata-keys.mjs";
3
+ //#region src/lifecycle.ts
4
+ function isPromiseLike(value) {
5
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
6
+ }
7
+ /**
8
+ * Runs `onActivation` synchronously; rejects async activations on sync resolution paths.
9
+ */
10
+ function runActivation(binding, instance, ctx, pathLabels) {
11
+ const handler = binding.onActivation;
12
+ if (handler === void 0) return instance;
13
+ const bindingLabel = pathLabels[pathLabels.length - 1] ?? "(unknown)";
14
+ const activationResult = handler(ctx, instance);
15
+ if (isPromiseLike(activationResult)) throw new AsyncResolutionError(bindingLabel, pathLabels, "onActivation returned a Promise during synchronous resolution");
16
+ return activationResult;
17
+ }
18
+ /**
19
+ * Reads lifecycle metadata directly from a constructor's Symbol.metadata.
20
+ */
21
+ function readLifecycleMetadataFromCtor(implementationClass) {
22
+ const metadataObject = implementationClass[decoratorMetadataObjectSymbol()];
23
+ if (typeof metadataObject !== "object" || metadataObject === null) return;
24
+ const raw = metadataObject[CODEFAST_DI_LIFECYCLE_METADATA];
25
+ return typeof raw === "object" && raw !== null ? raw : void 0;
26
+ }
27
+ /**
28
+ * Runs the `@postConstruct()` method synchronously if present. Throws if it returns a Promise.
29
+ */
30
+ function runPostConstruct(implementationClass, instance, pathLabels) {
31
+ const meta = readLifecycleMetadataFromCtor(implementationClass);
32
+ if (meta?.postConstruct === void 0) return;
33
+ const methodName = meta.postConstruct;
34
+ const lifecycleMethod = instance[methodName];
35
+ if (typeof lifecycleMethod !== "function") return;
36
+ const postConstructResult = lifecycleMethod.call(instance);
37
+ if (typeof postConstructResult === "object" && postConstructResult !== null && "then" in postConstructResult && typeof postConstructResult.then === "function") {
38
+ const labels = pathLabels ?? [];
39
+ throw new AsyncResolutionError(labels[labels.length - 1] ?? "(unknown)", labels, `@postConstruct() "${methodName}" returned a Promise during synchronous resolution`);
40
+ }
41
+ }
42
+ /**
43
+ * Runs the `@postConstruct()` method, awaiting if it returns a Promise.
44
+ */
45
+ async function runPostConstructAsync(implementationClass, instance) {
46
+ const meta = readLifecycleMetadataFromCtor(implementationClass);
47
+ if (meta?.postConstruct === void 0) return;
48
+ const lifecycleMethod = instance[meta.postConstruct];
49
+ if (typeof lifecycleMethod !== "function") return;
50
+ await lifecycleMethod.call(instance);
51
+ }
52
+ /**
53
+ * Runs the `@preDestroy()` method synchronously if present. Throws if it returns a Promise.
54
+ */
55
+ function runPreDestroy(implementationClass, instance) {
56
+ const meta = readLifecycleMetadataFromCtor(implementationClass);
57
+ if (meta?.preDestroy === void 0) return;
58
+ const methodName = meta.preDestroy;
59
+ const lifecycleMethod = instance[methodName];
60
+ if (typeof lifecycleMethod !== "function") return;
61
+ const preDestroyResult = lifecycleMethod.call(instance);
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().`);
63
+ }
64
+ /**
65
+ * Runs the `@preDestroy()` method, awaiting if it returns a Promise.
66
+ */
67
+ async function runPreDestroyAsync(implementationClass, instance) {
68
+ const meta = readLifecycleMetadataFromCtor(implementationClass);
69
+ if (meta?.preDestroy === void 0) return;
70
+ const lifecycleMethod = instance[meta.preDestroy];
71
+ if (typeof lifecycleMethod !== "function") return;
72
+ await lifecycleMethod.call(instance);
73
+ }
74
+ /**
75
+ * Runs `onActivation`, awaiting promises returned by the handler.
76
+ */
77
+ async function runActivationAsync(binding, instance, ctx, _pathLabels) {
78
+ const handler = binding.onActivation;
79
+ if (handler === void 0) return instance;
80
+ return await handler(ctx, instance);
81
+ }
82
+ //#endregion
83
+ export { readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
@@ -0,0 +1,17 @@
1
+ //#region src/metadata/metadata-keys.d.ts
2
+ /**
3
+ * Well-known key for Codefast DI constructor metadata on `Symbol.metadata`.
4
+ */
5
+ declare const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
6
+ /** Well-known key for accessor field injection metadata written by `@inject` on `accessor` fields. */
7
+ declare const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
8
+ /** Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`. */
9
+ declare const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
10
+ /**
11
+ * Runtime symbol for the decorator metadata object (TC39 `Symbol.metadata`).
12
+ * Node may expose this only via `Symbol.for("Symbol.metadata")` until the global
13
+ * `Symbol.metadata` property is available.
14
+ */
15
+ declare function decoratorMetadataObjectSymbol(): symbol;
16
+ //#endregion
17
+ export { CODEFAST_DI_ACCESSOR_INJECTIONS, CODEFAST_DI_CONSTRUCTOR_METADATA, CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol };
@@ -0,0 +1,19 @@
1
+ //#region src/metadata/metadata-keys.ts
2
+ /**
3
+ * Well-known key for Codefast DI constructor metadata on `Symbol.metadata`.
4
+ */
5
+ const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
6
+ /** Well-known key for accessor field injection metadata written by `@inject` on `accessor` fields. */
7
+ const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
8
+ /** Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`. */
9
+ const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
10
+ /**
11
+ * Runtime symbol for the decorator metadata object (TC39 `Symbol.metadata`).
12
+ * Node may expose this only via `Symbol.for("Symbol.metadata")` until the global
13
+ * `Symbol.metadata` property is available.
14
+ */
15
+ function decoratorMetadataObjectSymbol() {
16
+ return typeof Symbol.metadata === "symbol" ? Symbol.metadata : Symbol.for("Symbol.metadata");
17
+ }
18
+ //#endregion
19
+ export { CODEFAST_DI_ACCESSOR_INJECTIONS, CODEFAST_DI_CONSTRUCTOR_METADATA, CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol };
@@ -0,0 +1,55 @@
1
+ import { Token } from "../token.mjs";
2
+ import { Constructor } from "../binding.mjs";
3
+
4
+ //#region src/metadata/metadata-types.d.ts
5
+ /** Metadata written per `accessor` field decorated with `@inject`; collected into `Symbol.metadata`. */
6
+ type AccessorInjectionMetadata = {
7
+ readonly name: string;
8
+ readonly token: Token<unknown> | Constructor<unknown>;
9
+ readonly optional: boolean;
10
+ readonly resolveHint?: {
11
+ readonly name?: string;
12
+ readonly tag?: readonly [tag: string, value: unknown];
13
+ };
14
+ };
15
+ /** Lifecycle method names written by `@postConstruct()` / `@preDestroy()` into `Symbol.metadata`. */
16
+ type LifecycleMetadata = {
17
+ readonly postConstruct?: string;
18
+ readonly preDestroy?: string;
19
+ readonly accessorInjections?: readonly AccessorInjectionMetadata[];
20
+ };
21
+ /**
22
+ * Per-parameter injection description collected by `@injectable()`.
23
+ */
24
+ type ParamMetadata = {
25
+ readonly index: number;
26
+ readonly token: Token<unknown> | Constructor<unknown>;
27
+ readonly optional: boolean;
28
+ readonly name?: string;
29
+ readonly tag?: readonly [tag: string, value: unknown];
30
+ };
31
+ /**
32
+ * Resolved form of an `inject()` / `optional()` call: token + optional flag + optional resolve hint.
33
+ * Used both as a deps-array entry in `@injectable()` and as accessor-field injection metadata.
34
+ */
35
+ 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];
40
+ };
41
+ /**
42
+ * Constructor injection shape stored on the class `Symbol.metadata` object.
43
+ */
44
+ type ConstructorMetadata = {
45
+ readonly params: readonly ParamMetadata[];
46
+ };
47
+ /**
48
+ * Abstraction for reading DI metadata (section 6.4) without tying callers to `Symbol.metadata`.
49
+ */
50
+ type MetadataReader = {
51
+ getConstructorMetadata(implementationClass: Constructor<unknown>): ConstructorMetadata | undefined;
52
+ getLifecycleMetadata?(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
53
+ };
54
+ //#endregion
55
+ export { AccessorInjectionMetadata, ConstructorMetadata, InjectionDescriptor, LifecycleMetadata, MetadataReader, ParamMetadata };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { Constructor } from "../binding.mjs";
2
+ import { ParamMetadata } from "./metadata-types.mjs";
3
+
4
+ //#region src/metadata/param-registry.d.ts
5
+ /**
6
+ * Returns the pending `ParamMetadata` map for `implementationClass`, creating it on first access.
7
+ * Used by legacy parameter decorators that fire before the class decorator runs.
8
+ */
9
+ declare function getOrCreatePendingMap(implementationClass: Constructor<unknown>): Map<number, ParamMetadata>;
10
+ /**
11
+ * Removes and returns the pending map for `implementationClass` (transfer of ownership).
12
+ * Returns `undefined` if no pending entries exist.
13
+ */
14
+ declare function takePendingMap(implementationClass: Constructor<unknown>): Map<number, ParamMetadata> | undefined;
15
+ //#endregion
16
+ export { getOrCreatePendingMap, takePendingMap };
@@ -0,0 +1,25 @@
1
+ //#region src/metadata/param-registry.ts
2
+ const pendingByConstructor = /* @__PURE__ */ new WeakMap();
3
+ /**
4
+ * Returns the pending `ParamMetadata` map for `implementationClass`, creating it on first access.
5
+ * Used by legacy parameter decorators that fire before the class decorator runs.
6
+ */
7
+ function getOrCreatePendingMap(implementationClass) {
8
+ let map = pendingByConstructor.get(implementationClass);
9
+ if (!map) {
10
+ map = /* @__PURE__ */ new Map();
11
+ pendingByConstructor.set(implementationClass, map);
12
+ }
13
+ return map;
14
+ }
15
+ /**
16
+ * Removes and returns the pending map for `implementationClass` (transfer of ownership).
17
+ * Returns `undefined` if no pending entries exist.
18
+ */
19
+ function takePendingMap(implementationClass) {
20
+ const map = pendingByConstructor.get(implementationClass);
21
+ if (map) pendingByConstructor.delete(implementationClass);
22
+ return map;
23
+ }
24
+ //#endregion
25
+ export { getOrCreatePendingMap, takePendingMap };
@@ -0,0 +1,15 @@
1
+ import { Constructor } from "../binding.mjs";
2
+ import { ConstructorMetadata, LifecycleMetadata, MetadataReader } from "./metadata-types.mjs";
3
+
4
+ //#region src/metadata/symbol-metadata-reader.d.ts
5
+ /**
6
+ * Reads {@link ConstructorMetadata} from the standard `Symbol.metadata` object.
7
+ */
8
+ declare class SymbolMetadataReader implements MetadataReader {
9
+ /** Reads constructor param metadata written by `@injectable()`. Returns `undefined` if none present. */
10
+ getConstructorMetadata(implementationClass: Constructor<unknown>): ConstructorMetadata | undefined;
11
+ /** Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes. */
12
+ getLifecycleMetadata(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
13
+ }
14
+ //#endregion
15
+ export { SymbolMetadataReader };
@@ -0,0 +1,32 @@
1
+ import { CODEFAST_DI_CONSTRUCTOR_METADATA, CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol } from "./metadata-keys.mjs";
2
+ //#region src/metadata/symbol-metadata-reader.ts
3
+ /** Type guard — returns `true` when `value` has the shape of a {@link ConstructorMetadata} object. */
4
+ function isConstructorMetadata(value) {
5
+ if (typeof value !== "object" || value === null || !("params" in value)) return false;
6
+ return Array.isArray(value.params);
7
+ }
8
+ /**
9
+ * Reads {@link ConstructorMetadata} from the standard `Symbol.metadata` object.
10
+ */
11
+ var SymbolMetadataReader = class {
12
+ /** Reads constructor param metadata written by `@injectable()`. Returns `undefined` if none present. */
13
+ getConstructorMetadata(implementationClass) {
14
+ const rawMetadata = implementationClass[decoratorMetadataObjectSymbol()];
15
+ if (typeof rawMetadata !== "object" || rawMetadata === null) return;
16
+ const metadataObject = rawMetadata;
17
+ if (!Object.hasOwn(metadataObject, "codefast/di:constructor-metadata:v1")) return;
18
+ const raw = metadataObject[CODEFAST_DI_CONSTRUCTOR_METADATA];
19
+ if (!isConstructorMetadata(raw)) return;
20
+ return raw;
21
+ }
22
+ /** Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes. */
23
+ getLifecycleMetadata(implementationClass) {
24
+ const metadataObject = implementationClass[decoratorMetadataObjectSymbol()];
25
+ if (typeof metadataObject !== "object" || metadataObject === null) return;
26
+ const raw = metadataObject[CODEFAST_DI_LIFECYCLE_METADATA];
27
+ if (typeof raw !== "object" || raw === null) return;
28
+ return raw;
29
+ }
30
+ };
31
+ //#endregion
32
+ export { SymbolMetadataReader };
@@ -0,0 +1,60 @@
1
+ import { Token } from "./token.mjs";
2
+ import { BindingBuilder, Constructor } from "./binding.mjs";
3
+
4
+ //#region src/module.d.ts
5
+ /**
6
+ * Builder passed to synchronous module setup: register bindings and import other sync modules.
7
+ */
8
+ type ModuleBuilder = {
9
+ readonly import: (...modules: Module[]) => void;
10
+ readonly bind: <Value>(key: Token<Value> | Constructor<Value>) => BindingBuilder<Value>;
11
+ };
12
+ /**
13
+ * Builder passed to async module setup.
14
+ */
15
+ type AsyncModuleBuilder = {
16
+ readonly import: (...modules: (Module | AsyncModule)[]) => void;
17
+ readonly bind: <Value>(key: Token<Value> | Constructor<Value>) => BindingBuilder<Value>;
18
+ };
19
+ /**
20
+ * Immutable description of a bundle of bindings. A {@link Module} holds no runtime state and the
21
+ * same instance may be loaded into any number of containers independently (spec §7.3).
22
+ *
23
+ * The owning container is responsible for tracking which modules have been loaded and which
24
+ * binding ids each module produced; the module itself never sees a container reference.
25
+ */
26
+ declare class Module {
27
+ readonly name: string;
28
+ private readonly syncSetup;
29
+ private constructor();
30
+ /**
31
+ * Defines a synchronous module.
32
+ * @param name - Human-readable label used in error messages and debug output.
33
+ * @param setup - Callback that registers bindings via the {@link ModuleBuilder}.
34
+ */
35
+ static create(name: string, setup: (builder: ModuleBuilder) => void): Module;
36
+ /**
37
+ * Defines an async module — use when setup requires awaiting (e.g. reading config, dynamic imports).
38
+ * Load with {@link Container.loadAsync} or {@link Container.fromModulesAsync}.
39
+ */
40
+ static createAsync(name: string, setup: (builder: AsyncModuleBuilder) => Promise<void>): AsyncModule;
41
+ /**
42
+ * @internal Invoked by the container while loading this module.
43
+ */
44
+ runSyncSetup(builder: ModuleBuilder): void;
45
+ }
46
+ /**
47
+ * An async module whose setup callback may `await` before registering bindings.
48
+ * Prefer {@link Module.createAsync} over constructing this class directly.
49
+ */
50
+ declare class AsyncModule {
51
+ readonly name: string;
52
+ private readonly asyncSetup;
53
+ constructor(name: string, asyncSetup: (builder: AsyncModuleBuilder) => Promise<void>);
54
+ /**
55
+ * @internal Invoked by the container while loading this module.
56
+ */
57
+ runAsyncSetup(builder: AsyncModuleBuilder): Promise<void>;
58
+ }
59
+ //#endregion
60
+ export { AsyncModule, AsyncModuleBuilder, Module, ModuleBuilder };