@codefast/di 0.3.16-canary.2 → 0.3.16-canary.3

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 (60) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/binding.d.mts +24 -24
  3. package/dist/constraints.d.mts +3 -3
  4. package/dist/container.mjs +82 -150
  5. package/dist/decorators/inject.d.mts +3 -2
  6. package/dist/decorators/inject.mjs +13 -40
  7. package/dist/decorators/injectable.mjs +4 -12
  8. package/dist/decorators/lifecycle-decorators.mjs +11 -72
  9. package/dist/dependency-graph.mjs +6 -4
  10. package/dist/graph-adapters/cytoscape.d.mts +12 -12
  11. package/dist/graph-adapters/cytoscape.mjs +7 -7
  12. package/dist/graph-adapters/reactflow.d.mts +15 -15
  13. package/dist/graph-adapters/reactflow.mjs +6 -9
  14. package/dist/index.d.mts +3 -3
  15. package/dist/inspector.d.mts +3 -2
  16. package/dist/inspector.mjs +7 -10
  17. package/dist/lifecycle.mjs +2 -12
  18. package/dist/metadata/metadata-keys.d.mts +8 -33
  19. package/dist/metadata/metadata-keys.mjs +8 -21
  20. package/dist/metadata/metadata-types.d.mts +5 -0
  21. package/dist/metadata/symbol-metadata-reader.d.mts +1 -0
  22. package/dist/metadata/symbol-metadata-reader.mjs +11 -33
  23. package/dist/registry.mjs +3 -22
  24. package/dist/resolve-options.d.mts +2 -2
  25. package/dist/resolve-options.mjs +10 -8
  26. package/dist/resolver.d.mts +5 -0
  27. package/dist/resolver.mjs +15 -21
  28. package/dist/types.d.mts +10 -4
  29. package/package.json +39 -13
  30. package/src/binding-scope.ts +26 -0
  31. package/src/binding-select.ts +167 -0
  32. package/src/binding.ts +281 -0
  33. package/src/constraints.ts +149 -0
  34. package/src/constructor-type.ts +19 -0
  35. package/src/container.ts +1213 -0
  36. package/src/decorators/inject.ts +233 -0
  37. package/src/decorators/injectable.ts +85 -0
  38. package/src/decorators/lifecycle-decorators.ts +55 -0
  39. package/src/dependency-graph.ts +116 -0
  40. package/src/environment.ts +232 -0
  41. package/src/errors.ts +262 -0
  42. package/src/graph-adapters/cytoscape.ts +64 -0
  43. package/src/graph-adapters/dot.ts +22 -0
  44. package/src/graph-adapters/reactflow.ts +58 -0
  45. package/src/index.ts +101 -0
  46. package/src/inspector.ts +133 -0
  47. package/src/lifecycle.ts +238 -0
  48. package/src/metadata/metadata-keys.ts +25 -0
  49. package/src/metadata/metadata-reader-token.ts +8 -0
  50. package/src/metadata/metadata-types.ts +53 -0
  51. package/src/metadata/symbol-metadata-reader.ts +57 -0
  52. package/src/module.ts +93 -0
  53. package/src/registry.ts +241 -0
  54. package/src/resolve-options.ts +42 -0
  55. package/src/resolver.ts +1837 -0
  56. package/src/scope.ts +77 -0
  57. package/src/token.ts +40 -0
  58. package/src/types.ts +145 -0
  59. package/dist/graph-adapters/types.d.mts +0 -2
  60. package/dist/graph-adapters/types.mjs +0 -1
