@codefast/di 0.10.1 → 0.11.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 (55) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/README.md +69 -6
  3. package/dist/ambient/active-container.d.ts +7 -2
  4. package/dist/ambient/active-container.js +6 -1
  5. package/dist/container/binding-builders.d.ts +19 -1
  6. package/dist/container/binding-builders.js +75 -16
  7. package/dist/container/container.js +224 -47
  8. package/dist/core/binding-declaration.d.ts +120 -0
  9. package/dist/core/binding-declaration.js +186 -0
  10. package/dist/core/binding.d.ts +57 -5
  11. package/dist/core/binding.js +48 -1
  12. package/dist/core/module.d.ts +7 -4
  13. package/dist/core/module.js +17 -3
  14. package/dist/core/registry.d.ts +11 -7
  15. package/dist/core/registry.js +122 -38
  16. package/dist/core/state-epoch.d.ts +18 -1
  17. package/dist/core/state-epoch.js +17 -0
  18. package/dist/core/tag.js +1 -1
  19. package/dist/decorators/decorator-metadata.d.ts +9 -0
  20. package/dist/decorators/decorator-metadata.js +20 -0
  21. package/dist/decorators/inject.js +2 -1
  22. package/dist/decorators/injectable.js +3 -1
  23. package/dist/decorators/lifecycle-decorators.js +8 -2
  24. package/dist/errors/errors.d.ts +77 -3
  25. package/dist/errors/errors.js +93 -10
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.js +2 -1
  28. package/dist/injection/descriptor.js +3 -7
  29. package/dist/injection/resolve-options.js +6 -4
  30. package/dist/introspection/dependency-graph.d.ts +7 -2
  31. package/dist/introspection/dependency-graph.js +46 -23
  32. package/dist/introspection/graph-adapters/reactflow.js +6 -4
  33. package/dist/introspection/inspector.js +6 -9
  34. package/dist/lifecycle/lifecycle-manager.js +14 -2
  35. package/dist/lifecycle/scope-manager.js +28 -10
  36. package/dist/metadata/verifying-metadata-reader.d.ts +4 -3
  37. package/dist/metadata/verifying-metadata-reader.js +28 -6
  38. package/dist/resolution/async-fan-out.d.ts +12 -0
  39. package/dist/resolution/async-fan-out.js +26 -0
  40. package/dist/resolution/cache/activation-need.d.ts +0 -1
  41. package/dist/resolution/cache/activation-need.js +11 -18
  42. package/dist/resolution/cache/binding-lookup-cache.d.ts +0 -7
  43. package/dist/resolution/cache/binding-lookup-cache.js +30 -17
  44. package/dist/resolution/cache/class-introspector.d.ts +11 -0
  45. package/dist/resolution/cache/class-introspector.js +18 -0
  46. package/dist/resolution/context.d.ts +15 -23
  47. package/dist/resolution/context.js +47 -56
  48. package/dist/resolution/path/resolution-path.d.ts +48 -13
  49. package/dist/resolution/path/resolution-path.js +89 -38
  50. package/dist/resolution/plan/instantiation-plan.js +61 -21
  51. package/dist/resolution/plan/plan-codegen.d.ts +7 -4
  52. package/dist/resolution/plan/plan-codegen.js +60 -32
  53. package/dist/resolution/resolver.d.ts +4 -5
  54. package/dist/resolution/resolver.js +288 -258
  55. package/package.json +14 -2
@@ -7,7 +7,7 @@ import type { BindingIdentifier, BindingScope, ResolveOptions } from "#core/type
7
7
  */
8
8
  export declare abstract class DiError extends Error {
9
9
  abstract readonly code: string;
10
- constructor(message: string);
10
+ abstract readonly name: string;
11
11
  }
12
12
  /**
13
13
  * An internal assertion failure — a library bug, never caller misuse.
@@ -15,6 +15,7 @@ export declare abstract class DiError extends Error {
15
15
  * @since 0.3.16-canary.0
16
16
  */
17
17
  export declare class InternalError extends DiError {
18
+ readonly name = "InternalError";
18
19
  readonly code = "INTERNAL_ERROR";
19
20
  constructor(message: string);
20
21
  }
