@fluojs/di 1.0.0-beta.4 → 1.0.0-beta.6
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.ko.md +9 -5
- package/README.md +9 -5
- package/dist/container.d.ts +24 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +191 -19
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -76,6 +76,8 @@ const service = await container.resolve(UserService);
|
|
|
76
76
|
- **request**: `createRequestScope()`마다 새로 생성됩니다.
|
|
77
77
|
- **transient**: resolve할 때마다 새 인스턴스를 만듭니다.
|
|
78
78
|
|
|
79
|
+
dispose 중에는 루트 컨테이너가 먼저 살아 있는 request scope 자식을 정리한 뒤, 자식 dispose 중 하나 이상이 실패하더라도 루트가 소유한 singleton 정리를 계속 수행합니다. 자식/루트 dispose 실패가 여러 개 발생하면 `dispose()`는 모든 shutdown 실패를 확인할 수 있도록 `AggregateError`로 보고합니다.
|
|
80
|
+
|
|
79
81
|
### request scope 분리
|
|
80
82
|
|
|
81
83
|
```ts
|
|
@@ -91,7 +93,7 @@ provider 객체는 등록 시점에 검증됩니다. 모든 객체 provider는 n
|
|
|
91
93
|
|
|
92
94
|
컨테이너는 순환 의존성을 자동으로 감지하고 `CircularDependencyError`를 발생시켜 무한 루프를 방지합니다. 여기에는 직접 참조(A→A), 이중 노드(A→B→A), 깊은 순환(A→B→C→A)이 모두 포함됩니다.
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
선언 순서 때문에 아직 정의되지 않은 토큰을 참조해야 한다면 `forwardRef()`를 사용하세요. `forwardRef()`는 선언 순서 문제를 위해 토큰 조회를 지연할 뿐이며, 실제 생성자 순환을 해소하지는 않습니다. 그런 순환은 여전히 `CircularDependencyError`로 거부됩니다.
|
|
95
97
|
|
|
96
98
|
```typescript
|
|
97
99
|
import { forwardRef } from '@fluojs/di';
|
|
@@ -99,12 +101,13 @@ import { Inject } from '@fluojs/core';
|
|
|
99
101
|
|
|
100
102
|
@Inject(forwardRef(() => ServiceB))
|
|
101
103
|
class ServiceA {
|
|
102
|
-
constructor(private serviceB:
|
|
104
|
+
constructor(private readonly serviceB: ServiceB) {}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
@Inject(forwardRef(() => ServiceA))
|
|
106
107
|
class ServiceB {
|
|
107
|
-
|
|
108
|
+
getStatus() {
|
|
109
|
+
return 'ready';
|
|
110
|
+
}
|
|
108
111
|
}
|
|
109
112
|
```
|
|
110
113
|
|
|
@@ -131,7 +134,7 @@ const service = await container.resolve(DataService);
|
|
|
131
134
|
## 문제 해결
|
|
132
135
|
|
|
133
136
|
### CircularDependencyError
|
|
134
|
-
의존성 그래프에서 순환이 감지될 때 발생합니다. 생성자 주입 항목을 확인하고
|
|
137
|
+
의존성 그래프에서 순환이 감지될 때 발생합니다. 생성자 주입 항목을 확인하고 공유 상태 추출, 중재자 도입, 수명 주기 경계 변경 등으로 순환을 제거하세요. `forwardRef()`는 선언 순서 문제를 위해 토큰 조회만 지연하며, 실제 생성자 순환을 끊지는 않습니다.
|
|
135
138
|
|
|
136
139
|
### 토큰을 찾을 수 없음 (Token Not Found)
|
|
137
140
|
필요한 모든 provider가 컨테이너에 등록되어 있는지 확인하세요. `createRequestScope()`를 사용하는 경우 자식 컨테이너는 부모의 토큰을 해석할 수 있지만, 그 반대는 불가능합니다.
|
|
@@ -145,6 +148,7 @@ const service = await container.resolve(DataService);
|
|
|
145
148
|
| `resolve<T>(token)` | 토큰을 인스턴스로 비동기 해석합니다. |
|
|
146
149
|
| `createRequestScope()` | 요청 스코프 의존성을 위한 자식 컨테이너를 생성합니다. |
|
|
147
150
|
| `has(token)` | 컨테이너나 부모에 토큰이 등록되어 있는지 확인합니다. |
|
|
151
|
+
| `hasRequestScopedDependency(token)` | 토큰 해석 시 provider 그래프에 request-scoped 의존성이나 순환이 있어 request-scope 컨테이너가 필요할 수 있는지 확인합니다. |
|
|
148
152
|
|
|
149
153
|
## 관련 패키지
|
|
150
154
|
|
package/README.md
CHANGED
|
@@ -75,6 +75,8 @@ fluo DI supports three main provider shapes:
|
|
|
75
75
|
- **Request**: Instance is created once per `createRequestScope()` call.
|
|
76
76
|
- **Transient**: A new instance is created every time it is resolved.
|
|
77
77
|
|
|
78
|
+
During disposal, the root container first tears down live request-scope children and then continues with root-owned singleton cleanup even if one or more child disposals fail. When multiple child/root disposals fail, `dispose()` reports an `AggregateError` so callers can inspect every shutdown failure without losing cleanup progress.
|
|
79
|
+
|
|
78
80
|
### Request Scoping
|
|
79
81
|
Isolated containers can be created to handle per-request state without polluting the root container.
|
|
80
82
|
|
|
@@ -91,7 +93,7 @@ Provider objects are validated at registration time: every object provider must
|
|
|
91
93
|
|
|
92
94
|
The container automatically detects circular dependencies and throws a `CircularDependencyError` to prevent infinite loops. This includes direct (A→A), two-node (A→B→A), and deep (A→B→C→A) cycles.
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
Use `forwardRef()` when a token is referenced before its declaration. It defers token lookup for declaration-order issues, but it does not make true constructor cycles resolvable; those cycles are still rejected with `CircularDependencyError`.
|
|
95
97
|
|
|
96
98
|
```typescript
|
|
97
99
|
import { forwardRef } from '@fluojs/di';
|
|
@@ -99,12 +101,13 @@ import { Inject } from '@fluojs/core';
|
|
|
99
101
|
|
|
100
102
|
@Inject(forwardRef(() => ServiceB))
|
|
101
103
|
class ServiceA {
|
|
102
|
-
constructor(private serviceB:
|
|
104
|
+
constructor(private readonly serviceB: ServiceB) {}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
@Inject(forwardRef(() => ServiceA))
|
|
106
107
|
class ServiceB {
|
|
107
|
-
|
|
108
|
+
getStatus() {
|
|
109
|
+
return 'ready';
|
|
110
|
+
}
|
|
108
111
|
}
|
|
109
112
|
```
|
|
110
113
|
|
|
@@ -131,7 +134,7 @@ const service = await container.resolve(DataService);
|
|
|
131
134
|
## Troubleshooting
|
|
132
135
|
|
|
133
136
|
### CircularDependencyError
|
|
134
|
-
Thrown when the container detects a cycle in the dependency graph. Check your constructor injections and
|
|
137
|
+
Thrown when the container detects a cycle in the dependency graph. Check your constructor injections and remove the cycle by extracting shared state, introducing a mediator, or changing the lifetime boundary. `forwardRef()` only defers token lookup for declaration-order issues; it does not break true constructor cycles.
|
|
135
138
|
|
|
136
139
|
### Token Not Found
|
|
137
140
|
Ensure all required providers are registered in the container. If you use `createRequestScope()`, the child container can resolve tokens from the parent, but not vice versa.
|
|
@@ -145,6 +148,7 @@ Ensure all required providers are registered in the container. If you use `creat
|
|
|
145
148
|
| `resolve<T>(token)` | Asynchronously resolves a token to an instance. |
|
|
146
149
|
| `createRequestScope()` | Creates a child container for request-scoped dependencies. |
|
|
147
150
|
| `has(token)` | Checks if a token is registered in the container or its parents. |
|
|
151
|
+
| `hasRequestScopedDependency(token)` | Checks whether resolving a token may require a request-scope container because its provider graph contains request-scoped dependencies or is cyclic. |
|
|
148
152
|
|
|
149
153
|
## Related Packages
|
|
150
154
|
|
package/dist/container.d.ts
CHANGED
|
@@ -16,10 +16,15 @@ export declare class Container {
|
|
|
16
16
|
private readonly staleDisposalErrors;
|
|
17
17
|
private readonly singletonCache;
|
|
18
18
|
private readonly forwardRefTokenCache;
|
|
19
|
+
private readonly providerLookupPlanCache;
|
|
20
|
+
private readonly multiProviderPlanCache;
|
|
21
|
+
private readonly requestScopeVerdictPlanCache;
|
|
22
|
+
private readonly effectiveProviderPlanCache;
|
|
19
23
|
private childScopes;
|
|
20
24
|
private disposePromise;
|
|
21
25
|
private disposed;
|
|
22
26
|
private trackedByRoot;
|
|
27
|
+
private graphRevision;
|
|
23
28
|
constructor(parent?: Container | undefined, requestScopeEnabled?: boolean, singletonCache?: Map<Token, Promise<unknown>>);
|
|
24
29
|
/**
|
|
25
30
|
* Registers providers in the current container scope.
|
|
@@ -54,6 +59,13 @@ export declare class Container {
|
|
|
54
59
|
* @returns `true` when a single or multi provider exists for the token.
|
|
55
60
|
*/
|
|
56
61
|
has(token: Token): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Returns whether resolving a token may require a request-scope container.
|
|
64
|
+
*
|
|
65
|
+
* @param token Provider token to inspect through aliases, multi providers, and dependencies.
|
|
66
|
+
* @returns `true` when the provider graph contains request-scoped dependencies or is cyclic.
|
|
67
|
+
*/
|
|
68
|
+
hasRequestScopedDependency(token: Token): boolean;
|
|
57
69
|
/**
|
|
58
70
|
* Creates a child request-scope container that shares root singleton cache.
|
|
59
71
|
*
|
|
@@ -88,6 +100,10 @@ export declare class Container {
|
|
|
88
100
|
private hasAncestorMultiRegistration;
|
|
89
101
|
private hasMultiRegistration;
|
|
90
102
|
private collectMultiProviders;
|
|
103
|
+
private providerGraphRequiresRequestScope;
|
|
104
|
+
private unregisteredClassRequiresRequestScope;
|
|
105
|
+
private normalizedProviderRequiresRequestScope;
|
|
106
|
+
private dependencyEntryRequiresRequestScope;
|
|
91
107
|
private resolveWithChain;
|
|
92
108
|
private resolveFromRegisteredProviders;
|
|
93
109
|
private requireProvider;
|
|
@@ -126,12 +142,20 @@ export declare class Container {
|
|
|
126
142
|
private collectDisposableInstances;
|
|
127
143
|
private disposeInstancesInReverseOrder;
|
|
128
144
|
private clearDisposalCaches;
|
|
145
|
+
private currentLineageRevision;
|
|
146
|
+
private readCachedPlan;
|
|
147
|
+
private writePlanCache;
|
|
148
|
+
private advanceGraphRevision;
|
|
149
|
+
private clearResolutionPlanCaches;
|
|
129
150
|
private waitForStaleDisposalTasks;
|
|
130
151
|
private scheduleStaleDisposal;
|
|
131
152
|
private throwDisposalErrors;
|
|
153
|
+
private collectDisposalError;
|
|
132
154
|
private isDisposable;
|
|
133
155
|
private instantiate;
|
|
134
156
|
private assertSingletonDependencyScopes;
|
|
157
|
+
private findRequestScopedDependency;
|
|
158
|
+
private findRequestScopedDependencyToken;
|
|
135
159
|
private resolveEffectiveProvider;
|
|
136
160
|
private resolveProviderDependencyToken;
|
|
137
161
|
private resolveForwardRefToken;
|
package/dist/container.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAW3E,OAAO,KAAK,EASV,QAAQ,EAET,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"}
|
package/dist/container.js
CHANGED
|
@@ -126,10 +126,15 @@ export class Container {
|
|
|
126
126
|
staleDisposalErrors = [];
|
|
127
127
|
singletonCache;
|
|
128
128
|
forwardRefTokenCache = new WeakMap();
|
|
129
|
+
providerLookupPlanCache = new Map();
|
|
130
|
+
multiProviderPlanCache = new Map();
|
|
131
|
+
requestScopeVerdictPlanCache = new Map();
|
|
132
|
+
effectiveProviderPlanCache = new Map();
|
|
129
133
|
childScopes;
|
|
130
134
|
disposePromise;
|
|
131
135
|
disposed = false;
|
|
132
136
|
trackedByRoot = false;
|
|
137
|
+
graphRevision = 0;
|
|
133
138
|
constructor(parent, requestScopeEnabled = false, singletonCache) {
|
|
134
139
|
this.parent = parent;
|
|
135
140
|
this.requestScopeEnabled = requestScopeEnabled;
|
|
@@ -166,12 +171,14 @@ export class Container {
|
|
|
166
171
|
const existing = this.multiRegistrations.get(normalized.provide);
|
|
167
172
|
if (existing) {
|
|
168
173
|
existing.push(normalized);
|
|
174
|
+
this.advanceGraphRevision();
|
|
169
175
|
continue;
|
|
170
176
|
}
|
|
171
177
|
this.multiRegistrations.set(normalized.provide, [normalized]);
|
|
172
178
|
} else {
|
|
173
179
|
this.registrations.set(normalized.provide, normalized);
|
|
174
180
|
}
|
|
181
|
+
this.advanceGraphRevision();
|
|
175
182
|
}
|
|
176
183
|
return this;
|
|
177
184
|
}
|
|
@@ -205,10 +212,12 @@ export class Container {
|
|
|
205
212
|
if (normalized.multi) {
|
|
206
213
|
this.multiRegistrations.set(normalized.provide, [normalized]);
|
|
207
214
|
this.multiOverriddenTokens.add(normalized.provide);
|
|
215
|
+
this.advanceGraphRevision();
|
|
208
216
|
continue;
|
|
209
217
|
}
|
|
210
218
|
this.multiOverriddenTokens.add(normalized.provide);
|
|
211
219
|
this.registrations.set(normalized.provide, normalized);
|
|
220
|
+
this.advanceGraphRevision();
|
|
212
221
|
}
|
|
213
222
|
return this;
|
|
214
223
|
}
|
|
@@ -223,6 +232,20 @@ export class Container {
|
|
|
223
232
|
return this.lookupProvider(token) !== undefined || this.hasMulti(token);
|
|
224
233
|
}
|
|
225
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Returns whether resolving a token may require a request-scope container.
|
|
237
|
+
*
|
|
238
|
+
* @param token Provider token to inspect through aliases, multi providers, and dependencies.
|
|
239
|
+
* @returns `true` when the provider graph contains request-scoped dependencies or is cyclic.
|
|
240
|
+
*/
|
|
241
|
+
hasRequestScopedDependency(token) {
|
|
242
|
+
const cached = this.readCachedPlan(this.requestScopeVerdictPlanCache, token);
|
|
243
|
+
if (cached) {
|
|
244
|
+
return cached.value;
|
|
245
|
+
}
|
|
246
|
+
return this.writePlanCache(this.requestScopeVerdictPlanCache, token, this.providerGraphRequiresRequestScope(token, new Set()));
|
|
247
|
+
}
|
|
248
|
+
|
|
226
249
|
/**
|
|
227
250
|
* Creates a child request-scope container that shares root singleton cache.
|
|
228
251
|
*
|
|
@@ -270,6 +293,7 @@ export class Container {
|
|
|
270
293
|
return;
|
|
271
294
|
}
|
|
272
295
|
this.disposed = true;
|
|
296
|
+
this.advanceGraphRevision();
|
|
273
297
|
this.disposePromise = this.disposeAll();
|
|
274
298
|
try {
|
|
275
299
|
await this.disposePromise;
|
|
@@ -279,13 +303,24 @@ export class Container {
|
|
|
279
303
|
}
|
|
280
304
|
}
|
|
281
305
|
async disposeAll() {
|
|
306
|
+
const errors = [];
|
|
282
307
|
try {
|
|
283
308
|
// Dispose all live request-scope children first (root only)
|
|
284
309
|
if (!this.parent && this.childScopes && this.childScopes.size > 0) {
|
|
285
|
-
await Promise.
|
|
310
|
+
const childResults = await Promise.allSettled(Array.from(this.childScopes).map(child => child.dispose()));
|
|
311
|
+
for (const result of childResults) {
|
|
312
|
+
if (result.status === 'rejected') {
|
|
313
|
+
this.collectDisposalError(result.reason, errors);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
286
316
|
this.childScopes.clear();
|
|
287
317
|
}
|
|
288
|
-
|
|
318
|
+
try {
|
|
319
|
+
await this.disposeCache(this.disposalCacheEntries());
|
|
320
|
+
} catch (error) {
|
|
321
|
+
this.collectDisposalError(error, errors);
|
|
322
|
+
}
|
|
323
|
+
this.throwDisposalErrors(errors);
|
|
289
324
|
} finally {
|
|
290
325
|
if (this.parent && this.trackedByRoot) {
|
|
291
326
|
this.root().childScopes?.delete(this);
|
|
@@ -335,15 +370,65 @@ export class Container {
|
|
|
335
370
|
return this.parent?.hasMultiRegistration(token) ?? false;
|
|
336
371
|
}
|
|
337
372
|
collectMultiProviders(token) {
|
|
373
|
+
const cached = this.readCachedPlan(this.multiProviderPlanCache, token);
|
|
374
|
+
if (cached) {
|
|
375
|
+
return [...cached.value];
|
|
376
|
+
}
|
|
338
377
|
const local = this.multiRegistrations.get(token);
|
|
378
|
+
let providers;
|
|
339
379
|
if (this.multiOverriddenTokens.has(token)) {
|
|
340
|
-
|
|
380
|
+
providers = Object.freeze([...(local ?? [])]);
|
|
381
|
+
} else {
|
|
382
|
+
const fromParent = this.parent ? this.parent.collectMultiProviders(token) : [];
|
|
383
|
+
providers = Object.freeze(local ? [...fromParent, ...local] : [...fromParent]);
|
|
384
|
+
}
|
|
385
|
+
this.writePlanCache(this.multiProviderPlanCache, token, providers);
|
|
386
|
+
return [...providers];
|
|
387
|
+
}
|
|
388
|
+
providerGraphRequiresRequestScope(token, visited) {
|
|
389
|
+
if (visited.has(token)) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
visited.add(token);
|
|
393
|
+
try {
|
|
394
|
+
const provider = this.lookupProvider(token);
|
|
395
|
+
const multiProviders = this.collectMultiProviders(token);
|
|
396
|
+
if (!provider && multiProviders.length === 0) {
|
|
397
|
+
return this.unregisteredClassRequiresRequestScope(token, visited);
|
|
398
|
+
}
|
|
399
|
+
if (provider && this.normalizedProviderRequiresRequestScope(provider, visited)) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
return multiProviders.some(multiProvider => this.normalizedProviderRequiresRequestScope(multiProvider, visited));
|
|
403
|
+
} finally {
|
|
404
|
+
visited.delete(token);
|
|
341
405
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
406
|
+
}
|
|
407
|
+
unregisteredClassRequiresRequestScope(token, visited) {
|
|
408
|
+
if (typeof token !== 'function') {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
const metadata = getClassDiMetadata(token);
|
|
412
|
+
if (metadata?.scope === Scope.REQUEST) {
|
|
413
|
+
return true;
|
|
345
414
|
}
|
|
346
|
-
return
|
|
415
|
+
return (metadata?.inject ?? []).some(depEntry => this.dependencyEntryRequiresRequestScope(depEntry, visited));
|
|
416
|
+
}
|
|
417
|
+
normalizedProviderRequiresRequestScope(provider, visited) {
|
|
418
|
+
if (provider.scope === Scope.REQUEST) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
if (provider.type === 'existing' && provider.useExisting !== undefined) {
|
|
422
|
+
return this.providerGraphRequiresRequestScope(provider.useExisting, visited);
|
|
423
|
+
}
|
|
424
|
+
return provider.inject.some(depEntry => this.dependencyEntryRequiresRequestScope(depEntry, visited));
|
|
425
|
+
}
|
|
426
|
+
dependencyEntryRequiresRequestScope(depEntry, visited) {
|
|
427
|
+
const depToken = this.resolveProviderDependencyToken(depEntry);
|
|
428
|
+
if (isOptionalToken(depEntry) && !this.has(depToken)) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
return this.providerGraphRequiresRequestScope(depToken, visited);
|
|
347
432
|
}
|
|
348
433
|
async resolveWithChain(token, chain, activeTokens, allowForwardRef = false) {
|
|
349
434
|
const cachedForwardRef = this.resolveForwardRefCircularDependency(token, chain, activeTokens, allowForwardRef);
|
|
@@ -504,11 +589,13 @@ export class Container {
|
|
|
504
589
|
return this.multiRequestCache;
|
|
505
590
|
}
|
|
506
591
|
lookupProvider(token) {
|
|
507
|
-
const
|
|
508
|
-
if (
|
|
509
|
-
return
|
|
592
|
+
const cached = this.readCachedPlan(this.providerLookupPlanCache, token);
|
|
593
|
+
if (cached) {
|
|
594
|
+
return cached.value;
|
|
510
595
|
}
|
|
511
|
-
|
|
596
|
+
const local = this.registrations.get(token);
|
|
597
|
+
const provider = local ?? this.parent?.lookupProvider(token);
|
|
598
|
+
return this.writePlanCache(this.providerLookupPlanCache, token, provider);
|
|
512
599
|
}
|
|
513
600
|
|
|
514
601
|
/**
|
|
@@ -618,10 +705,40 @@ export class Container {
|
|
|
618
705
|
if (this.parent) {
|
|
619
706
|
this.requestCache?.clear();
|
|
620
707
|
this.multiRequestCache?.clear();
|
|
708
|
+
this.clearResolutionPlanCaches();
|
|
621
709
|
return;
|
|
622
710
|
}
|
|
623
711
|
this.singletonCache.clear();
|
|
624
712
|
this.multiSingletonCache.clear();
|
|
713
|
+
this.clearResolutionPlanCaches();
|
|
714
|
+
}
|
|
715
|
+
currentLineageRevision() {
|
|
716
|
+
const parentRevision = this.parent?.currentLineageRevision();
|
|
717
|
+
return parentRevision ? `${parentRevision}/${this.graphRevision}` : String(this.graphRevision);
|
|
718
|
+
}
|
|
719
|
+
readCachedPlan(cache, token) {
|
|
720
|
+
const cached = cache.get(token);
|
|
721
|
+
if (!cached || cached.lineageRevision !== this.currentLineageRevision()) {
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
return cached;
|
|
725
|
+
}
|
|
726
|
+
writePlanCache(cache, token, value) {
|
|
727
|
+
cache.set(token, {
|
|
728
|
+
lineageRevision: this.currentLineageRevision(),
|
|
729
|
+
value
|
|
730
|
+
});
|
|
731
|
+
return value;
|
|
732
|
+
}
|
|
733
|
+
advanceGraphRevision() {
|
|
734
|
+
this.graphRevision += 1;
|
|
735
|
+
this.clearResolutionPlanCaches();
|
|
736
|
+
}
|
|
737
|
+
clearResolutionPlanCaches() {
|
|
738
|
+
this.providerLookupPlanCache.clear();
|
|
739
|
+
this.multiProviderPlanCache.clear();
|
|
740
|
+
this.requestScopeVerdictPlanCache.clear();
|
|
741
|
+
this.effectiveProviderPlanCache.clear();
|
|
625
742
|
}
|
|
626
743
|
async waitForStaleDisposalTasks() {
|
|
627
744
|
while (this.staleDisposalTasks.size > 0) {
|
|
@@ -652,6 +769,13 @@ export class Container {
|
|
|
652
769
|
throw new AggregateError(errors, 'Container disposal failed for one or more providers.');
|
|
653
770
|
}
|
|
654
771
|
}
|
|
772
|
+
collectDisposalError(error, errors) {
|
|
773
|
+
if (error instanceof AggregateError) {
|
|
774
|
+
errors.push(...error.errors);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
errors.push(error);
|
|
778
|
+
}
|
|
655
779
|
isDisposable(value) {
|
|
656
780
|
return typeof value === 'object' && value !== null && 'onDestroy' in value && typeof value.onDestroy === 'function';
|
|
657
781
|
}
|
|
@@ -686,19 +810,61 @@ export class Container {
|
|
|
686
810
|
if (provider.scope !== Scope.DEFAULT) {
|
|
687
811
|
return;
|
|
688
812
|
}
|
|
689
|
-
|
|
813
|
+
const requestScopedDependency = this.findRequestScopedDependency(provider.inject, new Set([provider.provide]));
|
|
814
|
+
if (requestScopedDependency) {
|
|
815
|
+
throw new ScopeMismatchError(`Singleton provider ${formatTokenName(provider.provide)} depends on request-scoped provider ${formatTokenName(requestScopedDependency)}.`, {
|
|
816
|
+
token: provider.provide,
|
|
817
|
+
scope: 'singleton',
|
|
818
|
+
hint: `Singleton providers cannot depend on request-scoped providers. Either change ${formatTokenName(requestScopedDependency)} to singleton/transient scope, or change ${formatTokenName(provider.provide)} to request scope.`
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
findRequestScopedDependency(depEntries, visited) {
|
|
823
|
+
for (const depEntry of depEntries) {
|
|
690
824
|
const depToken = this.resolveProviderDependencyToken(depEntry);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
});
|
|
825
|
+
if (isOptionalToken(depEntry) && !this.has(depToken)) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const requestScopedToken = this.findRequestScopedDependencyToken(depToken, visited);
|
|
829
|
+
if (requestScopedToken) {
|
|
830
|
+
return requestScopedToken;
|
|
698
831
|
}
|
|
699
832
|
}
|
|
833
|
+
return undefined;
|
|
834
|
+
}
|
|
835
|
+
findRequestScopedDependencyToken(token, visited) {
|
|
836
|
+
if (visited.has(token)) {
|
|
837
|
+
return undefined;
|
|
838
|
+
}
|
|
839
|
+
visited.add(token);
|
|
840
|
+
try {
|
|
841
|
+
const provider = this.resolveEffectiveProvider(token);
|
|
842
|
+
if (provider) {
|
|
843
|
+
if (provider.scope === Scope.REQUEST) {
|
|
844
|
+
return provider.provide;
|
|
845
|
+
}
|
|
846
|
+
return this.findRequestScopedDependency(provider.inject, visited);
|
|
847
|
+
}
|
|
848
|
+
if (typeof token !== 'function') {
|
|
849
|
+
return undefined;
|
|
850
|
+
}
|
|
851
|
+
const metadata = getClassDiMetadata(token);
|
|
852
|
+
if (metadata?.scope === Scope.REQUEST) {
|
|
853
|
+
return token;
|
|
854
|
+
}
|
|
855
|
+
return this.findRequestScopedDependency(metadata?.inject ?? [], visited);
|
|
856
|
+
} finally {
|
|
857
|
+
visited.delete(token);
|
|
858
|
+
}
|
|
700
859
|
}
|
|
701
860
|
resolveEffectiveProvider(token, visited = new Set(), chain = []) {
|
|
861
|
+
const cacheable = visited.size === 0 && chain.length === 0;
|
|
862
|
+
if (cacheable) {
|
|
863
|
+
const cached = this.readCachedPlan(this.effectiveProviderPlanCache, token);
|
|
864
|
+
if (cached) {
|
|
865
|
+
return cached.value;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
702
868
|
let currentToken = token;
|
|
703
869
|
while (true) {
|
|
704
870
|
if (visited.has(currentToken)) {
|
|
@@ -707,9 +873,15 @@ export class Container {
|
|
|
707
873
|
visited.add(currentToken);
|
|
708
874
|
const provider = this.lookupProvider(currentToken);
|
|
709
875
|
if (!provider) {
|
|
876
|
+
if (cacheable) {
|
|
877
|
+
return this.writePlanCache(this.effectiveProviderPlanCache, token, undefined);
|
|
878
|
+
}
|
|
710
879
|
return undefined;
|
|
711
880
|
}
|
|
712
881
|
if (provider.type !== 'existing' || provider.useExisting === undefined) {
|
|
882
|
+
if (cacheable) {
|
|
883
|
+
return this.writePlanCache(this.effectiveProviderPlanCache, token, provider);
|
|
884
|
+
}
|
|
713
885
|
return provider;
|
|
714
886
|
}
|
|
715
887
|
chain.push(currentToken);
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"container",
|
|
10
10
|
"provider"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.6",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
39
|
+
"@fluojs/core": "^1.0.0-beta.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"vitest": "^3.2.4"
|