@fnioc/di 1.0.0 → 3.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/README.md +40 -28
- package/dist/index.d.ts +633 -8
- package/dist/index.js +504 -23
- package/package.json +6 -9
- package/dist/builder.d.ts +0 -81
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -85
- package/dist/builder.js.map +0 -1
- package/dist/errors.d.ts +0 -73
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -139
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/scope.d.ts +0 -180
- package/dist/scope.d.ts.map +0 -1
- package/dist/scope.js +0 -466
- package/dist/scope.js.map +0 -1
- package/dist/types.d.ts +0 -57
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -4
- package/dist/types.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,633 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
type Func$1<in Args extends readonly any[] = any[], out Return = any> = (...args: Args) => Return;
|
|
2
|
+
|
|
3
|
+
interface Ctor$1<in Args extends readonly any[] = any[], out Instance = any> {
|
|
4
|
+
new(...args: Args): Instance;
|
|
5
|
+
prototype: Instance;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The hole sentinel: marks a constructor parameter as caller-supplied rather
|
|
10
|
+
* than container-resolved.
|
|
11
|
+
*
|
|
12
|
+
* Used in signatures to communicate "this position is a hole — the factory
|
|
13
|
+
* caller supplies this argument, not the DI container." Detected by identity
|
|
14
|
+
* (`slot === hole`), and `null` is the sentinel value: it is exactly what the
|
|
15
|
+
* transformer emits for a hole slot, so the authoring surface and the lowered
|
|
16
|
+
* output agree on one representation.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* @signature("pkg:ILogger", hole, "pkg:IDb")
|
|
21
|
+
* class SqlRepo { constructor(log: ILogger, tableName: string, db: IDb) { ... } }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare const hole: null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Anything dependency metadata can be attached to: a class constructor (its
|
|
28
|
+
* deps are the ctor parameters) or a factory function (its deps are the call
|
|
29
|
+
* parameters). Both are objects, so both are valid global-WeakMap keys. The
|
|
30
|
+
* `never[]` rest keeps any concrete function assignable here regardless of its
|
|
31
|
+
* own parameter list.
|
|
32
|
+
*/
|
|
33
|
+
type DepTarget = Ctor$1 | Func$1<never[], unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* A stable string identifying an interface — the DI key.
|
|
36
|
+
*
|
|
37
|
+
* No branding, no literal types. Generated by the transformer from a TypeScript
|
|
38
|
+
* type at compile time; referenced by hand when using the manual authoring surfaces.
|
|
39
|
+
*/
|
|
40
|
+
type Token = string;
|
|
41
|
+
/**
|
|
42
|
+
* Marks a constructor parameter to be injected as a *factory* producing the
|
|
43
|
+
* registered token `factory`, rather than a resolved instance. The factory's
|
|
44
|
+
* own call signature is the target ctor's unregistered params, partitioned at
|
|
45
|
+
* resolve time.
|
|
46
|
+
*/
|
|
47
|
+
interface FactoryRef {
|
|
48
|
+
readonly factory: Token;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Marks a parameter to be injected with the live resolution scope itself,
|
|
52
|
+
* rather than a resolved token. Emitted for a factory parameter whose type is
|
|
53
|
+
* `ResolveScope` — the engine fills the slot with the scope the factory is
|
|
54
|
+
* resolved into, so the factory body can `scope.resolve(...)` / `createScope`
|
|
55
|
+
* dynamically. Only meaningful on registration-level factory functions (a class
|
|
56
|
+
* ctor never receives the scope); on a ctor it would simply never match.
|
|
57
|
+
*/
|
|
58
|
+
interface ScopeRef {
|
|
59
|
+
readonly scope: true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One positional slot in a constructor / factory signature:
|
|
63
|
+
* - a `Token` string — a container-resolved dependency,
|
|
64
|
+
* - the `hole` sentinel — a caller-supplied parameter,
|
|
65
|
+
* - a `FactoryRef` — a factory-injected parameter (see `FactoryRef`), or
|
|
66
|
+
* - a `ScopeRef` — the live resolution scope (see `ScopeRef`).
|
|
67
|
+
*/
|
|
68
|
+
type DepSlot = Token | typeof hole | FactoryRef | ScopeRef;
|
|
69
|
+
/**
|
|
70
|
+
* Per-constructor dependency metadata stored in the global WeakMap.
|
|
71
|
+
*
|
|
72
|
+
* `signatures` is an array of arrays: each element is one constructor signature
|
|
73
|
+
* (for overload support). `signatures[i][j]` is the `DepSlot` — a token, the
|
|
74
|
+
* `hole` sentinel, or a `FactoryRef` — for constructor parameter `j` of
|
|
75
|
+
* overload `i`.
|
|
76
|
+
*/
|
|
77
|
+
interface DepRecord {
|
|
78
|
+
readonly signatures: readonly (readonly DepSlot[])[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The single write path into the global WeakMap.
|
|
83
|
+
*
|
|
84
|
+
* Both the transformer-emitted code and `@signature` / `forCtor` funnel through
|
|
85
|
+
* this function. No other code writes to the store.
|
|
86
|
+
*
|
|
87
|
+
* Merge semantics: appends each incoming signature to the ctor's existing
|
|
88
|
+
* DepRecord, deduping exact-equal signatures (same length + same elements in
|
|
89
|
+
* order). Creates the record from scratch if the ctor is not yet registered.
|
|
90
|
+
*
|
|
91
|
+
* @param target The exact constructor OR factory function to annotate (no
|
|
92
|
+
* prototype-chain walk — subclasses do NOT inherit the
|
|
93
|
+
* parent's record).
|
|
94
|
+
* @param signatures An array of signatures; each signature is a positional array
|
|
95
|
+
* of DepSlot (Token | hole | FactoryRef | ScopeRef) parallel to
|
|
96
|
+
* the target's parameter list.
|
|
97
|
+
*/
|
|
98
|
+
declare function defineDeps(target: DepTarget, signatures: readonly (readonly DepSlot[])[]): void;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* TC39 class decorator factory that registers one constructor signature.
|
|
102
|
+
*
|
|
103
|
+
* Stacking multiple `@signature` decorators registers multiple overloads.
|
|
104
|
+
* TypeScript evaluates class decorators bottom-up, so the bottom-most
|
|
105
|
+
* `@signature` call runs first and appends first; tests should assert that
|
|
106
|
+
* ALL signatures are present rather than relying on a specific order.
|
|
107
|
+
*
|
|
108
|
+
* Returns `void` — does not replace the class.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* // Two overloads: one with a logger, one without
|
|
113
|
+
* @signature("pkg:ILogger", "pkg:IDb")
|
|
114
|
+
* @signature("pkg:IDb")
|
|
115
|
+
* class MyService {
|
|
116
|
+
* constructor(logOrDb: ILogger | IDb, db?: IDb) { ... }
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function signature(...tokens: readonly DepSlot[]): Func$1<[Ctor$1, ClassDecoratorContext], void>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The builder returned by `forCtor`. Chainable — each `.signature()` call
|
|
124
|
+
* appends one overload to the ctor's DepRecord.
|
|
125
|
+
*/
|
|
126
|
+
interface ForCtorBuilder {
|
|
127
|
+
/**
|
|
128
|
+
* Appends one constructor signature (a positional array of DepSlot —
|
|
129
|
+
* Token | hole | FactoryRef) to the ctor's dependency metadata. Returns
|
|
130
|
+
* `this` for chaining.
|
|
131
|
+
*
|
|
132
|
+
* Each `.signature(...)` call is one overload. Chaining two calls is
|
|
133
|
+
* equivalent to stacking two `@signature` decorators.
|
|
134
|
+
*/
|
|
135
|
+
signature(...tokens: readonly DepSlot[]): ForCtorBuilder;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Fluent free-function for registering dependency metadata on a constructor
|
|
139
|
+
* without using a decorator — useful for third-party classes you do not own.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* forCtor(ThirdPartyService)
|
|
144
|
+
* .signature("pkg:IDb")
|
|
145
|
+
* .signature("pkg:ILogger", "pkg:IDb"); // second overload
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
declare function forCtor(ctor: Ctor$1): ForCtorBuilder;
|
|
149
|
+
|
|
150
|
+
type Func<in Args extends readonly any[] = any[], out Return = any> = (...args: Args) => Return;
|
|
151
|
+
|
|
152
|
+
interface Ctor<in Args extends readonly any[] = any[], out Instance = any> {
|
|
153
|
+
new(...args: Args): Instance;
|
|
154
|
+
prototype: Instance;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A registration-level factory function. Its parameters are filled by the
|
|
159
|
+
* engine at resolve time, the same way a class constructor's are: a factory
|
|
160
|
+
* WITH a `defineDeps` record has each parameter resolved by its slot (token →
|
|
161
|
+
* resolved instance, `ScopeRef` → the live provider, hole → caller-supplied); a
|
|
162
|
+
* factory WITHOUT a record is the plugin-less escape hatch and is called with
|
|
163
|
+
* the live provider as its single argument (`(sp) => …`).
|
|
164
|
+
*
|
|
165
|
+
* May be async — it can return a `Promise<T>`. The container never awaits; the
|
|
166
|
+
* Promise flows through the sync resolution channel as a value (§"Async as
|
|
167
|
+
* values"). A consumer that depends on it declares `Promise<T>` and awaits.
|
|
168
|
+
*/
|
|
169
|
+
type Factory = Func<any[], unknown>;
|
|
170
|
+
/** A class registration: a token bound to a concrete constructor. */
|
|
171
|
+
interface ClassRegistration {
|
|
172
|
+
readonly kind: "class";
|
|
173
|
+
readonly ctor: Ctor;
|
|
174
|
+
/**
|
|
175
|
+
* The lifetime — the scope name that owns and caches the instance.
|
|
176
|
+
* `undefined` means transient (never cached; a fresh instance per resolve).
|
|
177
|
+
*/
|
|
178
|
+
readonly scope: string | undefined;
|
|
179
|
+
}
|
|
180
|
+
/** A factory-function registration — its params are injected like a ctor's. */
|
|
181
|
+
interface FactoryRegistration {
|
|
182
|
+
readonly kind: "factory";
|
|
183
|
+
readonly factory: Factory;
|
|
184
|
+
/**
|
|
185
|
+
* The lifetime — the scope name that owns and caches the result. `undefined`
|
|
186
|
+
* means transient (the factory runs on every resolve). Attached via `.as()`,
|
|
187
|
+
* exactly like a class registration.
|
|
188
|
+
*/
|
|
189
|
+
readonly scope: string | undefined;
|
|
190
|
+
}
|
|
191
|
+
/** A value registration — an already-built instance, no lifetime. */
|
|
192
|
+
interface ValueRegistration {
|
|
193
|
+
readonly kind: "value";
|
|
194
|
+
readonly useValue: unknown;
|
|
195
|
+
}
|
|
196
|
+
/** Any registration the engine can resolve. */
|
|
197
|
+
type Registration = ClassRegistration | FactoryRegistration | ValueRegistration;
|
|
198
|
+
/**
|
|
199
|
+
* The named lifetime tag for a registration. `"singleton"` and `"transient"`
|
|
200
|
+
* are the built-in names; `U` is the user-declared scope-name union (defaults
|
|
201
|
+
* to `"scoped"`). Transient is represented by the ABSENCE of a lifetime tag
|
|
202
|
+
* (`undefined` on the registration), not by the string `"transient"`.
|
|
203
|
+
*/
|
|
204
|
+
type Lifetime<U extends string = "scoped"> = "singleton" | "transient" | U;
|
|
205
|
+
/**
|
|
206
|
+
* The minimal resolution surface — resolve tokens and get factories. Injected
|
|
207
|
+
* into factory parameters typed `Resolver` (and for the plugin-less escape
|
|
208
|
+
* hatch as the sole argument of a record-less factory).
|
|
209
|
+
*
|
|
210
|
+
* `resolve` has two published shapes (the tokenless authoring form
|
|
211
|
+
* `resolve<T>()` is a PURE TYPING contributed by the `@fnioc/transformer`
|
|
212
|
+
* augmentation, not part of di's published surface):
|
|
213
|
+
* - `resolve<T>(token)` — explicit token, typed return.
|
|
214
|
+
* - `resolve(token)` — explicit token, `unknown` return (dynamic).
|
|
215
|
+
*/
|
|
216
|
+
interface Resolver {
|
|
217
|
+
resolve<T>(token: Token): T;
|
|
218
|
+
resolve(token: Token): unknown;
|
|
219
|
+
/**
|
|
220
|
+
* Returns a FACTORY for the token rather than an instance — the resolve-site
|
|
221
|
+
* mirror of a `FactoryRef` ctor param. The authored `resolve<(a: A) => T>()`
|
|
222
|
+
* (a function-typed type arg) lowers to this.
|
|
223
|
+
*/
|
|
224
|
+
resolveFactory(token: Token): unknown;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The scope-creation surface. Injected into factory parameters typed
|
|
228
|
+
* `ScopeFactory`, and implemented by `ServiceProvider`.
|
|
229
|
+
*/
|
|
230
|
+
interface ScopeFactory<S extends string = string> {
|
|
231
|
+
createScope(...args: "scoped" extends S ? [name?: S] : [name: S]): ServiceProvider<S>;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* @deprecated Use `Resolver` instead. Kept for backwards compatibility.
|
|
235
|
+
*
|
|
236
|
+
* The resolution surface a factory receives — either as an injected `ScopeRef`
|
|
237
|
+
* parameter, or (plugin-less escape hatch) as the sole argument of a
|
|
238
|
+
* record-less factory.
|
|
239
|
+
*/
|
|
240
|
+
interface ResolveScope extends Resolver {
|
|
241
|
+
createScope(name: string): ServiceProvider;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* A scope frame — a node in the parent-linked chain. Holds this scope's name,
|
|
246
|
+
* its instance cache, an ordered list for disposal, and an optional parent.
|
|
247
|
+
* It does NOT hold registrations (those live sealed on the ServiceProvider).
|
|
248
|
+
*
|
|
249
|
+
* The special "no frame" on the root ServiceProvider means transient-only /
|
|
250
|
+
* unscoped resolution — attempting to resolve a scoped registration from the
|
|
251
|
+
* root will throw MissingScopeError.
|
|
252
|
+
*/
|
|
253
|
+
declare class Scope {
|
|
254
|
+
/** This scope's name — must match the registration's lifetime tag. */
|
|
255
|
+
readonly name: string;
|
|
256
|
+
/** The parent scope, or omitted for the topmost frame. */
|
|
257
|
+
readonly parent?: Scope | undefined;
|
|
258
|
+
/** Instances this scope owns and caches, keyed by token. */
|
|
259
|
+
readonly cache: Map<Token, unknown>;
|
|
260
|
+
/** Owned instances in construction order — disposed in reverse. */
|
|
261
|
+
readonly owned: unknown[];
|
|
262
|
+
constructor(
|
|
263
|
+
/** This scope's name — must match the registration's lifetime tag. */
|
|
264
|
+
name: string,
|
|
265
|
+
/** The parent scope, or omitted for the topmost frame. */
|
|
266
|
+
parent?: Scope | undefined);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The public container surface. Implements `Resolver` (resolve + resolveFactory)
|
|
270
|
+
* and `ScopeFactory` (createScope), plus native `Disposable`/`AsyncDisposable`.
|
|
271
|
+
*
|
|
272
|
+
* `S` is the user-declared scope-name union. The root (`DiBuilder.build()`)
|
|
273
|
+
* has an EMPTY scope slot — it acts as the unscoped root that owns singletons
|
|
274
|
+
* when the root name is "singleton", reached by the first `createScope("singleton")`.
|
|
275
|
+
* Wait — actually the root SP from build() DOES have a scope frame (named after
|
|
276
|
+
* the builder's rootName), exactly as before: `build()` creates a root SP
|
|
277
|
+
* with `new Scope(rootName)` as its frame.
|
|
278
|
+
*/
|
|
279
|
+
declare class ServiceProvider<S extends string = string> implements Resolver, ScopeFactory<S>, Disposable, AsyncDisposable {
|
|
280
|
+
/** The sealed registration map (shared across all providers in the tree). */
|
|
281
|
+
private readonly registrations;
|
|
282
|
+
private disposed;
|
|
283
|
+
/**
|
|
284
|
+
* The scope frame for this provider. `undefined` means this is the "unscoped"
|
|
285
|
+
* root — a sentinel that exists only for transient-only trees (no build()
|
|
286
|
+
* call sets this to undefined in normal usage; build() always sets a root name).
|
|
287
|
+
*/
|
|
288
|
+
private readonly frame;
|
|
289
|
+
constructor(
|
|
290
|
+
/** The sealed registration map (shared across all providers in the tree). */
|
|
291
|
+
registrations: ReadonlyMap<Token, Registration[]>,
|
|
292
|
+
/** This provider's scope frame, if any. */
|
|
293
|
+
frame?: Scope);
|
|
294
|
+
/**
|
|
295
|
+
* The name of this provider's scope frame. Throws if the provider has no
|
|
296
|
+
* frame (unscoped root). Kept for backwards-compatibility with tests that
|
|
297
|
+
* inspect `root.name`.
|
|
298
|
+
*/
|
|
299
|
+
get name(): S;
|
|
300
|
+
/**
|
|
301
|
+
* Creates a child `ServiceProvider` whose scope frame is a new `Scope` named
|
|
302
|
+
* `name`, parented to this provider's frame (or a top-level frame if this
|
|
303
|
+
* provider is unscoped).
|
|
304
|
+
*
|
|
305
|
+
* Default name `"scoped"` is accepted only when `"scoped"` ∈ S (the
|
|
306
|
+
* conditional-rest-param type ensures this at the call site).
|
|
307
|
+
*/
|
|
308
|
+
createScope(...args: "scoped" extends S ? [name?: S] : [name: S]): ServiceProvider<S>;
|
|
309
|
+
/**
|
|
310
|
+
* Resolves a token to an instance, walking the scope chain for the owning
|
|
311
|
+
* frame. The public entry point starts a fresh cycle-detection stack.
|
|
312
|
+
*/
|
|
313
|
+
resolve<T>(token: Token): T;
|
|
314
|
+
resolve(token: Token): unknown;
|
|
315
|
+
/**
|
|
316
|
+
* Returns a FACTORY for `token` rather than an instance — the resolve-site
|
|
317
|
+
* mirror of a `FactoryRef` ctor param. The authored `resolve<(a: A) => T>()`
|
|
318
|
+
* lowers here. The returned callable exposes the target's UNREGISTERED /
|
|
319
|
+
* caller-supplied parameters in order (PRD §7 "Partial / positional
|
|
320
|
+
* factories"); a fully-resolvable target yields a zero-arg lazy factory that
|
|
321
|
+
* respects the target's registered lifetime.
|
|
322
|
+
*/
|
|
323
|
+
resolveFactory(token: Token): unknown;
|
|
324
|
+
/**
|
|
325
|
+
* Returns the most-recent registration for `token` from the sealed map.
|
|
326
|
+
* The sealed map is shared across all providers in the tree; local overrides
|
|
327
|
+
* are not supported in the new model (scope-local registration is deleted).
|
|
328
|
+
*/
|
|
329
|
+
private lookup;
|
|
330
|
+
/**
|
|
331
|
+
* Finds the nearest ancestor scope frame (inclusive) whose name matches
|
|
332
|
+
* `scopeName`, walking UP the chain. Returns `undefined` when none matches.
|
|
333
|
+
*/
|
|
334
|
+
private static findOwner;
|
|
335
|
+
/** The chain of scope names from `vantage` up to the root, for diagnostics. */
|
|
336
|
+
private static chainNames;
|
|
337
|
+
/**
|
|
338
|
+
* The internal resolver. `vantage` is the scope frame the walk starts from.
|
|
339
|
+
* `stack` is the active resolution path (for cycle detection); it is shared
|
|
340
|
+
* across the whole `resolve()` call but never across separate calls.
|
|
341
|
+
*/
|
|
342
|
+
private resolveWith;
|
|
343
|
+
/**
|
|
344
|
+
* Builds an instance for `registration`. `owningFrame` is the scope frame
|
|
345
|
+
* whose chain the dependencies are resolved against — THE critical rule.
|
|
346
|
+
*/
|
|
347
|
+
private instantiate;
|
|
348
|
+
/**
|
|
349
|
+
* The resolution view injected for a `ScopeRef` parameter (`Resolver` or
|
|
350
|
+
* `ScopeFactory` typed param). Produces a ServiceProvider-like view that
|
|
351
|
+
* continues the active cycle `stack` and resolves relative to `owningFrame`.
|
|
352
|
+
*/
|
|
353
|
+
private makeProviderView;
|
|
354
|
+
/**
|
|
355
|
+
* Invokes a factory registration under the metadata-vs-scope rule:
|
|
356
|
+
* - factory WITH a `defineDeps` record → resolve each slot (token →
|
|
357
|
+
* resolved instance, `ScopeRef` → the live provider view, `FactoryRef` →
|
|
358
|
+
* an injected callable) and call `factory(...args)`;
|
|
359
|
+
* - factory WITHOUT a record (the plugin-less escape hatch) → call
|
|
360
|
+
* `factory(providerView)` with the live provider view as its sole argument.
|
|
361
|
+
* Deps resolve relative to `owningFrame` (the owning scope) — §5.4.
|
|
362
|
+
*/
|
|
363
|
+
private invokeFactory;
|
|
364
|
+
/**
|
|
365
|
+
* Constructs a class instance, resolving its constructor dependencies
|
|
366
|
+
* relative to `owningFrame`. Performs greedy signature selection over the
|
|
367
|
+
* ctor's DepRecord, then fills each slot:
|
|
368
|
+
*
|
|
369
|
+
* - a string token → resolved through the owning frame's chain (selection
|
|
370
|
+
* guarantees every string-token slot here is resolvable);
|
|
371
|
+
* - a `FactoryRef` → injected as a callable (see `makeFactory`);
|
|
372
|
+
* - a `ScopeRef` → the live provider view.
|
|
373
|
+
*/
|
|
374
|
+
private construct;
|
|
375
|
+
/**
|
|
376
|
+
* Builds the callable injected for a `FactoryRef` parameter.
|
|
377
|
+
*
|
|
378
|
+
* The target ctor's signature is partitioned at CALL time against the live
|
|
379
|
+
* registration map: each slot that is a registered token is resolved; each
|
|
380
|
+
* slot that is an unregistered token or a `hole` takes the next
|
|
381
|
+
* caller-supplied argument, positionally. The injected callable therefore
|
|
382
|
+
* exposes only the target's unregistered parameters, in their relative order.
|
|
383
|
+
*
|
|
384
|
+
* Lifetime semantics:
|
|
385
|
+
* - A ZERO-ARG factory routes through the normal `resolve` path, so it
|
|
386
|
+
* RESPECTS the target's registered lifetime.
|
|
387
|
+
* - A PARAMETERIZED factory constructs a FRESH instance on every call and
|
|
388
|
+
* BYPASSES the instance cache. Caller args differ per call, so caching
|
|
389
|
+
* would be wrong.
|
|
390
|
+
*
|
|
391
|
+
* The closure captures `owningFrame`. §5.4 holds at call time: the target's
|
|
392
|
+
* deps resolve relative to the scope that owns the factory-holding instance.
|
|
393
|
+
*/
|
|
394
|
+
private makeFactory;
|
|
395
|
+
/**
|
|
396
|
+
* Builds a factory target, partitioning its already-selected signature
|
|
397
|
+
* against the live registration map: a registered token is resolved; a
|
|
398
|
+
* `ScopeRef` is the live provider view; a `FactoryRef` is injected; an
|
|
399
|
+
* unregistered token or a `hole` takes the next caller-supplied argument
|
|
400
|
+
* positionally. A class target is `new`ed, a factory target is called.
|
|
401
|
+
* Always a fresh result — a parameterized factory bypasses the instance cache.
|
|
402
|
+
* Runs on a fresh cycle stack since the factory is invoked outside the
|
|
403
|
+
* original resolve.
|
|
404
|
+
*/
|
|
405
|
+
private buildPartitioned;
|
|
406
|
+
/**
|
|
407
|
+
* Greedy signature selection. Scans signatures longest → shortest and returns
|
|
408
|
+
* the first SATISFIABLE one. A slot is satisfiable when it is:
|
|
409
|
+
*
|
|
410
|
+
* - a `FactoryRef` — always satisfiable; injected as a callable;
|
|
411
|
+
* - a `ScopeRef` — always satisfiable; filled with the live provider view; or
|
|
412
|
+
* - a string token whose registration exists in the sealed map.
|
|
413
|
+
*
|
|
414
|
+
* A `hole` (`null`) is NOT satisfiable on a direct resolve. An unregistered
|
|
415
|
+
* string token is also not satisfiable. Equal-arity ties break by registration
|
|
416
|
+
* order. None satisfiable ⇒ throw naming the unsatisfiable tokens.
|
|
417
|
+
*/
|
|
418
|
+
private selectSignature;
|
|
419
|
+
/**
|
|
420
|
+
* Greedy signature selection for a FACTORY TARGET. Unlike `selectSignature`,
|
|
421
|
+
* there is no resolvability gate: a target's unregistered tokens are not
|
|
422
|
+
* unsatisfiable — they are the factory's caller-supplied parameters. So the
|
|
423
|
+
* choice is purely the longest signature, equal-arity ties broken by
|
|
424
|
+
* registration order.
|
|
425
|
+
*/
|
|
426
|
+
private selectTargetSignature;
|
|
427
|
+
/**
|
|
428
|
+
* True when `slot` is a registered string token in the sealed map. A hole
|
|
429
|
+
* (`null`), `FactoryRef`, or `ScopeRef` is not a registered token, so it is
|
|
430
|
+
* never "resolvable" in this sense.
|
|
431
|
+
*/
|
|
432
|
+
private isResolvable;
|
|
433
|
+
/**
|
|
434
|
+
* Closes this provider synchronously, disposing the instances its scope frame
|
|
435
|
+
* owns in REVERSE construction order. Only native `Disposable` instances are
|
|
436
|
+
* disposed. NO cascade to child scopes.
|
|
437
|
+
*
|
|
438
|
+
* Throws `AsyncDisposalRequiredError` if any owned instance is a Promise
|
|
439
|
+
* (thenable) — a pending Promise cannot be disposed synchronously; the caller
|
|
440
|
+
* must use `disposeAsync()`. Idempotent: a second call is a no-op.
|
|
441
|
+
*/
|
|
442
|
+
dispose(): void;
|
|
443
|
+
/**
|
|
444
|
+
* Closes this provider asynchronously. Awaits each owned Promise-valued
|
|
445
|
+
* instance first (so an async factory's result settles before teardown), then
|
|
446
|
+
* disposes owned instances in REVERSE construction order — honoring both
|
|
447
|
+
* `Symbol.asyncDispose` and `Symbol.dispose`. Idempotent.
|
|
448
|
+
*/
|
|
449
|
+
disposeAsync(): Promise<void>;
|
|
450
|
+
/** Drops owned references after disposal so they can be collected. */
|
|
451
|
+
private clear;
|
|
452
|
+
/** Native `using` support — delegates to `dispose()`. */
|
|
453
|
+
[Symbol.dispose](): void;
|
|
454
|
+
/** Native `await using` support — delegates to `disposeAsync()`. */
|
|
455
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* The continuation returned by a class `DiBuilder.add`. Carries the just-added
|
|
460
|
+
* registration so `.as()` can attach its lifetime in place. An `.add()` with no
|
|
461
|
+
* trailing `.as()` leaves the registration scopeless ⇒ transient.
|
|
462
|
+
*
|
|
463
|
+
* `Scopes` is threaded so `.as()` only accepts a declared scope name —
|
|
464
|
+
* compile-time captive-misconfiguration guard at the registration site.
|
|
465
|
+
*/
|
|
466
|
+
interface AddBuilder<Scopes extends string> {
|
|
467
|
+
/**
|
|
468
|
+
* Attaches the lifetime — the RUNTIME (lowered) form. Must name a declared
|
|
469
|
+
* scope.
|
|
470
|
+
*
|
|
471
|
+
* `.as("singleton")` is what the engine executes: the transformer rewrites the
|
|
472
|
+
* authored type-arg form (`.as<"singleton">()`) to this value-arg form before
|
|
473
|
+
* runtime, and a plugin-less caller writes it directly. The AUTHORED type-arg
|
|
474
|
+
* form (`.as<S extends Scopes>(): void`) is a PURE TYPING contributed by the
|
|
475
|
+
* `@fnioc/transformer` augmentation — it is not part of di's published surface,
|
|
476
|
+
* so it only type-checks when the transformer's types are in the program.
|
|
477
|
+
*/
|
|
478
|
+
as(scope: Scopes): void;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* The registration builder.
|
|
482
|
+
*
|
|
483
|
+
* `Root` is the root scope's name (the app-lifetime tag singletons bind to);
|
|
484
|
+
* `Children` is the union of declarable child-scope names. The scopes
|
|
485
|
+
* `.as()`/`.createScope` accept are `Root | Children`. `"transient"` is NOT a
|
|
486
|
+
* member — transient is the absence of a scope, not a scope.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* const services = new DiBuilder<"singleton", "request">();
|
|
491
|
+
* services.add("pkg:ILogger", ConsoleLogger).as("singleton"); // lowered form
|
|
492
|
+
* const root = services.build(); // mints the "singleton" root
|
|
493
|
+
* const logger = root.resolve<ILogger>("pkg:ILogger");
|
|
494
|
+
* const req = root.createScope("request"); // nested child scope
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
declare class DiBuilder<Root extends string = "singleton", Children extends string = never> {
|
|
498
|
+
/**
|
|
499
|
+
* The service collection: each token maps to a LIST of registrations in
|
|
500
|
+
* registration order. Registering a token appends; resolution picks the
|
|
501
|
+
* most-recent (last) registration. Earlier registrations are retained, which
|
|
502
|
+
* is what lets a later `.add()` override an earlier one without deletion.
|
|
503
|
+
*/
|
|
504
|
+
private readonly registrations;
|
|
505
|
+
/**
|
|
506
|
+
* The root scope's runtime name. `Root` is erased at runtime, so the name is
|
|
507
|
+
* captured at construction (defaulting to `"singleton"`, matching the `Root`
|
|
508
|
+
* default). Most callers never set it — the default covers the common
|
|
509
|
+
* `DiBuilder<"singleton", …>` case; pass it only when `Root` is non-default.
|
|
510
|
+
*/
|
|
511
|
+
private readonly rootName;
|
|
512
|
+
constructor(rootName?: Root);
|
|
513
|
+
/** Appends a registration to `token`'s list, creating the list on first use. */
|
|
514
|
+
private append;
|
|
515
|
+
/**
|
|
516
|
+
* Appends a scopeless `class`/`factory` base registration and returns the
|
|
517
|
+
* `.as(scope?)` continuation. `.as()` appends a fresh SCOPED copy so the
|
|
518
|
+
* array's last entry wins; a bare `.add(...)`/`.addFactory(...)` with no
|
|
519
|
+
* trailing `.as()` leaves the base (transient) registration in place.
|
|
520
|
+
*/
|
|
521
|
+
private appendScoped;
|
|
522
|
+
/**
|
|
523
|
+
* Class registration — a string token bound to a concrete constructor. The
|
|
524
|
+
* runtime form: what the transformer emits for a class, and what a
|
|
525
|
+
* plugin-less caller writes directly. Returns the `.as(scope?)` continuation.
|
|
526
|
+
*/
|
|
527
|
+
add(token: Token, ctor: Ctor): AddBuilder<Root | Children>;
|
|
528
|
+
/**
|
|
529
|
+
* Factory registration — a string token bound to a factory function. The
|
|
530
|
+
* runtime form the transformer emits for an authored `add<I>(fn)`, and what a
|
|
531
|
+
* plugin-less caller writes directly.
|
|
532
|
+
*
|
|
533
|
+
* Parameter injection follows the metadata rule (see `ServiceProvider`): a
|
|
534
|
+
* factory WITH a `defineDeps` record (emitted by the transformer) has each
|
|
535
|
+
* parameter injected by its slot; a record-less factory (the plugin-less
|
|
536
|
+
* escape hatch) is called with the live provider — type it `(sp: Resolver)
|
|
537
|
+
* => T` and `sp.resolve(...)` its own deps. Returns the `.as(scope?)`
|
|
538
|
+
* continuation so a factory caches at a named scope exactly like a class.
|
|
539
|
+
*/
|
|
540
|
+
addFactory(token: Token, factory: Func<[Resolver], unknown>): AddBuilder<Root | Children>;
|
|
541
|
+
/**
|
|
542
|
+
* Value registration — an already-built instance, no deps and no lifetime.
|
|
543
|
+
* Separate from `add` because a value may itself be a function (a callable
|
|
544
|
+
* service), which is structurally indistinguishable from a factory inside one
|
|
545
|
+
* overload. The authoring form `addValue<I>(v)` (which lowers to
|
|
546
|
+
* `addValue("token", v)`) is a PURE TYPING contributed by the
|
|
547
|
+
* `@fnioc/transformer` augmentation, not part of di's published surface.
|
|
548
|
+
*/
|
|
549
|
+
addValue(token: Token, value: unknown): void;
|
|
550
|
+
/**
|
|
551
|
+
* Mints the root ServiceProvider with a SEALED copy of the registration map.
|
|
552
|
+
* Sealing (deep-freezing the map and each per-token list) ensures that any
|
|
553
|
+
* `.add()` call on the builder after `build()` cannot mutate what the root
|
|
554
|
+
* and its descendants see — the container's view is fixed at construction time.
|
|
555
|
+
*/
|
|
556
|
+
build(): ServiceProvider<Root | Children>;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** Base class for every error the container raises. */
|
|
560
|
+
declare class DiError extends Error {
|
|
561
|
+
constructor(message: string);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* A token was requested but no registration exists for it anywhere in the
|
|
565
|
+
* resolving scope's chain (nor on the builder's base map).
|
|
566
|
+
*/
|
|
567
|
+
declare class UnregisteredTokenError extends DiError {
|
|
568
|
+
readonly token: Token;
|
|
569
|
+
constructor(token: Token);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* A registration carries a lifetime scope, but no ancestor scope in the
|
|
573
|
+
* resolving chain has a matching name. This is the captive-dependency /
|
|
574
|
+
* misconfiguration detector — the engine never auto-creates a scope to satisfy
|
|
575
|
+
* the lifetime.
|
|
576
|
+
*/
|
|
577
|
+
declare class MissingScopeError extends DiError {
|
|
578
|
+
readonly token: Token;
|
|
579
|
+
readonly scope: string;
|
|
580
|
+
readonly availableScopes: readonly string[];
|
|
581
|
+
constructor(token: Token, scope: string, availableScopes: readonly string[]);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* A constructor with parameters has no DepRecord in the WeakMap — the
|
|
585
|
+
* transformer never saw it and it was never hand-annotated.
|
|
586
|
+
*/
|
|
587
|
+
declare class MissingMetadataError extends DiError {
|
|
588
|
+
readonly token: Token;
|
|
589
|
+
readonly ctorName: string;
|
|
590
|
+
constructor(token: Token, ctorName: string);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* A constructor has DepRecord signatures, but none of them is directly
|
|
594
|
+
* satisfiable in the owning scope (every signature names at least one token
|
|
595
|
+
* that is not registered, or contains a hole this phase cannot fill).
|
|
596
|
+
*/
|
|
597
|
+
declare class NoSatisfiableSignatureError extends DiError {
|
|
598
|
+
readonly token: Token;
|
|
599
|
+
readonly ctorName: string;
|
|
600
|
+
readonly unsatisfiable: readonly Token[];
|
|
601
|
+
constructor(token: Token, ctorName: string, unsatisfiable: readonly Token[]);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* A token reappeared on the active resolution stack — the dependency graph has
|
|
605
|
+
* a cycle. The message includes the full path that closed the loop.
|
|
606
|
+
*/
|
|
607
|
+
declare class CircularDependencyError extends DiError {
|
|
608
|
+
readonly path: readonly Token[];
|
|
609
|
+
constructor(path: readonly Token[]);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* A constructor parameter is typed as a factory of some token (a `FactoryRef`),
|
|
613
|
+
* but that token cannot be turned into a factory: either it is not registered,
|
|
614
|
+
* or it is registered as a `useValue` / `useFactory` override rather than a
|
|
615
|
+
* class. A factory injects a callable that constructs the target class on
|
|
616
|
+
* demand, so the target must be a class registration.
|
|
617
|
+
*/
|
|
618
|
+
declare class FactoryTargetError extends DiError {
|
|
619
|
+
readonly factoryToken: Token;
|
|
620
|
+
readonly reason: "unregistered" | "not-a-class";
|
|
621
|
+
constructor(factoryToken: Token, reason: "unregistered" | "not-a-class");
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Sync `dispose()` was called on a scope that owns a Promise-valued (thenable)
|
|
625
|
+
* cached instance. A pending Promise cannot be disposed synchronously — the
|
|
626
|
+
* caller must use `disposeAsync()`.
|
|
627
|
+
*/
|
|
628
|
+
declare class AsyncDisposalRequiredError extends DiError {
|
|
629
|
+
constructor();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export { AsyncDisposalRequiredError, CircularDependencyError, DiBuilder, DiError, FactoryTargetError, MissingMetadataError, MissingScopeError, NoSatisfiableSignatureError, Scope, ServiceProvider, UnregisteredTokenError, defineDeps, forCtor, hole, signature };
|
|
633
|
+
export type { AddBuilder, ClassRegistration, Ctor, DepRecord, Factory, FactoryRegistration, ForCtorBuilder, Lifetime, Registration, ResolveScope, Resolver, ScopeFactory, Token, ValueRegistration };
|