@@ -24,6 +25,7 @@ export declare class InternalError extends DiError {
24
25
  * @since 0.3.16-canary.0
25
26
  */
26
27
  export declare class TokenNotBoundError extends DiError {
28
+ readonly name = "TokenNotBoundError";
27
29
  readonly code = "TOKEN_NOT_BOUND";
28
30
  readonly tokenName: string;
29
31
  constructor(tokenName: string);
@@ -34,6 +36,7 @@ export declare class TokenNotBoundError extends DiError {
34
36
  * @since 0.3.16-canary.0
35
37
  */
36
38
  export declare class NoMatchingBindingError extends DiError {
39
+ readonly name = "NoMatchingBindingError";
37
40
  readonly code = "NO_MATCHING_BINDING";
38
41
  readonly tokenName: string;
39
42
  readonly options: ResolveOptions;
@@ -46,6 +49,7 @@ export declare class NoMatchingBindingError extends DiError {
46
49
  * @since 0.3.16-canary.0
47
50
  */
48
51
  export declare class AmbiguousBindingError extends DiError {
52
+ readonly name = "AmbiguousBindingError";
49
53
  readonly code = "AMBIGUOUS_BINDING";
50
54
  readonly tokenName: string;
51
55
  readonly candidateIds: ReadonlyArray<BindingIdentifier>;
@@ -57,6 +61,7 @@ export declare class AmbiguousBindingError extends DiError {
57
61
  * @since 0.3.16-canary.0
58
62
  */
59
63
  export declare class CircularDependencyError extends DiError {
64
+ readonly name = "CircularDependencyError";
60
65
  readonly code = "CIRCULAR_DEPENDENCY";
61
66
  readonly cycle: Array<string>;
62
67
  constructor(cycle: Array<string>);
@@ -67,6 +72,7 @@ export declare class CircularDependencyError extends DiError {
67
72
  * @since 0.3.16-canary.0
68
73
  */
69
74
  export declare class AsyncResolutionError extends DiError {
75
+ readonly name = "AsyncResolutionError";
70
76
  readonly code = "ASYNC_RESOLUTION";
71
77
  /** The token the caller asked for — what `resolveAsync` has to be called with. */
72
78
  readonly tokenName: string;
@@ -80,6 +86,7 @@ export declare class AsyncResolutionError extends DiError {
80
86
  * @since 0.3.16-canary.0
81
87
  */
82
88
  export declare class AsyncDeactivationError extends DiError {
89
+ readonly name = "AsyncDeactivationError";
83
90
  readonly code = "ASYNC_DEACTIVATION";
84
91
  readonly tokenName: string;
85
92
  constructor(tokenName: string);
@@ -102,6 +109,7 @@ export interface ScopeViolationDetails {
102
109
  * @since 0.3.16-canary.0
103
110
  */
104
111
  export declare class ScopeViolationError extends DiError {
112
+ readonly name = "ScopeViolationError";
105
113
  readonly code = "SCOPE_VIOLATION";
106
114
  readonly details: ScopeViolationDetails;
107
115
  constructor(details: ScopeViolationDetails);
@@ -116,6 +124,7 @@ export declare class ScopeViolationError extends DiError {
116
124
  * @since 0.6.0
117
125
  */
118
126
  export declare class EmptyTagCriteriaError extends DiError {
127
+ readonly name = "EmptyTagCriteriaError";
119
128
  readonly code = "EMPTY_TAG_CRITERIA";
120
129
  readonly helperName: string;
121
130
  constructor(helperName: string);
@@ -130,6 +139,7 @@ export declare class EmptyTagCriteriaError extends DiError {
130
139
  * @since 0.6.0
131
140
  */
132
141
  export declare class UnreachableConstraintError extends DiError {
142
+ readonly name = "UnreachableConstraintError";
133
143
  readonly code = "UNREACHABLE_CONSTRAINT";
134
144
  readonly tokenName: string;
135
145
  readonly requiredName: string;
@@ -148,10 +158,13 @@ export declare class UnreachableConstraintError extends DiError {
148
158
  * @since 0.6.0
149
159
  */
150
160
  export declare class UnreachableLifecycleHookError extends DiError {
161
+ readonly name = "UnreachableLifecycleHookError";
151
162
  readonly code = "UNREACHABLE_LIFECYCLE_HOOK";
152
163
  readonly tokenName: string;
153
164
  readonly phase: "onActivation" | "onDeactivation";
154
- constructor(tokenName: string, phase: "onActivation" | "onDeactivation");
165
+ /** Nothing binds the token, or — for a deactivation hook — every binding it has is transient or scoped. */
166
+ readonly reason: "unbound" | "no-deactivatable-binding";
167
+ constructor(tokenName: string, phase: "onActivation" | "onDeactivation", reason?: "unbound" | "no-deactivatable-binding");
155
168
  }
156
169
  /**
157
170
  * A {@link MetadataReader} described a class with something the container cannot use.
@@ -163,6 +176,7 @@ export declare class UnreachableLifecycleHookError extends DiError {
163
176
  * @since 0.6.0
164
177
  */
165
178
  export declare class InvalidMetadataError extends DiError {
179
+ readonly name = "InvalidMetadataError";
166
180
  readonly code = "INVALID_METADATA";
167
181
  readonly targetName: string;
168
182
  readonly reason: string;
@@ -174,9 +188,13 @@ export declare class InvalidMetadataError extends DiError {
174
188
  * @since 0.3.16-canary.0
175
189
  */
176
190
  export declare class MissingMetadataError extends DiError {
191
+ readonly name = "MissingMetadataError";
177
192
  readonly code = "MISSING_METADATA";
178
193
  readonly targetName: string;
179
- constructor(targetName: string);
194
+ constructor(targetName: string, inheritedFrom?: {
195
+ readonly baseName: string;
196
+ readonly dependencyCount: number;
197
+ });
180
198
  }
181
199
  /**
182
200
  * A sync `load()` given a module that needs async setup.
@@ -184,6 +202,7 @@ export declare class MissingMetadataError extends DiError {
184
202
  * @since 0.3.16-canary.0
185
203
  */
186
204
  export declare class AsyncModuleLoadError extends DiError {
205
+ readonly name = "AsyncModuleLoadError";
187
206
  readonly code = "ASYNC_MODULE_LOAD";
188
207
  readonly moduleName: string;
189
208
  constructor(moduleName: string);
@@ -194,6 +213,7 @@ export declare class AsyncModuleLoadError extends DiError {
194
213
  * @since 0.3.16-canary.0
195
214
  */
196
215
  export declare class SyncDisposalNotSupportedError extends DiError {
216
+ readonly name = "SyncDisposalNotSupportedError";
197
217
  readonly code = "SYNC_DISPOSAL_NOT_SUPPORTED";
198
218
  constructor();
199
219
  }
@@ -203,6 +223,7 @@ export declare class SyncDisposalNotSupportedError extends DiError {
203
223
  * @since 0.3.16-canary.0
204
224
  */
205
225
  export declare class MissingScopeContextError extends DiError {
226
+ readonly name = "MissingScopeContextError";
206
227
  readonly code = "MISSING_SCOPE_CONTEXT";
207
228
  readonly tokenName: string;
208
229
  constructor(tokenName: string);
@@ -217,6 +238,7 @@ export declare class MissingScopeContextError extends DiError {
217
238
  * @since 0.3.16-canary.0
218
239
  */
219
240
  export declare class MissingContainerContextError extends DiError {
241
+ readonly name = "MissingContainerContextError";
220
242
  readonly code = "MISSING_CONTAINER_CONTEXT";
221
243
  /** The class being constructed, or `undefined` when it has no readable name. */
222
244
  readonly className: string | undefined;
@@ -232,6 +254,7 @@ export declare class MissingContainerContextError extends DiError {
232
254
  * @since 0.10.0
233
255
  */
234
256
  export declare class ChainAlreadyRegisteredError extends DiError {
257
+ readonly name = "ChainAlreadyRegisteredError";
235
258
  readonly code = "CHAIN_ALREADY_REGISTERED";
236
259
  readonly tokenName: string;
237
260
  constructor(tokenName: string);
@@ -245,6 +268,7 @@ export declare class ChainAlreadyRegisteredError extends DiError {
245
268
  * @since 0.10.0
246
269
  */
247
270
  export declare class ManyBindingSlotError extends DiError {
271
+ readonly name = "ManyBindingSlotError";
248
272
  readonly code = "MANY_BINDING_SLOT";
249
273
  readonly tokenName: string;
250
274
  constructor(tokenName: string);
@@ -259,6 +283,7 @@ export declare class ManyBindingSlotError extends DiError {
259
283
  * @since 0.10.0
260
284
  */
261
285
  export declare class ChainNotRegisteredError extends DiError {
286
+ readonly name = "ChainNotRegisteredError";
262
287
  readonly code = "CHAIN_NOT_REGISTERED";
263
288
  readonly tokenName: string;
264
289
  constructor(tokenName: string);
@@ -269,6 +294,7 @@ export declare class ChainNotRegisteredError extends DiError {
269
294
  * @since 0.3.16-canary.0
270
295
  */
271
296
  export declare class RebindUnboundTokenError extends DiError {
297
+ readonly name = "RebindUnboundTokenError";
272
298
  readonly code = "REBIND_UNBOUND_TOKEN";
273
299
  readonly tokenName: string;
274
300
  constructor(tokenName: string);
@@ -279,6 +305,7 @@ export declare class RebindUnboundTokenError extends DiError {
279
305
  * @since 0.5.0-canary.9
280
306
  */
281
307
  export declare class SelfBindingRequiresClassError extends DiError {
308
+ readonly name = "SelfBindingRequiresClassError";
282
309
  readonly code = "SELF_BINDING_REQUIRES_CLASS";
283
310
  readonly tokenName: string;
284
311
  constructor(tokenName: string);
@@ -293,17 +320,63 @@ export declare class SelfBindingRequiresClassError extends DiError {
293
320
  * @since 0.6.0
294
321
  */
295
322
  export declare class StaticMemberDecoratorError extends DiError {
323
+ readonly name = "StaticMemberDecoratorError";
296
324
  readonly code = "STATIC_MEMBER_DECORATOR";
297
325
  readonly decoratorName: string;
298
326
  readonly memberName: string;
299
327
  constructor(decoratorName: string, memberName: string);
300
328
  }
329
+ /**
330
+ * A `@postConstruct` or `@preDestroy` on a method whose key is a symbol, which the lifecycle reader cannot name.
331
+ *
332
+ * @remarks Reported at decoration rather than at resolve, so the error points at the declaration
333
+ * instead of a `MetadataReader` the caller never configured.
334
+ *
335
+ * @since 0.11.0
336
+ */
337
+ export declare class SymbolKeyedLifecycleError extends DiError {
338
+ readonly name = "SymbolKeyedLifecycleError";
339
+ readonly code = "SYMBOL_KEYED_LIFECYCLE";
340
+ readonly decoratorName: string;
341
+ readonly memberName: string;
342
+ constructor(decoratorName: string, memberName: string);
343
+ }
344
+ /**
345
+ * A decorator handed no metadata object, because the runtime has no `Symbol.metadata` to key one by.
346
+ *
347
+ * @remarks TypeScript compiles `context.metadata` to `undefined` when `Symbol.metadata` is missing, so
348
+ * the fix is installing it before any decorated class is defined — the key this library reads.
349
+ *
350
+ * @since 0.11.0
351
+ */
352
+ export declare class MissingDecoratorMetadataError extends DiError {
353
+ readonly name = "MissingDecoratorMetadataError";
354
+ readonly code = "MISSING_DECORATOR_METADATA";
355
+ readonly decoratorName: string;
356
+ constructor(decoratorName: string);
357
+ }
358
+ /**
359
+ * A `binding()` definition no chain could spell: no strategy, several, or a key its strategy does not allow.
360
+ *
361
+ * @remarks Raised where the declaration is written, for callers the compiler cannot reach, so a
362
+ * declared module that was built can always be loaded.
363
+ *
364
+ * @since 0.11.0
365
+ */
366
+ export declare class InvalidBindingDeclarationError extends DiError {
367
+ readonly name = "InvalidBindingDeclarationError";
368
+ readonly code = "INVALID_BINDING_DECLARATION";
369
+ readonly tokenName: string;
370
+ readonly reason: string;
371
+ constructor(tokenName: string, reason: string);
372
+ }
301
373
  /**
302
374
  * An operation attempted on a container that has already been disposed.
303
375
  *
304
376
  * @since 0.3.16-canary.0
305
377
  */
306
378
  export declare class DisposedContainerError extends DiError {
379
+ readonly name = "DisposedContainerError";
307
380
  readonly code = "DISPOSED_CONTAINER";
308
381
  constructor();
309
382
  }
@@ -313,6 +386,7 @@ export declare class DisposedContainerError extends DiError {
313
386
  * @since 0.3.16-canary.0
314
387
  */
315
388
  export declare class AsyncActivationError extends DiError {
389
+ readonly name = "AsyncActivationError";
316
390
  readonly code = "ASYNC_ACTIVATION";
317
391
  readonly tokenName: string;
318
392
  readonly hookKind: "postConstruct" | "onActivation";
@@ -5,10 +5,6 @@ import { slotName } from "#core/tag";
5
5
  * @since 0.3.16-canary.0
6
6
  */
7
7
  export class DiError extends Error {
8
- constructor(message) {
9
- super(message);
10
- this.name = this.constructor.name;
11
- }
12
8
  }
13
9
  /**
14
10
  * An internal assertion failure — a library bug, never caller misuse.
@@ -16,6 +12,7 @@ export class DiError extends Error {
16
12
  * @since 0.3.16-canary.0
17
13
  */
18
14
  export class InternalError extends DiError {
15
+ name = "InternalError";
19
16
  code = "INTERNAL_ERROR";
20
17
  constructor(message) {
21
18
  super(message);
@@ -27,6 +24,7 @@ export class InternalError extends DiError {
27
24
  * @since 0.3.16-canary.0
28
25
  */
29
26
  export class TokenNotBoundError extends DiError {
27
+ name = "TokenNotBoundError";
30
28
  code = "TOKEN_NOT_BOUND";
31
29
  tokenName;
32
30
  constructor(tokenName) {
@@ -75,6 +73,7 @@ function describeResolveOptions(options) {
75
73
  * @since 0.3.16-canary.0
76
74
  */
77
75
  export class NoMatchingBindingError extends DiError {
76
+ name = "NoMatchingBindingError";
78
77
  code = "NO_MATCHING_BINDING";
79
78
  tokenName;
80
79
  options;
@@ -94,6 +93,7 @@ export class NoMatchingBindingError extends DiError {
94
93
  * @since 0.3.16-canary.0
95
94
  */
96
95
  export class AmbiguousBindingError extends DiError {
96
+ name = "AmbiguousBindingError";
97
97
  code = "AMBIGUOUS_BINDING";
98
98
  tokenName;
99
99
  candidateIds;
@@ -109,6 +109,7 @@ export class AmbiguousBindingError extends DiError {
109
109
  * @since 0.3.16-canary.0
110
110
  */
111
111
  export class CircularDependencyError extends DiError {
112
+ name = "CircularDependencyError";
112
113
  code = "CIRCULAR_DEPENDENCY";
113
114
  cycle;
114
115
  constructor(cycle) {
@@ -122,6 +123,7 @@ export class CircularDependencyError extends DiError {
122
123
  * @since 0.3.16-canary.0
123
124
  */
124
125
  export class AsyncResolutionError extends DiError {
126
+ name = "AsyncResolutionError";
125
127
  code = "ASYNC_RESOLUTION";
126
128
  /** The token the caller asked for — what `resolveAsync` has to be called with. */
127
129
  tokenName;
@@ -141,6 +143,7 @@ export class AsyncResolutionError extends DiError {
141
143
  * @since 0.3.16-canary.0
142
144
  */
143
145
  export class AsyncDeactivationError extends DiError {
146
+ name = "AsyncDeactivationError";
144
147
  code = "ASYNC_DEACTIVATION";
145
148
  tokenName;
146
149
  constructor(tokenName) {
@@ -154,6 +157,7 @@ export class AsyncDeactivationError extends DiError {
154
157
  * @since 0.3.16-canary.0
155
158
  */
156
159
  export class ScopeViolationError extends DiError {
160
+ name = "ScopeViolationError";
157
161
  code = "SCOPE_VIOLATION";
158
162
  details;
159
163
  constructor(details) {
@@ -171,6 +175,7 @@ export class ScopeViolationError extends DiError {
171
175
  * @since 0.6.0
172
176
  */
173
177
  export class EmptyTagCriteriaError extends DiError {
178
+ name = "EmptyTagCriteriaError";
174
179
  code = "EMPTY_TAG_CRITERIA";
175
180
  helperName;
176
181
  constructor(helperName) {
@@ -188,6 +193,7 @@ export class EmptyTagCriteriaError extends DiError {
188
193
  * @since 0.6.0
189
194
  */
190
195
  export class UnreachableConstraintError extends DiError {
196
+ name = "UnreachableConstraintError";
191
197
  code = "UNREACHABLE_CONSTRAINT";
192
198
  tokenName;
193
199
  requiredName;
@@ -215,13 +221,19 @@ export class UnreachableConstraintError extends DiError {
215
221
  * @since 0.6.0
216
222
  */
217
223
  export class UnreachableLifecycleHookError extends DiError {
224
+ name = "UnreachableLifecycleHookError";
218
225
  code = "UNREACHABLE_LIFECYCLE_HOOK";
219
226
  tokenName;
220
227
  phase;
221
- constructor(tokenName, phase) {
222
- super(`${phase}() is registered for '${tokenName}', which nothing is bound to in this container or its ancestors, so the hook can never run. Bind the token, or — if '${tokenName}' is a class you bound as an implementation via .to(${tokenName}) — register the hook against the token you bound instead.`);
228
+ /** Nothing binds the token, or — for a deactivation hook — every binding it has is transient or scoped. */
229
+ reason;
230
+ constructor(tokenName, phase, reason = "unbound") {
231
+ super(reason === "unbound"
232
+ ? `${phase}() is registered for '${tokenName}', which nothing is bound to in this container or its ancestors, so the hook can never run. Bind the token, or — if '${tokenName}' is a class you bound as an implementation via .to(${tokenName}) — register the hook against the token you bound instead.`
233
+ : `${phase}() is registered for '${tokenName}', whose every binding in this container and its ancestors is transient or scoped, so the hook can never run: only a singleton or a constant is deactivated. Make one of its bindings a singleton, or drop the hook.`);
223
234
  this.tokenName = tokenName;
224
235
  this.phase = phase;
236
+ this.reason = reason;
225
237
  }
226
238
  }
227
239
  /**
@@ -234,6 +246,7 @@ export class UnreachableLifecycleHookError extends DiError {
234
246
  * @since 0.6.0
235
247
  */
236
248
  export class InvalidMetadataError extends DiError {
249
+ name = "InvalidMetadataError";
237
250
  code = "INVALID_METADATA";
238
251
  targetName;
239
252
  reason;
@@ -249,10 +262,13 @@ export class InvalidMetadataError extends DiError {
249
262
  * @since 0.3.16-canary.0
250
263
  */
251
264
  export class MissingMetadataError extends DiError {
265
+ name = "MissingMetadataError";
252
266
  code = "MISSING_METADATA";
253
267
  targetName;
254
- constructor(targetName) {
255
- super(`Class '${targetName}' is missing @injectable() decorator. Add @injectable([...deps]) or use toDynamic()/toResolved() instead.`);
268
+ constructor(targetName, inheritedFrom) {
269
+ super(inheritedFrom === undefined
270
+ ? `Class '${targetName}' is missing @injectable() decorator. Add @injectable([...deps]) or use toDynamic()/toResolved() instead.`
271
+ : `Class '${targetName}' inherits ${String(inheritedFrom.dependencyCount)} declared constructor ${inheritedFrom.dependencyCount === 1 ? "dependency" : "dependencies"} from '${inheritedFrom.baseName}' but declares none of its own. Add @injectable([...deps]) to '${targetName}' — @injectable([]) if it takes none — or bind it with toDynamic()/toResolved().`);
256
272
  this.targetName = targetName;
257
273
  }
258
274
  }
@@ -262,6 +278,7 @@ export class MissingMetadataError extends DiError {
262
278
  * @since 0.3.16-canary.0
263
279
  */
264
280
  export class AsyncModuleLoadError extends DiError {
281
+ name = "AsyncModuleLoadError";
265
282
  code = "ASYNC_MODULE_LOAD";
266
283
  moduleName;
267
284
  constructor(moduleName) {
@@ -275,6 +292,7 @@ export class AsyncModuleLoadError extends DiError {
275
292
  * @since 0.3.16-canary.0
276
293
  */
277
294
  export class SyncDisposalNotSupportedError extends DiError {
295
+ name = "SyncDisposalNotSupportedError";
278
296
  code = "SYNC_DISPOSAL_NOT_SUPPORTED";
279
297
  constructor() {
280
298
  super("Container cannot be disposed synchronously because onDeactivation handlers may be async. Use `await using` or call container.dispose() explicitly.");
@@ -286,6 +304,7 @@ export class SyncDisposalNotSupportedError extends DiError {
286
304
  * @since 0.3.16-canary.0
287
305
  */
288
306
  export class MissingScopeContextError extends DiError {
307
+ name = "MissingScopeContextError";
289
308
  code = "MISSING_SCOPE_CONTEXT";
290
309
  tokenName;
291
310
  constructor(tokenName) {
@@ -303,6 +322,7 @@ export class MissingScopeContextError extends DiError {
303
322
  * @since 0.3.16-canary.0
304
323
  */
305
324
  export class MissingContainerContextError extends DiError {
325
+ name = "MissingContainerContextError";
306
326
  code = "MISSING_CONTAINER_CONTEXT";
307
327
  /** The class being constructed, or `undefined` when it has no readable name. */
308
328
  className;
@@ -310,8 +330,8 @@ export class MissingContainerContextError extends DiError {
310
330
  constructor(className, accessorName) {
311
331
  const accessor = `@inject accessor '${String(accessorName)}'`;
312
332
  super(className === undefined
313
- ? `An ${accessor} was initialized outside a container context. Resolve its class through a container, or open a context with runWithContainer().`
314
- : `Class '${className}' has an ${accessor} but was constructed outside a container context. Resolve it via container.resolve(${className}), or open a context with runWithContainer().`);
333
+ ? `An ${accessor} was initialized outside a container context. Resolve its class through a container, or open a synchronous context with runWithContainer() (the context does not survive an await).`
334
+ : `Class '${className}' has an ${accessor} but was constructed outside a container context. Resolve it via container.resolve(${className}), or open a synchronous context with runWithContainer() (the context does not survive an await).`);
315
335
  this.className = className;
316
336
  this.accessorName = accessorName;
317
337
  }
@@ -325,6 +345,7 @@ export class MissingContainerContextError extends DiError {
325
345
  * @since 0.10.0
326
346
  */
327
347
  export class ChainAlreadyRegisteredError extends DiError {
348
+ name = "ChainAlreadyRegisteredError";
328
349
  code = "CHAIN_ALREADY_REGISTERED";
329
350
  tokenName;
330
351
  constructor(tokenName) {
@@ -341,6 +362,7 @@ export class ChainAlreadyRegisteredError extends DiError {
341
362
  * @since 0.10.0
342
363
  */
343
364
  export class ManyBindingSlotError extends DiError {
365
+ name = "ManyBindingSlotError";
344
366
  code = "MANY_BINDING_SLOT";
345
367
  tokenName;
346
368
  constructor(tokenName) {
@@ -358,6 +380,7 @@ export class ManyBindingSlotError extends DiError {
358
380
  * @since 0.10.0
359
381
  */
360
382
  export class ChainNotRegisteredError extends DiError {
383
+ name = "ChainNotRegisteredError";
361
384
  code = "CHAIN_NOT_REGISTERED";
362
385
  tokenName;
363
386
  constructor(tokenName) {
@@ -371,6 +394,7 @@ export class ChainNotRegisteredError extends DiError {
371
394
  * @since 0.3.16-canary.0
372
395
  */
373
396
  export class RebindUnboundTokenError extends DiError {
397
+ name = "RebindUnboundTokenError";
374
398
  code = "REBIND_UNBOUND_TOKEN";
375
399
  tokenName;
376
400
  constructor(tokenName) {
@@ -384,6 +408,7 @@ export class RebindUnboundTokenError extends DiError {
384
408
  * @since 0.5.0-canary.9
385
409
  */
386
410
  export class SelfBindingRequiresClassError extends DiError {
411
+ name = "SelfBindingRequiresClassError";
387
412
  code = "SELF_BINDING_REQUIRES_CLASS";
388
413
  tokenName;
389
414
  constructor(tokenName) {
@@ -401,6 +426,7 @@ export class SelfBindingRequiresClassError extends DiError {
401
426
  * @since 0.6.0
402
427
  */
403
428
  export class StaticMemberDecoratorError extends DiError {
429
+ name = "StaticMemberDecoratorError";
404
430
  code = "STATIC_MEMBER_DECORATOR";
405
431
  decoratorName;
406
432
  memberName;
@@ -410,12 +436,68 @@ export class StaticMemberDecoratorError extends DiError {
410
436
  this.memberName = memberName;
411
437
  }
412
438
  }
439
+ /**
440
+ * A `@postConstruct` or `@preDestroy` on a method whose key is a symbol, which the lifecycle reader cannot name.
441
+ *
442
+ * @remarks Reported at decoration rather than at resolve, so the error points at the declaration
443
+ * instead of a `MetadataReader` the caller never configured.
444
+ *
445
+ * @since 0.11.0
446
+ */
447
+ export class SymbolKeyedLifecycleError extends DiError {
448
+ name = "SymbolKeyedLifecycleError";
449
+ code = "SYMBOL_KEYED_LIFECYCLE";
450
+ decoratorName;
451
+ memberName;
452
+ constructor(decoratorName, memberName) {
453
+ super(`@${decoratorName}() does not support a symbol-keyed method ('${memberName}'). Give the lifecycle method a string name.`);
454
+ this.decoratorName = decoratorName;
455
+ this.memberName = memberName;
456
+ }
457
+ }
458
+ /**
459
+ * A decorator handed no metadata object, because the runtime has no `Symbol.metadata` to key one by.
460
+ *
461
+ * @remarks TypeScript compiles `context.metadata` to `undefined` when `Symbol.metadata` is missing, so
462
+ * the fix is installing it before any decorated class is defined — the key this library reads.
463
+ *
464
+ * @since 0.11.0
465
+ */
466
+ export class MissingDecoratorMetadataError extends DiError {
467
+ name = "MissingDecoratorMetadataError";
468
+ code = "MISSING_DECORATOR_METADATA";
469
+ decoratorName;
470
+ constructor(decoratorName) {
471
+ super(`@${decoratorName}() received no decorator metadata: this runtime has no Symbol.metadata. Install it before any decorated class is defined, e.g. in a module imported first: (Symbol as { metadata?: symbol }).metadata ??= Symbol.for("Symbol.metadata");`);
472
+ this.decoratorName = decoratorName;
473
+ }
474
+ }
475
+ /**
476
+ * A `binding()` definition no chain could spell: no strategy, several, or a key its strategy does not allow.
477
+ *
478
+ * @remarks Raised where the declaration is written, for callers the compiler cannot reach, so a
479
+ * declared module that was built can always be loaded.
480
+ *
481
+ * @since 0.11.0
482
+ */
483
+ export class InvalidBindingDeclarationError extends DiError {
484
+ name = "InvalidBindingDeclarationError";
485
+ code = "INVALID_BINDING_DECLARATION";
486
+ tokenName;
487
+ reason;
488
+ constructor(tokenName, reason) {
489
+ super(`Invalid binding declaration for '${tokenName}': ${reason}.`);
490
+ this.tokenName = tokenName;
491
+ this.reason = reason;
492
+ }
493
+ }
413
494
  /**
414
495
  * An operation attempted on a container that has already been disposed.
415
496
  *
416
497
  * @since 0.3.16-canary.0
417
498
  */
418
499
  export class DisposedContainerError extends DiError {
500
+ name = "DisposedContainerError";
419
501
  code = "DISPOSED_CONTAINER";
420
502
  constructor() {
421
503
  super("Cannot perform operations on a disposed container.");
@@ -427,6 +509,7 @@ export class DisposedContainerError extends DiError {
427
509
  * @since 0.3.16-canary.0
428
510
  */
429
511
  export class AsyncActivationError extends DiError {
512
+ name = "AsyncActivationError";
430
513
  code = "ASYNC_ACTIVATION";
431
514
  tokenName;
432
515
  hookKind;
package/dist/index.d.ts CHANGED
@@ -13,6 +13,8 @@ export type { BindingSnapshot, ContainerSnapshot } from "#introspection/inspecto
13
13
  export type { ContainerGraphJson, GraphEdge, GraphNode, GraphOptions } from "#introspection/dependency-graph";
14
14
  export { AsyncModule, isSyncModule, Module, SyncModule } from "#core/module";
15
15
  export type { AsyncModuleBuilder, ModuleBuilder } from "#core/module";
16
+ export { binding } from "#core/binding-declaration";
17
+ export type { BindingDeclaration, BindingDefinition } from "#core/binding-declaration";
16
18
  export { inject } from "#decorators/inject";
17
19
  export { injectAll, isInjectionDescriptor, optional } from "#injection/descriptor";
18
20
  export type { InjectionDescriptor, InjectOptions } from "#injection/descriptor";
@@ -25,7 +27,7 @@ export { MetadataReaderToken } from "#metadata/metadata-reader-token";
25
27
  export type { ConstructorMetadata, LifecycleMetadata, MetadataReader, MutableLifecycleMetadata, ParamMetadata, } from "#metadata/metadata-types";
26
28
  export { defaultMetadataReader, SymbolMetadataReader } from "#metadata/symbol-metadata-reader";
27
29
  export { whenAnyAncestorIs, whenAnyAncestorNamed, whenAnyAncestorTagged, whenAnyAncestorTaggedAll, whenNoAncestorIs, whenNoParentIs, whenParentIs, whenParentNamed, whenParentTagged, whenParentTaggedAll, } from "#resolution/select/constraints";
28
- export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidMetadataError, MissingContainerContextError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
30
+ export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidBindingDeclarationError, InvalidMetadataError, MissingContainerContextError, MissingDecoratorMetadataError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SymbolKeyedLifecycleError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
29
31
  export type { ScopeViolationDetails } from "#errors/errors";
30
32
  export { toDotGraph } from "#introspection/graph-adapters/dot";
31
33
  export { toCytoscapeGraph } from "#introspection/graph-adapters/cytoscape";
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export { getActiveContainer, runWithContainer } from "#ambient/active-container"
12
12
  export { bindingSlotToResolveOptions, injectionSlotToResolveOptions, resolveOptionsForSlot, } from "#injection/resolve-options";
13
13
  // Module
14
14
  export { AsyncModule, isSyncModule, Module, SyncModule } from "#core/module";
15
+ export { binding } from "#core/binding-declaration";
15
16
  // Decorators
16
17
  export { inject } from "#decorators/inject";
17
18
  export { injectAll, isInjectionDescriptor, optional } from "#injection/descriptor";
@@ -25,7 +26,7 @@ export { defaultMetadataReader, SymbolMetadataReader } from "#metadata/symbol-me
25
26
  // Constraints — contextual injection predicates for .when()
26
27
  export { whenAnyAncestorIs, whenAnyAncestorNamed, whenAnyAncestorTagged, whenAnyAncestorTaggedAll, whenNoAncestorIs, whenNoParentIs, whenParentIs, whenParentNamed, whenParentTagged, whenParentTaggedAll, } from "#resolution/select/constraints";
27
28
  // Errors
28
- export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidMetadataError, MissingContainerContextError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
29
+ export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidBindingDeclarationError, InvalidMetadataError, MissingContainerContextError, MissingDecoratorMetadataError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SymbolKeyedLifecycleError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
29
30
  // Graph adapters — render `generateDependencyGraph()` output for common viewers
30
31
  export { toDotGraph } from "#introspection/graph-adapters/dot";
31
32
  export { toCytoscapeGraph } from "#introspection/graph-adapters/cytoscape";
@@ -4,12 +4,8 @@
4
4
  * @since 0.3.16-canary.0
5
5
  */
6
6
  export function isInjectionDescriptor(value) {
7
- if (value === null || value === undefined) {
8
- return false;
9
- }
10
- const type = typeof value;
11
- // inject() returns a function (dual-role), so must check both object and function
12
- if (type !== "object" && type !== "function") {
7
+ // inject() returns a function (dual-role), so both an object and a function qualify.
8
+ if ((typeof value !== "object" && typeof value !== "function") || value === null) {
13
9
  return false;
14
10
  }
15
11
  return ("token" in value &&
@@ -106,7 +102,7 @@ export function buildInjectionDescriptor(token, options) {
106
102
  */
107
103
  export function optional(token, options) {
108
104
  return withOptions({
109
- token: token,
105
+ token,
110
106
  optional: true,
111
107
  multi: false,
112
108
  }, options);
@@ -49,15 +49,17 @@ function loneNameCriterionOf(options) {
49
49
  }
50
50
  /** Shared core: build a ResolveOptions from already-normalised name + tags. */
51
51
  function buildOptions(name, tags) {
52
- if (name === undefined && tags === undefined) {
52
+ // An empty criterion list selects nothing, so it states no criterion at all.
53
+ const criteria = tags !== undefined && tags.length > 0 ? tags : undefined;
54
+ if (name === undefined && criteria === undefined) {
53
55
  return undefined;
54
56
  }
55
57
  const options = {};
56
58
  if (name !== undefined) {
57
59
  options.name = name;
58
60
  }
59
- if (tags !== undefined) {
60
- options.tags = tags;
61
+ if (criteria !== undefined) {
62
+ options.tags = criteria;
61
63
  }
62
64
  return options;
63
65
  }
@@ -85,7 +87,7 @@ const MEMOIZED_SINGLE_CRITERION = Symbol("di:single-criterion");
85
87
  */
86
88
  export function resolveOptionsForSlot(injectionSlot) {
87
89
  const { name, tags } = injectionSlot;
88
- if (name === undefined && tags === undefined) {
90
+ if (name === undefined && (tags === undefined || tags.length === 0)) {
89
91
  return undefined;
90
92
  }
91
93
  const slot = injectionSlot;
@@ -49,8 +49,13 @@ export interface GraphOptions {
49
49
  readonly includeParent?: boolean | undefined;
50
50
  }
51
51
  /**
52
- * Builds the JSON dependency graph of a registry's bindings, optionally including the parent's.
52
+ * Builds the JSON dependency graph of a registry's bindings, optionally including its ancestors'.
53
+ *
54
+ * @param registry - The registry whose bindings the graph is for.
55
+ * @param metadataReader - The reader class dependencies are read through.
56
+ * @param options - Whether the ancestors' bindings join the graph.
57
+ * @param ancestorRegistries - The ancestor containers' registries, nearest first.
53
58
  *
54
59
  * @since 0.3.16-canary.0
55
60
  */
56
- export declare function buildDependencyGraph(registry: BindingRegistry, metadataReader: MetadataReader, options: GraphOptions | undefined, parentRegistry?: BindingRegistry): ContainerGraphJson;
61
+ export declare function buildDependencyGraph(registry: BindingRegistry, metadataReader: MetadataReader, options: GraphOptions | undefined, ancestorRegistries?: ReadonlyArray<BindingRegistry>): ContainerGraphJson;