@fnioc/di 2.0.0 → 4.0.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.
package/dist/index.d.ts CHANGED
@@ -1,26 +1,10 @@
1
+ type Func$1<in Args extends readonly any[] = any[], out Return = any> = (...args: Args) => Return;
2
+
1
3
  interface Ctor$1<in Args extends readonly any[] = any[], out Instance = any> {
2
4
  new(...args: Args): Instance;
3
5
  prototype: Instance;
4
6
  }
5
7
 
6
- /**
7
- * The hole sentinel: marks a constructor parameter as caller-supplied rather
8
- * than container-resolved.
9
- *
10
- * Used in signatures to communicate "this position is a hole — the factory
11
- * caller supplies this argument, not the DI container." Detected by identity
12
- * (`slot === hole`), and `null` is the sentinel value: it is exactly what the
13
- * transformer emits for a hole slot, so the authoring surface and the lowered
14
- * output agree on one representation.
15
- *
16
- * @example
17
- * ```ts
18
- * @signature("pkg:ILogger", hole, "pkg:IDb")
19
- * class SqlRepo { constructor(log: ILogger, tableName: string, db: IDb) { ... } }
20
- * ```
21
- */
22
- declare const hole: null;
23
-
24
8
  /**
25
9
  * Anything dependency metadata can be attached to: a class constructor (its
26
10
  * deps are the ctor parameters) or a factory function (its deps are the call
@@ -28,7 +12,7 @@ declare const hole: null;
28
12
  * `never[]` rest keeps any concrete function assignable here regardless of its
29
13
  * own parameter list.
30
14
  */
31
- type DepTarget = Ctor$1 | ((...args: never[]) => unknown);
15
+ type DepTarget = Ctor$1 | Func$1<never[], unknown>;
32
16
  /**
33
17
  * A stable string identifying an interface — the DI key.
34
18
  *
@@ -38,12 +22,16 @@ type DepTarget = Ctor$1 | ((...args: never[]) => unknown);
38
22
  type Token = string;
39
23
  /**
40
24
  * Marks a constructor parameter to be injected as a *factory* producing the
41
- * registered token `factory`, rather than a resolved instance. The factory's
42
- * own call signature is the target ctor's unregistered params, partitioned at
43
- * resolve time.
25
+ * registered type token, rather than a resolved instance. The factory's own
26
+ * call signature is determined by the caller-supplied `params` list.
27
+ *
28
+ * `type` is the token of the produced type T (replaces the former `.factory` field).
29
+ * `params` is the complete, authored-order list of caller-supplied parameter tokens;
30
+ * when present it pins the factory shape so it no longer drifts with registration state.
44
31
  */
45
32
  interface FactoryRef {
46
- readonly factory: Token;
33
+ readonly type: Token;
34
+ readonly params?: readonly Token[];
47
35
  }
48
36
  /**
49
37
  * Marks a parameter to be injected with the live resolution scope itself,
@@ -56,20 +44,28 @@ interface FactoryRef {
56
44
  interface ScopeRef {
57
45
  readonly scope: true;
58
46
  }
47
+ /**
48
+ * A set of alternative dependency slots tried in declaration order (first
49
+ * resolvable member wins). If no member is resolvable, resolution throws.
50
+ * Each member is itself a `DepSlot` — nesting is allowed.
51
+ */
52
+ interface Union {
53
+ readonly union: readonly DepSlot[];
54
+ }
59
55
  /**
60
56
  * One positional slot in a constructor / factory signature:
61
57
  * - a `Token` string — a container-resolved dependency,
62
- * - the `hole` sentinel — a caller-supplied parameter,
63
- * - a `FactoryRef` a factory-injected parameter (see `FactoryRef`), or
64
- * - a `ScopeRef` the live resolution scope (see `ScopeRef`).
58
+ * - a `FactoryRef` — a factory-injected parameter (see `FactoryRef`),
59
+ * - a `ScopeRef` the live resolution scope (see `ScopeRef`), or
60
+ * - a `Union` member-level alternatives tried in order.
65
61
  */
66
- type DepSlot = Token | typeof hole | FactoryRef | ScopeRef;
62
+ type DepSlot = Token | FactoryRef | ScopeRef | Union;
67
63
  /**
68
64
  * Per-constructor dependency metadata stored in the global WeakMap.
69
65
  *
70
66
  * `signatures` is an array of arrays: each element is one constructor signature
71
- * (for overload support). `signatures[i][j]` is the `DepSlot` — a token, the
72
- * `hole` sentinel, or a `FactoryRef` — for constructor parameter `j` of
67
+ * (for overload support). `signatures[i][j]` is the `DepSlot` — a token, a
68
+ * `FactoryRef`, a `ScopeRef`, or a `Union` — for constructor parameter `j` of
73
69
  * overload `i`.
74
70
  */
