@fluojs/di 1.0.0-beta.1 → 1.0.0-beta.3
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 +4 -0
- package/README.md +4 -0
- package/dist/container.d.ts +3 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +59 -3
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -83,6 +83,10 @@ const requestContainer = container.createRequestScope();
|
|
|
83
83
|
const scopedService = await requestContainer.resolve(RequestScopedService);
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
request scope 컨테이너는 부모 체인의 provider를 해석할 수 있지만, request가 소유하는 등록은 새 singleton provider를 만들 수 없습니다. singleton provider는 request scope를 만들기 전에 루트 컨테이너에 등록하세요. request scope에 로컬 provider를 추가해야 한다면 `scope: 'request'`/`Scope.REQUEST`를 명시하거나 `override()`로 의도적인 request-local 교체를 표현하세요. multi provider에도 같은 규칙이 적용됩니다. 기본 scope의 multi provider는 루트 컨테이너에 등록하고, request-local multi provider는 request scope를 명시하거나 `override()`로 교체해야 합니다.
|
|
87
|
+
|
|
88
|
+
provider 객체는 등록 시점에 검증됩니다. 모든 객체 provider는 null이 아닌 `provide` 토큰과 정확히 하나의 전략(`useClass`, `useValue`, `useFactory`, `useExisting`)을 포함해야 합니다. 잘못된 provider 형태는 컨테이너 그래프에 영향을 주기 전에 `InvalidProviderError`를 발생시킵니다.
|
|
89
|
+
|
|
86
90
|
## 순환 의존성 처리
|
|
87
91
|
|
|
88
92
|
컨테이너는 순환 의존성을 자동으로 감지하고 `CircularDependencyError`를 발생시켜 무한 루프를 방지합니다. 여기에는 직접 참조(A→A), 이중 노드(A→B→A), 깊은 순환(A→B→C→A)이 모두 포함됩니다.
|
package/README.md
CHANGED
|
@@ -83,6 +83,10 @@ const requestContainer = container.createRequestScope();
|
|
|
83
83
|
const scopedService = await requestContainer.resolve(RequestScopedService);
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
Request-scope containers may resolve providers from their parent chain, but request-owned registrations must not introduce new singleton providers. Register singleton providers on the root container before creating request scopes. If a request scope needs local additions, declare them with `scope: 'request'`/`Scope.REQUEST` or use `override()` for an explicit request-local replacement. The same rule applies to multi providers: default-scope multi providers belong on the root container, while request-local multi providers must opt into request scope or be replaced through `override()`.
|
|
87
|
+
|
|
88
|
+
Provider objects are validated at registration time: every object provider must include a non-null `provide` token and exactly one strategy (`useClass`, `useValue`, `useFactory`, or `useExisting`). Invalid provider shapes throw `InvalidProviderError` before they can affect the container graph.
|
|
89
|
+
|
|
86
90
|
## Circular Dependency Handling
|
|
87
91
|
|
|
88
92
|
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.
|
package/dist/container.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class Container {
|
|
|
15
15
|
private readonly staleDisposalTasks;
|
|
16
16
|
private readonly staleDisposalErrors;
|
|
17
17
|
private readonly singletonCache;
|
|
18
|
+
private readonly forwardRefTokenCache;
|
|
18
19
|
private readonly childScopes;
|
|
19
20
|
private disposePromise;
|
|
20
21
|
private disposed;
|
|
@@ -94,6 +95,7 @@ export declare class Container {
|
|
|
94
95
|
private resolveMultiProviderInstance;
|
|
95
96
|
private resolveExistingProviderTarget;
|
|
96
97
|
private resolveScopedOrSingletonInstance;
|
|
98
|
+
private getCachedScopedOrSingletonInstance;
|
|
97
99
|
private shouldResolveFromRoot;
|
|
98
100
|
private shouldResolveMultiProviderFromRoot;
|
|
99
101
|
private resolveDepToken;
|
|
@@ -127,6 +129,7 @@ export declare class Container {
|
|
|
127
129
|
private assertSingletonDependencyScopes;
|
|
128
130
|
private resolveEffectiveProvider;
|
|
129
131
|
private resolveProviderDependencyToken;
|
|
132
|
+
private resolveForwardRefToken;
|
|
130
133
|
private resolveProviderDeps;
|
|
131
134
|
private invalidateCachedEntry;
|
|
132
135
|
}
|
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":"AAAA,OAAO,EAAmC,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAW3E,OAAO,KAAK,EASV,QAAQ,EAET,MAAM,YAAY,CAAC;AAuIpB;;GAEG;AACH,qBAAa,SAAS;IAgBlB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAhBtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA0C;IAC7E,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAoB;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsC;IACnE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmD;IACrF,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAmD;IACvF,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA4B;IAC/D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAiB;IACrD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA+B;IAC9D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAsC;IAC3E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwB;IACpD,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAS;gBAGN,MAAM,CAAC,EAAE,SAAS,YAAA,EAClB,mBAAmB,UAAQ,EAC5C,cAAc,CAAC,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAK/C;;;;;;;;;OASG;IACH,QAAQ,CAAC,GAAG,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI;IAyCxC;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,GAAG,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI;IA6BxC;;;;;OAKG;IACH,GAAG,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IAI1B;;;;;OAKG;IACH,kBAAkB,IAAI,SAAS;IAa/B;;;;;;;;;OASG;IACG,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAW7C;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBhB,UAAU;IAgBxB,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,4BAA4B;IAsBpC,OAAO,CAAC,6BAA6B;IAIrC,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,4BAA4B;IAIpC,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,qBAAqB;YAgBf,gBAAgB;YAehB,8BAA8B;IAqC5C,OAAO,CAAC,eAAe;YAgBT,kBAAkB;IAMhC,OAAO,CAAC,mCAAmC;YAoB7B,6BAA6B;YAc7B,4BAA4B;IA4B1C,OAAO,CAAC,6BAA6B;YAQvB,gCAAgC;IAuB9C,OAAO,CAAC,kCAAkC;IAY1C,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,kCAAkC;YAI5B,eAAe;YAwBf,gBAAgB;IAiB9B,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,cAAc;IAUtB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,oBAAoB;YAkBd,YAAY;YAaZ,0BAA0B;YA0B1B,8BAA8B;IAc5C,OAAO,CAAC,mBAAmB;YAWb,yBAAyB;IAMvC,OAAO,CAAC,qBAAqB;IAoB7B,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,YAAY;YAIN,WAAW;IA+BzB,OAAO,CAAC,+BAA+B;IAsBvC,OAAO,CAAC,wBAAwB;IA6BhC,OAAO,CAAC,8BAA8B;IAYtC,OAAO,CAAC,sBAAsB;YAUhB,mBAAmB;IAUjC,OAAO,CAAC,qBAAqB;CA6C9B"}
|
package/dist/container.js
CHANGED
|
@@ -17,6 +17,21 @@ function isClassProvider(value) {
|
|
|
17
17
|
function isExistingProvider(value) {
|
|
18
18
|
return typeof value === 'object' && value !== null && 'useExisting' in value;
|
|
19
19
|
}
|
|
20
|
+
function assertProviderToken(provider) {
|
|
21
|
+
if (!('provide' in provider) || provider.provide == null) {
|
|
22
|
+
throw new InvalidProviderError('Provider object must include a non-null provide token.');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function assertProviderStrategy(provider) {
|
|
26
|
+
const strategyCount = Number('useValue' in provider) + Number('useFactory' in provider) + Number('useClass' in provider) + Number('useExisting' in provider);
|
|
27
|
+
if (strategyCount !== 1) {
|
|
28
|
+
throw new InvalidProviderError('Provider object must declare exactly one of useValue, useFactory, useClass, or useExisting.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function assertObjectProvider(provider) {
|
|
32
|
+
assertProviderToken(provider);
|
|
33
|
+
assertProviderStrategy(provider);
|
|
34
|
+
}
|
|
20
35
|
function normalizeInjectToken(token) {
|
|
21
36
|
if (token == null) {
|
|
22
37
|
throw new InvalidProviderError('Inject token must not be null or undefined. Check that all tokens in @Inject(...) are defined at the point of decoration (forward-reference cycles require forwardRef()).');
|
|
@@ -35,6 +50,7 @@ function normalizeProvider(provider) {
|
|
|
35
50
|
};
|
|
36
51
|
}
|
|
37
52
|
if (isValueProvider(provider)) {
|
|
53
|
+
assertObjectProvider(provider);
|
|
38
54
|
return {
|
|
39
55
|
inject: [],
|
|
40
56
|
multi: provider.multi,
|
|
@@ -45,6 +61,12 @@ function normalizeProvider(provider) {
|
|
|
45
61
|
};
|
|
46
62
|
}
|
|
47
63
|
if (isFactoryProvider(provider)) {
|
|
64
|
+
assertObjectProvider(provider);
|
|
65
|
+
if (typeof provider.useFactory !== 'function') {
|
|
66
|
+
throw new InvalidProviderError('Factory provider useFactory must be a function.', {
|
|
67
|
+
token: provider.provide
|
|
68
|
+
});
|
|
69
|
+
}
|
|
48
70
|
const metadata = provider.resolverClass ? getClassDiMetadata(provider.resolverClass) : undefined;
|
|
49
71
|
return {
|
|
50
72
|
inject: (provider.inject ?? []).map(normalizeInjectToken),
|
|
@@ -56,6 +78,12 @@ function normalizeProvider(provider) {
|
|
|
56
78
|
};
|
|
57
79
|
}
|
|
58
80
|
if (isClassProvider(provider)) {
|
|
81
|
+
assertObjectProvider(provider);
|
|
82
|
+
if (typeof provider.useClass !== 'function') {
|
|
83
|
+
throw new InvalidProviderError('Class provider useClass must be a constructor.', {
|
|
84
|
+
token: provider.provide
|
|
85
|
+
});
|
|
86
|
+
}
|
|
59
87
|
const metadata = getClassDiMetadata(provider.useClass);
|
|
60
88
|
return {
|
|
61
89
|
inject: (provider.inject ?? metadata?.inject ?? []).map(normalizeInjectToken),
|
|
@@ -67,6 +95,12 @@ function normalizeProvider(provider) {
|
|
|
67
95
|
};
|
|
68
96
|
}
|
|
69
97
|
if (isExistingProvider(provider)) {
|
|
98
|
+
assertObjectProvider(provider);
|
|
99
|
+
if (provider.useExisting == null) {
|
|
100
|
+
throw new InvalidProviderError('Alias provider useExisting must be a non-null token.', {
|
|
101
|
+
token: provider.provide
|
|
102
|
+
});
|
|
103
|
+
}
|
|
70
104
|
return {
|
|
71
105
|
inject: [],
|
|
72
106
|
provide: provider.provide,
|
|
@@ -91,6 +125,7 @@ export class Container {
|
|
|
91
125
|
staleDisposalTasks = new Set();
|
|
92
126
|
staleDisposalErrors = [];
|
|
93
127
|
singletonCache;
|
|
128
|
+
forwardRefTokenCache = new WeakMap();
|
|
94
129
|
childScopes = new Set();
|
|
95
130
|
disposePromise;
|
|
96
131
|
disposed = false;
|
|
@@ -118,7 +153,7 @@ export class Container {
|
|
|
118
153
|
}
|
|
119
154
|
for (const provider of providers) {
|
|
120
155
|
const normalized = normalizeProvider(provider);
|
|
121
|
-
if (this.requestScopeEnabled && normalized.scope === Scope.DEFAULT
|
|
156
|
+
if (this.requestScopeEnabled && normalized.scope === Scope.DEFAULT) {
|
|
122
157
|
throw new ScopeMismatchError(`Singleton provider ${String(normalized.provide)} cannot be registered on a request-scope container.`, {
|
|
123
158
|
token: normalized.provide,
|
|
124
159
|
scope: 'singleton',
|
|
@@ -328,6 +363,10 @@ export class Container {
|
|
|
328
363
|
if (provider.scope === 'transient') {
|
|
329
364
|
return await this.withTokenInChain(token, chain, activeTokens, async (c, at) => this.instantiate(provider, c, at));
|
|
330
365
|
}
|
|
366
|
+
const cachedInstance = this.getCachedScopedOrSingletonInstance(provider);
|
|
367
|
+
if (cachedInstance) {
|
|
368
|
+
return await cachedInstance;
|
|
369
|
+
}
|
|
331
370
|
return await this.withTokenInChain(token, chain, activeTokens, async (c, at) => this.resolveScopedOrSingletonInstance(provider, c, at));
|
|
332
371
|
}
|
|
333
372
|
requireProvider(token) {
|
|
@@ -397,6 +436,15 @@ export class Container {
|
|
|
397
436
|
}
|
|
398
437
|
return cache.get(provider.provide);
|
|
399
438
|
}
|
|
439
|
+
getCachedScopedOrSingletonInstance(provider) {
|
|
440
|
+
if (provider.scope !== Scope.DEFAULT) {
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
if (this.shouldResolveFromRoot(provider)) {
|
|
444
|
+
return this.root().getCachedScopedOrSingletonInstance(provider);
|
|
445
|
+
}
|
|
446
|
+
return this.cacheFor(provider).get(provider.provide);
|
|
447
|
+
}
|
|
400
448
|
shouldResolveFromRoot(provider) {
|
|
401
449
|
return provider.scope === Scope.DEFAULT && this.requestScopeEnabled && !this.registrations.has(provider.provide);
|
|
402
450
|
}
|
|
@@ -412,7 +460,7 @@ export class Container {
|
|
|
412
460
|
return this.resolveWithChain(innerToken, chain, activeTokens);
|
|
413
461
|
}
|
|
414
462
|
if (isForwardRef(depEntry)) {
|
|
415
|
-
const resolvedToken =
|
|
463
|
+
const resolvedToken = this.resolveForwardRefToken(depEntry);
|
|
416
464
|
return this.resolveWithChain(resolvedToken, chain, activeTokens, /* allowForwardRef */true);
|
|
417
465
|
}
|
|
418
466
|
return this.resolveWithChain(depEntry, chain, activeTokens);
|
|
@@ -645,13 +693,21 @@ export class Container {
|
|
|
645
693
|
}
|
|
646
694
|
resolveProviderDependencyToken(depEntry) {
|
|
647
695
|
if (isForwardRef(depEntry)) {
|
|
648
|
-
return
|
|
696
|
+
return this.resolveForwardRefToken(depEntry);
|
|
649
697
|
}
|
|
650
698
|
if (isOptionalToken(depEntry)) {
|
|
651
699
|
return depEntry.token;
|
|
652
700
|
}
|
|
653
701
|
return depEntry;
|
|
654
702
|
}
|
|
703
|
+
resolveForwardRefToken(forwardRefEntry) {
|
|
704
|
+
if (this.forwardRefTokenCache.has(forwardRefEntry)) {
|
|
705
|
+
return this.forwardRefTokenCache.get(forwardRefEntry);
|
|
706
|
+
}
|
|
707
|
+
const resolvedToken = forwardRefEntry.forwardRef();
|
|
708
|
+
this.forwardRefTokenCache.set(forwardRefEntry, resolvedToken);
|
|
709
|
+
return resolvedToken;
|
|
710
|
+
}
|
|
655
711
|
async resolveProviderDeps(provider, chain, activeTokens) {
|
|
656
712
|
const deps = new Array(provider.inject.length);
|
|
657
713
|
for (const [index, entry] of provider.inject.entries()) {
|
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.3",
|
|
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.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"vitest": "^3.2.4"
|