@codefast/di 0.3.14-canary.0 → 0.3.14-canary.2

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 (66) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +54 -29
  3. package/dist/binding-scope.d.mts +11 -0
  4. package/dist/binding-scope.mjs +19 -0
  5. package/dist/binding-select.d.mts +8 -26
  6. package/dist/binding-select.mjs +56 -49
  7. package/dist/binding.d.mts +107 -327
  8. package/dist/binding.mjs +19 -324
  9. package/dist/constraints.d.mts +11 -29
  10. package/dist/constraints.mjs +32 -36
  11. package/dist/constructor-type.d.mts +17 -0
  12. package/dist/constructor-type.mjs +1 -0
  13. package/dist/container.d.mts +44 -128
  14. package/dist/container.mjs +664 -433
  15. package/dist/decorators/inject.d.mts +19 -50
  16. package/dist/decorators/inject.mjs +131 -93
  17. package/dist/decorators/injectable.d.mts +16 -37
  18. package/dist/decorators/injectable.mjs +47 -66
  19. package/dist/decorators/lifecycle-decorators.d.mts +2 -22
  20. package/dist/decorators/lifecycle-decorators.mjs +81 -37
  21. package/dist/dependency-graph.d.mts +24 -54
  22. package/dist/dependency-graph.mjs +51 -153
  23. package/dist/environment.d.mts +38 -12
  24. package/dist/environment.mjs +82 -16
  25. package/dist/errors.d.mts +69 -188
  26. package/dist/errors.mjs +92 -219
  27. package/dist/graph-adapters/cytoscape.d.mts +24 -0
  28. package/dist/graph-adapters/cytoscape.mjs +23 -0
  29. package/dist/graph-adapters/dot.d.mts +6 -0
  30. package/dist/graph-adapters/dot.mjs +17 -0
  31. package/dist/graph-adapters/reactflow.d.mts +29 -0
  32. package/dist/graph-adapters/reactflow.mjs +29 -0
  33. package/dist/graph-adapters/types.d.mts +2 -0
  34. package/dist/graph-adapters/types.mjs +1 -0
  35. package/dist/index.d.mts +16 -9
  36. package/dist/index.mjs +8 -5
  37. package/dist/inspector.d.mts +34 -95
  38. package/dist/inspector.mjs +57 -256
  39. package/dist/lifecycle.d.mts +20 -53
  40. package/dist/lifecycle.mjs +129 -99
  41. package/dist/metadata/metadata-keys.d.mts +9 -26
  42. package/dist/metadata/metadata-keys.mjs +7 -28
  43. package/dist/metadata/metadata-reader-token.d.mts +7 -0
  44. package/dist/metadata/metadata-reader-token.mjs +5 -0
  45. package/dist/metadata/metadata-types.d.mts +22 -71
  46. package/dist/metadata/symbol-metadata-reader.d.mts +10 -26
  47. package/dist/metadata/symbol-metadata-reader.mjs +32 -45
  48. package/dist/module.d.mts +30 -84
  49. package/dist/module.mjs +26 -72
  50. package/dist/registry.d.mts +32 -63
  51. package/dist/registry.mjs +131 -82
  52. package/dist/resolve-options.d.mts +18 -0
  53. package/dist/resolve-options.mjs +22 -0
  54. package/dist/resolver.d.mts +67 -190
  55. package/dist/resolver.mjs +715 -424
  56. package/dist/scope.d.mts +19 -102
  57. package/dist/scope.mjs +37 -192
  58. package/dist/token.d.mts +8 -22
  59. package/dist/token.mjs +9 -11
  60. package/dist/types.d.mts +48 -0
  61. package/dist/types.mjs +1 -0
  62. package/package.json +52 -14
  63. package/dist/metadata/param-registry.d.mts +0 -16
  64. package/dist/metadata/param-registry.mjs +0 -31
  65. package/dist/scope-validation.d.mts +0 -21
  66. package/dist/scope-validation.mjs +0 -35
