@codefast/di 0.3.13 → 0.3.14-canary.1

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 (53) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +270 -234
  3. package/dist/binding-select.d.mts +17 -6
  4. package/dist/binding-select.mjs +17 -6
  5. package/dist/binding.d.mts +167 -34
  6. package/dist/binding.mjs +111 -14
  7. package/dist/constraints.d.mts +18 -3
  8. package/dist/constraints.mjs +18 -3
  9. package/dist/container.d.mts +85 -35
  10. package/dist/container.mjs +140 -6
  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 +36 -13
  18. package/dist/dependency-graph.mjs +42 -8
  19. package/dist/errors.d.mts +132 -21
  20. package/dist/errors.mjs +126 -18
  21. package/dist/graph-adapters/cytoscape.d.mts +10 -0
  22. package/dist/graph-adapters/cytoscape.mjs +40 -0
  23. package/dist/graph-adapters/dot.d.mts +9 -0
  24. package/dist/graph-adapters/dot.mjs +97 -0
  25. package/dist/graph-adapters/reactflow.d.mts +10 -0
  26. package/dist/graph-adapters/reactflow.mjs +80 -0
  27. package/dist/graph-adapters/types.d.mts +91 -0
  28. package/dist/graph-adapters/types.mjs +1 -0
  29. package/dist/index.d.mts +2 -3
  30. package/dist/index.mjs +2 -2
  31. package/dist/inspector.d.mts +42 -40
  32. package/dist/inspector.mjs +18 -169
  33. package/dist/lifecycle.d.mts +28 -6
  34. package/dist/lifecycle.mjs +29 -10
  35. package/dist/metadata/metadata-keys.d.mts +17 -6
  36. package/dist/metadata/metadata-keys.mjs +17 -6
  37. package/dist/metadata/metadata-types.d.mts +42 -18
  38. package/dist/metadata/param-registry.mjs +6 -0
  39. package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
  40. package/dist/metadata/symbol-metadata-reader.mjs +23 -4
  41. package/dist/module.d.mts +46 -2
  42. package/dist/module.mjs +19 -0
  43. package/dist/registry.d.mts +39 -8
  44. package/dist/registry.mjs +39 -8
  45. package/dist/resolver.d.mts +107 -12
  46. package/dist/resolver.mjs +134 -37
  47. package/dist/scope-validation.d.mts +3 -2
  48. package/dist/scope-validation.mjs +3 -2
  49. package/dist/scope.d.mts +38 -6
  50. package/dist/scope.mjs +42 -13
  51. package/dist/token.d.mts +9 -2
  52. package/dist/token.mjs +7 -1
  53. package/package.json +18 -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,25 +17,37 @@ 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.
24
26
  */
25
27
  type ResolveHint = {
26
- readonly name?: string;
28
+ /** Matches bindings configured with `.whenNamed(name)`. */readonly name?: string; /** Matches bindings configured with `.whenTagged(tagKey, value)`. */
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
- readonly registryKey: RegistryKey;
36
- readonly bindingId: BindingIdentifier;
37
- readonly bindingKind: ConstraintBindingKind;
38
- readonly tags: ReadonlyMap<string, unknown>;
47
+ /** Registry key (token/constructor) that selected this binding. */readonly registryKey: RegistryKey; /** Stable identifier of the materialized binding. */
48
+ readonly bindingId: BindingIdentifier; /** Discriminant of the selected binding strategy. */
49
+ readonly bindingKind: ConstraintBindingKind; /** Immutable tag map present on the selected binding. */
50
+ readonly tags: ReadonlyMap<string, unknown>; /** Effective scope of the selected binding. */
39
51
  readonly scope: BindingScope;
40
52
  };
41
53
  /**
@@ -46,10 +58,10 @@ type MaterializationFrame = ConstraintParentFrame;
46
58
  * Context for {@link BindingBuilder.when} predicates: path, ancestor metadata, and the current resolve hint.
47
59
  */
