@codefast/di 0.3.13 → 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 +20 -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
@@ -3,19 +3,30 @@ import { RegistryKey } from "./registry.mjs";
3
3
  import { Binding, ConstraintContext, Constructor, ResolveHint } from "./binding.mjs";
4
4
 
5
5
  //#region src/binding-select.d.ts
6
- /** Returns a human-readable label for a token or constructor (used in error messages and graph output). */
6
+ /**
7
+ * Returns a human-readable label for a token or constructor (used in error messages and graph output).
8
+ */
7
9
  declare function registryKeyLabel(key: Token<unknown> | Constructor<unknown>): string;
8
10
  /**
9
- * Applies resolve hints and optional constraint predicates to a binding list.
11
+ * Narrows a binding list by applying name/tag hints and `when()` constraint predicates.
12
+ *
13
+ * Filtering order: name filter → tag filter → constraint predicate. Bindings without a
14
+ * constraint predicate always pass the constraint stage. Returns the surviving candidates
15
+ * (may be empty).
10
16
  */
11
- declare function filterMatchingBindings(bindings: readonly Binding<unknown>[], hint: ResolveHint | undefined, constraintCtx: ConstraintContext | undefined): Binding<unknown>[];
17
+ declare function filterMatchingBindings(bindings: readonly Binding<unknown>[], hint: ResolveHint | undefined, constraintCtx: ConstraintContext | undefined): readonly Binding<unknown>[];
12
18
  /**
13
- * Picks the binding that would be used for resolution with the given hint (same rules as {@link DependencyResolver}).
14
- * When `constraintCtx` is set, bindings with a {@link BindingBuilder.when} predicate must pass it.
19
+ * Selects exactly one binding from the provided list, applying hint and constraint filtering.
20
+ *
21
+ * @throws {@link TokenNotBoundError} — `bindings` list is empty, or no candidate survives
22
+ * filtering without a name/tag hint.
23
+ * @throws {@link NoMatchingBindingError} — a name/tag hint was provided but no candidate matched.
24
+ * @throws {@link InternalError} — multiple candidates survive filtering (ambiguous binding).
15
25
  */
16
26
  declare function selectBindingForRegistry(bindings: readonly Binding<unknown>[], hint: ResolveHint | undefined, tokenLabel: string, pathLabels: readonly string[], constraintCtx: ConstraintContext | undefined): Binding<unknown>;
17
27
  /**
18
- * Resolves the effective binding for a registry key using the default (no-hint) selection rules.
28
+ * Convenience wrapper: looks up bindings for `key` and selects the default (no-hint,
29
+ * no-constraint) binding. Throws {@link TokenNotBoundError} if the key is unregistered.
19
30
  */
20
31
  declare function selectDefaultBindingForKey(lookup: (key: RegistryKey) => readonly Binding<unknown>[] | undefined, key: RegistryKey, pathPrefix: readonly string[]): Binding<unknown>;
21
32
  //#endregion
@@ -1,15 +1,21 @@
1
1
  import { InternalError, NoMatchingBindingError, TokenNotBoundError } from "./errors.mjs";
2
2
  //#region src/binding-select.ts
3
- /** Returns a human-readable label for a token or constructor (used in error messages and graph output). */
3
+ /**
4
+ * Returns a human-readable label for a token or constructor (used in error messages and graph output).
5
+ */
4
6
  function registryKeyLabel(key) {
5
7
  if (typeof key === "function") return key.name.length > 0 ? key.name : "(anonymous class)";
6
8
  return key.name.trim().length > 0 ? key.name : "(anonymous token)";
7
9
  }
8
10
  /**
9
- * Applies resolve hints and optional constraint predicates to a binding list.
11
+ * Narrows a binding list by applying name/tag hints and `when()` constraint predicates.
12
+ *
13
+ * Filtering order: name filter → tag filter → constraint predicate. Bindings without a
14
+ * constraint predicate always pass the constraint stage. Returns the surviving candidates
15
+ * (may be empty).
10
16
  */