75
71
  interface DepRecord {
@@ -90,7 +86,7 @@ interface DepRecord {
90
86
  * prototype-chain walk — subclasses do NOT inherit the
91
87
  * parent's record).
92
88
  * @param signatures An array of signatures; each signature is a positional array
93
- * of DepSlot (Token | hole | FactoryRef | ScopeRef) parallel to
89
+ * of DepSlot (Token | FactoryRef | ScopeRef | Union) parallel to
94
90
  * the target's parameter list.
95
91
  */
96
92
  declare function defineDeps(target: DepTarget, signatures: readonly (readonly DepSlot[])[]): void;
@@ -115,7 +111,7 @@ declare function defineDeps(target: DepTarget, signatures: readonly (readonly De
115
111
  * }
116
112
  * ```
117
113
  */
118
- declare function signature(...tokens: readonly DepSlot[]): (value: Ctor$1, _context: ClassDecoratorContext) => void;
114
+ declare function signature(...tokens: readonly DepSlot[]): Func$1<[Ctor$1, ClassDecoratorContext], void>;
119
115
 
120
116
  /**
121
117
  * The builder returned by `forCtor`. Chainable — each `.signature()` call
@@ -124,8 +120,8 @@ declare function signature(...tokens: readonly DepSlot[]): (value: Ctor$1, _cont
124
120
  interface ForCtorBuilder {
125
121
  /**
126
122
  * Appends one constructor signature (a positional array of DepSlot —
127
- * Token | hole | FactoryRef) to the ctor's dependency metadata. Returns
128
- * `this` for chaining.
123
+ * Token | FactoryRef | ScopeRef | Union) to the ctor's dependency metadata.
124
+ * Returns `this` for chaining.
129
125
  *
130
126
  * Each `.signature(...)` call is one overload. Chaining two calls is
131
127
  * equivalent to stacking two `@signature` decorators.
@@ -145,6 +141,46 @@ interface ForCtorBuilder {
145
141
  */
146
142
  declare function forCtor(ctor: Ctor$1): ForCtorBuilder;
147
143
 
144
+ /**
145
+ * @fnioc/core — the immutable substrate and dependency-metadata format.
146
+ *
147
+ * Exports:
148
+ * - `Token` — string alias for a DI key
149
+ * - `FactoryRef` — marks a signature slot as a factory-injected parameter
150
+ * - `ScopeRef` — marks a signature slot as the live resolution scope
151
+ * - `Union` — member-level alternative slots tried in declaration order
152
+ * - `DepSlot` — one positional slot: Token | FactoryRef | ScopeRef | Union
153
+ * - `Inject` — compile-time brand that pins a token for one arg
154
+ * - `DepTarget` — a ctor or factory function metadata attaches to
155
+ * - `DepRecord` — shape of per-constructor metadata in the WeakMap
156
+ * - `defineDeps` — the single write path into the global WeakMap
157
+ * - `getDeps` — the read path (consumed by @fnioc/di)
158
+ * - `union` — runtime helper: constructs a Union slot from member slots
159
+ * - `isFactoryRef` — type guard for FactoryRef slots
160
+ * - `isScopeRef` — type guard for ScopeRef slots
161
+ * - `isUnionSlot` — type guard for Union slots
162
+ * - `signature` — TC39 class decorator factory
163
+ * - `ForCtorBuilder` — return type of `forCtor`
164
+ * - `forCtor` — fluent free-function for third-party classes
165
+ */
166
+
167
+ /**
168
+ * Constructs a `Union` slot — a set of alternative dependency slots tried in
169
+ * declaration order. The first resolvable member wins; if none is resolvable,
170
+ * resolution throws.
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * forCtor(Handler).signature(
175
+ * union("pkg:IRedis", "pkg:IMemoryCache"),
176
+ * "pkg:ILogger",
177
+ * );
178
+ * ```
179
+ */
180
+ declare function union(...slots: DepSlot[]): Union;
181
+
182
+ type Func<in Args extends readonly any[] = any[], out Return = any> = (...args: Args) => Return;
183
+
148
184
  interface Ctor<in Args extends readonly any[] = any[], out Instance = any> {
149
185
  new(...args: Args): Instance;
150
186
  prototype: Instance;
@@ -154,15 +190,15 @@ interface Ctor<in Args extends readonly any[] = any[], out Instance = any> {
154
190
  * A registration-level factory function. Its parameters are filled by the
155
191
  * engine at resolve time, the same way a class constructor's are: a factory
156
192
  * WITH a `defineDeps` record has each parameter resolved by its slot (token →
157
- * resolved instance, `ScopeRef` → the live scope, hole → caller-supplied); a
193
+ * resolved instance, `ScopeRef` → the live provider, hole → caller-supplied); a
158
194
  * factory WITHOUT a record is the plugin-less escape hatch and is called with
159
- * the live scope as its single argument (`(scope) => …`).
195
+ * the live provider as its single argument (`(sp) => …`).
160
196
  *
161
197
  * May be async — it can return a `Promise<T>`. The container never awaits; the
162
198
  * Promise flows through the sync resolution channel as a value (§"Async as
163
199
  * values"). A consumer that depends on it declares `Promise<T>` and awaits.
164
200
  */
165
- type Factory = (...args: any[]) => unknown;
201
+ type Factory = Func<any[], unknown>;
166
202
  /** A class registration: a token bound to a concrete constructor. */
167
203
  interface ClassRegistration {
168
204
  readonly kind: "class";
@@ -192,230 +228,240 @@ interface ValueRegistration {
192
228
  /** Any registration the engine can resolve. */
193
229
  type Registration = ClassRegistration | FactoryRegistration | ValueRegistration;
194
230
  /**
195
- * The resolution surface a factory receives either as an injected `ScopeRef`
196
- * parameter, or (plugin-less escape hatch) as the sole argument of a
197
- * record-less factory. A structural subset of `Scope`: resolve further tokens
198
- * and open child scopes.
231
+ * The named lifetime tag for a registration. `"singleton"` and `"transient"`
232
+ * are the built-in names; `U` is the user-declared scope-name union (defaults
233
+ * to `"scoped"`). Transient is represented by the ABSENCE of a lifetime tag
234
+ * (`undefined` on the registration), not by the string `"transient"`.
235
+ */
236
+ type Lifetime<U extends string = "scoped"> = "singleton" | "transient" | U;
237
+ /**
238
+ * The minimal resolution surface — resolve tokens and get factories. Injected
239
+ * into factory parameters typed `Resolver` (and for the plugin-less escape
240
+ * hatch as the sole argument of a record-less factory).
199
241
  *
200
- * `resolve` has three shapes:
201
- * - `resolve<T>()` tokenless; the transformer lowers it to a token.
242
+ * `resolve` has two published shapes (the tokenless authoring form
243
+ * `resolve<T>()` is a PURE TYPING contributed by the `@fnioc/transformer`
244
+ * augmentation, not part of di's published surface):
202
245
  * - `resolve<T>(token)` — explicit token, typed return.
203
246
  * - `resolve(token)` — explicit token, `unknown` return (dynamic).
204
247
  */
205
- interface ResolveScope {
206
- resolve<T>(): T;
248
+ interface Resolver {
207
249
  resolve<T>(token: Token): T;
208
250
  resolve(token: Token): unknown;
209
251
  /**
210
- * Returns a FACTORY for the token rather than an instance the resolve-site
211
- * mirror of a `FactoryRef` ctor param. The authored `resolve<(a: A) => T>()`
212
- * (a function-typed type arg) lowers to this.
252
+ * Returns a FACTORY for `type` rather than an instance. When `params` is
253
+ * absent or empty, returns a strict zero-arg `() => T` — every ctor slot must
254
+ * resolve from the container. When `params` is present, it is the complete
255
+ * authored-order list of caller-supplied parameter tokens; the returned factory
256
+ * has shape `(...params) => T`. The authored `resolve<(a: A) => T>()` lowers
257
+ * to `resolveFactory("pkg:T", ["pkg:A"])`.
213
258
  */
214
- resolveFactory(token: Token): unknown;
215
- createScope(name: string): ResolveScope;
259
+ resolveFactory(type: Token, params?: readonly Token[]): unknown;
260
+ }
261
+ /**
262
+ * The scope-creation surface. Injected into factory parameters typed
263
+ * `ScopeFactory`, and implemented by `ServiceProvider`.
264
+ */
265
+ interface ScopeFactory<S extends string = string> {
266
+ createScope(...args: "scoped" extends S ? [name?: S] : [name: S]): ServiceProvider<S>;
267
+ }
268
+ /**
269
+ * @deprecated Use `Resolver` instead. Kept for backwards compatibility.
270
+ *
271
+ * The resolution surface a factory receives — either as an injected `ScopeRef`
272
+ * parameter, or (plugin-less escape hatch) as the sole argument of a
273
+ * record-less factory.
274
+ */
275
+ interface ResolveScope extends Resolver {
276
+ createScope(name: string): ServiceProvider;
216
277
  }
217
278
 
218
279
  /**
219
- * A node in the scope chain. Created from a `DiBuilder` (the root) or from a
220
- * parent scope (`.createScope`). Holds the instances it owns and any local
221
- * override registrations.
280
+ * A scope frame — a node in the parent-linked chain. Holds this scope's name,
281
+ * its instance cache, an ordered list for disposal, and an optional parent.
282
+ * It does NOT hold registrations (those live sealed on the ServiceProvider).
222
283
  *
223
- * The generic `Scopes` is the user's scope-name union, threaded so
224
- * `.createScope` only accepts declared names.
284
+ * The special "no frame" on the root ServiceProvider means transient-only /
285
+ * unscoped resolution attempting to resolve a scoped registration from the
286
+ * root will throw MissingScopeError.
225
287
  */
226
- declare class Scope<Scopes extends string = string> implements ResolveScope {
227
- /** This scope's name. The root scope's name is its lifetime. */
228
- readonly name: Scopes;
229
- /** The parent scope, or `undefined` for the root. */
230
- private readonly parent;
231
- /** The builder's base registration map (shared, walked last). */
232
- private readonly baseRegistrations;
233
- /**
234
- * Local override registrations held at this scope (shadow ancestors). Each
235
- * token maps to a LIST in registration order; the most-recent (last) entry
236
- * wins, mirroring the builder's service collection.
237
- */
238
- private readonly localRegistrations;
288
+ declare class Scope {
289
+ /** This scope's name must match the registration's lifetime tag. */
290
+ readonly name: string;
291
+ /** The parent scope, or omitted for the topmost frame. */
292
+ readonly parent?: Scope | undefined;
239
293
  /** Instances this scope owns and caches, keyed by token. */
240
- private readonly instances;
294
+ readonly cache: Map<Token, unknown>;
241
295
  /** Owned instances in construction order — disposed in reverse. */
242
- private readonly ownedOrder;
243
- private disposed;
296
+ readonly owned: unknown[];
244
297
  constructor(
245
- /** This scope's name. The root scope's name is its lifetime. */
246
- name: Scopes,
247
- /** The parent scope, or `undefined` for the root. */
248
- parent: Scope<Scopes> | undefined,
249
- /** The builder's base registration map (shared, walked last). */
250
- baseRegistrations: ReadonlyMap<Token, Registration[]>);
251
- /** Appends a registration to a token's list in the given map. */
252
- private static appendTo;
253
- /**
254
- * Creates a parent-linked child scope with the given (declared) name. Scopes
255
- * MUST nest this parent chain IS the lifetime hierarchy. The root is minted
256
- * by `DiBuilder.build()`; every other scope descends from one via this call.
257
- */
258
- createScope(childName: Scopes): Scope<Scopes>;
298
+ /** This scope's name must match the registration's lifetime tag. */
299
+ name: string,
300
+ /** The parent scope, or omitted for the topmost frame. */
301
+ parent?: Scope | undefined);
302
+ }
303
+ /**
304
+ * The public container surface. Implements `Resolver` (resolve + resolveFactory)
305
+ * and `ScopeFactory` (createScope), plus native `Disposable`/`AsyncDisposable`.
306
+ *
307
+ * `S` is the user-declared scope-name union. The root (`DiBuilder.build()`)
308
+ * has an EMPTY scope slot it acts as the unscoped root that owns singletons
309
+ * when the root name is "singleton", reached by the first `createScope("singleton")`.
310
+ * Wait — actually the root SP from build() DOES have a scope frame (named after
311
+ * the builder's rootName), exactly as before: `build()` creates a root SP
312
+ * with `new Scope(rootName)` as its frame.
313
+ */
314
+ declare class ServiceProvider<S extends string = string> implements Resolver, ScopeFactory<S>, Disposable, AsyncDisposable {
315
+ /** The sealed registration map (shared across all providers in the tree). */
316
+ private readonly registrations;
317
+ private disposed;
259
318
  /**
260
- * Appends a scopeless `class`/`factory` LOCAL override and returns the
261
- * `.as(scope?)` continuation (mirrors `DiBuilder.appendScoped`). Local
262
- * overrides shadow any ancestor or base registration for the same token, for
263
- * this scope and its descendants only, most-recent-wins.
319
+ * The scope frame for this provider. `undefined` means this is the "unscoped"
320
+ * root a sentinel that exists only for transient-only trees (no build()
321
+ * call sets this to undefined in normal usage; build() always sets a root name).
264
322
  */
265
- private appendScopedLocal;
323
+ private readonly frame;
324
+ constructor(
325
+ /** The sealed registration map (shared across all providers in the tree). */
326
+ registrations: ReadonlyMap<Token, Registration[]>,
327
+ /** This provider's scope frame, if any. */
328
+ frame?: Scope);
266
329
  /**
267
- * Registers a scope-local CLASS override shadows ancestors for this scope
268
- * and its descendants. Returns the `.as(scope?)` continuation.
330
+ * The name of this provider's scope frame. Throws if the provider has no
331
+ * frame (unscoped root). Kept for backwards-compatibility with tests that
332
+ * inspect `root.name`.
269
333
  */
270
- add(token: Token, ctor: Ctor): AddBuilder<Scopes>;
334
+ get name(): S;
271
335
  /**
272
- * Registers a scope-local FACTORY override. Parameter injection follows the
273
- * same metadata rule as a builder factory (record inject; record-less
274
- * called with the live scope). Returns the `.as(scope?)` continuation.
336
+ * Creates a child `ServiceProvider` whose scope frame is a new `Scope` named
337
+ * `name`, parented to this provider's frame (or a top-level frame if this
338
+ * provider is unscoped).
339
+ *
340
+ * Default name `"scoped"` is accepted only when `"scoped"` ∈ S (the
341
+ * conditional-rest-param type ensures this at the call site).
275
342
  */
276
- addFactory(token: Token, factory: (scope: ResolveScope) => unknown): AddBuilder<Scopes>;
277
- /** Registers a scope-local VALUE override — the instance itself, no lifetime. */
278
- addValue(token: Token, value: unknown): this;
343
+ createScope(...args: "scoped" extends S ? [name?: S] : [name: S]): ServiceProvider<S>;
279
344
  /**
280
- * Resolves a token to an instance, walking the parent chain for both the
281
- * registration and the owning scope. The public entry point starts a fresh
282
- * cycle-detection stack.
345
+ * Resolves a token to an instance, walking the scope chain for the owning
346
+ * frame. The public entry point starts a fresh cycle-detection stack.
283
347
  */
284
- resolve<T>(): T;
285
348
  resolve<T>(token: Token): T;
286
349
  resolve(token: Token): unknown;
287
350
  /**
288
- * Returns a FACTORY for `token` rather than an instance the resolve-site
289
- * mirror of a `FactoryRef` ctor param. The authored `resolve<(a: A) => T>()`
290
- * lowers here. The returned callable exposes the target's UNREGISTERED /
291
- * caller-supplied parameters in order (PRD §7 "Partial / positional
292
- * factories"); a fully-resolvable target yields a zero-arg lazy factory that
293
- * respects the target's registered lifetime.
351
+ * Returns a FACTORY for `type` rather than an instance. When `params` is
352
+ * absent or empty, returns a strict zero-arg `() => T` — every ctor slot must
353
+ * resolve from the container (an unresolvable slot throws). When `params` is
354
+ * present, it is the complete authored-order list of caller-supplied parameter
355
+ * tokens; the returned factory has shape `(...params) => T`. The authored
356
+ * `resolve<(a: A) => T>()` lowers to `resolveFactory("pkg:T", ["pkg:A"])`.
294
357
  */
295
- resolveFactory(token: Token): unknown;
358
+ resolveFactory(type: Token, params?: readonly Token[]): unknown;
296
359
  /**
297
- * Walks UP the chain (this scope's locals ancestors' locals → base map),
298
- * returning the nearest registration for `token`. Child shadows parent, and
299
- * within any one scope's list the most-recent (last) registration wins — so a
300
- * later `.add()`/`.add(...)` override beats an earlier one without deletion.
360
+ * Returns the most-recent registration for `token` from the sealed map.
361
+ * The sealed map is shared across all providers in the tree; local overrides
362
+ * are not supported in the new model (scope-local registration is deleted).
301
363
  */
302
364
  private lookup;
303
365
  /**
304
- * Finds the nearest ancestor scope (inclusive of this one) whose name matches
305
- * `scope`, walking UP the chain. Returns `undefined` when none matches.
366
+ * Finds the nearest ancestor scope frame (inclusive) whose name matches
367
+ * `scopeName`, walking UP the chain. Returns `undefined` when none matches.
306
368
  */
307
- private findOwner;
308
- /** The chain of scope names from this scope up to the root, for diagnostics. */
309
- private chainNames;
369
+ private static findOwner;
370
+ /** The chain of scope names from `vantage` up to the root, for diagnostics. */
371
+ private static chainNames;
310
372
  /**
311
- * The internal resolver. `stack` is the active resolution path (for cycle
312
- * detection); it is shared across the whole `resolve()` call but never across
313
- * separate calls.
373
+ * The internal resolver. `vantage` is the scope frame the walk starts from.
374
+ * `stack` is the active resolution path (for cycle detection); it is shared
375
+ * across the whole `resolve()` call but never across separate calls.
314
376
  */
315
377
  private resolveWith;
316
378
  /**
317
- * Builds an instance for `registration`. `owningScope` is the scope whose
318
- * chain the dependencies are resolved against — THE critical rule. For a
319
- * factory override that is the scope passed to the closure; for a class it is
320
- * the scope its ctor deps resolve relative to.
379
+ * Builds an instance for `registration`. `owningFrame` is the scope frame
380
+ * whose chain the dependencies are resolved against — THE critical rule.
321
381
  */
322
382
  private instantiate;
323
383
  /**
324
- * The resolution view a factory receives `resolve` (overloaded; a tokenless
325
- * authored `resolve<T>()` is lowered to a token before runtime),
326
- * `resolveFactory`, and `createScope`, all continuing the active cycle
327
- * `stack` and resolving relative to THIS (the owning) scope.
384
+ * The resolution view injected for a `ScopeRef` parameter (`Resolver` or
385
+ * `ScopeFactory` typed param). Produces a ServiceProvider-like view that
386
+ * continues the active cycle `stack` and resolves relative to `owningFrame`.
328
387
  */
329
- private makeScopeView;
388
+ private makeProviderView;
330
389
  /**
331
390
  * Invokes a factory registration under the metadata-vs-scope rule:
332
391
  * - factory WITH a `defineDeps` record → resolve each slot (token →
333
- * resolved instance, `ScopeRef` → the live scope, `FactoryRef` → an
334
- * injected callable) and call `factory(...args)`;
392
+ * resolved instance, `ScopeRef` → the live provider view, `FactoryRef` →
393
+ * an injected callable) and call `factory(...args)`;
335
394
  * - factory WITHOUT a record (the plugin-less escape hatch) → call
336
- * `factory(scopeView)` with the live scope as its sole argument.
337
- * Deps resolve relative to `this` (the owning scope) — §5.4.
395
+ * `factory(providerView)` with the live provider view as its sole argument.
396
+ * Deps resolve relative to `owningFrame` (the owning scope) — §5.4.
338
397
  */
339
398
  private invokeFactory;
340
399
  /**
341
- * Constructs a class instance on a DIRECT resolve, resolving its constructor
342
- * dependencies relative to THIS scope (the owning scope). Performs greedy
343
- * signature selection over the ctor's DepRecord, then fills each slot:
400
+ * Constructs a class instance, resolving its constructor dependencies
401
+ * relative to `owningFrame`. Performs greedy signature selection over the
402
+ * ctor's DepRecord, then fills each slot:
344
403
  *
345
- * - a string token → resolved through this scope's chain (selection
404
+ * - a string token → resolved through the owning frame's chain (selection
346
405
  * guarantees every string-token slot here is resolvable);
347
406
  * - a `FactoryRef` → injected as a callable (see `makeFactory`);
348
- * - a `ScopeRef` → the live scope.
349
- *
350
- * A hole never reaches here on a direct resolve: it is an unresolvable token,
351
- * so selection rejects any signature carrying one (falling to a shorter
352
- * optional-overload, or throwing). Holes are filled by the caller only when
353
- * the class is a factory target — see `buildPartitioned`.
407
+ * - a `ScopeRef` → the live provider view.
354
408
  */
355
409
  private construct;
356
410
  /**
357
411
  * Builds the callable injected for a `FactoryRef` parameter.
358
412
  *
359
- * The target ctor's signature is partitioned at CALL time against the live
360
- * registration map: each slot that is a registered token is resolved; each
361
- * slot that is an unregistered token or a `hole` takes the next
362
- * caller-supplied argument, positionally. The injected callable therefore
363
- * exposes only the target's unregistered parameters, in their relative order
364
- * no Ramda-style placeholders, no leaked constructor arity.
413
+ * When `ref.params` is absent or empty, the factory is STRICT: every ctor slot
414
+ * of the target must resolve from the container. An unresolvable slot throws at
415
+ * build time (via `selectSignature`). The result is a zero-arg `() => T` that
416
+ * respects the target's registered lifetime.
417
+ *
418
+ * When `ref.params` is present, it is the COMPLETE authored-order list of
419
+ * caller-supplied parameter tokens. The caller-supplied set is pinned to those
420
+ * tokens (by first-occurrence left-to-right matching against ctor slots). A
421
+ * slot token that appears in `params` is caller-supplied even if it is also
422
+ * registered (caller wins). A slot that is neither claimed by `params` nor
423
+ * resolvable from the container → error. The factory shape is exactly
424
+ * `(...params) => T`; a fresh instance is built on every call (bypassing the
425
+ * instance cache — caller args differ per call so caching would be wrong).
365
426
  *
366
427
  * Lifetime semantics:
367
- * - A ZERO-ARG factory (the target ctor has no holes / unregistered params)
368
- * routes the build through the normal `resolve` path, so it RESPECTS the
369
- * target's registered lifetime: a singleton target yields the same
370
- * instance on every call; a transient target yields a fresh one.
371
- * - A PARAMETERIZED factory (the target has holes / unregistered params
372
- * filled per call) constructs a FRESH instance on every call and BYPASSES
373
- * the instance cache. Caller args differ per call, so caching would be
374
- * wrong — two calls with different arguments must not collapse to one
375
- * cached instance.
428
+ * - A ZERO-ARG (no-params) factory routes through the normal `resolve` path
429
+ * and RESPECTS the target's registered lifetime.
430
+ * - A PARAMETERIZED factory constructs a FRESH instance every call.
376
431
  *
377
- * The closure captures `this` as the owning scope. §5.4 holds at call time:
378
- * the target's deps resolve relative to the scope that owns the
379
- * factory-holding instance, exactly as a direct resolve would — so a factory
380
- * captured by a singleton that tries to build a request-scoped target still
381
- * throws `MissingScopeError` when invoked.
432
+ * The closure captures `owningFrame`. §5.4 holds at call time: the target's
433
+ * deps resolve relative to the scope that owns the factory-holding instance.
382
434
  */
383
435
  private makeFactory;
384
436
  /**
385
- * Builds a factory target, partitioning its already-selected signature
386
- * against the live registration map: a registered token is resolved; a
387
- * `ScopeRef` is the live scope; a `FactoryRef` is injected; an unregistered
388
- * token or a `hole` takes the next caller-supplied argument positionally. A
389
- * class target is `new`ed, a factory target is called. Always a fresh result
390
- * a parameterized factory bypasses the instance cache (caller args differ
391
- * per call). Runs on a fresh cycle stack since the factory is invoked outside
392
- * the original resolve.
437
+ * Builds a factory target with the params-driven caller-supplied partition.
438
+ *
439
+ * `callerParams` is the authored-order list of tokens whose values are
440
+ * supplied by the caller (from the `FactoryRef.params` list). Each ctor slot
441
+ * whose token appears in `callerParams` (first-occurrence left-to-right match)
442
+ * takes the corresponding `callArgs` value; every other slot resolves from the
443
+ * container. A slot that is neither claimed nor resolvable error (the factory
444
+ * cannot be built). A claimed slot that is also registered → caller wins.
445
+ *
446
+ * Always builds a fresh result — a parameterized factory bypasses the instance
447
+ * cache. Runs on a fresh cycle stack since the factory is invoked outside the
448
+ * original resolve.
449
+ *
450
+ * `signature` may be `undefined` when the target has no DepRecord (zero-arg
451
+ * ctor or record-less factory) — in that case args is empty.
393
452
  */
394
453
  private buildPartitioned;
395
454
  /**
396
455
  * Greedy signature selection. Scans signatures longest → shortest and returns
397
456
  * the first SATISFIABLE one. A slot is satisfiable when it is:
398
457
  *
399
- * - a `FactoryRef` — always satisfiable; injected as a callable. The
400
- * factory's target need not be resolvable for the slot to count (an
401
- * unregistered target surfaces a `FactoryTargetError` when the factory is
402
- * built / called, not here);
403
- * - a `ScopeRef` — always satisfiable; filled with the live scope; or
404
- * - a string token whose registration is resolvable in this (the owning)
405
- * scope's chain.
458
+ * - a `FactoryRef` — always satisfiable; injected as a callable;
459
+ * - a `ScopeRef` always satisfiable; filled with the live provider view;
460
+ * - a `Union` satisfiable iff at least one member is resolvable; or
461
+ * - a string token whose registration exists in the sealed map.
406
462
  *
407
- * A `hole` (`null`) is NOT special — it is an unregistered token, so it is
408
- * unsatisfiable on a direct resolve. A class with an optional/defaulted param
409
- * carries a shorter "without that arg" overload (emitted by the transformer);
410
- * greedy selection falls to it when the longer one can't be satisfied, so the
411
- * default / `undefined` applies. A required unfilled param has no such shorter
412
- * overload and surfaces a `NoSatisfiableSignatureError` (build it via a
413
- * factory instead). A signature is satisfiable iff every string-token slot is
414
- * resolvable.
415
- *
416
- * - Equal-arity ties break by registration order (the order signatures appear
417
- * in the DepRecord), which `sort`'s stability preserves.
418
- * - None satisfiable ⇒ throw naming the unsatisfiable tokens.
463
+ * An unregistered string token is not satisfiable. Equal-arity ties break by
464
+ * registration order. None satisfiable throw naming the unsatisfiable tokens.
419
465
  */
420
466
  private selectSignature;
421
467
  /**
@@ -423,19 +469,37 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
423
469
  * there is no resolvability gate: a target's unregistered tokens are not
424
470
  * unsatisfiable — they are the factory's caller-supplied parameters. So the
425
471
  * choice is purely the longest signature, equal-arity ties broken by
426
- * registration order (`sort` stability). Always returns a signature (the
427
- * caller has already checked `signatures.length > 0`).
472
+ * registration order.
428
473
  */
429
474
  private selectTargetSignature;
430
475
  /**
431
- * True when `slot` is a registered string token somewhere in this scope's
432
- * chain. A hole (`null`), `FactoryRef`, or `ScopeRef` is not a registered
433
- * token, so it is never "resolvable" in this sense.
476
+ * True when `slot` is a registered string token in the sealed map. A
477
+ * `FactoryRef`, `ScopeRef`, or `Union` is not tested here — use
478
+ * `isResolvableSlot` for a full slot check.
434
479
  */
435
480
  private isResolvable;
436
481
  /**
437
- * Closes this scope synchronously, disposing the instances it owns in REVERSE
438
- * construction order. Only native `Disposable` instances are disposed.
482
+ * True when a slot is resolvable in ANY form:
483
+ * - `FactoryRef` / `ScopeRef` always satisfiable (injected);
484
+ * - `Union` — satisfiable iff at least one member is resolvable (recursive);
485
+ * - string token — registered in the sealed map.
486
+ */
487
+ private isResolvableSlot;
488
+ /**
489
+ * Resolves a `Union` slot: tries each member in declaration order and returns
490
+ * the first resolvable one. If no member is resolvable, throws
491
+ * `NoSatisfiableUnionError`.
492
+ */
493
+ private resolveUnion;
494
+ /**
495
+ * Resolves a single `DepSlot` to its value, dispatching on slot kind.
496
+ * Used by `resolveUnion` to recurse into members.
497
+ */
498
+ private resolveSlot;
499
+ /**
500
+ * Closes this provider synchronously, disposing the instances its scope frame
501
+ * owns in REVERSE construction order. Only native `Disposable` instances are
502
+ * disposed. NO cascade to child scopes.
439
503
  *
440
504
  * Throws `AsyncDisposalRequiredError` if any owned instance is a Promise
441
505
  * (thenable) — a pending Promise cannot be disposed synchronously; the caller
@@ -443,9 +507,9 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
443
507
  */
444
508
  dispose(): void;
445
509
  /**
446
- * Closes this scope asynchronously. Awaits each owned Promise-valued instance
447
- * first (so an async factory's result settles before teardown), then disposes
448
- * owned instances in REVERSE construction order — honoring both
510
+ * Closes this provider asynchronously. Awaits each owned Promise-valued
511
+ * instance first (so an async factory's result settles before teardown), then
512
+ * disposes owned instances in REVERSE construction order — honoring both
449
513
  * `Symbol.asyncDispose` and `Symbol.dispose`. Idempotent.
450
514
  */
451
515
  disposeAsync(): Promise<void>;
@@ -467,23 +531,17 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
467
531
  */
468
532
  interface AddBuilder<Scopes extends string> {
469
533
  /**
470
- * Attaches the lifetime. Must name a declared scope.
471
- *
472
- * Two call shapes, by design (PRD §7):
473
- * - AUTHORED `.as<"singleton">()` — the scope name is a TYPE argument; the
474
- * `S extends Scopes` bound is the compile-time captive-misconfiguration
475
- * guard. No value argument is passed; this form is never executed (the
476
- * transformer rewrites it before it runs).
477
- * - LOWERED `.as("singleton")` — the transformer rewrites the type
478
- * argument to a value argument. This is the form the engine executes; the
479
- * runtime reads the scope from the value arg.
534
+ * Attaches the lifetime — the RUNTIME (lowered) form. Must name a declared
535
+ * scope.
480
536
  *
481
- * `scope` is therefore OPTIONAL at the type level: the authored form supplies
482
- * it as a type arg only, the lowered form as a value. A bare `.as()` with no
483
- * type arg leaves `S = Scopes` and is a degenerate (scopeless) call — use the
484
- * type arg.
485
- */
486
- as<S extends Scopes>(scope?: S): void;
537
+ * `.as("singleton")` is what the engine executes: the transformer rewrites the
538
+ * authored type-arg form (`.as<"singleton">()`) to this value-arg form before
539
+ * runtime, and a plugin-less caller writes it directly. The AUTHORED type-arg
540
+ * form (`.as<S extends Scopes>(): void`) is a PURE TYPING contributed by the
541
+ * `@fnioc/transformer` augmentation — it is not part of di's published surface,
542
+ * so it only type-checks when the transformer's types are in the program.
543
+ */
544
+ as(scope: Scopes): void;
487
545
  }
488
546
  /**
489
547
  * The registration builder.
@@ -527,18 +585,6 @@ declare class DiBuilder<Root extends string = "singleton", Children extends stri
527
585
  * trailing `.as()` leaves the base (transient) registration in place.
528
586
  */
529
587
  private appendScoped;
530
- /**
531
- * Type-only authoring overloads — the forms the transformer rewrites FROM:
532
- * - `add<I>(C)` → `add("token", C)` (class)
533
- * - `add<I>(fn)` → `addFactory("token", fn)` (factory; the transformer
534
- * knows the arg is a function and routes it to `addFactory`).
535
- * The ctor is typed `Ctor<any[], I>` (plain construct signature, so an
536
- * abstract class is rejected); the factory is any `(...args) => I`. Neither
537
- * runs post-transform — they exist purely so type-driven authoring
538
- * type-checks before lowering.
539
- */
540
- add<I>(ctor: Ctor<any[], I>): AddBuilder<Root | Children>;
541
- add<I>(factory: (...args: any[]) => I): AddBuilder<Root | Children>;
542
588
  /**
543
589
  * Class registration — a string token bound to a concrete constructor. The
544
590
  * runtime form: what the transformer emits for a class, and what a
@@ -550,29 +596,30 @@ declare class DiBuilder<Root extends string = "singleton", Children extends stri
550
596
  * runtime form the transformer emits for an authored `add<I>(fn)`, and what a
551
597
  * plugin-less caller writes directly.
552
598
  *
553
- * Parameter injection follows the metadata rule (see `Scope.instantiate`): a
599
+ * Parameter injection follows the metadata rule (see `ServiceProvider`): a
554
600
  * factory WITH a `defineDeps` record (emitted by the transformer) has each
555
601
  * parameter injected by its slot; a record-less factory (the plugin-less
556
- * escape hatch) is called with the live scope — type it `(scope: ResolveScope)
557
- * => T` and `scope.resolve(...)` its own deps. Returns the `.as(scope?)`
602
+ * escape hatch) is called with the live provider — type it `(sp: Resolver)
603
+ * => T` and `sp.resolve(...)` its own deps. Returns the `.as(scope?)`
558
604
  * continuation so a factory caches at a named scope exactly like a class.
559
605
  */
560
- addFactory(token: Token, factory: (scope: ResolveScope) => unknown): AddBuilder<Root | Children>;
606
+ addFactory(token: Token, factory: Func<[Resolver], unknown>): AddBuilder<Root | Children>;
561
607
  /**
562
608
  * Value registration — an already-built instance, no deps and no lifetime.
563
609
  * Separate from `add` because a value may itself be a function (a callable
564
610
  * service), which is structurally indistinguishable from a factory inside one
565
- * overload. Authoring `addValue<I>(v)` lowers to `addValue("token", v)`.
611
+ * overload. The authoring form `addValue<I>(v)` (which lowers to
612
+ * `addValue("token", v)`) is a PURE TYPING contributed by the
613
+ * `@fnioc/transformer` augmentation, not part of di's published surface.
566
614
  */
567
- addValue<I>(value: I): void;
568
615
  addValue(token: Token, value: unknown): void;
569
616
  /**
570
- * Mints the root scope. The root is a real, app-lifetime object its name is
571
- * `Root` (the lifetime that singletons, or whatever the app's longest
572
- * lifetime is, bind to). No argument: `build()` owns the root name via the
573
- * `Root` type parameter.
617
+ * Mints the root ServiceProvider with a SEALED copy of the registration map.
618
+ * Sealing (deep-freezing the map and each per-token list) ensures that any
619
+ * `.add()` call on the builder after `build()` cannot mutate what the root
620
+ * and its descendants see — the container's view is fixed at construction time.
574
621
  */
575
- build(): Scope<Root | Children>;
622
+ build(): ServiceProvider<Root | Children>;
576
623
  }
577
624
 
578
625
  /** Base class for every error the container raises. */
@@ -639,6 +686,14 @@ declare class FactoryTargetError extends DiError {
639
686
  readonly reason: "unregistered" | "not-a-class";
640
687
  constructor(factoryToken: Token, reason: "unregistered" | "not-a-class");
641
688
  }
689
+ /**
690
+ * A `Union` slot was encountered during resolution but none of its member slots
691
+ * was resolvable. Resolution cannot proceed without at least one registered member.
692
+ */
693
+ declare class NoSatisfiableUnionError extends DiError {
694
+ readonly members: readonly DepSlot[];
695
+ constructor(members: readonly DepSlot[]);
696
+ }
642
697
  /**
643
698
  * Sync `dispose()` was called on a scope that owns a Promise-valued (thenable)
644
699
  * cached instance. A pending Promise cannot be disposed synchronously — the
@@ -648,5 +703,5 @@ declare class AsyncDisposalRequiredError extends DiError {
648
703
  constructor();
649
704
  }
650
705
 
651
- export { AsyncDisposalRequiredError, CircularDependencyError, DiBuilder, DiError, FactoryTargetError, MissingMetadataError, MissingScopeError, NoSatisfiableSignatureError, Scope, UnregisteredTokenError, defineDeps, forCtor, hole, signature };
652
- export type { AddBuilder, ClassRegistration, Ctor, DepRecord, Factory, FactoryRegistration, ForCtorBuilder, Registration, ResolveScope, Token, ValueRegistration };
706
+ export { AsyncDisposalRequiredError, CircularDependencyError, DiBuilder, DiError, FactoryTargetError, MissingMetadataError, MissingScopeError, NoSatisfiableSignatureError, NoSatisfiableUnionError, Scope, ServiceProvider, UnregisteredTokenError, defineDeps, forCtor, signature, union };
707
+ export type { AddBuilder, ClassRegistration, Ctor, DepRecord, Factory, FactoryRegistration, ForCtorBuilder, Lifetime, Registration, ResolveScope, Resolver, ScopeFactory, Token, Union, ValueRegistration };