48
60
  type ConstraintContext = {
49
- readonly resolutionPath: readonly string[];
50
- readonly materializationStack: readonly ConstraintParentFrame[];
51
- readonly parent: ConstraintParentFrame | undefined;
52
- readonly ancestors: readonly ConstraintParentFrame[];
61
+ /** Resolution labels from root request to current key. */readonly resolutionPath: readonly string[]; /** Full chain of materialized parent frames (oldest → newest). */
62
+ readonly materializationStack: readonly ConstraintParentFrame[]; /** Immediate parent frame, if the current resolution has one. */
63
+ readonly parent: ConstraintParentFrame | undefined; /** Parent chain excluding the immediate parent frame. */
64
+ readonly ancestors: readonly ConstraintParentFrame[]; /** Name/tag hint used for the current lookup, if provided. */
53
65
  readonly currentResolveHint: ResolveHint | undefined;
54
66
  };
55
67
  /**
@@ -60,11 +72,20 @@ type ConstraintContext = {
60
72
  * context-sensitive bindings such as `whenParentIs` or `whenAnyAncestorIs`.
61
73
  */
62
74
  type ResolutionContext = {
63
- readonly resolve: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value;
64
- 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. */
75
+ /** Resolves one binding synchronously using current path/stack context. */readonly resolve: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value; /** Async variant of {@link ResolutionContext.resolve}. */
76
+ readonly resolveAsync: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Promise<Value>; /** Returns `undefined` when the requested root key is unbound. */
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,134 @@ 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;
216
+ /**
217
+ * Latest `onActivation` hook provided by `.onActivation(...)`.
218
+ * Applied to emitted binding snapshots until replaced.
219
+ */
144
220
  private onActivationHandler;
221
+ /**
222
+ * Latest `onDeactivation` hook provided by `.onDeactivation(...)`.
223
+ * Emitted only on builder variants that expose deactivation support.
224
+ */
145
225
  private onDeactivationHandler;
226
+ /**
227
+ * Set when this binding was created inside a {@link Module} / {@link AsyncModule} setup callback.
228
+ */
146
229
  private readonly moduleId;
230
+ /**
231
+ * The most recently emitted {@link Binding} snapshot; `undefined` before the first `to*()` call.
232
+ */
147
233
  private currentBinding;
234
+ /**
235
+ * Container-injected hooks that sync builder mutations into the live registry.
236
+ */
148
237
  private readonly callbacks;
149
238
  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. */
239
+ /**
240
+ * Binds the token to a concrete implementation class; the container constructs it on demand.
241
+ */
151
242
  to<C extends Constructor<Value>>(implementationClass: C): TransientBindingBuilder<Value>;
152
- /** Binds the class key to itself — only valid when the key is a constructor. */
243
+ /**
244
+ * Binds the class key to itself — only valid when the key is a constructor.
245
+ */
153
246
  toSelf(): TransientBindingBuilder<Value>;
154
- /** Binds the token to a pre-existing value; always resolved as singleton, no construction. */
247
+ /**
248
+ * Binds the token to a pre-existing value; always resolved as singleton, no construction.
249
+ */
155
250
  toConstantValue<const ConcreteValue extends Value>(value: ConcreteValue): ConstantBindingBuilder<Value>;
156
- /** Binds to a synchronous factory; `ctx` provides nested resolution within the same path. */
251
+ /**
252
+ * Binds to a synchronous factory; `ctx` provides nested resolution within the same path.
253
+ */
157
254
  toDynamic(factory: (ctx: ResolutionContext) => Value): TransientBindingBuilder<Value>;
158
- /** Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
255
+ /**
256
+ * Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`.
257
+ */
159
258
  toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): TransientBindingBuilder<Value>;
160
259
  /**
161
260
  * Binds to a factory whose dependencies are declared explicitly in `deps` and pre-resolved by
162
261
  * the container before the factory is called — no `ResolutionContext` needed inside the factory.
163
262
  */
164
263
  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. */