@@ -1,56 +1,23 @@
1
- import { Binding, Constructor, ResolutionContext } from "./binding.mjs";
2
- import { LifecycleMetadata } from "./metadata/metadata-types.mjs";
1
+ import { Constructor } from "./constructor-type.mjs";
2
+ import { Token } from "./token.mjs";
3
+ import { ActivationHandler, DeactivationHandler, ResolutionContext } from "./types.mjs";
4
+ import { Binding } from "./binding.mjs";
5
+ import { MetadataReader } from "./metadata/metadata-types.mjs";
3
6
 
4
7
  //#region src/lifecycle.d.ts
5
- /**
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.
16
- */
17
- declare function runActivation(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, pathLabels: readonly string[]): unknown;
18
- /**
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.
22
- */
23
- declare function readLifecycleMetadataFromCtor(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
24
- /**
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.
30
- */
31
- declare function runPostConstruct(implementationClass: Constructor<unknown>, instance: unknown, pathLabels?: string[]): void;
32
- /**
33
- * Runs the `@postConstruct()` method, awaiting if it returns a Promise.
34
- */
35
- declare function runPostConstructAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
36
- /**
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`**.
41
- */
42
- declare function runPreDestroy(implementationClass: Constructor<unknown>, instance: unknown): void;
43
- /**
44
- * Runs the `@preDestroy()` method, awaiting if it returns a Promise.
45
- *
46
- * Lifecycle ordering: `onDeactivation` → **`@preDestroy`** (async variant).
47
- */
48
- declare function runPreDestroyAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
49
- /**
50
- * Runs `onActivation`, awaiting if the handler returns a Promise.
51
- *
52
- * Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** (async variant) → cache.
53
- */
54
- declare function runActivationAsync(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, _pathLabels: readonly string[]): Promise<unknown>;
8
+ declare class LifecycleManager {
9
+ private readonly _activationHooks;
10
+ private readonly _deactivationHooks;
11
+ private _activationVersion;
12
+ registerActivation<const Value>(t: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void;
13
+ hasActivationHandlers<const Value>(t: Token<Value> | Constructor<Value>): boolean;
14
+ get activationVersion(): number;
15
+ registerDeactivation<const Value>(t: Token<Value> | Constructor<Value>, handler: DeactivationHandler<Value>): void;
16
+ runActivation<const Value>(ctx: ResolutionContext, binding: Binding<Value>, instance: Value, metadataReader: MetadataReader): Promise<Value>;
17
+ runActivationSync<const Value>(ctx: ResolutionContext, binding: Binding<Value>, instance: Value, metadataReader: MetadataReader): Value;
18
+ runDeactivation<const Value>(binding: Binding<Value>, instance: Value, metadataReader: MetadataReader): Promise<void>;
19
+ runDeactivationSync<const Value>(binding: Binding<Value>, instance: Value, metadataReader: MetadataReader): void;
20
+ hasAsyncDeactivation<const Value>(binding: Binding<Value>, instance: Value, _metadataReader: MetadataReader): boolean;
21
+ }
55
22
  //#endregion
56
- export { isPromiseLike, readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
23
+ export { LifecycleManager };
@@ -1,102 +1,132 @@
1
- import { AsyncResolutionError } from "./errors.mjs";
2
- import { CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol } from "./metadata/metadata-keys.mjs";
1
+ import { AsyncDeactivationError } from "./errors.mjs";
2
+ import { tokenName } from "./token.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
- */
8
- function isPromiseLike(value) {
9
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
10
- }
11
- /**
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.
17
- */
18
- function runActivation(binding, instance, ctx, pathLabels) {
19
- const handler = binding.onActivation;
20
- if (handler === void 0) return instance;
21
- const bindingLabel = pathLabels[pathLabels.length - 1] ?? "(unknown)";
22
- const activationResult = handler(ctx, instance);
23
- if (isPromiseLike(activationResult)) throw new AsyncResolutionError(bindingLabel, pathLabels, "onActivation returned a Promise during synchronous resolution");
24
- return activationResult;
25
- }
26
- /**
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.
30
- */
31
- function readLifecycleMetadataFromCtor(implementationClass) {
32
- const metadataObject = implementationClass[decoratorMetadataObjectSymbol()];
33
- if (typeof metadataObject !== "object" || metadataObject === null) return;
34
- const raw = metadataObject[CODEFAST_DI_LIFECYCLE_METADATA];
35
- return typeof raw === "object" && raw !== null ? raw : void 0;
36
- }
37
- /**
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.
43
- */
44
- function runPostConstruct(implementationClass, instance, pathLabels) {
45
- const meta = readLifecycleMetadataFromCtor(implementationClass);
46
- if (meta?.postConstruct === void 0) return;
47
- const methodName = meta.postConstruct;
48
- const lifecycleMethod = instance[methodName];
49
- if (typeof lifecycleMethod !== "function") return;
50
- if (isPromiseLike(lifecycleMethod.call(instance))) {
51
- const labels = pathLabels ?? [];
52
- throw new AsyncResolutionError(labels[labels.length - 1] ?? "(unknown)", labels, `@postConstruct() "${methodName}" returned a Promise during synchronous resolution`);
4
+ var LifecycleManager = class {
5
+ _activationHooks = /* @__PURE__ */ new Map();
6
+ _deactivationHooks = /* @__PURE__ */ new Map();
7
+ _activationVersion = 0;
8
+ registerActivation(t, handler) {
9
+ this._activationVersion += 1;
10
+ let list = this._activationHooks.get(t);
11
+ if (list === void 0) {
12
+ list = [];
13
+ this._activationHooks.set(t, list);
14
+ }
15
+ list.push(handler);
53
16
  }
54
- }
55
- /**
56
- * Runs the `@postConstruct()` method, awaiting if it returns a Promise.
57
- */
58
- async function runPostConstructAsync(implementationClass, instance) {
59
- const meta = readLifecycleMetadataFromCtor(implementationClass);
60
- if (meta?.postConstruct === void 0) return;
61
- const lifecycleMethod = instance[meta.postConstruct];
62
- if (typeof lifecycleMethod !== "function") return;
63
- await lifecycleMethod.call(instance);
64
- }
65
- /**
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`**.
70
- */
71
- function runPreDestroy(implementationClass, instance) {
72
- const meta = readLifecycleMetadataFromCtor(implementationClass);
73
- if (meta?.preDestroy === void 0) return;
74
- const methodName = meta.preDestroy;
75
- const lifecycleMethod = instance[methodName];
76
- if (typeof lifecycleMethod !== "function") return;
77
- if (isPromiseLike(lifecycleMethod.call(instance))) throw new Error(`@preDestroy() "${methodName}" returned a Promise during synchronous disposal; use disposeAsync() / unloadAsync().`);
78
- }
79
- /**
80
- * Runs the `@preDestroy()` method, awaiting if it returns a Promise.
81
- *
82
- * Lifecycle ordering: `onDeactivation` → **`@preDestroy`** (async variant).
83
- */
84
- async function runPreDestroyAsync(implementationClass, instance) {
85
- const meta = readLifecycleMetadataFromCtor(implementationClass);
86
- if (meta?.preDestroy === void 0) return;
87
- const lifecycleMethod = instance[meta.preDestroy];
88
- if (typeof lifecycleMethod !== "function") return;
89
- await lifecycleMethod.call(instance);
90
- }
91
- /**
92
- * Runs `onActivation`, awaiting if the handler returns a Promise.
93
- *
94
- * Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** (async variant) → cache.
95
- */
96
- async function runActivationAsync(binding, instance, ctx, _pathLabels) {
97
- const handler = binding.onActivation;
98
- if (handler === void 0) return instance;
99
- return await handler(ctx, instance);
100
- }
17
+ hasActivationHandlers(t) {
18
+ if (this._activationHooks.size === 0) return false;
19
+ const list = this._activationHooks.get(t);
20
+ return list !== void 0 && list.length > 0;
21
+ }
22
+ get activationVersion() {
23
+ return this._activationVersion;
24
+ }
25
+ registerDeactivation(t, handler) {
26
+ let list = this._deactivationHooks.get(t);
27
+ if (list === void 0) {
28
+ list = [];
29
+ this._deactivationHooks.set(t, list);
30
+ }
31
+ list.push(handler);
32
+ }
33
+ async runActivation(ctx, binding, instance, metadataReader) {
34
+ let result = instance;
35
+ if (binding.kind === "class") {
36
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
37
+ if (lifecycle?.postConstruct && lifecycle.postConstruct.length > 0) for (const methodName of lifecycle.postConstruct) {
38
+ const method = result[methodName];
39
+ if (typeof method === "function") {
40
+ const r = method.call(result);
41
+ if (r instanceof Promise) await r;
42
+ }
43
+ }
44
+ }
45
+ if (binding.kind !== "alias" && binding.onActivation !== void 0) {
46
+ const activated = binding.onActivation(ctx, result);
47
+ result = activated instanceof Promise ? await activated : activated;
48
+ }
49
+ const containerHooks = this._activationHooks.get(binding.token);
50
+ if (containerHooks !== void 0) for (const hook of containerHooks) {
51
+ const activated = hook(ctx, result);
52
+ result = activated instanceof Promise ? await activated : activated;
53
+ }
54
+ return result;
55
+ }
56
+ runActivationSync(ctx, binding, instance, metadataReader) {
57
+ let result = instance;
58
+ if (binding.kind === "class") {
59
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
60
+ if (lifecycle?.postConstruct && lifecycle.postConstruct.length > 0) for (const methodName of lifecycle.postConstruct) {
61
+ const method = result[methodName];
62
+ if (typeof method === "function") {
63
+ if (method.call(result) instanceof Promise) throw new Error(`@postConstruct method '${methodName}' returned a Promise. Use resolveAsync() instead.`);
64
+ }
65
+ }
66
+ }
67
+ if (binding.kind !== "alias" && binding.onActivation !== void 0) {
68
+ const activated = binding.onActivation(ctx, result);
69
+ if (activated instanceof Promise) throw new Error(`onActivation for '${tokenName(binding.token)}' returned a Promise. Use resolveAsync() instead.`);
70
+ result = activated;
71
+ }
72
+ const key = tokenName(binding.token);
73
+ const containerHooks = this._activationHooks.get(binding.token);
74
+ if (containerHooks !== void 0) for (const hook of containerHooks) {
75
+ const activated = hook(ctx, result);
76
+ if (activated instanceof Promise) throw new Error(`Container-level onActivation for '${key}' returned a Promise. Use resolveAsync() instead.`);
77
+ result = activated;
78
+ }
79
+ return result;
80
+ }
81
+ async runDeactivation(binding, instance, metadataReader) {
82
+ const key = binding.token;
83
+ const containerHooks = this._deactivationHooks.get(key);
84
+ if (containerHooks !== void 0) for (const hook of containerHooks) {
85
+ const r = hook(instance);
86
+ if (r instanceof Promise) await r;
87
+ }
88
+ if (binding.kind !== "alias" && binding.onDeactivation !== void 0) {
89
+ const r = binding.onDeactivation(instance);
90
+ if (r instanceof Promise) await r;
91
+ }
92
+ if (binding.kind === "class") {
93
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
94
+ if (lifecycle?.preDestroy && lifecycle.preDestroy.length > 0) for (const methodName of lifecycle.preDestroy) {
95
+ const method = instance[methodName];
96
+ if (typeof method === "function") {
97
+ const r = method.call(instance);
98
+ if (r instanceof Promise) await r;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ runDeactivationSync(binding, instance, metadataReader) {
104
+ const tName = tokenName(binding.token);
105
+ const key = binding.token;
106
+ const containerHooks = this._deactivationHooks.get(key);
107
+ if (containerHooks !== void 0) {
108
+ for (const hook of containerHooks) if (hook(instance) instanceof Promise) throw new AsyncDeactivationError(tName);
109
+ }
110
+ if (binding.kind !== "alias" && binding.onDeactivation !== void 0) {
111
+ if (binding.onDeactivation(instance) instanceof Promise) throw new AsyncDeactivationError(tName);
112
+ }
113
+ if (binding.kind === "class") {
114
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
115
+ if (lifecycle?.preDestroy && lifecycle.preDestroy.length > 0) for (const methodName of lifecycle.preDestroy) {
116
+ const method = instance[methodName];
117
+ if (typeof method === "function") {
118
+ if (method.call(instance) instanceof Promise) throw new AsyncDeactivationError(tName);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ hasAsyncDeactivation(binding, instance, _metadataReader) {
124
+ if (binding.kind === "alias") return false;
125
+ if (binding.onDeactivation !== void 0) {
126
+ if (binding.onDeactivation(instance) instanceof Promise) return true;
127
+ }
128
+ return false;
129
+ }
130
+ };
101
131
  //#endregion
102
- export { isPromiseLike, readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
132
+ export { LifecycleManager };
@@ -1,28 +1,11 @@
1
+ import { ConstructorMetadata, MutableLifecycleMetadata } from "./metadata-types.mjs";
2
+
1
3
  //#region src/metadata/metadata-keys.d.ts
2
- /**
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.
6
- */
7
- declare const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
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
- */
13
- declare const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
14
- /**
15
- * Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`.
16
- */
17
- declare const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
18
- /**
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.
25
- */
26
- declare function decoratorMetadataObjectSymbol(): symbol;
4
+ declare const INJECTABLE_KEY: unique symbol;
5
+ declare const LIFECYCLE_KEY: unique symbol;
6
+ declare const INJECT_ACCESSOR_KEY: unique symbol;
7
+ declare const constructorMetadataMap: WeakMap<object, ConstructorMetadata>;
8
+ declare const lifecycleMetadataMap: WeakMap<object, MutableLifecycleMetadata>;
9
+ declare const lifecycleByConstructorMetadataMap: WeakMap<object, MutableLifecycleMetadata>;
27
10
  //#endregion
28
- export { CODEFAST_DI_ACCESSOR_INJECTIONS, CODEFAST_DI_CONSTRUCTOR_METADATA, CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol };
11
+ export { INJECTABLE_KEY, INJECT_ACCESSOR_KEY, LIFECYCLE_KEY, constructorMetadataMap, lifecycleByConstructorMetadataMap, lifecycleMetadataMap };
@@ -1,30 +1,9 @@
1
1
  //#region src/metadata/metadata-keys.ts
2
- /**
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.
6
- */
7
- const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
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
- */
13
- const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
14
- /**
15
- * Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`.
16
- */
17
- const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
18
- /**
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.
25
- */
26
- function decoratorMetadataObjectSymbol() {
27
- return typeof Symbol.metadata === "symbol" ? Symbol.metadata : Symbol.for("Symbol.metadata");
28
- }
2
+ const INJECTABLE_KEY = Symbol("di:injectable");
3
+ const LIFECYCLE_KEY = Symbol("di:lifecycle");
4
+ const INJECT_ACCESSOR_KEY = Symbol("di:inject-accessor");
5
+ const constructorMetadataMap = /* @__PURE__ */ new WeakMap();
6
+ const lifecycleMetadataMap = /* @__PURE__ */ new WeakMap();
7
+ const lifecycleByConstructorMetadataMap = /* @__PURE__ */ new WeakMap();
29
8
  //#endregion
30
- export { CODEFAST_DI_ACCESSOR_INJECTIONS, CODEFAST_DI_CONSTRUCTOR_METADATA, CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol };
9
+ export { INJECTABLE_KEY, INJECT_ACCESSOR_KEY, LIFECYCLE_KEY, constructorMetadataMap, lifecycleByConstructorMetadataMap, lifecycleMetadataMap };
@@ -0,0 +1,7 @@
1
+ import { Token } from "../token.mjs";
2
+ import { MetadataReader } from "./metadata-types.mjs";
3
+
4
+ //#region src/metadata/metadata-reader-token.d.ts
5
+ declare const MetadataReaderToken: Token<MetadataReader>;
6
+ //#endregion
7
+ export { MetadataReaderToken };
@@ -0,0 +1,5 @@
1
+ import { token } from "../token.mjs";
2
+ //#region src/metadata/metadata-reader-token.ts
3
+ const MetadataReaderToken = token("MetadataReader");
4
+ //#endregion
5
+ export { MetadataReaderToken };
@@ -1,79 +1,30 @@
1
+ import { Constructor } from "../constructor-type.mjs";
1
2
  import { Token } from "../token.mjs";
2
- import { Constructor } from "../binding.mjs";
3
3
 
4
4
  //#region src/metadata/metadata-types.d.ts
5
- /**
6
- * Metadata written per `accessor` field decorated with `@inject`; collected into `Symbol.metadata`.
7
- */
8
- type AccessorInjectionMetadata = {
9
- readonly name: string;
10
- readonly token: Token<unknown> | Constructor<unknown>;
11
- readonly optional: boolean;
12
- readonly resolveHint?: {
13
- readonly name?: string;
14
- readonly tag?: readonly [tag: string, value: unknown];
15
- };
16
- };
17
- /**
18
- * Lifecycle method names written by `@postConstruct()` / `@preDestroy()` into `Symbol.metadata`.
19
- */
20
- type LifecycleMetadata = {
21
- readonly postConstruct?: string;
22
- readonly preDestroy?: string;
23
- };
24
- /**
25
- * Per-parameter injection description collected by `@injectable()`.
26
- */
27
- type ParamMetadata = {
5
+ interface ParamMetadata {
28
6
  readonly index: number;
29
- readonly token: Token<unknown> | Constructor<unknown>;
30
- readonly optional: boolean;
31
- readonly name?: string;
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;
38
- };
39
- /**
40
- * Resolved form of an `inject()` / `optional()` call: token + optional flag + optional resolve hint.
41
- * Used both as a deps-array entry in `@injectable()` and as accessor-field injection metadata.
42
- */
43
- type InjectionDescriptor<Value = unknown> = {
44
- readonly token: Token<Value> | Constructor<Value>;
7
+ readonly token: Token<unknown> | Constructor;
45
8
  readonly optional: boolean;
9
+ readonly multi: boolean;
46
10
  readonly name?: string;
47
- readonly tag?: readonly [tag: string, value: unknown]; /** When true, resolve every binding for {@link InjectionDescriptor.token} into an array. */
48
- readonly all?: boolean;
49
- };
50
- /**
51
- * Constructor injection shape stored on the class `Symbol.metadata` object.
52
- */
53
- type ConstructorMetadata = {
11
+ readonly tags?: ReadonlyArray<readonly [string, unknown]>;
12
+ }
13
+ interface ConstructorMetadata {
54
14
  readonly params: readonly ParamMetadata[];
55
- };
56
- /**
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`.
62
- */
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
- */
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
- */
76
- getLifecycleMetadata?(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
77
- };
15
+ }
16
+ interface LifecycleMetadata {
17
+ readonly postConstruct: readonly string[];
18
+ readonly preDestroy: readonly string[];
19
+ }
20
+ /** Mutable buckets used while aggregating decorator metadata (same keys as {@link LifecycleMetadata}). */
21
+ interface MutableLifecycleMetadata {
22
+ postConstruct: string[];
23
+ preDestroy: string[];
24
+ }
25
+ interface MetadataReader {
26
+ getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
27
+ getLifecycleMetadata(target: Constructor): LifecycleMetadata | undefined;
28
+ }
78
29
  //#endregion
79
- export { AccessorInjectionMetadata, ConstructorMetadata, InjectionDescriptor, LifecycleMetadata, MetadataReader, ParamMetadata };
30
+ export { ConstructorMetadata, LifecycleMetadata, MetadataReader, MutableLifecycleMetadata, ParamMetadata };
@@ -1,32 +1,16 @@
1
- import { Constructor } from "../binding.mjs";
1
+ import { Constructor } from "../constructor-type.mjs";
2
+ import { InjectionDescriptor } from "../decorators/inject.mjs";
2
3
  import { ConstructorMetadata, LifecycleMetadata, MetadataReader } from "./metadata-types.mjs";
3
4
 
4
5
  //#region src/metadata/symbol-metadata-reader.d.ts
5
- /**
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.
14
- */
15
6
  declare class SymbolMetadataReader implements MetadataReader {
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
- */
25
- getConstructorMetadata(implementationClass: Constructor<unknown>): ConstructorMetadata | undefined;
26
- /**
27
- * Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes.
28
- */
29
- getLifecycleMetadata(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
7
+ getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
8
+ getLifecycleMetadata(target: Constructor): LifecycleMetadata | undefined;
9
+ getAccessorMetadata(target: Constructor): Array<{
10
+ key: string | symbol;
11
+ descriptor: InjectionDescriptor;
12
+ }> | undefined;
30
13
  }
14
+ declare const defaultMetadataReader: SymbolMetadataReader;
31
15
  //#endregion
32
- export { SymbolMetadataReader };
16
+ export { SymbolMetadataReader, defaultMetadataReader };
@@ -1,51 +1,38 @@
1
- import { CODEFAST_DI_CONSTRUCTOR_METADATA, CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol } from "./metadata-keys.mjs";
1
+ import { INJECTABLE_KEY, INJECT_ACCESSOR_KEY, LIFECYCLE_KEY, constructorMetadataMap, lifecycleByConstructorMetadataMap, lifecycleMetadataMap } from "./metadata-keys.mjs";
2
2
  //#region src/metadata/symbol-metadata-reader.ts
3
- /**
4
- * Type guard — returns `true` when `value` has the shape of a {@link ConstructorMetadata} object.
5
- */
6
- function isConstructorMetadata(value) {
7
- if (typeof value !== "object" || value === null || !("params" in value)) return false;
8
- return Array.isArray(value.params);
9
- }
10
- /**
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.
19
- */
20
3
  var SymbolMetadataReader = class {
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
- */
30
- getConstructorMetadata(implementationClass) {
31
- const rawMetadata = implementationClass[decoratorMetadataObjectSymbol()];
32
- if (typeof rawMetadata !== "object" || rawMetadata === null) return;
33
- const metadataObject = rawMetadata;
34
- if (!Object.hasOwn(metadataObject, "codefast/di:constructor-metadata:v1")) return;
35
- const raw = metadataObject[CODEFAST_DI_CONSTRUCTOR_METADATA];
36
- if (!isConstructorMetadata(raw)) return;
37
- return raw;
4
+ getConstructorMetadata(target) {
5
+ const fromWeakMap = constructorMetadataMap.get(target);
6
+ if (fromWeakMap !== void 0) return fromWeakMap;
7
+ const own = Object.getOwnPropertyDescriptor(target, Symbol.metadata);
8
+ if (own === void 0) return;
9
+ const meta = own.value;
10
+ if (!meta || typeof meta !== "object" || !Object.hasOwn(meta, INJECTABLE_KEY)) return;
11
+ return meta[INJECTABLE_KEY];
38
12
  }
39
- /**
40
- * Reads lifecycle method names written by `@postConstruct()` / `@preDestroy()`. Inherits from parent classes.
41
- */
42
- getLifecycleMetadata(implementationClass) {
43
- const metadataObject = implementationClass[decoratorMetadataObjectSymbol()];
44
- if (typeof metadataObject !== "object" || metadataObject === null) return;
45
- const raw = metadataObject[CODEFAST_DI_LIFECYCLE_METADATA];
46
- if (typeof raw !== "object" || raw === null) return;
47
- return raw;
13
+ getLifecycleMetadata(target) {
14
+ const byConstructor = lifecycleByConstructorMetadataMap.get(target);
15
+ if (byConstructor !== void 0) return byConstructor;
16
+ const own = Object.getOwnPropertyDescriptor(target, Symbol.metadata);
17
+ if (own !== void 0) {
18
+ const meta = own.value;
19
+ if (meta && typeof meta === "object" && Object.hasOwn(meta, LIFECYCLE_KEY)) return meta[LIFECYCLE_KEY];
20
+ }
21
+ const classMeta = target[Symbol.metadata];
22
+ if (classMeta !== void 0) {
23
+ const fromWeakMap = lifecycleMetadataMap.get(classMeta);
24
+ if (fromWeakMap !== void 0) return fromWeakMap;
25
+ }
26
+ }
27
+ getAccessorMetadata(target) {
28
+ const own = Object.getOwnPropertyDescriptor(target, Symbol.metadata);
29
+ if (own === void 0) return;
30
+ const meta = own.value;
31
+ if (!meta || typeof meta !== "object") return;
32
+ if (!Object.hasOwn(meta, INJECT_ACCESSOR_KEY)) return;
33
+ return meta[INJECT_ACCESSOR_KEY];
48
34
  }
49
35
  };
36
+ const defaultMetadataReader = new SymbolMetadataReader();
50
37
  //#endregion
51
- export { SymbolMetadataReader };
38
+ export { SymbolMetadataReader, defaultMetadataReader };