11
17
  function filterMatchingBindings(bindings, hint, constraintCtx) {
12
- let candidates = [...bindings];
18
+ let candidates = bindings;
13
19
  if (hint?.name !== void 0) candidates = candidates.filter((binding) => binding.bindingName === hint.name);
14
20
  if (hint?.tag !== void 0) {
15
21
  const [tagKey, tagValue] = hint.tag;
@@ -19,8 +25,12 @@ function filterMatchingBindings(bindings, hint, constraintCtx) {
19
25
  return candidates;
20
26
  }
21
27
  /**
22
- * Picks the binding that would be used for resolution with the given hint (same rules as {@link DependencyResolver}).
23
- * When `constraintCtx` is set, bindings with a {@link BindingBuilder.when} predicate must pass it.
28
+ * Selects exactly one binding from the provided list, applying hint and constraint filtering.
29
+ *
30
+ * @throws {@link TokenNotBoundError} — `bindings` list is empty, or no candidate survives
31
+ * filtering without a name/tag hint.
32
+ * @throws {@link NoMatchingBindingError} — a name/tag hint was provided but no candidate matched.
33
+ * @throws {@link InternalError} — multiple candidates survive filtering (ambiguous binding).
24
34
  */
25
35
  function selectBindingForRegistry(bindings, hint, tokenLabel, pathLabels, constraintCtx) {
26
36
  if (bindings.length === 0) throw new TokenNotBoundError(tokenLabel, [...pathLabels]);
@@ -37,7 +47,8 @@ function selectBindingForRegistry(bindings, hint, tokenLabel, pathLabels, constr
37
47
  throw new InternalError(`Ambiguous binding for "${tokenLabel}": ${String(candidates.length)} candidates matched after applying ResolveHint (resolution path: ${pathLabels.join(" -> ")})`);
38
48
  }
39
49
  /**
40
- * Resolves the effective binding for a registry key using the default (no-hint) selection rules.
50
+ * Convenience wrapper: looks up bindings for `key` and selects the default (no-hint,
51
+ * no-constraint) binding. Throws {@link TokenNotBoundError} if the key is unregistered.
41
52
  */
42
53
  function selectDefaultBindingForKey(lookup, key, pathPrefix) {
43
54
  const label = registryKeyLabel(key);
@@ -17,7 +17,9 @@ declare function createBindingIdentifier(): BindingIdentifier;
17
17
  * Runtime constructor token used as a registry key (no reflection metadata).
18
18
  */
19
19
  type Constructor<Value> = abstract new (...args: never[]) => Value;
20
- /** Lifetime strategy for a resolved instance. */
20
+ /**
21
+ * Lifetime strategy for a resolved instance.
22
+ */
21
23
  type BindingScope = "singleton" | "transient" | "scoped";
22
24
  /**
23
25
  * Hint for disambiguating multi-bindings registered against the same token or constructor.
@@ -26,11 +28,21 @@ type ResolveHint = {
26
28
  readonly name?: string;
27
29
  readonly tag?: readonly [tag: string, value: unknown];
28
30
  };
31
+ /**
32
+ * Public alias for {@link ResolveHint} — the `hint` parameter accepted by
33
+ * `Container.resolve`, `Container.resolveAsync`, and related methods.
34
+ * Passes `name` and/or `tag` to select among multi-bindings.
35
+ */
29
36
  type ResolveOptions = ResolveHint;
30
37
  /**
31
38
  * Snapshot of a binding on the materialization stack (for {@link ConstraintContext}).
32
39
  */
33
40
  type ConstraintBindingKind = "constant" | "class" | "dynamic" | "async-dynamic" | "resolved" | "alias";
41
+ /**
42
+ * Snapshot of a single binding on the materialization stack during resolution.
43
+ * Each frame captures enough identity to implement contextual constraints
44
+ * ({@link whenParentIs}, {@link whenAnyAncestorIs}) and captive-dependency detection.
45
+ */
34
46
  type ConstraintParentFrame = {
35
47
  readonly registryKey: RegistryKey;
36
48
  readonly bindingId: BindingIdentifier;
@@ -62,9 +74,18 @@ type ConstraintContext = {
62
74
  type ResolutionContext = {
63
75
  readonly resolve: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value;
64
76
  readonly resolveAsync: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Promise<Value>;
65
- readonly resolveOptional: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value | undefined; /** Dependency-graph navigation context — path, materialization stack, parent/ancestor frames. */
77
+ readonly resolveOptional: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value | undefined; /** Resolves every binding registered for `token` (multi-binding). */
78
+ readonly resolveAll: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value[]; /** Async variant of {@link ResolutionContext.resolveAll}. */
79
+ readonly resolveAllAsync: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Promise<Value[]>;
80
+ /**
81
+ * Dependency-graph navigation context — path, materialization stack, parent/ancestor frames.
82
+ */
66
83
  readonly graph: ConstraintContext;
67
84
  };
85
+ /**
86
+ * Lifecycle and constraint fields shared by every concrete {@link Binding} variant.
87
+ * Separated from {@link BindingBase} so the builder can accumulate them independently of `id` / `scope`.
88
+ */
68
89
  type BindingLifecycle = {
69
90
  readonly bindingName?: string;
70
91
  readonly tags: ReadonlyMap<string, unknown>;
@@ -85,36 +106,51 @@ type ActivationHandler<Value> = (ctx: ResolutionContext, instance: Value) => Val
85
106
  type DeactivationHandler<Value> = (instance: Value) => void | Promise<void>;
86
107
  type BindingBase = BindingLifecycle & {
87
108
  readonly id: BindingIdentifier;
88
- readonly scope: BindingScope; /** Set when the binding was registered from {@link Module} / {@link AsyncModule} setup. */
109
+ readonly scope: BindingScope;
110
+ /**
111
+ * Set when the binding was registered from {@link Module} / {@link AsyncModule} setup.
112
+ */
89
113
  readonly moduleId?: string;
90
114
  };
91
- /** Binding backed by a pre-existing constant value; always singleton, no construction cost. */
115
+ /**
116
+ * Binding backed by a pre-existing constant value; always singleton, no construction cost.
117
+ */
92
118
  type ConstantBinding<Value> = BindingBase & {
93
119
  readonly kind: "constant";
94
120
  readonly value: Value;
95
121
  };
96
- /** Binding that constructs `implementationClass` via the container's metadata-driven instantiation. */
122
+ /**
123
+ * Binding that constructs `implementationClass` via the container's metadata-driven instantiation.
124
+ */
97
125
  type ClassBinding<Value> = BindingBase & {
98
126
  readonly kind: "class";
99
127
  readonly implementationClass: Constructor<Value>;
100
128
  };
101
- /** Binding backed by a synchronous factory that receives a {@link ResolutionContext}. */
129
+ /**
130
+ * Binding backed by a synchronous factory that receives a {@link ResolutionContext}.
131
+ */
102
132
  type DynamicBinding<Value> = BindingBase & {
103
133
  readonly kind: "dynamic";
104
134
  readonly factory: (ctx: ResolutionContext) => Value;
105
135
  };
106
- /** Binding backed by an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
136
+ /**
137
+ * Binding backed by an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`.
138
+ */
107
139
  type AsyncDynamicBinding<Value> = BindingBase & {
108
140
  readonly kind: "async-dynamic";
109
141
  readonly factory: (ctx: ResolutionContext) => Promise<Value>;
110
142
  };
111
- /** Binding whose dependencies are declared statically and pre-resolved before the factory is called. */
143
+ /**
144
+ * Binding whose dependencies are declared statically and pre-resolved before the factory is called.
145
+ */
112
146
  type ResolvedBinding<Value> = BindingBase & {
113
147
  readonly kind: "resolved";
114
148
  readonly dependencyTokens: readonly (Token<unknown> | Constructor<unknown>)[];
115
149
  readonly factory: (...args: unknown[]) => Value;
116
150
  };
117
- /** Binding that forwards resolution to `targetToken`; the container resolves whatever is bound there. */
151
+ /**
152
+ * Binding that forwards resolution to `targetToken`; the container resolves whatever is bound there.
153
+ */
118
154
  type AliasBinding<Value> = BindingBase & {
119
155
  readonly kind: "alias";
120
156
  readonly targetToken: Token<Value>;
@@ -132,53 +168,126 @@ type RegistryCallbacks<Value> = {
132
168
  readonly register?: (binding: Binding<Value>) => void;
133
169
  readonly update?: (binding: Binding<Value>) => void;
134
170
  };
171
+ /**
172
+ * Fluent builder for registering a single binding against a {@link Token} or {@link Constructor}.
173
+ *
174
+ * A builder has two phases:
175
+ * 1. **Strategy selection** — exactly one `to*()` call (`to`, `toSelf`, `toConstantValue`,
176
+ * `toDynamic`, `toDynamicAsync`, `toResolved`, `toAlias`) that determines how the value is produced.
177
+ * 2. **Refinement chain** — optional calls to `singleton()`, `transient()`, `scoped()`,
178
+ * `onActivation()`, `onDeactivation()`, `whenNamed()`, `whenTagged()`, `when()`, and `id()`.
179
+ *
180
+ * Calling a second `to*()` method throws {@link InternalError}.
181
+ *
182
+ * The builder is created by {@link Container.bind} or by `bind` on {@link ModuleBuilder}.
183
+ * The container injects {@link RegistryCallbacks} so that every strategy selection and
184
+ * refinement is immediately reflected in the live registry.
185
+ */
135
186
  declare class BindingBuilder<Value> {
136
187
  protected readonly bindingKey: Token<Value> | Constructor<Value>;
188
+ /**
189
+ * Current resolution strategy; starts as `"unset"` until a `to*()` method is called.
190
+ */
137
191
  private strategy;
192
+ /**
193
+ * Lifetime scope applied to the next binding snapshot; defaults to `"transient"`.
194
+ */
138
195
  private scope;
196
+ /**
197
+ * True after an explicit `.singleton()` / `.transient()` / `.scoped()` call (prevents constant scope change).
198
+ */
139
199
  private isScopeExplicit;
200
+ /**
201
+ * Pre-allocated binding ID set via `id(identifier)` before the first `to*()` call.
202
+ */
140
203
  private explicitId;
204
+ /**
205
+ * Resolve-hint name filter set by `.whenNamed()`.
206
+ */
141
207
  private bindingName;
208
+ /**
209
+ * Tag filters accumulated by successive `.whenTagged()` calls.
210
+ */
142
211
  private readonly tags;
212
+ /**
213
+ * Custom constraint predicates accumulated by `.when()`; all must pass for this binding to be selected.
214
+ */
143
215
  private readonly constraintPredicates;
144
216
  private onActivationHandler;
145
217
  private onDeactivationHandler;
218
+ /**
219
+ * Set when this binding was created inside a {@link Module} / {@link AsyncModule} setup callback.
220
+ */
146
221
  private readonly moduleId;
222
+ /**
223
+ * The most recently emitted {@link Binding} snapshot; `undefined` before the first `to*()` call.
224
+ */
147
225
  private currentBinding;
226
+ /**
227
+ * Container-injected hooks that sync builder mutations into the live registry.
228
+ */
148
229
  private readonly callbacks;
149
230
  constructor(bindingKey: Token<Value> | Constructor<Value>, moduleId?: string, callbacks?: RegistryCallbacks<Value>);
150
- /** Binds the token to a concrete implementation class; the container constructs it on demand. */
231
+ /**
232
+ * Binds the token to a concrete implementation class; the container constructs it on demand.
233
+ */
151
234
  to<C extends Constructor<Value>>(implementationClass: C): TransientBindingBuilder<Value>;
152
- /** Binds the class key to itself — only valid when the key is a constructor. */
235
+ /**
236
+ * Binds the class key to itself — only valid when the key is a constructor.
237
+ */
153
238
  toSelf(): TransientBindingBuilder<Value>;
154
- /** Binds the token to a pre-existing value; always resolved as singleton, no construction. */
239
+ /**
240
+ * Binds the token to a pre-existing value; always resolved as singleton, no construction.
241
+ */
155
242
  toConstantValue<const ConcreteValue extends Value>(value: ConcreteValue): ConstantBindingBuilder<Value>;
156
- /** Binds to a synchronous factory; `ctx` provides nested resolution within the same path. */
243
+ /**
244
+ * Binds to a synchronous factory; `ctx` provides nested resolution within the same path.
245
+ */
157
246
  toDynamic(factory: (ctx: ResolutionContext) => Value): TransientBindingBuilder<Value>;
158
- /** Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
247
+ /**
248
+ * Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`.
249
+ */
159
250
  toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): TransientBindingBuilder<Value>;
160
251
  /**
161
252
  * Binds to a factory whose dependencies are declared explicitly in `deps` and pre-resolved by
162
253
  * the container before the factory is called — no `ResolutionContext` needed inside the factory.
163
254
  */
164
255
  toResolved<Deps extends readonly (Token<unknown> | Constructor<unknown>)[]>(factory: (...args: { [Index in keyof Deps]: TokenValue<Deps[Index]> }) => Value, deps: Deps): TransientBindingBuilder<Value>;
165
- /** Redirects resolution to `targetToken`; the container resolves whatever is bound there. */
256
+ /**
257
+ * Redirects resolution to `targetToken`; the container resolves whatever is bound there.
258
+ */
166
259
  toAlias(targetToken: Token<Value>): TransientBindingBuilder<Value>;
167
- /** One instance per container; supports `onDeactivation`. */
260
+ /**
261
+ * One instance per container; supports `onDeactivation`.
262
+ */
168
263
  singleton(): SingletonBindingBuilder<Value>;
169
- /** New instance on every resolution (default scope). */
264
+ /**
265
+ * New instance on every resolution (default scope).
266
+ */
170
267
  transient(): TransientBindingBuilder<Value>;
171
- /** One instance per child container scope. */
268
+ /**
269
+ * One instance per child container scope.
270
+ */
172
271
  scoped(): ScopedBindingBuilder<Value>;
173
- /** Called with the resolved instance after construction; the return value replaces the instance. */
272
+ /**
273
+ * Called with the resolved instance after construction; the return value replaces the instance.
274
+ */
174
275
  onActivation(handler: ActivationHandler<Value>): this;
175
- /** @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases. */
276
+ /**
277
+ * @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases.
278
+ */
176
279
  onDeactivation(handler: DeactivationHandler<Value>): this;
177
- /** This binding only resolves when the caller passes `{ name }` as the resolve hint. */
280
+ /**
281
+ * This binding only resolves when the caller passes `{ name }` as the resolve hint.
282
+ */
178
283
  whenNamed(name: string): this;
179
- /** This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint. */
284
+ /**
285
+ * This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint.
286
+ */
180
287
  whenTagged(tag: string, tagValue: unknown): this;
181
- /** Adds a custom predicate; all predicates must pass for the binding to be selected. */
288
+ /**
289
+ * Adds a custom predicate; all predicates must pass for the binding to be selected.
290
+ */
182
291
  when(constraint: (ctx: ConstraintContext) => boolean): this;
183
292
  /**
184
293
  * Returns the binding's stable ID, allocating one if needed.
@@ -187,9 +296,25 @@ declare class BindingBuilder<Value> {
187
296
  */
188
297
  id(): BindingIdentifier;
189
298
  id(identifier: BindingIdentifier): BindingIdentifier;
299
+ /**
300
+ * Records the chosen strategy and emits the first {@link Binding} snapshot.
301
+ * Throws {@link InternalError} if a strategy was already selected (double `to*()` call).
302
+ */
190
303
  private registerWithStrategy;
304
+ /**
305
+ * Re-creates the {@link Binding} snapshot from the current builder state and pushes
306
+ * the update to the registry. No-op if no strategy has been set yet.
307
+ */
191
308
  private refreshRegisteredBinding;
309
+ /**
310
+ * Guards against calling `.singleton()` / `.transient()` / `.scoped()` on a constant binding,
311
+ * which is locked to `"singleton"` scope by invariant.
312
+ */
192
313
  private assertScopeMutable;
314
+ /**
315
+ * Produces an immutable {@link Binding} snapshot from the current builder fields.
316
+ * Called by both {@link registerWithStrategy} and {@link refreshRegisteredBinding}.
317
+ */
193
318
  private createBinding;
194
319
  }
195
320
  /**
package/dist/binding.mjs CHANGED
@@ -6,25 +6,72 @@ import { InternalError } from "./errors.mjs";
6
6
  function createBindingIdentifier() {
7
7
  return globalThis.crypto.randomUUID();
8
8
  }
9
+ /**
10
+ * Fluent builder for registering a single binding against a {@link Token} or {@link Constructor}.
11
+ *
12
+ * A builder has two phases:
13
+ * 1. **Strategy selection** — exactly one `to*()` call (`to`, `toSelf`, `toConstantValue`,
14
+ * `toDynamic`, `toDynamicAsync`, `toResolved`, `toAlias`) that determines how the value is produced.
15
+ * 2. **Refinement chain** — optional calls to `singleton()`, `transient()`, `scoped()`,
16
+ * `onActivation()`, `onDeactivation()`, `whenNamed()`, `whenTagged()`, `when()`, and `id()`.
17
+ *
18
+ * Calling a second `to*()` method throws {@link InternalError}.
19
+ *
20
+ * The builder is created by {@link Container.bind} or by `bind` on {@link ModuleBuilder}.
21
+ * The container injects {@link RegistryCallbacks} so that every strategy selection and
22
+ * refinement is immediately reflected in the live registry.
23
+ */
9
24
  var BindingBuilder = class {
25
+ /**
26
+ * Current resolution strategy; starts as `"unset"` until a `to*()` method is called.
27
+ */
10
28
  strategy = { type: "unset" };
29
+ /**
30
+ * Lifetime scope applied to the next binding snapshot; defaults to `"transient"`.
31
+ */
11
32
  scope = "transient";
33
+ /**
34
+ * True after an explicit `.singleton()` / `.transient()` / `.scoped()` call (prevents constant scope change).
35
+ */
12
36
  isScopeExplicit = false;
37
+ /**
38
+ * Pre-allocated binding ID set via `id(identifier)` before the first `to*()` call.
39
+ */
13
40
  explicitId;
41
+ /**
42
+ * Resolve-hint name filter set by `.whenNamed()`.
43
+ */
14
44
  bindingName;
45
+ /**
46
+ * Tag filters accumulated by successive `.whenTagged()` calls.
47
+ */
15
48
  tags = /* @__PURE__ */ new Map();
49
+ /**
50
+ * Custom constraint predicates accumulated by `.when()`; all must pass for this binding to be selected.
51
+ */
16
52
  constraintPredicates = [];
17
53
  onActivationHandler;
18
54
  onDeactivationHandler;
55
+ /**
56
+ * Set when this binding was created inside a {@link Module} / {@link AsyncModule} setup callback.
57
+ */
19
58
  moduleId;
59
+ /**
60
+ * The most recently emitted {@link Binding} snapshot; `undefined` before the first `to*()` call.
61
+ */
20
62
  currentBinding;
63
+ /**
64
+ * Container-injected hooks that sync builder mutations into the live registry.
65
+ */
21
66
  callbacks;
22
67
  constructor(bindingKey, moduleId, callbacks) {
23
68
  this.bindingKey = bindingKey;
24
69
  this.moduleId = moduleId;
25
70
  this.callbacks = callbacks ?? {};
26
71
  }
27
- /** Binds the token to a concrete implementation class; the container constructs it on demand. */
72
+ /**
73
+ * Binds the token to a concrete implementation class; the container constructs it on demand.
74
+ */
28
75
  to(implementationClass) {
29
76
  this.registerWithStrategy({
30
77
  type: "class",
@@ -32,7 +79,9 @@ var BindingBuilder = class {
32
79
  });
33
80
  return this;
34
81
  }
35
- /** Binds the class key to itself — only valid when the key is a constructor. */
82
+ /**
83
+ * Binds the class key to itself — only valid when the key is a constructor.
84
+ */
36
85
  toSelf() {
37
86
  if (typeof this.bindingKey !== "function") throw new InternalError("toSelf() requires the binding key to be a constructor; use bind(SomeClass) or call to(Class) instead.");
38
87
  this.registerWithStrategy({
@@ -41,7 +90,9 @@ var BindingBuilder = class {
41
90
  });
42
91
  return this;
43
92
  }
44
- /** Binds the token to a pre-existing value; always resolved as singleton, no construction. */
93
+ /**
94
+ * Binds the token to a pre-existing value; always resolved as singleton, no construction.
95
+ */
45
96
  toConstantValue(value) {
46
97
  this.scope = "singleton";
47
98
  this.registerWithStrategy({
@@ -50,7 +101,9 @@ var BindingBuilder = class {
50
101
  });
51
102
  return this;
52
103
  }
53
- /** Binds to a synchronous factory; `ctx` provides nested resolution within the same path. */
104
+ /**
105
+ * Binds to a synchronous factory; `ctx` provides nested resolution within the same path.
106
+ */
54
107
  toDynamic(factory) {
55
108
  this.registerWithStrategy({
56
109
  type: "dynamic",
@@ -58,7 +111,9 @@ var BindingBuilder = class {
58
111
  });
59
112
  return this;
60
113
  }
61
- /** Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
114
+ /**
115
+ * Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`.
116
+ */
62
117
  toDynamicAsync(factory) {
63
118
  this.registerWithStrategy({
64
119
  type: "async-dynamic",
@@ -78,7 +133,9 @@ var BindingBuilder = class {
78
133
  });
79
134
  return this;
80
135
  }
81
- /** Redirects resolution to `targetToken`; the container resolves whatever is bound there. */
136
+ /**
137
+ * Redirects resolution to `targetToken`; the container resolves whatever is bound there.
138
+ */
82
139
  toAlias(targetToken) {
83
140
  this.registerWithStrategy({
84
141
  type: "alias",
@@ -86,7 +143,9 @@ var BindingBuilder = class {
86
143
  });
87
144
  return this;
88
145
  }
89
- /** One instance per container; supports `onDeactivation`. */
146
+ /**
147
+ * One instance per container; supports `onDeactivation`.
148
+ */
90
149
  singleton() {
91
150
  this.assertScopeMutable();
92
151
  this.scope = "singleton";
@@ -94,7 +153,9 @@ var BindingBuilder = class {
94
153
  this.refreshRegisteredBinding();
95
154
  return this;
96
155
  }
97
- /** New instance on every resolution (default scope). */
156
+ /**
157
+ * New instance on every resolution (default scope).
158
+ */
98
159
  transient() {
99
160
  this.assertScopeMutable();
100
161
  this.scope = "transient";
@@ -102,7 +163,9 @@ var BindingBuilder = class {
102
163
  this.refreshRegisteredBinding();
103
164
  return this;
104
165
  }
105
- /** One instance per child container scope. */
166
+ /**
167
+ * One instance per child container scope.
168
+ */
106
169
  scoped() {
107
170
  this.assertScopeMutable();
108
171
  this.scope = "scoped";
@@ -110,31 +173,41 @@ var BindingBuilder = class {
110
173
  this.refreshRegisteredBinding();
111
174
  return this;
112
175
  }
113
- /** Called with the resolved instance after construction; the return value replaces the instance. */
176
+ /**
177
+ * Called with the resolved instance after construction; the return value replaces the instance.
178
+ */
114
179
  onActivation(handler) {
115
180
  this.onActivationHandler = handler;
116
181
  this.refreshRegisteredBinding();
117
182
  return this;
118
183
  }
119
- /** @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases. */
184
+ /**
185
+ * @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases.
186
+ */
120
187
  onDeactivation(handler) {
121
188
  this.onDeactivationHandler = handler;
122
189
  this.refreshRegisteredBinding();
123
190
  return this;
124
191
  }
125
- /** This binding only resolves when the caller passes `{ name }` as the resolve hint. */
192
+ /**
193
+ * This binding only resolves when the caller passes `{ name }` as the resolve hint.
194
+ */
126
195
  whenNamed(name) {
127
196
  this.bindingName = name;
128
197
  this.refreshRegisteredBinding();
129
198
  return this;
130
199
  }
131
- /** This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint. */
200
+ /**
201
+ * This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint.
202
+ */
132
203
  whenTagged(tag, tagValue) {
133
204
  this.tags.set(tag, tagValue);
134
205
  this.refreshRegisteredBinding();
135
206
  return this;
136
207
  }
137
- /** Adds a custom predicate; all predicates must pass for the binding to be selected. */
208
+ /**
209
+ * Adds a custom predicate; all predicates must pass for the binding to be selected.
210
+ */
138
211
  when(constraint) {
139
212
  this.constraintPredicates.push(constraint);
140
213
  this.refreshRegisteredBinding();
@@ -152,6 +225,10 @@ var BindingBuilder = class {
152
225
  this.explicitId = this.explicitId ?? createBindingIdentifier();
153
226
  return this.explicitId;
154
227
  }
228
+ /**
229
+ * Records the chosen strategy and emits the first {@link Binding} snapshot.
230
+ * Throws {@link InternalError} if a strategy was already selected (double `to*()` call).
231
+ */
155
232
  registerWithStrategy(next) {
156
233
  if (this.strategy.type !== "unset") throw new InternalError("A binding strategy was already selected; only one to*(...) chain is allowed per builder.");
157
234
  this.strategy = next;
@@ -160,15 +237,27 @@ var BindingBuilder = class {
160
237
  this.currentBinding = binding;
161
238
  this.callbacks.register?.(binding);
162
239
  }
240
+ /**
241
+ * Re-creates the {@link Binding} snapshot from the current builder state and pushes
242
+ * the update to the registry. No-op if no strategy has been set yet.
243
+ */
163
244
  refreshRegisteredBinding() {
164
245
  if (this.currentBinding === void 0 || this.strategy.type === "unset") return;
165
246
  const next = this.createBinding(this.currentBinding.id, this.strategy);
166
247
  this.currentBinding = next;
167
248
  this.callbacks.update?.(next);
168
249
  }
250
+ /**
251
+ * Guards against calling `.singleton()` / `.transient()` / `.scoped()` on a constant binding,
252
+ * which is locked to `"singleton"` scope by invariant.
253
+ */
169
254
  assertScopeMutable() {
170
255
  if (this.strategy.type === "constant") throw new InternalError("Constant bindings are always singleton and do not support scope changes.");
171
256
  }
257
+ /**
258
+ * Produces an immutable {@link Binding} snapshot from the current builder fields.
259
+ * Called by both {@link registerWithStrategy} and {@link refreshRegisteredBinding}.
260
+ */
172
261
  createBinding(id, strategy) {
173
262
  const constraint = this.constraintPredicates.length === 0 ? void 0 : (ctx) => this.constraintPredicates.every((predicate) => predicate(ctx));
174
263
  const lifecycle = {
@@ -3,15 +3,30 @@ import { ConstraintContext, Constructor } from "./binding.mjs";
3
3
 
4
4
  //#region src/constraints.d.ts
5
5
  /**
6
- * Matches when the direct parent materialization was registered for `registryKey`.
6
+ * Constraint predicate factory: matches when the direct parent on the materialization stack
7
+ * was registered under `registryKey`. Pass the result to {@link BindingBuilder.when}.
8
+ *
9
+ * @param registryKey - Token or constructor that the parent binding must be registered against.
10
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
7
11
  */
8
12
  declare function whenParentIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
9
13
  /**
10
- * Matches when any ancestor on the materialization stack was registered for `registryKey`.
14
+ * Constraint predicate factory: matches when *any* ancestor on the materialization stack
15
+ * (not just the immediate parent) was registered under `registryKey`.
16
+ * Pass the result to {@link BindingBuilder.when}.
17
+ *
18
+ * @param registryKey - Token or constructor to search for across the full construction chain.
19
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
11
20
  */
12
21
  declare function whenAnyAncestorIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
13
22
  /**
14
- * Matches when the immediate parent binding carries `tag` with `tagValue` (same metadata as {@link BindingBuilder.whenTagged} on the parent).
23
+ * Constraint predicate factory: matches when the immediate parent binding carries a tag
24
+ * whose key is `tag` and whose value is reference-equal to `tagValue` (`Object.is`).
25
+ * Pass the result to {@link BindingBuilder.when}.
26
+ *
27
+ * @param tag - Tag key to check on the parent binding.
28
+ * @param tagValue - Expected value; compared via `Object.is`.
29
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
15
30
  */
16
31
  declare function whenTargetTagged(tag: string, tagValue: unknown): (ctx: ConstraintContext) => boolean;
17
32
  //#endregion