264
+ /**
265
+ * Redirects resolution to `targetToken`; the container resolves whatever is bound there.
266
+ */
166
267
  toAlias(targetToken: Token<Value>): TransientBindingBuilder<Value>;
167
- /** One instance per container; supports `onDeactivation`. */
268
+ /**
269
+ * One instance per container; supports `onDeactivation`.
270
+ */
168
271
  singleton(): SingletonBindingBuilder<Value>;
169
- /** New instance on every resolution (default scope). */
272
+ /**
273
+ * New instance on every resolution (default scope).
274
+ */
170
275
  transient(): TransientBindingBuilder<Value>;
171
- /** One instance per child container scope. */
276
+ /**
277
+ * One instance per child container scope.
278
+ */
172
279
  scoped(): ScopedBindingBuilder<Value>;
173
- /** Called with the resolved instance after construction; the return value replaces the instance. */
280
+ /**
281
+ * Called with the resolved instance after construction; the return value replaces the instance.
282
+ */
174
283
  onActivation(handler: ActivationHandler<Value>): this;
175
- /** @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases. */
284
+ /**
285
+ * @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases.
286
+ */
176
287
  onDeactivation(handler: DeactivationHandler<Value>): this;
177
- /** This binding only resolves when the caller passes `{ name }` as the resolve hint. */
288
+ /**
289
+ * This binding only resolves when the caller passes `{ name }` as the resolve hint.
290
+ */
178
291
  whenNamed(name: string): this;
179
- /** This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint. */
292
+ /**
293
+ * This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint.
294
+ */
180
295
  whenTagged(tag: string, tagValue: unknown): this;
181
- /** Adds a custom predicate; all predicates must pass for the binding to be selected. */
296
+ /**
297
+ * Adds a custom predicate; all predicates must pass for the binding to be selected.
298
+ */
182
299
  when(constraint: (ctx: ConstraintContext) => boolean): this;
183
300
  /**
184
301
  * Returns the binding's stable ID, allocating one if needed.
@@ -187,9 +304,25 @@ declare class BindingBuilder<Value> {
187
304
  */
188
305
  id(): BindingIdentifier;
189
306
  id(identifier: BindingIdentifier): BindingIdentifier;
307
+ /**
308
+ * Records the chosen strategy and emits the first {@link Binding} snapshot.
309
+ * Throws {@link InternalError} if a strategy was already selected (double `to*()` call).
310
+ */
190
311
  private registerWithStrategy;
312
+ /**
313
+ * Re-creates the {@link Binding} snapshot from the current builder state and pushes
314
+ * the update to the registry. No-op if no strategy has been set yet.
315
+ */
191
316
  private refreshRegisteredBinding;
317
+ /**
318
+ * Guards against calling `.singleton()` / `.transient()` / `.scoped()` on a constant binding,
319
+ * which is locked to `"singleton"` scope by invariant.
320
+ */
192
321
  private assertScopeMutable;
322
+ /**
323
+ * Produces an immutable {@link Binding} snapshot from the current builder fields.
324
+ * Called by both {@link registerWithStrategy} and {@link refreshRegisteredBinding}.
325
+ */
193
326
  private createBinding;
194
327
  }
195
328
  /**
package/dist/binding.mjs CHANGED
@@ -6,25 +6,80 @@ 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 = [];
53
+ /**
54
+ * Latest `onActivation` hook provided by `.onActivation(...)`.
55
+ * Applied to emitted binding snapshots until replaced.
56
+ */
17
57
  onActivationHandler;
58
+ /**
59
+ * Latest `onDeactivation` hook provided by `.onDeactivation(...)`.
60
+ * Emitted only on builder variants that expose deactivation support.
61
+ */
18
62
  onDeactivationHandler;
63
+ /**
64
+ * Set when this binding was created inside a {@link Module} / {@link AsyncModule} setup callback.
65
+ */
19
66
  moduleId;
67
+ /**
68
+ * The most recently emitted {@link Binding} snapshot; `undefined` before the first `to*()` call.
69
+ */
20
70
  currentBinding;