package/src/index.ts ADDED
@@ -0,0 +1,101 @@
1
+ // Foundation types
2
+ export type {
3
+ ActivationHandler,
4
+ BindingIdentifier,
5
+ BindingKind,
6
+ BindingScope,
7
+ BindingTag,
8
+ ConstraintContext,
9
+ Constructor,
10
+ DependencyKey,
11
+ DeactivationHandler,
12
+ ResolutionFrame,
13
+ ResolveOptions,
14
+ ResolutionContext,
15
+ TokenValue,
16
+ } from "#/types";
17
+
18
+ // Token
19
+ export { token, tokenName, isToken } from "#/token";
20
+ export type { Token } from "#/token";
21
+
22
+ // Binding builders — types only
23
+ export type {
24
+ AliasBindingBuilder,
25
+ BindToBuilder,
26
+ BindingBuilder,
27
+ ConstantBindingBuilder,
28
+ ScopedBindingBuilder,
29
+ SingletonBindingBuilder,
30
+ SingletonLifecycleBuilder,
31
+ SlotConstrainedBuilder,
32
+ TransientBindingBuilder,
33
+ } from "#/binding";
34
+
35
+ // Container
36
+ export { Container } from "#/container";
37
+ export type { Container as ContainerInterface, ContainerStatic } from "#/container";
38
+
39
+ export { effectiveBindingScope } from "#/binding-scope";
40
+ export { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#/resolve-options";
41
+
42
+ // Introspection types
43
+ export type { BindingSnapshot, ContainerSnapshot } from "#/inspector";
44
+
45
+ // Graph types
46
+ export type { ContainerGraphJson, GraphEdge, GraphNode, GraphOptions } from "#/dependency-graph";
47
+
48
+ // Module
49
+ export { AsyncModule, isSyncModule, Module, SyncModule } from "#/module";
50
+ export type { AsyncModuleBuilder, ModuleBuilder } from "#/module";
51
+
52
+ // Decorators
53
+ export { inject, injectAll, isInjectionDescriptor, optional } from "#/decorators/inject";
54
+ export type { InjectionDescriptor, InjectOptions } from "#/decorators/inject";
55
+ export { injectable } from "#/decorators/injectable";
56
+ export type { InjectableDependency, InjectableOptions } from "#/decorators/injectable";
57
+ export { postConstruct, preDestroy } from "#/decorators/lifecycle-decorators";
58
+
59
+ // Auto-register
60
+ export { createAutoRegisterRegistry } from "#/decorators/injectable";
61
+ export type { AutoRegisterRegistry } from "#/decorators/injectable";
62
+
63
+ // MetadataReader
64
+ export { MetadataReaderToken } from "#/metadata/metadata-reader-token";
65
+ export type { MetadataReader, MutableLifecycleMetadata } from "#/metadata/metadata-types";
66
+
67
+ // Constraints — contextual injection predicates for .when()
68
+ export {
69
+ whenAnyAncestorIs,
70
+ whenAnyAncestorNamed,
71
+ whenAnyAncestorTagged,
72
+ whenAnyAncestorTaggedAll,
73
+ whenNoAncestorIs,
74
+ whenNoParentIs,
75
+ whenParentIs,
76
+ whenParentNamed,
77
+ whenParentTagged,
78
+ whenParentTaggedAll,
79
+ } from "#/constraints";
80
+
81
+ // Errors
82
+ export {
83
+ AmbiguousBindingError,
84
+ AsyncActivationError,
85
+ AsyncDeactivationError,
86
+ AsyncModuleLoadError,
87
+ AsyncResolutionError,
88
+ CircularDependencyError,
89
+ DiError,
90
+ DisposedContainerError,
91
+ InternalError,
92
+ MissingContainerContextError,
93
+ MissingMetadataError,
94
+ MissingScopeContextError,
95
+ NoMatchingBindingError,
96
+ RebindUnboundTokenError,
97
+ ScopeViolationError,
98
+ SyncDisposalNotSupportedError,
99
+ TokenNotBoundError,
100
+ } from "#/errors";
101
+ export type { ScopeViolationDetails } from "#/errors";
@@ -0,0 +1,133 @@
1
+ import type { Binding } from "#/binding";
2
+ import type {
3
+ BindingIdentifier,
4
+ BindingKind,
5
+ BindingScope,
6
+ BindingTag,
7
+ ConstraintContext,
8
+ Constructor,
9
+ ResolveOptions,
10
+ } from "#/types";
11
+ import type { Token } from "#/token";
12
+ import type { BindingRegistry } from "#/registry";
13
+ import type { ScopeManager } from "#/scope";
14
+ import { tokenName } from "#/token";
15
+ import { selectBinding } from "#/binding-select";
16
+ import { effectiveBindingScope } from "#/binding-scope";
17
+
18
+ // ── Public types ──────────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * @since 0.3.16-canary.0
22
+ */
23
+ export interface BindingSnapshot {
24
+ readonly tokenName: string;
25
+ readonly kind: BindingKind;
26
+ readonly scope: BindingScope;
27
+ readonly slot: {
28
+ readonly name?: string;
29
+ readonly tags: ReadonlyArray<BindingTag>;
30
+ };
31
+ readonly id: BindingIdentifier;
32
+ }
33
+
34
+ /**
35
+ * @since 0.3.16-canary.0
36
+ */
37
+ export interface ContainerSnapshot {
38
+ readonly ownBindings: ReadonlyArray<BindingSnapshot>;
39
+ readonly cachedSingletonCount: number;
40
+ readonly hasParent: boolean;
41
+ readonly isDisposed: boolean;
42
+ }
43
+
44
+ // ── Inspector ─────────────────────────────────────────────────────────────────
45
+
46
+ /**
47
+ * @since 0.3.16-canary.0
48
+ */
49
+ export class Inspector {
50
+ constructor(
51
+ private readonly _registry: BindingRegistry,
52
+ private readonly _scope: ScopeManager,
53
+ private readonly _hasParent: boolean,
54
+ private readonly _isDisposed: () => boolean,
55
+ ) {}
56
+
57
+ inspect(): ContainerSnapshot {
58
+ const snapshots = this.allBindingSnapshots();
59
+ return {
60
+ ownBindings: snapshots,
61
+ cachedSingletonCount: this._scope.getAllSingletons().size,
62
+ hasParent: this._hasParent,
63
+ isDisposed: this._isDisposed(),
64
+ };
65
+ }
66
+
67
+ lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot> {
68
+ const bindings = this._registry.getAll(token);
69
+ return bindings.map((binding) => this._toSnapshot(binding));
70
+ }
71
+
72
+ has(
73
+ token: Token<unknown> | Constructor,
74
+ hint?: ResolveOptions,
75
+ parentHas?: () => boolean,
76
+ ): boolean {
77
+ const bindings = this._registry.getAll(token);
78
+ if (bindings.length > 0) {
79
+ if (hint !== undefined) {
80
+ if (
81
+ selectBinding(bindings, hint, this._makeHintContext(hint), tokenName(token)) !== undefined
82
+ ) {
83
+ return true;
84
+ }
85
+ } else {
86
+ return true;
87
+ }
88
+ }
89
+ return parentHas?.() ?? false;
90
+ }
91
+
92
+ hasOwn(token: Token<unknown> | Constructor, hint?: ResolveOptions): boolean {
93
+ const bindings = this._registry.getAll(token);
94
+ if (bindings.length === 0) {
95
+ return false;
96
+ }
97
+ if (hint !== undefined) {
98
+ return (
99
+ selectBinding(bindings, hint, this._makeHintContext(hint), tokenName(token)) !== undefined
100
+ );
101
+ }
102
+ return true;
103
+ }
104
+
105
+ private _makeHintContext(hint: ResolveOptions): ConstraintContext {
106
+ return {
107
+ resolutionPath: [],
108
+ resolutionStack: [],
109
+ parent: undefined,
110
+ ancestors: [],
111
+ currentResolveHint: hint,
112
+ };
113
+ }
114
+
115
+ private allBindingSnapshots(): ReadonlyArray<BindingSnapshot> {
116
+ return this._registry.allBindings().map((binding) => this._toSnapshot(binding));
117
+ }
118
+
119
+ private _toSnapshot(binding: Binding): BindingSnapshot {
120
+ const scope = effectiveBindingScope(binding);
121
+ const slot: BindingSnapshot["slot"] =
122
+ binding.slot.name !== undefined
123
+ ? { name: binding.slot.name, tags: binding.slot.tags }
124
+ : { tags: binding.slot.tags };
125
+ return {
126
+ tokenName: tokenName(binding.token),
127
+ kind: binding.kind,
128
+ scope,
129
+ slot,
130
+ id: binding.id,
131
+ };
132
+ }
133
+ }
@@ -0,0 +1,238 @@
1
+ import type {
2
+ ActivationHandler,
3
+ Constructor,
4
+ DeactivationHandler,
5
+ ResolutionContext,
6
+ } from "#/types";
7
+ import type { Token } from "#/token";
8
+ import type { Binding } from "#/binding";
9
+ import type { MetadataReader } from "#/metadata/metadata-types";
10
+ import { AsyncActivationError, AsyncDeactivationError } from "#/errors";
11
+ import { tokenName } from "#/token";
12
+
13
+ /**
14
+ * @since 0.3.16-canary.0
15
+ */
16
+ export class LifecycleManager {
17
+ // Container-level activation/deactivation hooks per token
18
+ private readonly _activationHooks = new Map<
19
+ Token<unknown> | Constructor,
20
+ Array<ActivationHandler<unknown>>
21
+ >();
22
+ private readonly _deactivationHooks = new Map<
23
+ Token<unknown> | Constructor,
24
+ Array<DeactivationHandler<unknown>>
25
+ >();
26
+ private _activationVersion = 0;
27
+
28
+ registerActivation<const Value>(
29
+ token: Token<Value> | Constructor<Value>,
30
+ handler: ActivationHandler<Value>,
31
+ ): void {
32
+ this._activationVersion += 1;
33
+ // ✓ TS6.0: Map.getOrInsert (ES2025)
34
+ const list = this._activationHooks.getOrInsert(token as Token<unknown> | Constructor, []);
35
+ list.push(handler as ActivationHandler<unknown>);
36
+ }
37
+
38
+ hasActivationHandlers<const Value>(token: Token<Value> | Constructor<Value>): boolean {
39
+ if (this._activationHooks.size === 0) {
40
+ return false;
41
+ }
42
+ const list = this._activationHooks.get(token as Token<unknown> | Constructor);
43
+ return list !== undefined && list.length > 0;
44
+ }
45
+
46
+ get activationVersion(): number {
47
+ return this._activationVersion;
48
+ }
49
+
50
+ registerDeactivation<const Value>(
51
+ token: Token<Value> | Constructor<Value>,
52
+ handler: DeactivationHandler<Value>,
53
+ ): void {
54
+ const list = this._deactivationHooks.getOrInsert(token as Token<unknown> | Constructor, []);
55
+ list.push(handler as DeactivationHandler<unknown>);
56
+ }
57
+
58
+ async runActivation<const Value>(
59
+ resolutionContext: ResolutionContext,
60
+ binding: Binding<Value>,
61
+ instance: Value,
62
+ metadataReader: MetadataReader,
63
+ ): Promise<Value> {
64
+ let activatedInstance: Value = instance;
65
+
66
+ // 1. @postConstruct() — after TC39 construction (constructor + accessor addInitializer callbacks)
67
+ if (binding.kind === "class") {
68
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
69
+ if (lifecycle?.postConstruct && lifecycle.postConstruct.length > 0) {
70
+ for (const methodName of lifecycle.postConstruct) {
71
+ const method = (activatedInstance as Record<string, unknown>)[methodName];
72
+ if (typeof method === "function") {
73
+ const hookResult = (method as () => unknown).call(activatedInstance);
74
+ if (hookResult instanceof Promise) {
75
+ await hookResult;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ // 2. per-binding onActivation
83
+ if (binding.kind !== "alias" && binding.onActivation !== undefined) {
84
+ const activationResult = binding.onActivation(resolutionContext, activatedInstance);
85
+ activatedInstance =
86
+ activationResult instanceof Promise ? await activationResult : activationResult;
87
+ }
88
+
89
+ // 3. container-level onActivation
90
+ const containerHooks = this._activationHooks.get(binding.token as Token<unknown> | Constructor);
91
+ if (containerHooks !== undefined) {
92
+ for (const hook of containerHooks) {
93
+ const activationResult = hook(resolutionContext, activatedInstance);
94
+ activatedInstance = (
95
+ activationResult instanceof Promise ? await activationResult : activationResult
96
+ ) as Value;
97
+ }
98
+ }
99
+
100
+ return activatedInstance;
101
+ }
102
+
103
+ runActivationSync<const Value>(
104
+ resolutionContext: ResolutionContext,
105
+ binding: Binding<Value>,
106
+ instance: Value,
107
+ metadataReader: MetadataReader,
108
+ ): Value {
109
+ let activatedInstance: Value = instance;
110
+
111
+ // 1. @postConstruct() — must be sync (instance fully constructed per TC39 order)
112
+ if (binding.kind === "class") {
113
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
114
+ if (lifecycle?.postConstruct && lifecycle.postConstruct.length > 0) {
115
+ for (const methodName of lifecycle.postConstruct) {
116
+ const method = (activatedInstance as Record<string, unknown>)[methodName];
117
+ if (typeof method === "function") {
118
+ const hookResult = (method as () => unknown).call(activatedInstance);
119
+ if (hookResult instanceof Promise) {
120
+ throw new AsyncActivationError(tokenName(binding.token), "postConstruct", methodName);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ // 2. per-binding onActivation (must be sync)
128
+ if (binding.kind !== "alias" && binding.onActivation !== undefined) {
129
+ const activationResult = binding.onActivation(resolutionContext, activatedInstance);
130
+ if (activationResult instanceof Promise) {
131
+ throw new AsyncActivationError(tokenName(binding.token), "onActivation");
132
+ }
133
+ activatedInstance = activationResult;
134
+ }
135
+
136
+ // 3. container-level onActivation (must be sync)
137
+ const tokenDisplayName = tokenName(binding.token);
138
+ const containerHooks = this._activationHooks.get(binding.token as Token<unknown> | Constructor);
139
+ if (containerHooks !== undefined) {
140
+ for (const hook of containerHooks) {
141
+ const activationResult = hook(resolutionContext, activatedInstance);
142
+ if (activationResult instanceof Promise) {
143
+ throw new AsyncActivationError(tokenDisplayName, "onActivation");
144
+ }
145
+ activatedInstance = activationResult as Value;
146
+ }
147
+ }
148
+
149
+ return activatedInstance;
150
+ }
151
+
152
+ async runDeactivation<const Value>(
153
+ binding: Binding<Value>,
154
+ instance: Value,
155
+ metadataReader: MetadataReader,
156
+ ): Promise<void> {
157
+ const tokenKey = binding.token as Token<unknown> | Constructor;
158
+
159
+ // 1. container-level onDeactivation
160
+ const containerHooks = this._deactivationHooks.get(tokenKey);
161
+ if (containerHooks !== undefined) {
162
+ for (const hook of containerHooks) {
163
+ const hookResult = hook(instance);
164
+ if (hookResult instanceof Promise) {
165
+ await hookResult;
166
+ }
167
+ }
168
+ }
169
+
170
+ // 2. per-binding onDeactivation
171
+ if (binding.kind !== "alias" && binding.onDeactivation !== undefined) {
172
+ const hookResult = binding.onDeactivation(instance);
173
+ if (hookResult instanceof Promise) {
174
+ await hookResult;
175
+ }
176
+ }
177
+
178
+ // 3. @preDestroy() — all methods in declaration order
179
+ if (binding.kind === "class") {
180
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
181
+ if (lifecycle?.preDestroy && lifecycle.preDestroy.length > 0) {
182
+ for (const methodName of lifecycle.preDestroy) {
183
+ const method = (instance as Record<string, unknown>)[methodName];
184
+ if (typeof method === "function") {
185
+ const hookResult = (method as () => unknown).call(instance);
186
+ if (hookResult instanceof Promise) {
187
+ await hookResult;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ runDeactivationSync<const Value>(
196
+ binding: Binding<Value>,
197
+ instance: Value,
198
+ metadataReader: MetadataReader,
199
+ ): void {
200
+ const tokenDisplayName = tokenName(binding.token);
201
+ const tokenKey = binding.token as Token<unknown> | Constructor;
202
+
203
+ // 1. container-level onDeactivation
204
+ const containerHooks = this._deactivationHooks.get(tokenKey);
205
+ if (containerHooks !== undefined) {
206
+ for (const hook of containerHooks) {
207
+ const hookResult = hook(instance);
208
+ if (hookResult instanceof Promise) {
209
+ throw new AsyncDeactivationError(tokenDisplayName);
210
+ }
211
+ }
212
+ }
213
+
214
+ // 2. per-binding onDeactivation
215
+ if (binding.kind !== "alias" && binding.onDeactivation !== undefined) {
216
+ const hookResult = binding.onDeactivation(instance);
217
+ if (hookResult instanceof Promise) {
218
+ throw new AsyncDeactivationError(tokenDisplayName);
219
+ }
220
+ }
221
+
222
+ // 3. @preDestroy()
223
+ if (binding.kind === "class") {
224
+ const lifecycle = metadataReader.getLifecycleMetadata(binding.target);
225
+ if (lifecycle?.preDestroy && lifecycle.preDestroy.length > 0) {
226
+ for (const methodName of lifecycle.preDestroy) {
227
+ const method = (instance as Record<string, unknown>)[methodName];
228
+ if (typeof method === "function") {
229
+ const hookResult = (method as () => unknown).call(instance);
230
+ if (hookResult instanceof Promise) {
231
+ throw new AsyncDeactivationError(tokenDisplayName);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
238
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @since 0.3.16-canary.0
3
+ */
4
+ export const INJECTABLE_KEY: unique symbol = Symbol("di:injectable");
5
+ /**
6
+ * @since 0.3.16-canary.0
7
+ */
8
+ export const LIFECYCLE_KEY: unique symbol = Symbol("di:lifecycle");
9
+ /**
10
+ * @since 0.3.16-canary.0
11
+ */
12
+ export const INJECT_ACCESSOR_KEY: unique symbol = Symbol("di:inject-accessor");
13
+
14
+ /**
15
+ * The well-known symbol used by TC39 Stage 3 decorator transforms to store class metadata.
16
+ *
17
+ * `Symbol.metadata` is defined natively once the runtime ships the full TC39 decorator
18
+ * proposal. Until then (current Node.js / browsers), Babel and esbuild both fall back to
19
+ * `Symbol.for("Symbol.metadata")` — a global-registry symbol with the same string key.
20
+ * Resolving it here once keeps the reader and the decorator transforms in sync regardless
21
+ * of which path is taken.
22
+ *
23
+ * @since 0.3.16-canary.0
24
+ */
25
+ export const METADATA_SYMBOL: symbol = Symbol.metadata ?? Symbol.for("Symbol.metadata");
@@ -0,0 +1,8 @@
1
+ import { token } from "#/token";
2
+ import type { MetadataReader } from "#/metadata/metadata-types";
3
+ import type { Token } from "#/token";
4
+
5
+ /**
6
+ * @since 0.3.16-canary.0
7
+ */
8
+ export const MetadataReaderToken: Token<MetadataReader> = token<MetadataReader>("MetadataReader");
@@ -0,0 +1,53 @@
1
+ import type { Constructor } from "#/types";
2
+ import type { Token } from "#/token";
3
+ import type { InjectionDescriptor } from "#/decorators/inject";
4
+
5
+ /**
6
+ * @since 0.3.16-canary.0
7
+ */
8
+ export interface ParamMetadata {
9
+ readonly index: number;
10
+ readonly token: Token<unknown> | Constructor;
11
+ readonly optional: boolean;
12
+ readonly multi: boolean;
13
+ readonly name?: string;
14
+ readonly tags?: ReadonlyArray<readonly [string, unknown]>;
15
+ }
16
+
17
+ /**
18
+ * @since 0.3.16-canary.0
19
+ */
20
+ export interface ConstructorMetadata {
21
+ readonly params: ReadonlyArray<ParamMetadata>;
22
+ }
23
+
24
+ /**
25
+ * @since 0.3.16-canary.0
26
+ */
27
+ export interface LifecycleMetadata {
28
+ readonly postConstruct: ReadonlyArray<string>;
29
+ readonly preDestroy: ReadonlyArray<string>;
30
+ }
31
+
32
+ /**
33
+ * Mutable buckets used while aggregating decorator metadata (same keys as {@link LifecycleMetadata}).
34
+ *
35
+ * @since 0.3.16-canary.0
36
+ */
37
+ export interface MutableLifecycleMetadata {
38
+ postConstruct: Array<string>;
39
+ preDestroy: Array<string>;
40
+ }
41
+
42
+ /**
43
+ * @since 0.3.16-canary.0
44
+ */
45
+ export interface MetadataReader {
46
+ getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
47
+ getLifecycleMetadata(target: Constructor): LifecycleMetadata | undefined;
48
+ getAccessorMetadata?(
49
+ target: Constructor,
50
+ ):
51
+ | ReadonlyArray<{ readonly key: string | symbol; readonly descriptor: InjectionDescriptor }>
52
+ | undefined;
53
+ }
@@ -0,0 +1,57 @@
1
+ import type { Constructor } from "#/types";
2
+ import type {
3
+ ConstructorMetadata,
4
+ LifecycleMetadata,
5
+ MetadataReader,
6
+ } from "#/metadata/metadata-types";
7
+ import type { InjectionDescriptor } from "#/decorators/inject";
8
+ import {
9
+ INJECT_ACCESSOR_KEY,
10
+ INJECTABLE_KEY,
11
+ LIFECYCLE_KEY,
12
+ METADATA_SYMBOL,
13
+ } from "#/metadata/metadata-keys";
14
+
15
+ /**
16
+ * @since 0.3.16-canary.0
17
+ */
18
+ export class SymbolMetadataReader implements MetadataReader {
19
+ private _getMetadataRecord(
20
+ target: Constructor,
21
+ key: string | symbol,
22
+ ): Record<string | symbol, unknown> | undefined {
23
+ const descriptor = Object.getOwnPropertyDescriptor(target, METADATA_SYMBOL);
24
+ if (descriptor === undefined) {
25
+ return undefined;
26
+ }
27
+ const record = descriptor.value as Record<string | symbol, unknown> | null | undefined;
28
+ if (!record || typeof record !== "object" || !Object.hasOwn(record, key)) {
29
+ return undefined;
30
+ }
31
+ return record;
32
+ }
33
+
34
+ getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined {
35
+ const record = this._getMetadataRecord(target, INJECTABLE_KEY);
36
+ return record?.[INJECTABLE_KEY] as ConstructorMetadata | undefined;
37
+ }
38
+
39
+ getLifecycleMetadata(target: Constructor): LifecycleMetadata | undefined {
40
+ const record = this._getMetadataRecord(target, LIFECYCLE_KEY);
41
+ return record?.[LIFECYCLE_KEY] as LifecycleMetadata | undefined;
42
+ }
43
+
44
+ getAccessorMetadata(
45
+ target: Constructor,
46
+ ): Array<{ key: string | symbol; descriptor: InjectionDescriptor }> | undefined {
47
+ const record = this._getMetadataRecord(target, INJECT_ACCESSOR_KEY);
48
+ return record?.[INJECT_ACCESSOR_KEY] as
49
+ | Array<{ key: string | symbol; descriptor: InjectionDescriptor }>
50
+ | undefined;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @since 0.3.16-canary.0
56
+ */
57
+ export const defaultMetadataReader = new SymbolMetadataReader();