@fnioc/di 2.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 +9 -6
- package/dist/index.d.ts +175 -194
- package/dist/index.js +96 -113
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ services.add("pkg:ICache", {
|
|
|
58
58
|
|
|
59
59
|
A `useFactory` with `scope: "singleton"` runs once and caches the result; without a `scope` it runs on every resolve (transient). `useValue` is always the same reference.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
To override a registration for a specific context (e.g. a test double), register a later spec for the same token on the `DiBuilder` before calling `build()`. The registration map is append-only and last-registration-wins, so a later `.add(token, ...)` / `.addFactory(token, ...)` / `.addValue(token, ...)` call shadows the earlier one without deleting it. The map seals at `build()` — no post-build mutation is possible.
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
@@ -272,16 +272,19 @@ Note: `FactoryTargetError` is thrown when the factory callable is constructed (a
|
|
|
272
272
|
| `add<I>(Concrete)` | `(ctor: new (...) => I) => AddBuilder` | Register a concrete class against interface `I`. |
|
|
273
273
|
| `.as<S>()` | `(scope: S) → void` | Set the lifetime scope. No call → transient. |
|
|
274
274
|
| `add(token, ctor)` | `(token: string, ctor) => AddBuilder` | Class registration (lowered form). |
|
|
275
|
-
| `
|
|
276
|
-
| `
|
|
275
|
+
| `addFactory(token, factory)` | `(token: string, factory: (sp: Resolver) => T) => AddBuilder` | Factory registration. No dep metadata required — the factory receives the live `Resolver`. |
|
|
276
|
+
| `addValue(token, value)` | `(token: string, value: unknown) => void` | Value registration. A pre-built instance, re-used as-is. |
|
|
277
|
+
| `build()` | `() => ServiceProvider<Root \| Children>` | Seal the registration map and mint the root `ServiceProvider`. No post-build mutation is possible. |
|
|
277
278
|
|
|
278
|
-
### `
|
|
279
|
+
### `ServiceProvider<Scopes>`
|
|
280
|
+
|
|
281
|
+
Implements `Resolver` + `ScopeFactory` + `Disposable` / `AsyncDisposable`.
|
|
279
282
|
|
|
280
283
|
| Member | Signature | Description |
|
|
281
284
|
|---|---|---|
|
|
282
|
-
| `createScope(name)` | `(name: Scopes) => Scope<Scopes>` | Create a nested child scope. |
|
|
283
|
-
| `add(token, spec)` | `(token, { useFactory, scope? } \| { useValue }) => this` | Scope-local override registration. |
|
|
284
285
|
| `resolve<T>(token)` | `(token: string) => T` | Resolve an instance. Throws on captive-dep violation, missing scope ancestor, or cycle. |
|
|
286
|
+
| `resolveFactory(token)` | `(token: string) => (...args) => T` | Resolve a factory callable for the token rather than an instance. |
|
|
287
|
+
| `createScope(name)` | `(name: Scopes) => ServiceProvider<Scopes>` | Create a nested child scope. |
|
|
285
288
|
| `dispose()` | `() => void` | Sync close. Throws if any owned instance has async-only disposal. |
|
|
286
289
|
| `disposeAsync()` | `() => Promise<void>` | Async close. |
|
|
287
290
|
| `[Symbol.dispose]()` | — | Native `using` support. |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
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;
|
|
@@ -28,7 +30,7 @@ declare const hole: null;
|
|
|
28
30
|
* `never[]` rest keeps any concrete function assignable here regardless of its
|
|
29
31
|
* own parameter list.
|
|
30
32
|
*/
|
|
31
|
-
type DepTarget = Ctor$1 |
|
|
33
|
+
type DepTarget = Ctor$1 | Func$1<never[], unknown>;
|
|
32
34
|
/**
|
|
33
35
|
* A stable string identifying an interface — the DI key.
|
|
34
36
|
*
|
|
@@ -115,7 +117,7 @@ declare function defineDeps(target: DepTarget, signatures: readonly (readonly De
|
|
|
115
117
|
* }
|
|
116
118
|
* ```
|
|
117
119
|
*/
|
|
118
|
-
declare function signature(...tokens: readonly DepSlot[]):
|
|
120
|
+
declare function signature(...tokens: readonly DepSlot[]): Func$1<[Ctor$1, ClassDecoratorContext], void>;
|
|
119
121
|
|
|
120
122
|
/**
|
|
121
123
|
* The builder returned by `forCtor`. Chainable — each `.signature()` call
|
|
@@ -145,6 +147,8 @@ interface ForCtorBuilder {
|
|
|
145
147
|
*/
|
|
146
148
|
declare function forCtor(ctor: Ctor$1): ForCtorBuilder;
|
|
147
149
|
|
|
150
|
+
type Func<in Args extends readonly any[] = any[], out Return = any> = (...args: Args) => Return;
|
|
151
|
+
|
|
148
152
|
interface Ctor<in Args extends readonly any[] = any[], out Instance = any> {
|
|
149
153
|
new(...args: Args): Instance;
|
|
150
154
|
prototype: Instance;
|
|
@@ -154,15 +158,15 @@ interface Ctor<in Args extends readonly any[] = any[], out Instance = any> {
|
|
|
154
158
|
* A registration-level factory function. Its parameters are filled by the
|
|
155
159
|
* engine at resolve time, the same way a class constructor's are: a factory
|
|
156
160
|
* WITH a `defineDeps` record has each parameter resolved by its slot (token →
|
|
157
|
-
* resolved instance, `ScopeRef` → the live
|
|
161
|
+
* resolved instance, `ScopeRef` → the live provider, hole → caller-supplied); a
|
|
158
162
|
* factory WITHOUT a record is the plugin-less escape hatch and is called with
|
|
159
|
-
* the live
|
|
163
|
+
* the live provider as its single argument (`(sp) => …`).
|
|
160
164
|
*
|
|
161
165
|
* May be async — it can return a `Promise<T>`. The container never awaits; the
|
|
162
166
|
* Promise flows through the sync resolution channel as a value (§"Async as
|
|
163
167
|
* values"). A consumer that depends on it declares `Promise<T>` and awaits.
|
|
164
168
|
*/
|
|
165
|
-
type Factory =
|
|
169
|
+
type Factory = Func<any[], unknown>;
|
|
166
170
|
/** A class registration: a token bound to a concrete constructor. */
|
|
167
171
|
interface ClassRegistration {
|
|
168
172
|
readonly kind: "class";
|
|
@@ -192,18 +196,24 @@ interface ValueRegistration {
|
|
|
192
196
|
/** Any registration the engine can resolve. */
|
|
193
197
|
type Registration = ClassRegistration | FactoryRegistration | ValueRegistration;
|
|
194
198
|
/**
|
|
195
|
-
* The
|
|
196
|
-
*
|
|
197
|
-
*
|
|
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).
|
|
199
209
|
*
|
|
200
|
-
* `resolve` has
|
|
201
|
-
*
|
|
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):
|
|
202
213
|
* - `resolve<T>(token)` — explicit token, typed return.
|
|
203
214
|
* - `resolve(token)` — explicit token, `unknown` return (dynamic).
|
|
204
215
|
*/
|
|
205
|
-
interface
|
|
206
|
-
resolve<T>(): T;
|
|
216
|
+
interface Resolver {
|
|
207
217
|
resolve<T>(token: Token): T;
|
|
208
218
|
resolve(token: Token): unknown;
|
|
209
219
|
/**
|
|
@@ -212,76 +222,94 @@ interface ResolveScope {
|
|
|
212
222
|
* (a function-typed type arg) lowers to this.
|
|
213
223
|
*/
|
|
214
224
|
resolveFactory(token: Token): unknown;
|
|
215
|
-
|
|
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;
|
|
216
242
|
}
|
|
217
243
|
|
|
218
244
|
/**
|
|
219
|
-
* A node in the
|
|
220
|
-
*
|
|
221
|
-
*
|
|
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).
|
|
222
248
|
*
|
|
223
|
-
* The
|
|
224
|
-
*
|
|
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.
|
|
225
252
|
*/
|
|
226
|
-
declare class Scope
|
|
227
|
-
/** This scope's name
|
|
228
|
-
readonly name:
|
|
229
|
-
/** The parent scope, or
|
|
230
|
-
|
|
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;
|
|
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;
|
|
239
258
|
/** Instances this scope owns and caches, keyed by token. */
|
|
240
|
-
|
|
259
|
+
readonly cache: Map<Token, unknown>;
|
|
241
260
|
/** Owned instances in construction order — disposed in reverse. */
|
|
242
|
-
|
|
243
|
-
private disposed;
|
|
261
|
+
readonly owned: unknown[];
|
|
244
262
|
constructor(
|
|
245
|
-
/** This scope's name
|
|
246
|
-
name:
|
|
247
|
-
/** The parent scope, or
|
|
248
|
-
parent
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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;
|
|
259
283
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* this scope and its descendants only, most-recent-wins.
|
|
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).
|
|
264
287
|
*/
|
|
265
|
-
private
|
|
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);
|
|
266
294
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
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`.
|
|
269
298
|
*/
|
|
270
|
-
|
|
299
|
+
get name(): S;
|
|
271
300
|
/**
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
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).
|
|
275
307
|
*/
|
|
276
|
-
|
|
277
|
-
/** Registers a scope-local VALUE override — the instance itself, no lifetime. */
|
|
278
|
-
addValue(token: Token, value: unknown): this;
|
|
308
|
+
createScope(...args: "scoped" extends S ? [name?: S] : [name: S]): ServiceProvider<S>;
|
|
279
309
|
/**
|
|
280
|
-
* Resolves a token to an instance, walking the
|
|
281
|
-
*
|
|
282
|
-
* cycle-detection stack.
|
|
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.
|
|
283
312
|
*/
|
|
284
|
-
resolve<T>(): T;
|
|
285
313
|
resolve<T>(token: Token): T;
|
|
286
314
|
resolve(token: Token): unknown;
|
|
287
315
|
/**
|
|
@@ -294,63 +322,54 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
|
|
|
294
322
|
*/
|
|
295
323
|
resolveFactory(token: Token): unknown;
|
|
296
324
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
* later `.add()`/`.add(...)` override beats an earlier one without deletion.
|
|
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).
|
|
301
328
|
*/
|
|
302
329
|
private lookup;
|
|
303
330
|
/**
|
|
304
|
-
* Finds the nearest ancestor scope (inclusive
|
|
305
|
-
* `
|
|
331
|
+
* Finds the nearest ancestor scope frame (inclusive) whose name matches
|
|
332
|
+
* `scopeName`, walking UP the chain. Returns `undefined` when none matches.
|
|
306
333
|
*/
|
|
307
|
-
private findOwner;
|
|
308
|
-
/** The chain of scope names from
|
|
309
|
-
private chainNames;
|
|
334
|
+
private static findOwner;
|
|
335
|
+
/** The chain of scope names from `vantage` up to the root, for diagnostics. */
|
|
336
|
+
private static chainNames;
|
|
310
337
|
/**
|
|
311
|
-
* The internal resolver. `
|
|
312
|
-
*
|
|
313
|
-
* separate calls.
|
|
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.
|
|
314
341
|
*/
|
|
315
342
|
private resolveWith;
|
|
316
343
|
/**
|
|
317
|
-
* Builds an instance for `registration`. `
|
|
318
|
-
* chain the dependencies are resolved against — THE critical rule.
|
|
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.
|
|
344
|
+
* Builds an instance for `registration`. `owningFrame` is the scope frame
|
|
345
|
+
* whose chain the dependencies are resolved against — THE critical rule.
|
|
321
346
|
*/
|
|
322
347
|
private instantiate;
|
|
323
348
|
/**
|
|
324
|
-
* The resolution view
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
* `stack` and resolving relative to THIS (the owning) scope.
|
|
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`.
|
|
328
352
|
*/
|
|
329
|
-
private
|
|
353
|
+
private makeProviderView;
|
|
330
354
|
/**
|
|
331
355
|
* Invokes a factory registration under the metadata-vs-scope rule:
|
|
332
356
|
* - factory WITH a `defineDeps` record → resolve each slot (token →
|
|
333
|
-
* resolved instance, `ScopeRef` → the live
|
|
334
|
-
* injected callable) and call `factory(...args)`;
|
|
357
|
+
* resolved instance, `ScopeRef` → the live provider view, `FactoryRef` →
|
|
358
|
+
* an injected callable) and call `factory(...args)`;
|
|
335
359
|
* - factory WITHOUT a record (the plugin-less escape hatch) → call
|
|
336
|
-
* `factory(
|
|
337
|
-
* Deps resolve relative to `
|
|
360
|
+
* `factory(providerView)` with the live provider view as its sole argument.
|
|
361
|
+
* Deps resolve relative to `owningFrame` (the owning scope) — §5.4.
|
|
338
362
|
*/
|
|
339
363
|
private invokeFactory;
|
|
340
364
|
/**
|
|
341
|
-
* Constructs a class instance
|
|
342
|
-
*
|
|
343
|
-
*
|
|
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:
|
|
344
368
|
*
|
|
345
|
-
* - a string token → resolved through
|
|
369
|
+
* - a string token → resolved through the owning frame's chain (selection
|
|
346
370
|
* guarantees every string-token slot here is resolvable);
|
|
347
371
|
* - a `FactoryRef` → injected as a callable (see `makeFactory`);
|
|
348
|
-
* - a `ScopeRef` → the live
|
|
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`.
|
|
372
|
+
* - a `ScopeRef` → the live provider view.
|
|
354
373
|
*/
|
|
355
374
|
private construct;
|
|
356
375
|
/**
|
|
@@ -360,62 +379,41 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
|
|
|
360
379
|
* registration map: each slot that is a registered token is resolved; each
|
|
361
380
|
* slot that is an unregistered token or a `hole` takes the next
|
|
362
381
|
* 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.
|
|
382
|
+
* exposes only the target's unregistered parameters, in their relative order.
|
|
365
383
|
*
|
|
366
384
|
* Lifetime semantics:
|
|
367
|
-
* - A ZERO-ARG factory
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
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.
|
|
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.
|
|
376
390
|
*
|
|
377
|
-
* The closure captures `
|
|
378
|
-
*
|
|
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.
|
|
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.
|
|
382
393
|
*/
|
|
383
394
|
private makeFactory;
|
|
384
395
|
/**
|
|
385
396
|
* Builds a factory target, partitioning its already-selected signature
|
|
386
397
|
* against the live registration map: a registered token is resolved; a
|
|
387
|
-
* `ScopeRef` is the live
|
|
388
|
-
* token or a `hole` takes the next caller-supplied argument
|
|
389
|
-
* class target is `new`ed, a factory target is called.
|
|
390
|
-
* — a parameterized factory bypasses the instance cache
|
|
391
|
-
*
|
|
392
|
-
*
|
|
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.
|
|
393
404
|
*/
|
|
394
405
|
private buildPartitioned;
|
|
395
406
|
/**
|
|
396
407
|
* Greedy signature selection. Scans signatures longest → shortest and returns
|
|
397
408
|
* the first SATISFIABLE one. A slot is satisfiable when it is:
|
|
398
409
|
*
|
|
399
|
-
* - a `FactoryRef` — always satisfiable; injected as a callable
|
|
400
|
-
*
|
|
401
|
-
*
|
|
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.
|
|
406
|
-
*
|
|
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.
|
|
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.
|
|
415
413
|
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
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.
|
|
419
417
|
*/
|
|
420
418
|
private selectSignature;
|
|
421
419
|
/**
|
|
@@ -423,19 +421,19 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
|
|
|
423
421
|
* there is no resolvability gate: a target's unregistered tokens are not
|
|
424
422
|
* unsatisfiable — they are the factory's caller-supplied parameters. So the
|
|
425
423
|
* choice is purely the longest signature, equal-arity ties broken by
|
|
426
|
-
* registration order
|
|
427
|
-
* caller has already checked `signatures.length > 0`).
|
|
424
|
+
* registration order.
|
|
428
425
|
*/
|
|
429
426
|
private selectTargetSignature;
|
|
430
427
|
/**
|
|
431
|
-
* True when `slot` is a registered string token
|
|
432
|
-
*
|
|
433
|
-
*
|
|
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.
|
|
434
431
|
*/
|
|
435
432
|
private isResolvable;
|
|
436
433
|
/**
|
|
437
|
-
* Closes this
|
|
438
|
-
* construction order. Only native `Disposable` instances are
|
|
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.
|
|
439
437
|
*
|
|
440
438
|
* Throws `AsyncDisposalRequiredError` if any owned instance is a Promise
|
|
441
439
|
* (thenable) — a pending Promise cannot be disposed synchronously; the caller
|
|
@@ -443,9 +441,9 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
|
|
|
443
441
|
*/
|
|
444
442
|
dispose(): void;
|
|
445
443
|
/**
|
|
446
|
-
* Closes this
|
|
447
|
-
* first (so an async factory's result settles before teardown), then
|
|
448
|
-
* owned instances in REVERSE construction order — honoring both
|
|
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
|
|
449
447
|
* `Symbol.asyncDispose` and `Symbol.dispose`. Idempotent.
|
|
450
448
|
*/
|
|
451
449
|
disposeAsync(): Promise<void>;
|
|
@@ -467,23 +465,17 @@ declare class Scope<Scopes extends string = string> implements ResolveScope {
|
|
|
467
465
|
*/
|
|
468
466
|
interface AddBuilder<Scopes extends string> {
|
|
469
467
|
/**
|
|
470
|
-
* Attaches the lifetime. Must name a declared
|
|
468
|
+
* Attaches the lifetime — the RUNTIME (lowered) form. Must name a declared
|
|
469
|
+
* scope.
|
|
471
470
|
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
*
|
|
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;
|
|
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;
|
|
487
479
|
}
|
|
488
480
|
/**
|
|
489
481
|
* The registration builder.
|
|
@@ -527,18 +519,6 @@ declare class DiBuilder<Root extends string = "singleton", Children extends stri
|
|
|
527
519
|
* trailing `.as()` leaves the base (transient) registration in place.
|
|
528
520
|
*/
|
|
529
521
|
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
522
|
/**
|
|
543
523
|
* Class registration — a string token bound to a concrete constructor. The
|
|
544
524
|
* runtime form: what the transformer emits for a class, and what a
|
|
@@ -550,29 +530,30 @@ declare class DiBuilder<Root extends string = "singleton", Children extends stri
|
|
|
550
530
|
* runtime form the transformer emits for an authored `add<I>(fn)`, and what a
|
|
551
531
|
* plugin-less caller writes directly.
|
|
552
532
|
*
|
|
553
|
-
* Parameter injection follows the metadata rule (see `
|
|
533
|
+
* Parameter injection follows the metadata rule (see `ServiceProvider`): a
|
|
554
534
|
* factory WITH a `defineDeps` record (emitted by the transformer) has each
|
|
555
535
|
* parameter injected by its slot; a record-less factory (the plugin-less
|
|
556
|
-
* escape hatch) is called with the live
|
|
557
|
-
* => T` and `
|
|
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?)`
|
|
558
538
|
* continuation so a factory caches at a named scope exactly like a class.
|
|
559
539
|
*/
|
|
560
|
-
addFactory(token: Token, factory:
|
|
540
|
+
addFactory(token: Token, factory: Func<[Resolver], unknown>): AddBuilder<Root | Children>;
|
|
561
541
|
/**
|
|
562
542
|
* Value registration — an already-built instance, no deps and no lifetime.
|
|
563
543
|
* Separate from `add` because a value may itself be a function (a callable
|
|
564
544
|
* service), which is structurally indistinguishable from a factory inside one
|
|
565
|
-
* overload.
|
|
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.
|
|
566
548
|
*/
|
|
567
|
-
addValue<I>(value: I): void;
|
|
568
549
|
addValue(token: Token, value: unknown): void;
|
|
569
550
|
/**
|
|
570
|
-
* Mints the root
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
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.
|
|
574
555
|
*/
|
|
575
|
-
build():
|
|
556
|
+
build(): ServiceProvider<Root | Children>;
|
|
576
557
|
}
|
|
577
558
|
|
|
578
559
|
/** Base class for every error the container raises. */
|
|
@@ -648,5 +629,5 @@ declare class AsyncDisposalRequiredError extends DiError {
|
|
|
648
629
|
constructor();
|
|
649
630
|
}
|
|
650
631
|
|
|
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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -160,98 +160,65 @@ function isScopeRef2(slot) {
|
|
|
160
160
|
class Scope {
|
|
161
161
|
name;
|
|
162
162
|
parent;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
ownedOrder = [];
|
|
167
|
-
disposed = false;
|
|
168
|
-
constructor(name, parent, baseRegistrations) {
|
|
163
|
+
cache = new Map;
|
|
164
|
+
owned = [];
|
|
165
|
+
constructor(name, parent) {
|
|
169
166
|
this.name = name;
|
|
170
167
|
this.parent = parent;
|
|
171
|
-
this.baseRegistrations = baseRegistrations;
|
|
172
168
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
class ServiceProvider {
|
|
172
|
+
registrations;
|
|
173
|
+
disposed = false;
|
|
174
|
+
frame;
|
|
175
|
+
constructor(registrations, frame) {
|
|
176
|
+
this.registrations = registrations;
|
|
177
|
+
this.frame = frame;
|
|
178
|
+
}
|
|
179
|
+
get name() {
|
|
180
|
+
if (this.frame === undefined) {
|
|
181
|
+
throw new TypeError("This ServiceProvider has no scope frame (unscoped root).");
|
|
179
182
|
}
|
|
183
|
+
return this.frame.name;
|
|
180
184
|
}
|
|
181
|
-
createScope(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
Scope.appendTo(this.localRegistrations, token, base);
|
|
186
|
-
const map = this.localRegistrations;
|
|
187
|
-
return {
|
|
188
|
-
as(scope) {
|
|
189
|
-
if (scope === undefined)
|
|
190
|
-
return;
|
|
191
|
-
Scope.appendTo(map, token, { ...base, scope });
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
add(token, ctor) {
|
|
196
|
-
return this.appendScopedLocal(token, {
|
|
197
|
-
kind: "class",
|
|
198
|
-
ctor,
|
|
199
|
-
scope: undefined
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
addFactory(token, factory) {
|
|
203
|
-
return this.appendScopedLocal(token, {
|
|
204
|
-
kind: "factory",
|
|
205
|
-
factory,
|
|
206
|
-
scope: undefined
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
addValue(token, value) {
|
|
210
|
-
Scope.appendTo(this.localRegistrations, token, {
|
|
211
|
-
kind: "value",
|
|
212
|
-
useValue: value
|
|
213
|
-
});
|
|
214
|
-
return this;
|
|
185
|
+
createScope(...args) {
|
|
186
|
+
const name = args[0] ?? "scoped";
|
|
187
|
+
const childFrame = new Scope(name, this.frame);
|
|
188
|
+
return new ServiceProvider(this.registrations, childFrame);
|
|
215
189
|
}
|
|
216
190
|
resolve(token) {
|
|
217
191
|
if (token === undefined) {
|
|
218
192
|
throw new TypeError("resolve<T>() requires the @fnioc/transformer plugin (no token at " + "runtime). Without it, resolve with an explicit token: " + 'resolve<T>("my:token").');
|
|
219
193
|
}
|
|
220
|
-
return this.resolveWith(token, []);
|
|
194
|
+
return this.resolveWith(token, this.frame, []);
|
|
221
195
|
}
|
|
222
196
|
resolveFactory(token) {
|
|
223
|
-
return this.makeFactory({ factory: token });
|
|
197
|
+
return this.makeFactory({ factory: token }, this.frame);
|
|
224
198
|
}
|
|
225
199
|
lookup(token) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const local = node.localRegistrations.get(token);
|
|
229
|
-
if (local !== undefined && local.length > 0)
|
|
230
|
-
return local[local.length - 1];
|
|
231
|
-
node = node.parent;
|
|
232
|
-
}
|
|
233
|
-
const base = this.baseRegistrations.get(token);
|
|
234
|
-
return base !== undefined && base.length > 0 ? base[base.length - 1] : undefined;
|
|
200
|
+
const list = this.registrations.get(token);
|
|
201
|
+
return list !== undefined && list.length > 0 ? list[list.length - 1] : undefined;
|
|
235
202
|
}
|
|
236
|
-
findOwner(
|
|
237
|
-
let node =
|
|
203
|
+
static findOwner(vantage, scopeName) {
|
|
204
|
+
let node = vantage;
|
|
238
205
|
while (node !== undefined) {
|
|
239
|
-
if (node.name ===
|
|
206
|
+
if (node.name === scopeName)
|
|
240
207
|
return node;
|
|
241
208
|
node = node.parent;
|
|
242
209
|
}
|
|
243
210
|
return;
|
|
244
211
|
}
|
|
245
|
-
chainNames() {
|
|
212
|
+
static chainNames(vantage) {
|
|
246
213
|
const names = [];
|
|
247
|
-
let node =
|
|
214
|
+
let node = vantage;
|
|
248
215
|
while (node !== undefined) {
|
|
249
216
|
names.push(node.name);
|
|
250
217
|
node = node.parent;
|
|
251
218
|
}
|
|
252
219
|
return names;
|
|
253
220
|
}
|
|
254
|
-
resolveWith(token, stack) {
|
|
221
|
+
resolveWith(token, vantage, stack) {
|
|
255
222
|
if (stack.includes(token)) {
|
|
256
223
|
throw new CircularDependencyError([...stack, token]);
|
|
257
224
|
}
|
|
@@ -265,65 +232,68 @@ class Scope {
|
|
|
265
232
|
if (registration.scope === undefined) {
|
|
266
233
|
stack.push(token);
|
|
267
234
|
try {
|
|
268
|
-
return this.instantiate(token, registration,
|
|
235
|
+
return this.instantiate(token, registration, vantage, stack);
|
|
269
236
|
} finally {
|
|
270
237
|
stack.pop();
|
|
271
238
|
}
|
|
272
239
|
}
|
|
273
|
-
const owner =
|
|
240
|
+
const owner = ServiceProvider.findOwner(vantage, registration.scope);
|
|
274
241
|
if (owner === undefined) {
|
|
275
|
-
throw new MissingScopeError(token, registration.scope,
|
|
242
|
+
throw new MissingScopeError(token, registration.scope, ServiceProvider.chainNames(vantage));
|
|
276
243
|
}
|
|
277
|
-
if (owner.
|
|
278
|
-
return owner.
|
|
244
|
+
if (owner.cache.has(token)) {
|
|
245
|
+
return owner.cache.get(token);
|
|
279
246
|
}
|
|
280
247
|
stack.push(token);
|
|
281
248
|
try {
|
|
282
|
-
const instance =
|
|
283
|
-
owner.
|
|
284
|
-
owner.
|
|
249
|
+
const instance = this.instantiate(token, registration, owner, stack);
|
|
250
|
+
owner.cache.set(token, instance);
|
|
251
|
+
owner.owned.push(instance);
|
|
285
252
|
return instance;
|
|
286
253
|
} finally {
|
|
287
254
|
stack.pop();
|
|
288
255
|
}
|
|
289
256
|
}
|
|
290
|
-
instantiate(token, registration,
|
|
257
|
+
instantiate(token, registration, owningFrame, stack) {
|
|
291
258
|
if (registration.kind === "factory") {
|
|
292
|
-
return
|
|
259
|
+
return this.invokeFactory(token, registration.factory, owningFrame, stack);
|
|
293
260
|
}
|
|
294
|
-
return
|
|
261
|
+
return this.construct(token, registration.ctor, owningFrame, stack);
|
|
295
262
|
}
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
263
|
+
makeProviderView(owningFrame, stack) {
|
|
264
|
+
const sp = this;
|
|
265
|
+
return {
|
|
299
266
|
resolve: (depToken) => {
|
|
300
267
|
if (depToken === undefined) {
|
|
301
268
|
throw new TypeError("resolve<T>() requires the @fnioc/transformer plugin (no token at " + "runtime).");
|
|
302
269
|
}
|
|
303
|
-
return
|
|
270
|
+
return sp.resolveWith(depToken, owningFrame, stack);
|
|
304
271
|
},
|
|
305
|
-
resolveFactory: (depToken) =>
|
|
306
|
-
createScope: (
|
|
272
|
+
resolveFactory: (depToken) => sp.makeFactory({ factory: depToken }, owningFrame),
|
|
273
|
+
createScope: (...args) => {
|
|
274
|
+
const name = args[0] ?? "scoped";
|
|
275
|
+
const childFrame = new Scope(name, owningFrame);
|
|
276
|
+
return new ServiceProvider(sp.registrations, childFrame);
|
|
277
|
+
}
|
|
307
278
|
};
|
|
308
|
-
return view;
|
|
309
279
|
}
|
|
310
|
-
invokeFactory(token, factory, stack) {
|
|
311
|
-
const
|
|
280
|
+
invokeFactory(token, factory, owningFrame, stack) {
|
|
281
|
+
const providerView = this.makeProviderView(owningFrame, stack);
|
|
312
282
|
const record = getDeps(factory);
|
|
313
283
|
if (record === undefined || record.signatures.length === 0) {
|
|
314
|
-
return factory(
|
|
284
|
+
return factory(providerView);
|
|
315
285
|
}
|
|
316
|
-
const signature2 = this.selectSignature(token, factory.name, record.signatures);
|
|
286
|
+
const signature2 = this.selectSignature(token, factory.name, record.signatures, owningFrame);
|
|
317
287
|
const args = signature2.map((slot) => {
|
|
318
288
|
if (isScopeRef2(slot))
|
|
319
|
-
return
|
|
289
|
+
return providerView;
|
|
320
290
|
if (isFactoryRef2(slot))
|
|
321
|
-
return this.makeFactory(slot);
|
|
322
|
-
return this.resolveWith(slot, stack);
|
|
291
|
+
return this.makeFactory(slot, owningFrame);
|
|
292
|
+
return this.resolveWith(slot, owningFrame, stack);
|
|
323
293
|
});
|
|
324
294
|
return factory(...args);
|
|
325
295
|
}
|
|
326
|
-
construct(token, ctor, stack) {
|
|
296
|
+
construct(token, ctor, owningFrame, stack) {
|
|
327
297
|
const record = getDeps(ctor);
|
|
328
298
|
if (record === undefined || record.signatures.length === 0) {
|
|
329
299
|
if (ctor.length > 0) {
|
|
@@ -331,52 +301,54 @@ class Scope {
|
|
|
331
301
|
}
|
|
332
302
|
return new ctor;
|
|
333
303
|
}
|
|
334
|
-
const signature2 = this.selectSignature(token, ctor.name, record.signatures);
|
|
304
|
+
const signature2 = this.selectSignature(token, ctor.name, record.signatures, owningFrame);
|
|
305
|
+
const providerView = this.makeProviderView(owningFrame, stack);
|
|
335
306
|
const args = signature2.map((slot) => {
|
|
336
307
|
if (isScopeRef2(slot)) {
|
|
337
|
-
return
|
|
308
|
+
return providerView;
|
|
338
309
|
}
|
|
339
310
|
if (isFactoryRef2(slot)) {
|
|
340
|
-
return this.makeFactory(slot);
|
|
311
|
+
return this.makeFactory(slot, owningFrame);
|
|
341
312
|
}
|
|
342
|
-
return this.resolveWith(slot, stack);
|
|
313
|
+
return this.resolveWith(slot, owningFrame, stack);
|
|
343
314
|
});
|
|
344
315
|
return new ctor(...args);
|
|
345
316
|
}
|
|
346
|
-
makeFactory(ref) {
|
|
347
|
-
const
|
|
317
|
+
makeFactory(ref, owningFrame) {
|
|
318
|
+
const sp = this;
|
|
348
319
|
const target = this.lookup(ref.factory);
|
|
349
320
|
if (target === undefined) {
|
|
350
321
|
throw new FactoryTargetError(ref.factory, "unregistered");
|
|
351
322
|
}
|
|
352
323
|
if (target.kind === "value") {
|
|
353
|
-
return () =>
|
|
324
|
+
return () => sp.resolveWith(ref.factory, owningFrame, []);
|
|
354
325
|
}
|
|
355
326
|
const depTarget = target.kind === "class" ? target.ctor : target.factory;
|
|
356
327
|
const record = getDeps(depTarget);
|
|
357
|
-
const targetSignature = record === undefined || record.signatures.length === 0 ? undefined :
|
|
358
|
-
const parameterized = targetSignature !== undefined && targetSignature.some((slot) => !isFactoryRef2(slot) && !isScopeRef2(slot) && !
|
|
328
|
+
const targetSignature = record === undefined || record.signatures.length === 0 ? undefined : sp.selectTargetSignature(record.signatures);
|
|
329
|
+
const parameterized = targetSignature !== undefined && targetSignature.some((slot) => !isFactoryRef2(slot) && !isScopeRef2(slot) && !sp.isResolvable(slot));
|
|
359
330
|
if (!parameterized) {
|
|
360
|
-
return () =>
|
|
331
|
+
return () => sp.resolveWith(ref.factory, owningFrame, []);
|
|
361
332
|
}
|
|
362
|
-
return (...callArgs) =>
|
|
333
|
+
return (...callArgs) => sp.buildPartitioned(target, targetSignature, callArgs, owningFrame);
|
|
363
334
|
}
|
|
364
|
-
buildPartitioned(target, signature2, callerArgs) {
|
|
335
|
+
buildPartitioned(target, signature2, callerArgs, owningFrame) {
|
|
365
336
|
const stack = [];
|
|
337
|
+
const providerView = this.makeProviderView(owningFrame, stack);
|
|
366
338
|
let nextCallerArg = 0;
|
|
367
339
|
const args = signature2.map((slot) => {
|
|
368
340
|
if (isScopeRef2(slot))
|
|
369
|
-
return
|
|
341
|
+
return providerView;
|
|
370
342
|
if (isFactoryRef2(slot))
|
|
371
|
-
return this.makeFactory(slot);
|
|
343
|
+
return this.makeFactory(slot, owningFrame);
|
|
372
344
|
if (!this.isResolvable(slot)) {
|
|
373
345
|
return callerArgs[nextCallerArg++];
|
|
374
346
|
}
|
|
375
|
-
return this.resolveWith(slot, stack);
|
|
347
|
+
return this.resolveWith(slot, owningFrame, stack);
|
|
376
348
|
});
|
|
377
349
|
return target.kind === "class" ? new target.ctor(...args) : target.factory(...args);
|
|
378
350
|
}
|
|
379
|
-
selectSignature(token, targetName, signatures) {
|
|
351
|
+
selectSignature(token, targetName, signatures, _owningFrame) {
|
|
380
352
|
const ordered = signatures.map((sig, index) => ({ sig, index })).sort((a, b) => b.sig.length !== a.sig.length ? b.sig.length - a.sig.length : a.index - b.index);
|
|
381
353
|
const unsatisfiable = new Set;
|
|
382
354
|
for (const { sig } of ordered) {
|
|
@@ -404,14 +376,15 @@ class Scope {
|
|
|
404
376
|
dispose() {
|
|
405
377
|
if (this.disposed)
|
|
406
378
|
return;
|
|
407
|
-
|
|
379
|
+
const owned = this.frame?.owned ?? [];
|
|
380
|
+
for (const instance of owned) {
|
|
408
381
|
if (isThenable(instance)) {
|
|
409
382
|
throw new AsyncDisposalRequiredError;
|
|
410
383
|
}
|
|
411
384
|
}
|
|
412
385
|
this.disposed = true;
|
|
413
|
-
for (let i =
|
|
414
|
-
const instance =
|
|
386
|
+
for (let i = owned.length - 1;i >= 0; i--) {
|
|
387
|
+
const instance = owned[i];
|
|
415
388
|
if (isDisposable(instance)) {
|
|
416
389
|
instance[Symbol.dispose]();
|
|
417
390
|
}
|
|
@@ -422,8 +395,9 @@ class Scope {
|
|
|
422
395
|
if (this.disposed)
|
|
423
396
|
return;
|
|
424
397
|
this.disposed = true;
|
|
398
|
+
const owned = this.frame?.owned ?? [];
|
|
425
399
|
const settled = [];
|
|
426
|
-
for (const instance of
|
|
400
|
+
for (const instance of owned) {
|
|
427
401
|
settled.push(isThenable(instance) ? await instance : instance);
|
|
428
402
|
}
|
|
429
403
|
for (let i = settled.length - 1;i >= 0; i--) {
|
|
@@ -437,8 +411,10 @@ class Scope {
|
|
|
437
411
|
this.clear();
|
|
438
412
|
}
|
|
439
413
|
clear() {
|
|
440
|
-
this.
|
|
441
|
-
|
|
414
|
+
if (this.frame) {
|
|
415
|
+
this.frame.cache.clear();
|
|
416
|
+
this.frame.owned.length = 0;
|
|
417
|
+
}
|
|
442
418
|
}
|
|
443
419
|
[Symbol.dispose]() {
|
|
444
420
|
this.dispose();
|
|
@@ -500,7 +476,13 @@ class DiBuilder {
|
|
|
500
476
|
this.append(token, { kind: "value", useValue: value });
|
|
501
477
|
}
|
|
502
478
|
build() {
|
|
503
|
-
|
|
479
|
+
const sealed = new Map;
|
|
480
|
+
for (const [token, list] of this.registrations) {
|
|
481
|
+
sealed.set(token, Object.freeze([...list]));
|
|
482
|
+
}
|
|
483
|
+
Object.freeze(sealed);
|
|
484
|
+
const rootFrame = new Scope(this.rootName);
|
|
485
|
+
return new ServiceProvider(sealed, rootFrame);
|
|
504
486
|
}
|
|
505
487
|
}
|
|
506
488
|
export {
|
|
@@ -509,6 +491,7 @@ export {
|
|
|
509
491
|
forCtor,
|
|
510
492
|
defineDeps,
|
|
511
493
|
UnregisteredTokenError,
|
|
494
|
+
ServiceProvider,
|
|
512
495
|
Scope,
|
|
513
496
|
NoSatisfiableSignatureError,
|
|
514
497
|
MissingScopeError,
|
package/package.json
CHANGED