@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 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.
@@ -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
  }
@@ -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;AA8FpB;;GAEG;AACH,qBAAa,SAAS;IAelB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAftC,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,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;IA+B5C,OAAO,CAAC,eAAe;YAgBT,kBAAkB;IAMhC,OAAO,CAAC,mCAAmC;YAoB7B,6BAA6B;YAc7B,4BAA4B;IA4B1C,OAAO,CAAC,6BAA6B;YAQvB,gCAAgC;IAuB9C,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;YAYxB,mBAAmB;IAUjC,OAAO,CAAC,qBAAqB;CA6C9B"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"}
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 && normalized.multi !== true) {
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 = depEntry.forwardRef();
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 depEntry.forwardRef();
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.1",
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.1"
39
+ "@fluojs/core": "^1.0.0-beta.2"
40
40
  },
41
41
  "devDependencies": {
42
42
  "vitest": "^3.2.4"