@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +262 -235
  3. package/dist/binding-select.d.mts +17 -6
  4. package/dist/binding-select.mjs +17 -6
  5. package/dist/binding.d.mts +148 -23
  6. package/dist/binding.mjs +103 -14
  7. package/dist/constraints.d.mts +18 -3
  8. package/dist/constraints.mjs +18 -3
  9. package/dist/container.d.mts +81 -26
  10. package/dist/container.mjs +91 -3
  11. package/dist/decorators/inject.d.mts +40 -9
  12. package/dist/decorators/inject.mjs +50 -11
  13. package/dist/decorators/injectable.d.mts +2 -1
  14. package/dist/decorators/injectable.mjs +14 -2
  15. package/dist/decorators/lifecycle-decorators.d.mts +16 -4
  16. package/dist/decorators/lifecycle-decorators.mjs +16 -4
  17. package/dist/dependency-graph.d.mts +31 -8
  18. package/dist/dependency-graph.mjs +42 -8
  19. package/dist/errors.d.mts +124 -13
  20. package/dist/errors.mjs +126 -18
  21. package/dist/index.d.mts +2 -2
  22. package/dist/index.mjs +2 -2
  23. package/dist/inspector.d.mts +38 -14
  24. package/dist/inspector.mjs +36 -15
  25. package/dist/lifecycle.d.mts +28 -6
  26. package/dist/lifecycle.mjs +29 -10
  27. package/dist/metadata/metadata-keys.d.mts +17 -6
  28. package/dist/metadata/metadata-keys.mjs +17 -6
  29. package/dist/metadata/metadata-types.d.mts +29 -5
  30. package/dist/metadata/param-registry.mjs +6 -0
  31. package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
  32. package/dist/metadata/symbol-metadata-reader.mjs +23 -4
  33. package/dist/module.d.mts +34 -2
  34. package/dist/module.mjs +19 -0
  35. package/dist/registry.d.mts +39 -8
  36. package/dist/registry.mjs +39 -8
  37. package/dist/resolver.d.mts +107 -12
  38. package/dist/resolver.mjs +134 -37
  39. package/dist/scope-validation.d.mts +3 -2
  40. package/dist/scope-validation.mjs +3 -2
  41. package/dist/scope.d.mts +34 -6
  42. package/dist/scope.mjs +38 -13
  43. package/dist/token.d.mts +9 -2
  44. package/dist/token.mjs +7 -1
  45. 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 };
@@ -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
- /** Whether a singleton/scoped binding's instance is currently held in the scope cache. */
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
- /** Per-binding row inside a {@link ContainerSnapshot}. */
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; /** True when {@link BindingBuilder.when} was used (runtime predicate; static graph may still show edges). */
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
- /** Full debug snapshot returned by {@link Container.inspect}. */
26
+ /**
27
+ * Full debug snapshot returned by {@link Container.inspect}.
28
+ */
20
29
  type ContainerSnapshot = {
21
30
  readonly bindings: readonly ContainerBindingSnapshot[];
22
31
  };
23
- /** Structured dependency graph returned by {@link Container.generateDependencyGraph} with `format: "json"`. */
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
- /** Read-only view of the container internals exposed to {@link ContainerInspector}. */
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
- /** Options for {@link Container.generateDependencyGraph}. */
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
- * Read-only introspection and Graphviz DOT export for a container graph.
45
- */
46
- /**
47
- * Reads the registry and scope-cache state to produce debug snapshots and dependency graphs.
48
- * Instantiated internally by the container; advanced consumers can construct it directly via
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
- /** Collects all registered bindings into a flat, serialisable snapshot. */
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`.
@@ -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
- /** Maps a binding's scope and cache state to a {@link BindingActivationStatus} label. */
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
- /** Escapes a string for use as a DOT `label="..."` attribute value. */
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
- /** Escapes a string for safe embedding inside a DOT HTML-label (`<...>`) table cell. */
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
16
22
  }
17
- /** Returns the Graphviz node shape name for a given binding kind. */
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
- /** Returns DOT fill-color and style attributes that visually distinguish binding scopes. */
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
- /** Strips non-alphanumeric characters from a module name to produce a valid DOT subgraph identifier. */
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
- /** Returns `true` when the label string belongs to a framework-internal registry key. */
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
- /** Returns `true` when the registry key resolves to an internal framework label. */
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
- * 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.
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
- /** Collects all registered bindings into a flat, serialisable snapshot. */
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();
@@ -3,15 +3,30 @@ import { LifecycleMetadata } from "./metadata/metadata-types.mjs";
3
3
 
4
4
  //#region src/lifecycle.d.ts
5
5
  /**
6
- * Runs `onActivation` synchronously; rejects async activations on sync resolution paths.
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 Symbol.metadata.
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. Throws if it returns a Promise.
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. Throws if it returns a Promise.
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 promises returned by the handler.
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 };
@@ -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; rejects async activations on sync resolution paths.
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 Symbol.metadata.
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. Throws if it returns a Promise.
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
- const postConstructResult = lifecycleMethod.call(instance);
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. Throws if it returns a Promise.
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
- 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().`);
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 promises returned by the handler.
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 Codefast DI constructor metadata on `Symbol.metadata`.
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
- /** Well-known key for accessor field injection metadata written by `@inject` on `accessor` fields. */
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
- /** Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`. */
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
- * 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.
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 Codefast DI constructor metadata on `Symbol.metadata`.
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
- /** Well-known key for accessor field injection metadata written by `@inject` on `accessor` fields. */
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
- /** Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`. */
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
- * 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.
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
- /** Metadata written per `accessor` field decorated with `@inject`; collected into `Symbol.metadata`. */
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
- /** Lifecycle method names written by `@postConstruct()` / `@preDestroy()` into `Symbol.metadata`. */
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 (section 6.4) without tying callers to `Symbol.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
- * Reads {@link ConstructorMetadata} from the standard `Symbol.metadata` object.
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
- /** Reads constructor param metadata written by `@injectable()`. Returns `undefined` if none present. */
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
- /** Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes. */
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
- /** Type guard — returns `true` when `value` has the shape of a {@link ConstructorMetadata} object. */
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
- * Reads {@link ConstructorMetadata} from the standard `Symbol.metadata` object.
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
- /** Reads constructor param metadata written by `@injectable()`. Returns `undefined` if none present. */
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
- /** Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes. */
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;