@fnioc/di 3.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -24
- package/dist/index.d.ts +142 -68
- package/dist/index.js +115 -26
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ No decorators. No `reflect-metadata`. No runtime type introspection. Feed it str
|
|
|
10
10
|
|
|
11
11
|
The entry point. `Root` is the root scope's name (the app-lifetime scope singletons bind to, default `"singleton"`); `Children` is the union of declarable child-scope names. Scopes are `Root | Children`. Transient (no cache, fresh instance on every resolve) is the default — there is no `"transient"` scope; the absence of `.as()` is what makes a registration transient.
|
|
12
12
|
|
|
13
|
-
```
|
|
13
|
+
```ts
|
|
14
14
|
import { DiBuilder } from "@fnioc/di";
|
|
15
15
|
|
|
16
16
|
const services = new DiBuilder<"singleton", "request">();
|
|
@@ -22,7 +22,7 @@ Registration is append-only: each token holds a **list** of registrations in reg
|
|
|
22
22
|
|
|
23
23
|
Register a concrete implementation against an interface token. The transformer rewrites `add<IFoo>(Foo)` to `add("pkg:IFoo", Foo)` at build time. Hand-fed consumers pass the token string directly.
|
|
24
24
|
|
|
25
|
-
```
|
|
25
|
+
```ts
|
|
26
26
|
// With transformer (author form):
|
|
27
27
|
services.add<ILogger>(ConsoleLogger).as<"singleton">();
|
|
28
28
|
services.add<IUserRepo>(SqlUserRepo).as<"request">();
|
|
@@ -38,13 +38,24 @@ The type constraint on `Concrete` is `new (...args: any[]) => Interface` — pla
|
|
|
38
38
|
|
|
39
39
|
`.as<S>()` checks at compile time that `S` is a declared scope name. Passing an undeclared string is a type error.
|
|
40
40
|
|
|
41
|
+
### `add<I>(Concrete, sig)` — registration-time signature override
|
|
42
|
+
|
|
43
|
+
For third-party classes (ctor not editable) or generic instantiations the transformer cannot infer, supply a positional override array alongside the class:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
add<ICache>(RedisCache, ["pkg:IRedisClient", undefined, "pkg:ILogger"]);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`sig` is `readonly (DepSlot | undefined)[]` — a positional sparse override over the transformer-generated signature. A `DepSlot` at a position overrides the generated token there; `undefined` keeps the generated token. Use explicit `undefined` rather than sparse elision (no-sparse-arrays).
|
|
50
|
+
|
|
51
|
+
Pure token users (no transformer) supply a complete signature via `forCtor(C).signature(...)` instead.
|
|
52
|
+
|
|
41
53
|
### `add(token, { useFactory })` and `add(token, { useValue })`
|
|
42
54
|
|
|
43
55
|
The same `add` surface also takes factory and value specs — registration paths that bypass the dep-metadata system entirely. Recommended for test doubles, third-party instances, and plugin-less consumers. Both return the builder for chaining.
|
|
44
56
|
|
|
45
|
-
```
|
|
46
|
-
// Factory: receives the scope, returns the instance.
|
|
47
|
-
// caches the result at the matching ancestor (singleton-style).
|
|
57
|
+
```ts
|
|
58
|
+
// Factory: receives the scope, returns the instance.
|
|
48
59
|
services.add("pkg:IDb", {
|
|
49
60
|
useFactory: (c) => new PostgresDb(c.resolve<IConfig>("pkg:IConfig")),
|
|
50
61
|
scope: "singleton",
|
|
@@ -58,7 +69,7 @@ services.add("pkg:ICache", {
|
|
|
58
69
|
|
|
59
70
|
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
71
|
|
|
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
|
|
72
|
+
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. The map seals at `build()` — no post-build mutation is possible.
|
|
62
73
|
|
|
63
74
|
---
|
|
64
75
|
|
|
@@ -66,7 +77,7 @@ To override a registration for a specific context (e.g. a test double), register
|
|
|
66
77
|
|
|
67
78
|
Scopes form a parent-linked chain. The root scope is a real, app-lifetime object — minted by `build()`, never auto-created by the container. Child scopes nest from a scope via `createScope`.
|
|
68
79
|
|
|
69
|
-
```
|
|
80
|
+
```ts
|
|
70
81
|
const root = services.build(); // app lifetime — named by Root
|
|
71
82
|
const req = root.createScope("request"); // per HTTP request
|
|
72
83
|
```
|
|
@@ -89,7 +100,7 @@ const req = root.createScope("request"); // per HTTP request
|
|
|
89
100
|
|
|
90
101
|
The critical correctness rule: deps are resolved **relative to the scope that will own the instance**, not the scope that triggered the resolve.
|
|
91
102
|
|
|
92
|
-
```
|
|
103
|
+
```ts
|
|
93
104
|
const services = new DiBuilder<"singleton", "request">();
|
|
94
105
|
services.add<ICache>(RedisCache).as<"singleton">();
|
|
95
106
|
services.add<IUserContext>(HttpUserContext).as<"request">();
|
|
@@ -114,9 +125,9 @@ This mirrors `Microsoft.Extensions.DependencyInjection`'s scope-validation disci
|
|
|
114
125
|
|
|
115
126
|
## Greedy overload selection
|
|
116
127
|
|
|
117
|
-
When a constructor has multiple registered signatures (stacked `@signature` decorators or chained `forCtor.signature()` calls), the engine selects by scanning **longest → shortest** and picking the first signature where every
|
|
128
|
+
When a constructor has multiple registered signatures (stacked `@signature` decorators or chained `forCtor.signature()` calls), the engine selects by scanning **longest → shortest** and picking the first signature where every resolvable parameter token is satisfiable (registered in the container). Equal-arity ties break by registration order.
|
|
118
129
|
|
|
119
|
-
```
|
|
130
|
+
```ts
|
|
120
131
|
// Two overloads: prefer the one with ILogger if available
|
|
121
132
|
@signature("pkg:ILogger", "pkg:IDb")
|
|
122
133
|
@signature("pkg:IDb")
|
|
@@ -144,7 +155,7 @@ Circular dependency detected:
|
|
|
144
155
|
|
|
145
156
|
Closing a scope disposes the instances it owns in **reverse construction order**. Only instances implementing the native TC39 disposal contract are disposed.
|
|
146
157
|
|
|
147
|
-
```
|
|
158
|
+
```ts
|
|
148
159
|
// Sync disposal
|
|
149
160
|
scope.dispose(): void
|
|
150
161
|
|
|
@@ -168,7 +179,7 @@ Instances owned by ancestor scopes are disposed when those scopes close, not whe
|
|
|
168
179
|
|
|
169
180
|
The container never awaits. Async is expressed as `Promise<T>` values through the sync channel.
|
|
170
181
|
|
|
171
|
-
```
|
|
182
|
+
```ts
|
|
172
183
|
// Register an async factory
|
|
173
184
|
services.add("pkg:IDb", {
|
|
174
185
|
useFactory: async (c) => {
|
|
@@ -197,11 +208,11 @@ The transformer unwraps `Promise<X>` at dep-extraction: a parameter typed `Promi
|
|
|
197
208
|
|
|
198
209
|
A constructor parameter whose type annotation is an inline function type returning a registered interface is injected as a **factory** — a callable that builds the target on demand — rather than a resolved instance.
|
|
199
210
|
|
|
200
|
-
```
|
|
211
|
+
```ts
|
|
201
212
|
// IDb is a registered class. This parameter receives a callable:
|
|
202
213
|
constructor(makeDb: () => IDb) { ... }
|
|
203
214
|
|
|
204
|
-
// Partial factory — the
|
|
215
|
+
// Partial factory — the caller fills caller-supplied params:
|
|
205
216
|
constructor(makeRepo: (tableName: string) => IUserRepo) { ... }
|
|
206
217
|
```
|
|
207
218
|
|
|
@@ -209,7 +220,7 @@ constructor(makeRepo: (tableName: string) => IUserRepo) { ... }
|
|
|
209
220
|
|
|
210
221
|
A **named** callable interface is NOT treated as a factory — it resolves as a normal service keyed on that interface's own token:
|
|
211
222
|
|
|
212
|
-
```
|
|
223
|
+
```ts
|
|
213
224
|
interface IDbFactory { (): IDb }
|
|
214
225
|
|
|
215
226
|
// Resolves as the "pkg:IDbFactory" token, not a factory for IDb
|
|
@@ -218,13 +229,29 @@ constructor(dbFactory: IDbFactory) { ... }
|
|
|
218
229
|
|
|
219
230
|
Name the interface to opt out of factory interpretation whenever your function-typed service should itself be a registered dep.
|
|
220
231
|
|
|
232
|
+
### `resolveFactory(type, params?)`
|
|
233
|
+
|
|
234
|
+
Resolve a factory callable for the token rather than an instance:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
// Without params → strict zero-arg () => T; every slot must resolve from the container
|
|
238
|
+
const makeDb = scope.resolveFactory("pkg:IDb");
|
|
239
|
+
const db = makeDb(); // all deps resolved from container
|
|
240
|
+
|
|
241
|
+
// With params → factory (...params) => T; named tokens filled by caller, rest from container
|
|
242
|
+
const makeRepo = scope.resolveFactory("pkg:IUserRepo", ["app:tableName"]);
|
|
243
|
+
const repo = makeRepo("users"); // tableName filled by caller; ILogger, IDb from container
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`params` is the complete authored-order list of caller-supplied token strings, matched by token (first-occurrence, left-to-right). Passing `params` pins the factory's shape — it no longer drifts as registration state changes.
|
|
247
|
+
|
|
221
248
|
### Partial / positional factories
|
|
222
249
|
|
|
223
|
-
The injected callable exposes **only the target constructor's
|
|
250
|
+
The injected callable exposes **only the target constructor's caller-supplied parameters**, in their relative order. Registered deps are resolved by the container at call time.
|
|
224
251
|
|
|
225
|
-
```
|
|
252
|
+
```ts
|
|
226
253
|
// IUserRepo concrete: constructor(log: ILogger, tableName: string, db: IDb)
|
|
227
|
-
// ILogger and IDb are registered; tableName is not (
|
|
254
|
+
// ILogger and IDb are registered; tableName is not registered (caller-supplied).
|
|
228
255
|
// Injected factory type: (tableName: string) => IUserRepo
|
|
229
256
|
|
|
230
257
|
class RequestHandler {
|
|
@@ -237,7 +264,7 @@ class RequestHandler {
|
|
|
237
264
|
}
|
|
238
265
|
```
|
|
239
266
|
|
|
240
|
-
There are no Ramda-style placeholders. The factory's call arity is exactly the count of
|
|
267
|
+
There are no Ramda-style placeholders. The factory's call arity is exactly the count of caller-supplied parameters; the caller never sees the full constructor shape.
|
|
241
268
|
|
|
242
269
|
### Lifetime semantics
|
|
243
270
|
|
|
@@ -245,10 +272,10 @@ The injected factory is a closure captured at injection time, referencing the ow
|
|
|
245
272
|
|
|
246
273
|
| Factory kind | Lifetime behavior |
|
|
247
274
|
|---|---|
|
|
248
|
-
| **Zero-arg** (`() => IFoo`, no
|
|
249
|
-
| **Parameterized** (caller args fill
|
|
275
|
+
| **Zero-arg** (`() => IFoo`, no caller-supplied params) | Routes through normal `resolve` — respects the target's registered lifetime. A singleton target returns the same instance on every call; a transient target yields a fresh one. |
|
|
276
|
+
| **Parameterized** (caller args fill caller-supplied params) | Builds a **fresh instance on every call**, bypassing the instance cache. Caller args differ per invocation, so caching would be wrong — two calls with different arguments must not collapse to one cached instance. |
|
|
250
277
|
|
|
251
|
-
The captive-dependency rule
|
|
278
|
+
The captive-dependency rule holds at call time: the target's own deps are resolved relative to the scope that owns the factory-holding instance. A factory captured by a singleton that tries to build a request-scoped target still throws `MissingScopeError` when invoked.
|
|
252
279
|
|
|
253
280
|
### `FactoryTargetError`
|
|
254
281
|
|
|
@@ -263,6 +290,23 @@ Note: `FactoryTargetError` is thrown when the factory callable is constructed (a
|
|
|
263
290
|
|
|
264
291
|
---
|
|
265
292
|
|
|
293
|
+
## Union slots
|
|
294
|
+
|
|
295
|
+
A `Union` dep slot tries each member in declaration order and resolves to the first registered one. Throw if none resolves.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { union } from "@fnioc/di";
|
|
299
|
+
|
|
300
|
+
forCtor(Handler).signature(
|
|
301
|
+
union("pkg:IRedis", "pkg:IMemoryCache"),
|
|
302
|
+
"pkg:ILogger",
|
|
303
|
+
);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Token users construct `Union` slots with `union(...)`. Transformer users write an inline `A | B` annotation and the transformer lowers it automatically. See [`@fnioc/transformer`](../transformer/README.md) for the named-vs-inline distinction.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
266
310
|
## API reference
|
|
267
311
|
|
|
268
312
|
### `DiBuilder<Root, Children>`
|
|
@@ -270,7 +314,8 @@ Note: `FactoryTargetError` is thrown when the factory callable is constructed (a
|
|
|
270
314
|
| Member | Signature | Description |
|
|
271
315
|
|---|---|---|
|
|
272
316
|
| `add<I>(Concrete)` | `(ctor: new (...) => I) => AddBuilder` | Register a concrete class against interface `I`. |
|
|
273
|
-
|
|
|
317
|
+
| `add<I>(Concrete, sig)` | `(ctor, sig: readonly (DepSlot \| undefined)[]) => AddBuilder` | Register with a positional signature override. |
|
|
318
|
+
| `.as<S>()` | `(scope: S) => void` | Set the lifetime scope. No call → transient. |
|
|
274
319
|
| `add(token, ctor)` | `(token: string, ctor) => AddBuilder` | Class registration (lowered form). |
|
|
275
320
|
| `addFactory(token, factory)` | `(token: string, factory: (sp: Resolver) => T) => AddBuilder` | Factory registration. No dep metadata required — the factory receives the live `Resolver`. |
|
|
276
321
|
| `addValue(token, value)` | `(token: string, value: unknown) => void` | Value registration. A pre-built instance, re-used as-is. |
|
|
@@ -283,7 +328,7 @@ Implements `Resolver` + `ScopeFactory` + `Disposable` / `AsyncDisposable`.
|
|
|
283
328
|
| Member | Signature | Description |
|
|
284
329
|
|---|---|---|
|
|
285
330
|
| `resolve<T>(token)` | `(token: string) => T` | Resolve an instance. Throws on captive-dep violation, missing scope ancestor, or cycle. |
|
|
286
|
-
| `resolveFactory(
|
|
331
|
+
| `resolveFactory(type, params?)` | `(type: string, params?: readonly string[]) => (...args) => T` | Resolve a factory callable. Without `params`, strict zero-arg `() => T`; with `params`, `(...params) => T` matched by token. |
|
|
287
332
|
| `createScope(name)` | `(name: Scopes) => ServiceProvider<Scopes>` | Create a nested child scope. |
|
|
288
333
|
| `dispose()` | `() => void` | Sync close. Throws if any owned instance has async-only disposal. |
|
|
289
334
|
| `disposeAsync()` | `() => Promise<void>` | Async close. |
|
package/dist/index.d.ts
CHANGED
|
@@ -5,24 +5,6 @@ interface Ctor$1<in Args extends readonly any[] = any[], out Instance = any> {
|
|
|
5
5
|
prototype: Instance;
|
|
6
6
|
}
|
|
7
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
8
|
/**
|
|
27
9
|
* Anything dependency metadata can be attached to: a class constructor (its
|
|
28
10
|
* deps are the ctor parameters) or a factory function (its deps are the call
|
|
@@ -40,12 +22,16 @@ type DepTarget = Ctor$1 | Func$1<never[], unknown>;
|
|
|
40
22
|
type Token = string;
|
|
41
23
|
/**
|
|
42
24
|
* Marks a constructor parameter to be injected as a *factory* producing the
|
|
43
|
-
* registered token
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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.
|
|
46
31
|
*/
|
|
47
32
|
interface FactoryRef {
|
|
48
|
-
readonly
|
|
33
|
+
readonly type: Token;
|
|
34
|
+
readonly params?: readonly Token[];
|
|
49
35
|
}
|
|
50
36
|
/**
|
|
51
37
|
* Marks a parameter to be injected with the live resolution scope itself,
|
|
@@ -58,20 +44,28 @@ interface FactoryRef {
|
|
|
58
44
|
interface ScopeRef {
|
|
59
45
|
readonly scope: true;
|
|
60
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
|
+
}
|
|
61
55
|
/**
|
|
62
56
|
* One positional slot in a constructor / factory signature:
|
|
63
57
|
* - a `Token` string — a container-resolved dependency,
|
|
64
|
-
* -
|
|
65
|
-
* - a `
|
|
66
|
-
* - a `
|
|
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.
|
|
67
61
|
*/
|
|
68
|
-
type DepSlot = Token |
|
|
62
|
+
type DepSlot = Token | FactoryRef | ScopeRef | Union;
|
|
69
63
|
/**
|
|
70
64
|
* Per-constructor dependency metadata stored in the global WeakMap.
|
|
71
65
|
*
|
|
72
66
|
* `signatures` is an array of arrays: each element is one constructor signature
|
|
73
|
-
* (for overload support). `signatures[i][j]` is the `DepSlot` — a token,
|
|
74
|
-
* `
|
|
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
|
|
75
69
|
* overload `i`.
|
|
76
70
|
*/
|
|
77
71
|
interface DepRecord {
|
|
@@ -92,7 +86,7 @@ interface DepRecord {
|
|
|
92
86
|
* prototype-chain walk — subclasses do NOT inherit the
|
|
93
87
|
* parent's record).
|
|
94
88
|
* @param signatures An array of signatures; each signature is a positional array
|
|
95
|
-
* of DepSlot (Token |
|
|
89
|
+
* of DepSlot (Token | FactoryRef | ScopeRef | Union) parallel to
|
|
96
90
|
* the target's parameter list.
|
|
97
91
|
*/
|
|
98
92
|
declare function defineDeps(target: DepTarget, signatures: readonly (readonly DepSlot[])[]): void;
|
|
@@ -126,8 +120,8 @@ declare function signature(...tokens: readonly DepSlot[]): Func$1<[Ctor$1, Class
|
|
|
126
120
|
interface ForCtorBuilder {
|
|
127
121
|
/**
|
|
128
122
|
* Appends one constructor signature (a positional array of DepSlot —
|
|
129
|
-
* Token |
|
|
130
|
-
* `this` for chaining.
|
|
123
|
+
* Token | FactoryRef | ScopeRef | Union) to the ctor's dependency metadata.
|
|
124
|
+
* Returns `this` for chaining.
|
|
131
125
|
*
|
|
132
126
|
* Each `.signature(...)` call is one overload. Chaining two calls is
|
|
133
127
|
* equivalent to stacking two `@signature` decorators.
|
|
@@ -147,6 +141,44 @@ interface ForCtorBuilder {
|
|
|
147
141
|
*/
|
|
148
142
|
declare function forCtor(ctor: Ctor$1): ForCtorBuilder;
|
|
149
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
|
+
|
|
150
182
|
type Func<in Args extends readonly any[] = any[], out Return = any> = (...args: Args) => Return;
|
|
151
183
|
|
|
152
184
|
interface Ctor<in Args extends readonly any[] = any[], out Instance = any> {
|
|
@@ -217,11 +249,14 @@ interface Resolver {
|
|
|
217
249
|
resolve<T>(token: Token): T;
|
|
218
250
|
resolve(token: Token): unknown;
|
|
219
251
|
/**
|
|
220
|
-
* Returns a FACTORY for
|
|
221
|
-
*
|
|
222
|
-
*
|
|
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"])`.
|
|
223
258
|
*/
|
|
224
|
-
resolveFactory(
|
|
259
|
+
resolveFactory(type: Token, params?: readonly Token[]): unknown;
|
|
225
260
|
}
|
|
226
261
|
/**
|
|
227
262
|
* The scope-creation surface. Injected into factory parameters typed
|
|
@@ -313,14 +348,14 @@ declare class ServiceProvider<S extends string = string> implements Resolver, Sc
|
|
|
313
348
|
resolve<T>(token: Token): T;
|
|
314
349
|
resolve(token: Token): unknown;
|
|
315
350
|
/**
|
|
316
|
-
* Returns a FACTORY for `
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
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"])`.
|
|
322
357
|
*/
|
|
323
|
-
resolveFactory(
|
|
358
|
+
resolveFactory(type: Token, params?: readonly Token[]): unknown;
|
|
324
359
|
/**
|
|
325
360
|
* Returns the most-recent registration for `token` from the sealed map.
|
|
326
361
|
* The sealed map is shared across all providers in the tree; local overrides
|
|
@@ -375,32 +410,45 @@ declare class ServiceProvider<S extends string = string> implements Resolver, Sc
|
|
|
375
410
|
/**
|
|
376
411
|
* Builds the callable injected for a `FactoryRef` parameter.
|
|
377
412
|
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
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).
|
|
383
426
|
*
|
|
384
427
|
* Lifetime semantics:
|
|
385
|
-
* - A ZERO-ARG factory routes through the normal `resolve` path
|
|
386
|
-
* RESPECTS the target's registered lifetime.
|
|
387
|
-
* - A PARAMETERIZED factory constructs a FRESH instance
|
|
388
|
-
* BYPASSES the instance cache. Caller args differ per call, so caching
|
|
389
|
-
* would be wrong.
|
|
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.
|
|
390
431
|
*
|
|
391
432
|
* The closure captures `owningFrame`. §5.4 holds at call time: the target's
|
|
392
433
|
* deps resolve relative to the scope that owns the factory-holding instance.
|
|
393
434
|
*/
|
|
394
435
|
private makeFactory;
|
|
395
436
|
/**
|
|
396
|
-
* Builds a factory target
|
|
397
|
-
*
|
|
398
|
-
* `
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
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
|
|
403
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.
|
|
404
452
|
*/
|
|
405
453
|
private buildPartitioned;
|
|
406
454
|
/**
|
|
@@ -408,12 +456,12 @@ declare class ServiceProvider<S extends string = string> implements Resolver, Sc
|
|
|
408
456
|
* the first SATISFIABLE one. A slot is satisfiable when it is:
|
|
409
457
|
*
|
|
410
458
|
* - a `FactoryRef` — always satisfiable; injected as a callable;
|
|
411
|
-
* - a `ScopeRef` — always satisfiable; filled with the live provider view;
|
|
459
|
+
* - a `ScopeRef` — always satisfiable; filled with the live provider view;
|
|
460
|
+
* - a `Union` — satisfiable iff at least one member is resolvable; or
|
|
412
461
|
* - a string token whose registration exists in the sealed map.
|
|
413
462
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* order. 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.
|
|
417
465
|
*/
|
|
418
466
|
private selectSignature;
|
|
419
467
|
/**
|
|
@@ -425,11 +473,29 @@ declare class ServiceProvider<S extends string = string> implements Resolver, Sc
|
|
|
425
473
|
*/
|
|
426
474
|
private selectTargetSignature;
|
|
427
475
|
/**
|
|
428
|
-
* True when `slot` is a registered string token in the sealed map. A
|
|
429
|
-
*
|
|
430
|
-
*
|
|
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.
|
|
431
479
|
*/
|
|
432
480
|
private isResolvable;
|
|
481
|
+
/**
|
|
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;
|
|
433
499
|
/**
|
|
434
500
|
* Closes this provider synchronously, disposing the instances its scope frame
|
|
435
501
|
* owns in REVERSE construction order. Only native `Disposable` instances are
|
|
@@ -620,6 +686,14 @@ declare class FactoryTargetError extends DiError {
|
|
|
620
686
|
readonly reason: "unregistered" | "not-a-class";
|
|
621
687
|
constructor(factoryToken: Token, reason: "unregistered" | "not-a-class");
|
|
622
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
|
+
}
|
|
623
697
|
/**
|
|
624
698
|
* Sync `dispose()` was called on a scope that owns a Promise-valued (thenable)
|
|
625
699
|
* cached instance. A pending Promise cannot be disposed synchronously — the
|
|
@@ -629,5 +703,5 @@ declare class AsyncDisposalRequiredError extends DiError {
|
|
|
629
703
|
constructor();
|
|
630
704
|
}
|
|
631
705
|
|
|
632
|
-
export { AsyncDisposalRequiredError, CircularDependencyError, DiBuilder, DiError, FactoryTargetError, MissingMetadataError, MissingScopeError, NoSatisfiableSignatureError, Scope, ServiceProvider, UnregisteredTokenError, defineDeps, forCtor,
|
|
633
|
-
export type { AddBuilder, ClassRegistration, Ctor, DepRecord, Factory, FactoryRegistration, ForCtorBuilder, Lifetime, Registration, ResolveScope, Resolver, ScopeFactory, 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 };
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
// ../core/src/store.ts
|
|
2
|
-
var hole = null;
|
|
3
2
|
var GLOBAL_KEY = Symbol.for("fnioc:deps");
|
|
4
3
|
var globals = globalThis;
|
|
5
4
|
var store = globals[GLOBAL_KEY] ??= new Map;
|
|
5
|
+
|
|
6
6
|
// ../core/src/defineDeps.ts
|
|
7
7
|
function isFactoryRef(slot) {
|
|
8
|
-
return typeof slot === "object" && slot !== null && typeof slot.
|
|
8
|
+
return typeof slot === "object" && slot !== null && typeof slot.type === "string";
|
|
9
9
|
}
|
|
10
10
|
function isScopeRef(slot) {
|
|
11
11
|
return typeof slot === "object" && slot !== null && slot.scope === true;
|
|
12
12
|
}
|
|
13
|
+
function isUnionSlot(slot) {
|
|
14
|
+
return typeof slot === "object" && slot !== null && Array.isArray(slot.union);
|
|
15
|
+
}
|
|
13
16
|
function slotsEqual(a, b) {
|
|
14
17
|
const aIsRef = isFactoryRef(a);
|
|
15
18
|
const bIsRef = isFactoryRef(b);
|
|
16
19
|
if (aIsRef || bIsRef) {
|
|
17
|
-
|
|
20
|
+
if (!aIsRef || !bIsRef)
|
|
21
|
+
return false;
|
|
22
|
+
if (a.type !== b.type)
|
|
23
|
+
return false;
|
|
24
|
+
const aParams = a.params ?? [];
|
|
25
|
+
const bParams = b.params ?? [];
|
|
26
|
+
if (aParams.length !== bParams.length)
|
|
27
|
+
return false;
|
|
28
|
+
for (let i = 0;i < aParams.length; i++) {
|
|
29
|
+
if (aParams[i] !== bParams[i])
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
18
33
|
}
|
|
19
34
|
const aIsScope = isScopeRef(a);
|
|
20
35
|
const bIsScope = isScopeRef(b);
|
|
21
36
|
if (aIsScope || bIsScope) {
|
|
22
37
|
return aIsScope && bIsScope;
|
|
23
38
|
}
|
|
39
|
+
const aIsUnion = isUnionSlot(a);
|
|
40
|
+
const bIsUnion = isUnionSlot(b);
|
|
41
|
+
if (aIsUnion || bIsUnion) {
|
|
42
|
+
if (!aIsUnion || !bIsUnion)
|
|
43
|
+
return false;
|
|
44
|
+
if (a.union.length !== b.union.length)
|
|
45
|
+
return false;
|
|
46
|
+
for (let i = 0;i < a.union.length; i++) {
|
|
47
|
+
if (!slotsEqual(a.union[i], b.union[i]))
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
24
52
|
return a === b;
|
|
25
53
|
}
|
|
26
54
|
function signaturesEqual(a, b) {
|
|
@@ -65,6 +93,12 @@ function forCtor(ctor) {
|
|
|
65
93
|
};
|
|
66
94
|
return builder;
|
|
67
95
|
}
|
|
96
|
+
|
|
97
|
+
// ../core/src/index.ts
|
|
98
|
+
function union(...slots) {
|
|
99
|
+
return { union: slots };
|
|
100
|
+
}
|
|
101
|
+
|
|
68
102
|
// src/errors.ts
|
|
69
103
|
class DiError extends Error {
|
|
70
104
|
constructor(message) {
|
|
@@ -134,6 +168,15 @@ class FactoryTargetError extends DiError {
|
|
|
134
168
|
}
|
|
135
169
|
}
|
|
136
170
|
|
|
171
|
+
class NoSatisfiableUnionError extends DiError {
|
|
172
|
+
members;
|
|
173
|
+
constructor(members) {
|
|
174
|
+
const memberList = members.map((m) => typeof m === "string" ? `"${m}"` : JSON.stringify(m)).join(", ");
|
|
175
|
+
super(`No satisfiable union member found. Tried: [${memberList}]. ` + `Register at least one of the union members before resolving.`);
|
|
176
|
+
this.members = members;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
137
180
|
class AsyncDisposalRequiredError extends DiError {
|
|
138
181
|
constructor() {
|
|
139
182
|
super(`Cannot dispose synchronously: this scope owns a Promise-valued ` + `instance (an async useFactory result). Awaiting it is required ` + `before disposal — call disposeAsync() instead of dispose().`);
|
|
@@ -150,12 +193,9 @@ function isAsyncDisposable(value) {
|
|
|
150
193
|
function isThenable(value) {
|
|
151
194
|
return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
152
195
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
function isScopeRef2(slot) {
|
|
157
|
-
return slot !== null && typeof slot === "object" && slot.scope === true;
|
|
158
|
-
}
|
|
196
|
+
var isFactoryRef2 = isFactoryRef;
|
|
197
|
+
var isScopeRef2 = isScopeRef;
|
|
198
|
+
var isUnion = isUnionSlot;
|
|
159
199
|
|
|
160
200
|
class Scope {
|
|
161
201
|
name;
|
|
@@ -193,8 +233,8 @@ class ServiceProvider {
|
|
|
193
233
|
}
|
|
194
234
|
return this.resolveWith(token, this.frame, []);
|
|
195
235
|
}
|
|
196
|
-
resolveFactory(
|
|
197
|
-
return this.makeFactory({
|
|
236
|
+
resolveFactory(type, params) {
|
|
237
|
+
return this.makeFactory({ type, params }, this.frame);
|
|
198
238
|
}
|
|
199
239
|
lookup(token) {
|
|
200
240
|
const list = this.registrations.get(token);
|
|
@@ -269,7 +309,7 @@ class ServiceProvider {
|
|
|
269
309
|
}
|
|
270
310
|
return sp.resolveWith(depToken, owningFrame, stack);
|
|
271
311
|
},
|
|
272
|
-
resolveFactory: (depToken) => sp.makeFactory({
|
|
312
|
+
resolveFactory: (depToken, depParams) => sp.makeFactory({ type: depToken, params: depParams }, owningFrame),
|
|
273
313
|
createScope: (...args) => {
|
|
274
314
|
const name = args[0] ?? "scoped";
|
|
275
315
|
const childFrame = new Scope(name, owningFrame);
|
|
@@ -289,6 +329,8 @@ class ServiceProvider {
|
|
|
289
329
|
return providerView;
|
|
290
330
|
if (isFactoryRef2(slot))
|
|
291
331
|
return this.makeFactory(slot, owningFrame);
|
|
332
|
+
if (isUnion(slot))
|
|
333
|
+
return this.resolveUnion(slot, owningFrame, stack);
|
|
292
334
|
return this.resolveWith(slot, owningFrame, stack);
|
|
293
335
|
});
|
|
294
336
|
return factory(...args);
|
|
@@ -310,41 +352,56 @@ class ServiceProvider {
|
|
|
310
352
|
if (isFactoryRef2(slot)) {
|
|
311
353
|
return this.makeFactory(slot, owningFrame);
|
|
312
354
|
}
|
|
355
|
+
if (isUnion(slot)) {
|
|
356
|
+
return this.resolveUnion(slot, owningFrame, stack);
|
|
357
|
+
}
|
|
313
358
|
return this.resolveWith(slot, owningFrame, stack);
|
|
314
359
|
});
|
|
315
360
|
return new ctor(...args);
|
|
316
361
|
}
|
|
317
362
|
makeFactory(ref, owningFrame) {
|
|
318
363
|
const sp = this;
|
|
319
|
-
const target = this.lookup(ref.
|
|
364
|
+
const target = this.lookup(ref.type);
|
|
320
365
|
if (target === undefined) {
|
|
321
|
-
throw new FactoryTargetError(ref.
|
|
366
|
+
throw new FactoryTargetError(ref.type, "unregistered");
|
|
322
367
|
}
|
|
323
368
|
if (target.kind === "value") {
|
|
324
|
-
return () => sp.resolveWith(ref.
|
|
369
|
+
return () => sp.resolveWith(ref.type, owningFrame, []);
|
|
370
|
+
}
|
|
371
|
+
const callerParams = ref.params !== undefined && ref.params.length > 0 ? ref.params : undefined;
|
|
372
|
+
if (callerParams === undefined) {
|
|
373
|
+
return () => sp.resolveWith(ref.type, owningFrame, []);
|
|
325
374
|
}
|
|
326
375
|
const depTarget = target.kind === "class" ? target.ctor : target.factory;
|
|
327
376
|
const record = getDeps(depTarget);
|
|
328
377
|
const targetSignature = record === undefined || record.signatures.length === 0 ? undefined : sp.selectTargetSignature(record.signatures);
|
|
329
|
-
|
|
330
|
-
if (!parameterized) {
|
|
331
|
-
return () => sp.resolveWith(ref.factory, owningFrame, []);
|
|
332
|
-
}
|
|
333
|
-
return (...callArgs) => sp.buildPartitioned(target, targetSignature, callArgs, owningFrame);
|
|
378
|
+
return (...callArgs) => sp.buildPartitioned(target, targetSignature, callerParams, callArgs, owningFrame);
|
|
334
379
|
}
|
|
335
|
-
buildPartitioned(target, signature2,
|
|
380
|
+
buildPartitioned(target, signature2, callerParams, callArgs, owningFrame) {
|
|
336
381
|
const stack = [];
|
|
337
382
|
const providerView = this.makeProviderView(owningFrame, stack);
|
|
338
|
-
|
|
383
|
+
if (signature2 === undefined || signature2.length === 0) {
|
|
384
|
+
return target.kind === "class" ? new target.ctor : target.factory(providerView);
|
|
385
|
+
}
|
|
386
|
+
const remainingParamIndices = callerParams.map((_, i) => i);
|
|
339
387
|
const args = signature2.map((slot) => {
|
|
340
388
|
if (isScopeRef2(slot))
|
|
341
389
|
return providerView;
|
|
342
390
|
if (isFactoryRef2(slot))
|
|
343
391
|
return this.makeFactory(slot, owningFrame);
|
|
344
|
-
if (
|
|
345
|
-
return
|
|
392
|
+
if (isUnion(slot))
|
|
393
|
+
return this.resolveUnion(slot, owningFrame, stack);
|
|
394
|
+
const token = slot;
|
|
395
|
+
const matchIdx = remainingParamIndices.findIndex((pi) => callerParams[pi] === token);
|
|
396
|
+
if (matchIdx !== -1) {
|
|
397
|
+
const paramIdx = remainingParamIndices[matchIdx];
|
|
398
|
+
remainingParamIndices.splice(matchIdx, 1);
|
|
399
|
+
return callArgs[paramIdx];
|
|
346
400
|
}
|
|
347
|
-
|
|
401
|
+
if (!this.isResolvable(token)) {
|
|
402
|
+
throw new NoSatisfiableSignatureError(token, token, [token]);
|
|
403
|
+
}
|
|
404
|
+
return this.resolveWith(token, owningFrame, stack);
|
|
348
405
|
});
|
|
349
406
|
return target.kind === "class" ? new target.ctor(...args) : target.factory(...args);
|
|
350
407
|
}
|
|
@@ -356,6 +413,12 @@ class ServiceProvider {
|
|
|
356
413
|
for (const slot of sig) {
|
|
357
414
|
if (isFactoryRef2(slot) || isScopeRef2(slot))
|
|
358
415
|
continue;
|
|
416
|
+
if (isUnion(slot)) {
|
|
417
|
+
if (!this.isResolvableSlot(slot)) {
|
|
418
|
+
satisfiable = false;
|
|
419
|
+
}
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
359
422
|
if (!this.isResolvable(slot)) {
|
|
360
423
|
satisfiable = false;
|
|
361
424
|
if (typeof slot === "string")
|
|
@@ -373,6 +436,31 @@ class ServiceProvider {
|
|
|
373
436
|
isResolvable(slot) {
|
|
374
437
|
return typeof slot === "string" && this.lookup(slot) !== undefined;
|
|
375
438
|
}
|
|
439
|
+
isResolvableSlot(slot) {
|
|
440
|
+
if (isFactoryRef2(slot) || isScopeRef2(slot))
|
|
441
|
+
return true;
|
|
442
|
+
if (isUnion(slot)) {
|
|
443
|
+
return slot.union.some((member) => this.isResolvableSlot(member));
|
|
444
|
+
}
|
|
445
|
+
return this.isResolvable(slot);
|
|
446
|
+
}
|
|
447
|
+
resolveUnion(slot, owningFrame, stack) {
|
|
448
|
+
for (const member of slot.union) {
|
|
449
|
+
if (this.isResolvableSlot(member)) {
|
|
450
|
+
return this.resolveSlot(member, owningFrame, stack);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
throw new NoSatisfiableUnionError(slot.union);
|
|
454
|
+
}
|
|
455
|
+
resolveSlot(slot, owningFrame, stack) {
|
|
456
|
+
if (isScopeRef2(slot))
|
|
457
|
+
return this.makeProviderView(owningFrame, stack);
|
|
458
|
+
if (isFactoryRef2(slot))
|
|
459
|
+
return this.makeFactory(slot, owningFrame);
|
|
460
|
+
if (isUnion(slot))
|
|
461
|
+
return this.resolveUnion(slot, owningFrame, stack);
|
|
462
|
+
return this.resolveWith(slot, owningFrame, stack);
|
|
463
|
+
}
|
|
376
464
|
dispose() {
|
|
377
465
|
if (this.disposed)
|
|
378
466
|
return;
|
|
@@ -486,13 +574,14 @@ class DiBuilder {
|
|
|
486
574
|
}
|
|
487
575
|
}
|
|
488
576
|
export {
|
|
577
|
+
union,
|
|
489
578
|
signature,
|
|
490
|
-
hole,
|
|
491
579
|
forCtor,
|
|
492
580
|
defineDeps,
|
|
493
581
|
UnregisteredTokenError,
|
|
494
582
|
ServiceProvider,
|
|
495
583
|
Scope,
|
|
584
|
+
NoSatisfiableUnionError,
|
|
496
585
|
NoSatisfiableSignatureError,
|
|
497
586
|
MissingScopeError,
|
|
498
587
|
MissingMetadataError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fnioc/di",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "The ioc runtime engine: DiBuilder, scopes, resolution, captive-dependency protection, factories, and native disposal.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dependency-injection",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
|
+
"source": "./src/index.ts",
|
|
24
25
|
"types": "./dist/index.d.ts",
|
|
25
26
|
"import": "./dist/index.js",
|
|
26
27
|
"default": "./dist/index.js"
|