@codefast/di 0.3.13-canary.4 → 0.3.14-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +262 -235
  3. package/dist/binding-select.d.mts +17 -6
  4. package/dist/binding-select.mjs +17 -6
  5. package/dist/binding.d.mts +148 -23
  6. package/dist/binding.mjs +103 -14
  7. package/dist/constraints.d.mts +18 -3
  8. package/dist/constraints.mjs +18 -3
  9. package/dist/container.d.mts +81 -26
  10. package/dist/container.mjs +91 -3
  11. package/dist/decorators/inject.d.mts +40 -9
  12. package/dist/decorators/inject.mjs +50 -11
  13. package/dist/decorators/injectable.d.mts +2 -1
  14. package/dist/decorators/injectable.mjs +14 -2
  15. package/dist/decorators/lifecycle-decorators.d.mts +16 -4
  16. package/dist/decorators/lifecycle-decorators.mjs +16 -4
  17. package/dist/dependency-graph.d.mts +31 -8
  18. package/dist/dependency-graph.mjs +42 -8
  19. package/dist/errors.d.mts +124 -13
  20. package/dist/errors.mjs +126 -18
  21. package/dist/index.d.mts +2 -2
  22. package/dist/index.mjs +2 -2
  23. package/dist/inspector.d.mts +38 -14
  24. package/dist/inspector.mjs +36 -15
  25. package/dist/lifecycle.d.mts +28 -6
  26. package/dist/lifecycle.mjs +29 -10
  27. package/dist/metadata/metadata-keys.d.mts +17 -6
  28. package/dist/metadata/metadata-keys.mjs +17 -6
  29. package/dist/metadata/metadata-types.d.mts +29 -5
  30. package/dist/metadata/param-registry.mjs +6 -0
  31. package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
  32. package/dist/metadata/symbol-metadata-reader.mjs +23 -4
  33. package/dist/module.d.mts +34 -2
  34. package/dist/module.mjs +19 -0
  35. package/dist/registry.d.mts +39 -8
  36. package/dist/registry.mjs +39 -8
  37. package/dist/resolver.d.mts +107 -12
  38. package/dist/resolver.mjs +134 -37
  39. package/dist/scope-validation.d.mts +3 -2
  40. package/dist/scope-validation.mjs +3 -2
  41. package/dist/scope.d.mts +34 -6
  42. package/dist/scope.mjs +38 -13
  43. package/dist/token.d.mts +9 -2
  44. package/dist/token.mjs +7 -1
  45. package/package.json +2 -2
@@ -1,18 +1,33 @@
1
1
  //#region src/constraints.ts
2
2
  /**
3
- * Matches when the direct parent materialization was registered for `registryKey`.
3
+ * Constraint predicate factory: matches when the direct parent on the materialization stack
4
+ * was registered under `registryKey`. Pass the result to {@link BindingBuilder.when}.
5
+ *
6
+ * @param registryKey - Token or constructor that the parent binding must be registered against.
7
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
4
8
  */
5
9
  function whenParentIs(registryKey) {
6
10
  return (ctx) => ctx.parent?.registryKey === registryKey;
7
11
  }
8
12
  /**
9
- * Matches when any ancestor on the materialization stack was registered for `registryKey`.
13
+ * Constraint predicate factory: matches when *any* ancestor on the materialization stack
14
+ * (not just the immediate parent) was registered under `registryKey`.
15
+ * Pass the result to {@link BindingBuilder.when}.
16
+ *
17
+ * @param registryKey - Token or constructor to search for across the full construction chain.
18
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
10
19
  */
11
20
  function whenAnyAncestorIs(registryKey) {
12
21
  return (ctx) => ctx.materializationStack.some((frame) => frame.registryKey === registryKey);
13
22
  }
14
23
  /**
15
- * Matches when the immediate parent binding carries `tag` with `tagValue` (same metadata as {@link BindingBuilder.whenTagged} on the parent).
24
+ * Constraint predicate factory: matches when the immediate parent binding carries a tag
25
+ * whose key is `tag` and whose value is reference-equal to `tagValue` (`Object.is`).
26
+ * Pass the result to {@link BindingBuilder.when}.
27
+ *
28
+ * @param tag - Tag key to check on the parent binding.
29
+ * @param tagValue - Expected value; compared via `Object.is`.
30
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
16
31
  */