71
+ /**
72
+ * Container-injected hooks that sync builder mutations into the live registry.
73
+ */
21
74
  callbacks;
22
75
  constructor(bindingKey, moduleId, callbacks) {
23
76
  this.bindingKey = bindingKey;
24
77
  this.moduleId = moduleId;
25
78
  this.callbacks = callbacks ?? {};
26
79
  }
27
- /** Binds the token to a concrete implementation class; the container constructs it on demand. */
80
+ /**
81
+ * Binds the token to a concrete implementation class; the container constructs it on demand.
82
+ */
28
83
  to(implementationClass) {
29
84
  this.registerWithStrategy({
30
85
  type: "class",
@@ -32,7 +87,9 @@ var BindingBuilder = class {
32
87
  });
33
88
  return this;
34
89
  }
35
- /** Binds the class key to itself — only valid when the key is a constructor. */
90
+ /**
91
+ * Binds the class key to itself — only valid when the key is a constructor.
92
+ */
36
93
  toSelf() {
37
94
  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
95
  this.registerWithStrategy({
@@ -41,7 +98,9 @@ var BindingBuilder = class {
41
98
  });
42
99
  return this;
43
100
  }
44
- /** Binds the token to a pre-existing value; always resolved as singleton, no construction. */
101
+ /**
102
+ * Binds the token to a pre-existing value; always resolved as singleton, no construction.
103
+ */
45
104
  toConstantValue(value) {
46
105
  this.scope = "singleton";
47
106
  this.registerWithStrategy({
@@ -50,7 +109,9 @@ var BindingBuilder = class {
50
109
  });
51
110
  return this;
52
111
  }
53
- /** Binds to a synchronous factory; `ctx` provides nested resolution within the same path. */
112
+ /**
113
+ * Binds to a synchronous factory; `ctx` provides nested resolution within the same path.
114
+ */
54
115
  toDynamic(factory) {
55
116
  this.registerWithStrategy({
56
117
  type: "dynamic",
@@ -58,7 +119,9 @@ var BindingBuilder = class {
58
119
  });
59
120
  return this;
60
121
  }
61
- /** Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
122
+ /**
123
+ * Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`.
124
+ */
62
125
  toDynamicAsync(factory) {
63
126
  this.registerWithStrategy({
64
127
  type: "async-dynamic",
@@ -78,7 +141,9 @@ var BindingBuilder = class {
78
141
  });
79
142
  return this;
80
143
  }
81
- /** Redirects resolution to `targetToken`; the container resolves whatever is bound there. */
144
+ /**
145
+ * Redirects resolution to `targetToken`; the container resolves whatever is bound there.
146
+ */
82
147
  toAlias(targetToken) {
83
148
  this.registerWithStrategy({
84
149
  type: "alias",
@@ -86,7 +151,9 @@ var BindingBuilder = class {
86
151
  });
87
152
  return this;
88
153
  }
89
- /** One instance per container; supports `onDeactivation`. */
154
+ /**
155
+ * One instance per container; supports `onDeactivation`.
156
+ */
90
157
  singleton() {
91
158
  this.assertScopeMutable();
92
159
  this.scope = "singleton";
@@ -94,7 +161,9 @@ var BindingBuilder = class {
94
161
  this.refreshRegisteredBinding();
95
162
  return this;
96
163
  }
97
- /** New instance on every resolution (default scope). */
164
+ /**
165
+ * New instance on every resolution (default scope).
166
+ */
98
167
  transient() {
99
168
  this.assertScopeMutable();
100
169
  this.scope = "transient";
@@ -102,7 +171,9 @@ var BindingBuilder = class {
102
171
  this.refreshRegisteredBinding();
103
172
  return this;
104
173
  }
105
- /** One instance per child container scope. */
174
+ /**
175
+ * One instance per child container scope.
176
+ */
106
177
  scoped() {
107
178
  this.assertScopeMutable();
108
179
  this.scope = "scoped";
@@ -110,31 +181,41 @@ var BindingBuilder = class {
110
181
  this.refreshRegisteredBinding();
111
182
  return this;
112
183
  }
113
- /** Called with the resolved instance after construction; the return value replaces the instance. */
184
+ /**
185
+ * Called with the resolved instance after construction; the return value replaces the instance.
186
+ */
114
187
  onActivation(handler) {
115
188
  this.onActivationHandler = handler;
116
189
  this.refreshRegisteredBinding();
117
190
  return this;
118
191
  }
119
- /** @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases. */
192
+ /**
193
+ * @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases.
194
+ */
120
195
  onDeactivation(handler) {
121
196
  this.onDeactivationHandler = handler;
122
197
  this.refreshRegisteredBinding();
123
198
  return this;
124
199
  }
125
- /** This binding only resolves when the caller passes `{ name }` as the resolve hint. */
200
+ /**
201
+ * This binding only resolves when the caller passes `{ name }` as the resolve hint.
202
+ */
126
203
  whenNamed(name) {
127
204
  this.bindingName = name;
128
205
  this.refreshRegisteredBinding();
129
206
  return this;
130
207
  }
131
- /** This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint. */
208
+ /**
209
+ * This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint.
210
+ */
132
211
  whenTagged(tag, tagValue) {
133
212
  this.tags.set(tag, tagValue);
134
213
  this.refreshRegisteredBinding();
135
214
  return this;
136
215
  }
137
- /** Adds a custom predicate; all predicates must pass for the binding to be selected. */
216
+ /**
217
+ * Adds a custom predicate; all predicates must pass for the binding to be selected.
218
+ */
138
219
  when(constraint) {
139
220
  this.constraintPredicates.push(constraint);
140
221
  this.refreshRegisteredBinding();
@@ -152,6 +233,10 @@ var BindingBuilder = class {
152
233
  this.explicitId = this.explicitId ?? createBindingIdentifier();
153
234
  return this.explicitId;
154
235
  }
236
+ /**
237
+ * Records the chosen strategy and emits the first {@link Binding} snapshot.
238
+ * Throws {@link InternalError} if a strategy was already selected (double `to*()` call).
239
+ */
155
240
  registerWithStrategy(next) {
156
241
  if (this.strategy.type !== "unset") throw new InternalError("A binding strategy was already selected; only one to*(...) chain is allowed per builder.");
157
242
  this.strategy = next;
@@ -160,15 +245,27 @@ var BindingBuilder = class {
160
245
  this.currentBinding = binding;
161
246
  this.callbacks.register?.(binding);
162
247
  }
248
+ /**
249
+ * Re-creates the {@link Binding} snapshot from the current builder state and pushes
250
+ * the update to the registry. No-op if no strategy has been set yet.
251
+ */
163
252
  refreshRegisteredBinding() {
164
253
  if (this.currentBinding === void 0 || this.strategy.type === "unset") return;
165
254
  const next = this.createBinding(this.currentBinding.id, this.strategy);
166
255
  this.currentBinding = next;
167
256
  this.callbacks.update?.(next);
168
257
  }
258
+ /**
259
+ * Guards against calling `.singleton()` / `.transient()` / `.scoped()` on a constant binding,
260
+ * which is locked to `"singleton"` scope by invariant.
261
+ */
169
262
  assertScopeMutable() {
170
263
  if (this.strategy.type === "constant") throw new InternalError("Constant bindings are always singleton and do not support scope changes.");
171
264
  }
265
+ /**
266
+ * Produces an immutable {@link Binding} snapshot from the current builder fields.
267
+ * Called by both {@link registerWithStrategy} and {@link refreshRegisteredBinding}.
268
+ */
172
269
  createBinding(id, strategy) {
173
270
  const constraint = this.constraintPredicates.length === 0 ? void 0 : (ctx) => this.constraintPredicates.every((predicate) => predicate(ctx));
174
271
  const lifecycle = {