17
32
  function whenTargetTagged(tag, tagValue) {
18
33
  return (ctx) => {
@@ -5,6 +5,9 @@ import { ContainerGraphJson, ContainerSnapshot, DotGraphOptions } from "./inspec
5
5
  import { AsyncModule, Module } from "./module.mjs";
6
6
 
7
7
  //#region src/container.d.ts
8
+ /**
9
+ * Union of sync and async modules accepted by `loadAsync` / `unloadAsync`.
10
+ */
8
11
  type ModuleLike = Module | AsyncModule;
9
12
  /**
10
13
  * Public contract for an IoC container (registry, modules, resolution, lifecycle).
@@ -14,56 +17,102 @@ type ModuleLike = Module | AsyncModule;
14
17
  * {@link Container.dispose} automatically at scope exit (TC39 Explicit Resource Management).
15
18
  */
16
19
  interface Container extends AsyncDisposable {
17
- /** Starts a fluent binding builder for the given token or constructor. */
20
+ /**
21
+ * Starts a fluent binding builder for the given token or constructor.
22
+ */
18
23
  bind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
19
- /** Removes all existing bindings for the token (with sync deactivation) then starts a fresh builder. */
24
+ /**
25
+ * Removes all existing bindings for the token (with sync deactivation) then starts a fresh builder.
26
+ */
20
27
  rebind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
21
- /** Removes all bindings for a token or a single binding by its {@link BindingIdentifier}; runs sync deactivation. */
28
+ /**
29
+ * Removes all bindings for a token or a single binding by its {@link BindingIdentifier}; runs sync deactivation.
30
+ */
22
31
  unbind(tokenOrId: RegistryKey | BindingIdentifier): void;
23
- /** Same as {@link unbind} but awaits async `onDeactivation` handlers before removing. */
32
+ /**
33
+ * Same as {@link unbind} but awaits async `onDeactivation` handlers before removing.
34
+ */
24
35
  unbindAsync(tokenOrId: RegistryKey | BindingIdentifier): Promise<void>;
25
- /** Returns `true` if at least one binding exists for `token`, optionally filtered by `hint`. */
36
+ /**
37
+ * Returns `true` if at least one binding exists for `token`, optionally filtered by `hint`.
38
+ */
26
39
  has(token: RegistryKey, hint?: ResolveHint): boolean;
27
- /** Resolves the token synchronously. Throws {@link AsyncResolutionError} if any binding in the chain is async. */
40
+ /**
41
+ * Resolves the token synchronously. Throws {@link AsyncResolutionError} if any binding in the chain is async.
42
+ */
28
43
  resolve<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value;
29
- /** Resolves the token, awaiting any async factory in the chain. Safe for both sync and async bindings. */
44
+ /**
45
+ * Resolves the token, awaiting any async factory in the chain. Safe for both sync and async bindings.
46
+ */
30
47
  resolveAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value>;
31
- /** Resolves all bindings registered for the token (multi-binding). Throws {@link AsyncResolutionError} if any is async. */
48
+ /**
49
+ * Resolves all bindings registered for the token (multi-binding). Throws {@link AsyncResolutionError} if any is async.
50
+ */
32
51
  resolveAll<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value[];
33
- /** Async variant of {@link resolveAll} — safe when the multi-binding set contains async factories. */
52
+ /**
53
+ * Async variant of {@link resolveAll} — safe when the multi-binding set contains async factories.
54
+ */
34
55
  resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value[]>;
35
- /** Resolves the token or returns `undefined` if no binding is registered (never throws on missing). */
56
+ /**
57
+ * Resolves the token or returns `undefined` if no binding is registered (never throws on missing).
58
+ */
36
59
  resolveOptional<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value | undefined;
37
- /** Registers bindings from one or more synchronous modules. Re-loading a module already present is a no-op. */
60
+ /**
61
+ * Registers bindings from one or more synchronous modules. Re-loading a module already present is a no-op.
62
+ */
38
63
  load(...modules: Module[]): void;
39
- /** Registers bindings from sync and/or async modules, awaiting each async setup in sequence. */
64
+ /**
65
+ * Registers bindings from sync and/or async modules, awaiting each async setup in sequence.
66
+ */
40
67
  loadAsync(...modules: ModuleLike[]): Promise<void>;
41
- /** Removes all bindings contributed by the given modules; runs sync deactivation on released singletons. */
68
+ /**
69
+ * Removes all bindings contributed by the given modules; runs sync deactivation on released singletons.
70
+ */
42
71
  unload(...modules: ModuleLike[]): void;
43
- /** Same as {@link unload} but awaits async `onDeactivation` handlers. */
72
+ /**
73
+ * Same as {@link unload} but awaits async `onDeactivation` handlers.
74
+ */
44
75
  unloadAsync(...modules: ModuleLike[]): Promise<void>;
45
- /** Eagerly constructs every singleton binding so the first request is never cold. */
76
+ /**
77
+ * Eagerly constructs every singleton binding so the first request is never cold.
78
+ */
46
79
  initializeAsync(): Promise<void>;
47
- /** Scans {@link getAutoRegistered} entries and binds each to its declared scope. Returns the count added. */
80
+ /**
81
+ * Scans {@link getAutoRegistered} entries and binds each to its declared scope. Returns the count added.
82
+ */
48
83
  loadAutoRegistered(): number;
49
- /** Checks for scope violations (captive dependencies). Throws {@link ScopeViolationError} on the first violation found. */
84
+ /**
85
+ * Checks for scope violations (captive dependencies). Throws {@link ScopeViolationError} on the first violation found.
86
+ */
50
87
  validate(): void;
51
- /** Returns a debug snapshot of all registered bindings and their activation state. */
88
+ /**
89
+ * Returns a debug snapshot of all registered bindings and their activation state.
90
+ */
52
91
  inspect(): ContainerSnapshot;
53
- /** Renders the dependency graph as a Graphviz DOT string (default) or a typed JSON object. */
92
+ /**
93
+ * Renders the dependency graph as a Graphviz DOT string (default) or a typed JSON object.
94
+ */
54
95
  generateDependencyGraph(options?: DotGraphOptions & {
55
96
  format?: "dot";
56
97
  }): string;
57
98
  generateDependencyGraph(options: DotGraphOptions & {
58
99
  format: "json";
59
100
  }): ContainerGraphJson;
60
- /** Creates a child container that inherits bindings from this container without polluting its registry. */
101
+ /**
102
+ * Creates a child container that inherits bindings from this container without polluting its registry.
103
+ */
61
104
  createChild(): Container;
62
- /** @throws Always — container disposal is async; use `await using` or `await container.dispose()`. */
105
+ /**
106
+ * @throws Always — container disposal is async; use `await using` or `await container.dispose()`.
107
+ */
63
108
  [Symbol.dispose](): never;
64
- /** Returns the raw binding list for a token without triggering resolution. `undefined` means no binding. */
109
+ /**
110
+ * Returns the raw binding list for a token without triggering resolution. `undefined` means no binding.
111
+ */
65
112
  lookupBindings(token: RegistryKey): readonly Binding<unknown>[] | undefined;
66
- /** Runs all `onDeactivation` hooks on active singletons and releases all caches. */
113
+ /**
114
+ * Runs all `onDeactivation` hooks on active singletons and releases all caches.
115
+ */
67
116
  dispose(): Promise<void>;
68
117
  [Symbol.asyncDispose](): Promise<void>;
69
118
  }
@@ -71,11 +120,17 @@ interface Container extends AsyncDisposable {
71
120
  * Factory functions for {@link Container} instances (interface + namespace merge).
72
121
  */
73
122
  declare namespace Container {
74
- /** Creates an empty container with no bindings. */
123
+ /**
124
+ * Creates an empty container with no bindings.
125
+ */
75
126
  function create(): Container;
76
- /** Creates a container and immediately loads the given sync modules. */
127
+ /**
128
+ * Creates a container and immediately loads the given sync modules.
129
+ */
77
130
  function fromModules(...modules: Module[]): Container;
78
- /** Creates a container and awaits loading of sync and/or async modules. */
131
+ /**
132
+ * Creates a container and awaits loading of sync and/or async modules.
133
+ */
79
134
  function fromModulesAsync(...modules: (Module | AsyncModule)[]): Promise<Container>;
80
135
  }
81
136
  //#endregion
@@ -10,18 +10,48 @@ import { validateScopeRules } from "./scope-validation.mjs";
10
10
  import { ScopeManager } from "./scope.mjs";
11
11
  import { isDevelopmentOrTestEnvironment } from "./environment.mjs";
12
12
  //#region src/container.ts
13
+ /**
14
+ * Derives a {@link ResolveHint} from a binding's name or first tag.
15
+ * Used by {@link DefaultContainer.initializeAsync} to re-resolve named/tagged singletons
16
+ * through the standard resolution path.
17
+ */
13
18
  function resolveHintForBinding(binding) {
14
19
  if (binding.bindingName !== void 0) return { name: binding.bindingName };
15
20
  for (const [tagKey, tagValue] of binding.tags) return { tag: [tagKey, tagValue] };
16
21
  }
17
22
  /**
23
+ * Module {@link ModuleBuilder.bind}: append when the binding is disambiguated **at first
24
+ * registration** (`whenNamed` / `whenTagged` / `when` before `to*()`). Otherwise replace
25
+ * all bindings for the token (last-wins). Chaining `.whenNamed()` after `.to*()` only updates
26
+ * in place and does not enable multi-binding for subsequent module lines — use hint-before-`to*()`
27
+ * in modules (same style as `container.bind(...).whenNamed("x").to*(...)` in the package README).
28
+ */
29
+ function moduleBindingUsesMultiSlot(built) {
30
+ return built.bindingName !== void 0 || built.tags.size > 0 || built.constraint !== void 0;
31
+ }
32
+ /**
18
33
  * Default IoC container: registry + scoped caches + synchronous / asynchronous resolution.
19
34
  * @internal Implementation of {@link Container}; not part of the public package contract.
20
35
  */
21
36
  var DefaultContainer = class DefaultContainer {
37
+ /**
38
+ * Stack guard for detecting circular sync module imports during {@link ensureSyncModuleLoaded}.
39
+ */
22
40
  syncModuleStack = [];
41
+ /**
42
+ * Stack guard for detecting circular async module imports during {@link ensureAsyncModuleLoaded}.
43
+ */
23
44
  asyncModuleStack = [];
45
+ /**
46
+ * Tracks loaded modules → their binding IDs so {@link unload} / {@link unloadAsync} can
47
+ * remove exactly the bindings contributed by each module.
48
+ * Also serves as a deduplication set: a module present as a key is considered loaded.
49
+ */
24
50
  loadedModules = /* @__PURE__ */ new Map();
51
+ /**
52
+ * True after the first dev/test one-shot scope validation has run for the current registry state.
53
+ * Reset to `false` by {@link invalidateDevValidationState} on every registry mutation.
54
+ */
25
55
  devValidationRan = false;
26
56
  constructor(ownRegistry, ownScopeManager, parent, resolver, metadataReader) {
27
57
  this.ownRegistry = ownRegistry;
@@ -30,6 +60,10 @@ var DefaultContainer = class DefaultContainer {
30
60
  this.resolver = resolver;
31
61
  this.metadataReader = metadataReader;
32
62
  }
63
+ /**
64
+ * Creates a root container with an empty registry and fresh singleton/scoped caches.
65
+ * Wires the circular container↔resolver reference via a mutable {@link ContainerRef} holder.
66
+ */
33
67
  static create() {
34
68
  const ownRegistry = new BindingRegistry();
35
69
  const ownScopeManager = ScopeManager.createRoot();
@@ -48,7 +82,9 @@ var DefaultContainer = class DefaultContainer {
48
82
  return container;
49
83
  }
50
84
  /**
51
- * Starts a fluent binding registered on this container when {@link BindingBuilder.build} runs.
85
+ * Starts a fluent {@link BindingBuilder} for the given token or constructor.
86
+ * The binding is registered into this container's registry immediately when a
87
+ * `to*()` strategy method is called on the returned builder.
52
88
  */
53
89
  bind(token) {
54
90
  return new BindingBuilder(token, void 0, {
@@ -162,7 +198,9 @@ var DefaultContainer = class DefaultContainer {
162
198
  this.maybeRunDevValidationOnce();
163
199
  }
164
200
  }
165
- /** Async variant of {@link resolve}; same dev/test validation and runtime scope enforcement. */
201
+ /**
202
+ * Async variant of {@link resolve}; same dev/test validation and runtime scope enforcement.
203
+ */
166
204
  resolveAsync(key, hint) {
167
205
  return this.resolver.resolveAsyncRoot(key, hint).finally(() => {
168
206
  this.maybeRunDevValidationOnce();
@@ -205,9 +243,17 @@ var DefaultContainer = class DefaultContainer {
205
243
  }
206
244
  }
207
245
  }
246
+ /**
247
+ * Marks the dev/test one-shot validation as stale so the next resolve or load triggers it again.
248
+ * Called on every registry mutation (bind, unbind, rebind, load, unload).
249
+ */
208
250
  invalidateDevValidationState() {
209
251
  this.devValidationRan = false;
210
252
  }
253
+ /**
254
+ * Runs scope validation at most once per registry epoch when `NODE_ENV` is not `"production"`.
255
+ * Guards against repeated validation on successive resolves without intervening mutations.
256
+ */
211
257
  maybeRunDevValidationOnce() {
212
258
  if (!isDevelopmentOrTestEnvironment()) return;
213
259
  if (this.devValidationRan) return;
@@ -257,6 +303,9 @@ var DefaultContainer = class DefaultContainer {
257
303
  [Symbol.dispose]() {
258
304
  throw new InternalError("Container disposal is async. Use `await using container = Container.create()` or call `await container.dispose()` instead of `using`.");
259
305
  }
306
+ /**
307
+ * Constructs a {@link ContainerInspector} wired to this container's full hierarchy.
308
+ */
260
309
  createInspector() {
261
310
  return new ContainerInspector({
262
311
  collectAllRegistryKeys: () => this.collectAllRegistryKeysInHierarchy(),
@@ -265,11 +314,18 @@ var DefaultContainer = class DefaultContainer {
265
314
  metadataReader: this.metadataReader
266
315
  });
267
316
  }
317
+ /**
318
+ * Collects the union of all registry keys from this container and every ancestor.
319
+ * Deduplicates by reference equality (tokens are objects).
320
+ */
268
321
  collectAllRegistryKeysInHierarchy() {
269
322
  const keys = /* @__PURE__ */ new Set();
270
323
  this.accumulateRegistryKeysFromHierarchy(keys, this);
271
324
  return [...keys];
272
325
  }
326
+ /**
327
+ * Recursive helper: walks the parent chain bottom-up, adding each level's registry keys.
328
+ */
273
329
  accumulateRegistryKeysFromHierarchy(keys, container) {
274
330
  if (container === void 0) return;
275
331
  for (const entry of container.ownRegistry.listEntries()) keys.add(entry.key);
@@ -307,11 +363,22 @@ var DefaultContainer = class DefaultContainer {
307
363
  [Symbol.asyncDispose]() {
308
364
  return this.dispose();
309
365
  }
366
+ /**
367
+ * Returns a `bind` function scoped to a module. Registrations are tracked in
368
+ * {@link loadedModules} for {@link unload}.
369
+ *
370
+ * - **Last-wins** (replaces every binding for that token): `bind(token).to*(...)` with no
371
+ * `whenNamed` / `whenTagged` / `when` **before** the `to*()` call.
372
+ * - **Multi-binding** (append): call `whenNamed`, `whenTagged`, and/or `when` **before** `to*()`
373
+ * so the disambiguator exists at registration time — supports `resolveAll` and per-binding
374
+ * hints in {@link Container.initializeAsync}.
375
+ */
310
376
  bindForModule(owner) {
311
377
  return (token) => new BindingBuilder(token, owner.name, {
312
378
  register: (built) => {
313
379
  this.invalidateDevValidationState();
314
- this.ownRegistry.replaceKeyLastWins(token, built, (removed) => {
380
+ if (moduleBindingUsesMultiSlot(built)) this.ownRegistry.add(token, built);
381
+ else this.ownRegistry.replaceKeyLastWins(token, built, (removed) => {
315
382
  this.ownScopeManager.releaseBinding(removed);
316
383
  });
317
384
  this.recordBindingForModule(owner, built.id);
@@ -322,6 +389,9 @@ var DefaultContainer = class DefaultContainer {
322
389
  }
323
390
  });
324
391
  }
392
+ /**
393
+ * Appends a binding ID to the tracking list for `owner` so {@link unload} can remove it later.
394
+ */
325
395
  recordBindingForModule(owner, id) {
326
396
  const list = this.loadedModules.get(owner);
327
397
  if (list === void 0) {
@@ -330,6 +400,10 @@ var DefaultContainer = class DefaultContainer {
330
400
  }
331
401
  list.push(id);
332
402
  }
403
+ /**
404
+ * Creates the {@link ModuleBuilder} passed to a sync module's setup callback.
405
+ * The `import` method throws {@link InternalError} if an {@link AsyncModule} is passed.
406
+ */
333
407
  createSyncModuleBuilder(module) {
334
408
  return {
335
409
  import: (...deps) => {
@@ -341,6 +415,11 @@ var DefaultContainer = class DefaultContainer {
341
415
  bind: this.bindForModule(module)
342
416
  };
343
417
  }
418
+ /**
419
+ * Creates the {@link AsyncModuleBuilder} and a companion `awaitImports` thunk.
420
+ * Async sub-imports are collected into `pendingImports` and flushed after the module's
421
+ * own setup returns — this avoids interleaving setup code with dependency loading.
422
+ */
344
423
  createAsyncModuleBuilder(module) {
345
424
  const pendingImports = [];
346
425
  return {
@@ -357,6 +436,11 @@ var DefaultContainer = class DefaultContainer {
357
436
  }
358
437
  };
359
438
  }
439
+ /**
440
+ * Loads a sync module if not already loaded. Detects circular module imports via
441
+ * {@link syncModuleStack} and throws {@link CircularDependencyError} with the full cycle path.
442
+ * The module is marked as loaded *before* its setup runs so that re-entrant imports are deduped.
443
+ */
360
444
  ensureSyncModuleLoaded(module) {
361
445
  if (this.loadedModules.has(module)) return;
362
446
  if (this.syncModuleStack.includes(module)) throw new CircularDependencyError([...this.syncModuleStack.map((stackedModule) => stackedModule.name), module.name]);
@@ -369,6 +453,10 @@ var DefaultContainer = class DefaultContainer {
369
453
  this.syncModuleStack.pop();
370
454
  }
371
455
  }
456
+ /**
457
+ * Async counterpart of {@link ensureSyncModuleLoaded}: loads the module's async setup,
458
+ * then flushes any pending async sub-imports collected during setup.
459
+ */
372
460
  async ensureAsyncModuleLoaded(asyncModule) {
373
461
  if (this.loadedModules.has(asyncModule)) return;
374
462
  if (this.asyncModuleStack.includes(asyncModule)) throw new CircularDependencyError([...this.asyncModuleStack.map((stackedAsyncModule) => stackedAsyncModule.name), asyncModule.name]);
@@ -3,22 +3,53 @@ import { Constructor, ResolveHint } from "../binding.mjs";
3
3
  import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
4
4
 
5
5
  //#region src/decorators/inject.d.ts
6
- /** Options forwarded to the container when resolving an injected dependency. */
6
+ /**
7
+ * Name/tag hint forwarded to the container when resolving an injected dependency.
8
+ * Alias for {@link ResolveHint}; used as the second parameter of {@link inject} and {@link optional}.
9
+ */
7
10
  type InjectOptions = ResolveHint;
8
11
  /**
9
- * Creates an {@link InjectionDescriptor} for use in an `@injectable(deps)` array, or as an
10
- * `accessor` field decorator for post-construction property injection.
12
+ * Dual-purpose injection helper:
13
+ *
14
+ * **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
15
+ * optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
16
+ *
17
+ * ```ts
18
+ * @injectable([inject(Logger, { name: 'file' })])
19
+ * class UserService { constructor(log: Logger) {} }
20
+ * ```
21
+ *
22
+ * **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
23
+ * `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
24
+ * injection after construction.
25
+ *
26
+ * ```ts
27
+ * @inject(Logger) accessor logger!: LoggerService;
28
+ * ```
11
29
  *
12
- * As a deps-array entry: `@injectable([inject(Logger, { name: 'file' })])`
13
- * As an accessor decorator: `@inject(Logger) accessor logger!: LoggerService`
30
+ * @param token - The injection key (token or constructor) to resolve.
31
+ * @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
32
+ * `ClassAccessorDecoratorContext` automatically supplied by the runtime.
14
33
  */
15
34
  declare function inject<Value>(token: Token<Value> | Constructor<Value>, optionsOrContext?: InjectOptions | ClassAccessorDecoratorContext): InjectionDescriptor<Value>;
16
35
  /**
17
- * Same as {@link inject} but marks the dependency as optional — resolves to `undefined` instead
18
- * of throwing {@link TokenNotBoundError} when no binding exists.
36
+ * Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
37
+ * During resolution, an unbound token resolves to `undefined` instead of throwing
38
+ * {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
19
39
  */
20
40
  declare function optional<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
21
- /** Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}. */
41
+ /**
42
+ * Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
43
+ * (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
44
+ * Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
45
+ *
46
+ * Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
47
+ * callers omit options and register disambiguators on each binding instead).
48
+ */
49
+ declare function injectAll<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
50
+ /**
51
+ * Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
52
+ */
22
53
  declare function isInjectionDescriptor(value: unknown): value is InjectionDescriptor;
23
54
  //#endregion
24
- export { InjectOptions, inject, isInjectionDescriptor, optional };
55
+ export { InjectOptions, inject, injectAll, isInjectionDescriptor, optional };
@@ -1,7 +1,9 @@
1
1
  import { InternalError } from "../errors.mjs";
2
2
  import { CODEFAST_DI_ACCESSOR_INJECTIONS } from "../metadata/metadata-keys.mjs";
3
3
  //#region src/decorators/inject.ts
4
- /** Validates and normalises the `tag` option from {@link InjectOptions}; throws {@link InternalError} on bad input. */
4
+ /**
5
+ * Validates and normalises the `tag` option from {@link InjectOptions}; throws {@link InternalError} on bad input.
6
+ */
5
7
  function normalizeTag(tag) {
6
8
  if (tag === void 0) return;
7
9
  if (!Array.isArray(tag) || tag.length !== 2) throw new InternalError(`@inject tag must be a tuple [tagKey, value] with length 2; received ${String(tag)}`);
@@ -9,7 +11,9 @@ function normalizeTag(tag) {
9
11
  if (typeof tagName !== "string") throw new InternalError(`@inject tag key must be a string; received ${typeof tagName}`);
10
12
  return [tagName, value];
11
13
  }
12
- /** Builds an {@link InjectionDescriptor} from a token, optional flag, and raw inject options. */
14
+ /**
15
+ * Builds an {@link InjectionDescriptor} from a token, optional flag, and raw inject options.
16
+ */
13
17
  function toDescriptor(token, optional, options) {
14
18
  const normalizedTag = normalizeTag(options?.tag);
15
19
  if (options?.name !== void 0) return {
@@ -27,16 +31,34 @@ function toDescriptor(token, optional, options) {
27
31
  optional
28
32
  };
29
33
  }
30
- /** Type guard — returns `true` when `value` is a TC39 `ClassAccessorDecoratorContext` (accessor field). */
34
+ /**
35
+ * Type guard — returns `true` when `value` is a TC39 `ClassAccessorDecoratorContext` (accessor field).
36
+ */
31
37
  function isAccessorDecoratorContext(value) {
32
38
  return typeof value === "object" && value !== null && "kind" in value && value.kind === "accessor";
33
39
  }
34
40
  /**
35
- * Creates an {@link InjectionDescriptor} for use in an `@injectable(deps)` array, or as an
36
- * `accessor` field decorator for post-construction property injection.
41
+ * Dual-purpose injection helper:
42
+ *
43
+ * **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
44
+ * optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
45
+ *
46
+ * ```ts
47
+ * @injectable([inject(Logger, { name: 'file' })])
48
+ * class UserService { constructor(log: Logger) {} }
49
+ * ```
50
+ *
51
+ * **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
52
+ * `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
53
+ * injection after construction.
37
54
  *
38
- * As a deps-array entry: `@injectable([inject(Logger, { name: 'file' })])`
39
- * As an accessor decorator: `@inject(Logger) accessor logger!: LoggerService`
55
+ * ```ts
56
+ * @inject(Logger) accessor logger!: LoggerService;
57
+ * ```
58
+ *
59
+ * @param token - The injection key (token or constructor) to resolve.
60
+ * @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
61
+ * `ClassAccessorDecoratorContext` automatically supplied by the runtime.
40
62
  */
41
63
  function inject(token, optionsOrContext) {
42
64
  if (isAccessorDecoratorContext(optionsOrContext)) {
@@ -54,16 +76,33 @@ function inject(token, optionsOrContext) {
54
76
  return toDescriptor(token, false, optionsOrContext);
55
77
  }
56
78
  /**
57
- * Same as {@link inject} but marks the dependency as optional — resolves to `undefined` instead
58
- * of throwing {@link TokenNotBoundError} when no binding exists.
79
+ * Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
80
+ * During resolution, an unbound token resolves to `undefined` instead of throwing
81
+ * {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
59
82
  */
60
83
  function optional(token, options) {
61
84
  return toDescriptor(token, true, options);
62
85
  }
63
- /** Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}. */
86
+ /**
87
+ * Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
88
+ * (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
89
+ * Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
90
+ *
91
+ * Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
92
+ * callers omit options and register disambiguators on each binding instead).
93
+ */
94
+ function injectAll(token, options) {
95
+ return {
96
+ ...toDescriptor(token, false, options),
97
+ all: true
98
+ };
99
+ }
100
+ /**
101
+ * Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
102
+ */
64
103
  function isInjectionDescriptor(value) {
65
104
  if (typeof value !== "object" || value === null) return false;
66
105
  return "token" in value && "optional" in value;
67
106
  }
68
107
  //#endregion
69
- export { inject, isInjectionDescriptor, optional };
108
+ export { inject, injectAll, isInjectionDescriptor, optional };
@@ -6,7 +6,8 @@ import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
6
6
  /**
7
7
  * A single entry in the `deps` array passed to `@injectable()`.
8
8
  * Can be a plain token/constructor (resolved with no hint) or an {@link InjectionDescriptor}
9
- * produced by {@link inject} / {@link optional} when name, tag, or optional semantics are needed.
9
+ * produced by `inject` / `optional` / `injectAll` when name, tag, optional, or resolve-all
10
+ * semantics are needed.
10
11
  */
11
12
  type InjectableDependency = Token<unknown> | Constructor<unknown> | InjectionDescriptor<unknown>;
12
13
  /**
@@ -2,6 +2,11 @@ import { InternalError } from "../errors.mjs";
2
2
  import { CODEFAST_DI_CONSTRUCTOR_METADATA } from "../metadata/metadata-keys.mjs";
3
3
  import { isInjectionDescriptor } from "./inject.mjs";
4
4
  //#region src/decorators/injectable.ts
5
+ /**
6
+ * Global mutable registry of classes decorated with `@injectable({ autoRegister: true })`.
7
+ * Populated at class-definition time (via `context.addInitializer`), drained by
8
+ * {@link Container.loadAutoRegistered}. Entries accumulate for the lifetime of the process.
9
+ */
5
10
  const AUTO_REGISTER_REGISTRY = [];
6
11
  /**
7
12
  * Returns all classes decorated with `@injectable({ autoRegister: true })`.
@@ -10,14 +15,21 @@ const AUTO_REGISTER_REGISTRY = [];
10
15
  function getAutoRegistered() {
11
16
  return AUTO_REGISTER_REGISTRY;
12
17
  }
13
- /** Converts a single `@injectable` deps-array entry into a {@link ParamMetadata} record. */
18
+ /**
19
+ * Normalises a single `@injectable` deps-array entry into the uniform {@link ParamMetadata}
20
+ * shape used by the resolver's constructor-instantiation path.
21
+ *
22
+ * - {@link InjectionDescriptor} entries carry `optional`, `name`, and `tag` fields.
23
+ * - Plain token / constructor entries are wrapped with `optional: false` and no hint.
24
+ */
14
25
  function toParamMetadata(dependency, index) {
15
26
  if (isInjectionDescriptor(dependency)) return {
16
27
  index,
17
28
  token: dependency.token,
18
29
  optional: dependency.optional,
19
30
  name: dependency.name,
20
- tag: dependency.tag
31
+ tag: dependency.tag,
32
+ all: dependency.all === true ? true : void 0
21
33
  };
22
34
  return {
23
35
  index,
@@ -1,12 +1,24 @@
1
1
  //#region src/decorators/lifecycle-decorators.d.ts
2
2
  /**
3
- * Stage 3 method decorator: marks a method to be called after the class is instantiated by the container.
4
- * Order: construct → `@postConstruct()` → `.onActivation()` → cache.
3
+ * Stage 3 method decorator: marks a method to be called after the class is instantiated
4
+ * by the container and before the `onActivation` hook runs.
5
+ *
6
+ * Lifecycle order: `new Class(…)` → **`@postConstruct()`** → `onActivation()` → scope cache.
7
+ *
8
+ * Only one method per class may carry this decorator; a second application throws.
9
+ * If the decorated method returns a `Promise` during synchronous resolution,
10
+ * {@link AsyncResolutionError} is thrown — use `Container.resolveAsync()` instead.
5
11
  */
6
12
  declare function postConstruct(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
7
13
  /**
8
- * Stage 3 method decorator: marks a method to be called before the instance is destroyed by the container.
9
- * Order: `.onDeactivation()` → `@preDestroy()`.
14
+ * Stage 3 method decorator: marks a method to be called when the container disposes or
15
+ * unloads the owning binding.
16
+ *
17
+ * Lifecycle order: `onDeactivation()` → **`@preDestroy()`**.
18
+ *
19
+ * Only one method per class may carry this decorator; a second application throws.
20
+ * If the decorated method returns a `Promise` during synchronous disposal,
21
+ * an error is thrown — use `Container.disposeAsync()` instead.
10
22
  */
11
23
  declare function preDestroy(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
12
24
  //#endregion