@fluojs/di
Advanced tools
+13
-0
@@ -19,2 +19,6 @@ import { type Token } from '@fluojs/core'; | ||
| private readonly forwardRefTokenCache; | ||
| private readonly providerLookupPlanCache; | ||
| private readonly multiProviderPlanCache; | ||
| private readonly requestScopeVerdictPlanCache; | ||
| private readonly effectiveProviderPlanCache; | ||
| private childScopes; | ||
@@ -24,2 +28,3 @@ private disposePromise; | ||
| private trackedByRoot; | ||
| private graphRevision; | ||
| constructor(parent?: Container | undefined, requestScopeEnabled?: boolean, singletonCache?: Map<Token, Promise<unknown>>); | ||
@@ -142,8 +147,16 @@ /** | ||
| private clearDisposalCaches; | ||
| private currentLineageRevision; | ||
| private readCachedPlan; | ||
| private writePlanCache; | ||
| private advanceGraphRevision; | ||
| private clearResolutionPlanCaches; | ||
| private waitForStaleDisposalTasks; | ||
| private scheduleStaleDisposal; | ||
| private throwDisposalErrors; | ||
| private collectDisposalError; | ||
| private isDisposable; | ||
| private instantiate; | ||
| private assertSingletonDependencyScopes; | ||
| private findRequestScopedDependency; | ||
| private findRequestScopedDependencyToken; | ||
| private resolveEffectiveProvider; | ||
@@ -150,0 +163,0 @@ private resolveProviderDependencyToken; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"} |
+138
-21
@@ -129,2 +129,6 @@ import { InvariantError, formatTokenName } from '@fluojs/core'; | ||
| forwardRefTokenCache = new WeakMap(); | ||
| providerLookupPlanCache = new Map(); | ||
| multiProviderPlanCache = new Map(); | ||
| requestScopeVerdictPlanCache = new Map(); | ||
| effectiveProviderPlanCache = new Map(); | ||
| childScopes; | ||
@@ -134,2 +138,3 @@ disposePromise; | ||
| trackedByRoot = false; | ||
| graphRevision = 0; | ||
| constructor(parent, requestScopeEnabled = false, singletonCache) { | ||
@@ -171,2 +176,3 @@ this.parent = parent; | ||
| existing.push(normalized); | ||
| this.advanceGraphRevision(); | ||
| continue; | ||
@@ -178,2 +184,3 @@ } | ||
| } | ||
| this.advanceGraphRevision(); | ||
| } | ||
@@ -212,2 +219,3 @@ return this; | ||
| this.multiOverriddenTokens.add(normalized.provide); | ||
| this.advanceGraphRevision(); | ||
| continue; | ||
@@ -217,2 +225,3 @@ } | ||
| this.registrations.set(normalized.provide, normalized); | ||
| this.advanceGraphRevision(); | ||
| } | ||
@@ -239,3 +248,7 @@ return this; | ||
| hasRequestScopedDependency(token) { | ||
| return this.providerGraphRequiresRequestScope(token, new Set()); | ||
| const cached = this.readCachedPlan(this.requestScopeVerdictPlanCache, token); | ||
| if (cached) { | ||
| return cached.value; | ||
| } | ||
| return this.writePlanCache(this.requestScopeVerdictPlanCache, token, this.providerGraphRequiresRequestScope(token, new Set())); | ||
| } | ||
@@ -290,2 +303,3 @@ | ||
| this.disposed = true; | ||
| this.advanceGraphRevision(); | ||
| this.disposePromise = this.disposeAll(); | ||
@@ -300,9 +314,20 @@ try { | ||
| async disposeAll() { | ||
| const errors = []; | ||
| try { | ||
| // Dispose all live request-scope children first (root only) | ||
| if (!this.parent && this.childScopes && this.childScopes.size > 0) { | ||
| await Promise.all(Array.from(this.childScopes).map(child => child.dispose())); | ||
| const childResults = await Promise.allSettled(Array.from(this.childScopes).map(child => child.dispose())); | ||
| for (const result of childResults) { | ||
| if (result.status === 'rejected') { | ||
| this.collectDisposalError(result.reason, errors); | ||
| } | ||
| } | ||
| this.childScopes.clear(); | ||
| } | ||
| await this.disposeCache(this.disposalCacheEntries()); | ||
| try { | ||
| await this.disposeCache(this.disposalCacheEntries()); | ||
| } catch (error) { | ||
| this.collectDisposalError(error, errors); | ||
| } | ||
| this.throwDisposalErrors(errors); | ||
| } finally { | ||
@@ -357,11 +382,16 @@ if (this.parent && this.trackedByRoot) { | ||
| collectMultiProviders(token) { | ||
| const cached = this.readCachedPlan(this.multiProviderPlanCache, token); | ||
| if (cached) { | ||
| return [...cached.value]; | ||
| } | ||
| const local = this.multiRegistrations.get(token); | ||
| let providers; | ||
| if (this.multiOverriddenTokens.has(token)) { | ||
| return local ?? []; | ||
| providers = Object.freeze([...(local ?? [])]); | ||
| } else { | ||
| const fromParent = this.parent ? this.parent.collectMultiProviders(token) : []; | ||
| providers = Object.freeze(local ? [...fromParent, ...local] : [...fromParent]); | ||
| } | ||
| const fromParent = this.parent ? this.parent.collectMultiProviders(token) : []; | ||
| if (local) { | ||
| return [...fromParent, ...local]; | ||
| } | ||
| return fromParent; | ||
| this.writePlanCache(this.multiProviderPlanCache, token, providers); | ||
| return [...providers]; | ||
| } | ||
@@ -572,7 +602,9 @@ providerGraphRequiresRequestScope(token, visited) { | ||
| lookupProvider(token) { | ||
| const cached = this.readCachedPlan(this.providerLookupPlanCache, token); | ||
| if (cached) { | ||
| return cached.value; | ||
| } | ||
| const local = this.registrations.get(token); | ||
| if (local) { | ||
| return local; | ||
| } | ||
| return this.parent?.lookupProvider(token); | ||
| const provider = local ?? this.parent?.lookupProvider(token); | ||
| return this.writePlanCache(this.providerLookupPlanCache, token, provider); | ||
| } | ||
@@ -687,2 +719,3 @@ | ||
| this.multiRequestCache?.clear(); | ||
| this.clearResolutionPlanCaches(); | ||
| return; | ||
@@ -692,3 +725,32 @@ } | ||
| this.multiSingletonCache.clear(); | ||
| this.clearResolutionPlanCaches(); | ||
| } | ||
| currentLineageRevision() { | ||
| const parentRevision = this.parent?.currentLineageRevision(); | ||
| return parentRevision ? `${parentRevision}/${this.graphRevision}` : String(this.graphRevision); | ||
| } | ||
| readCachedPlan(cache, token) { | ||
| const cached = cache.get(token); | ||
| if (!cached || cached.lineageRevision !== this.currentLineageRevision()) { | ||
| return undefined; | ||
| } | ||
| return cached; | ||
| } | ||
| writePlanCache(cache, token, value) { | ||
| cache.set(token, { | ||
| lineageRevision: this.currentLineageRevision(), | ||
| value | ||
| }); | ||
| return value; | ||
| } | ||
| advanceGraphRevision() { | ||
| this.graphRevision += 1; | ||
| this.clearResolutionPlanCaches(); | ||
| } | ||
| clearResolutionPlanCaches() { | ||
| this.providerLookupPlanCache.clear(); | ||
| this.multiProviderPlanCache.clear(); | ||
| this.requestScopeVerdictPlanCache.clear(); | ||
| this.effectiveProviderPlanCache.clear(); | ||
| } | ||
| async waitForStaleDisposalTasks() { | ||
@@ -723,2 +785,9 @@ while (this.staleDisposalTasks.size > 0) { | ||
| } | ||
| collectDisposalError(error, errors) { | ||
| if (error instanceof AggregateError) { | ||
| errors.push(...error.errors); | ||
| return; | ||
| } | ||
| errors.push(error); | ||
| } | ||
| isDisposable(value) { | ||
@@ -758,15 +827,57 @@ return typeof value === 'object' && value !== null && 'onDestroy' in value && typeof value.onDestroy === 'function'; | ||
| } | ||
| for (const depEntry of provider.inject) { | ||
| const requestScopedDependency = this.findRequestScopedDependency(provider.inject, new Set([provider.provide])); | ||
| if (requestScopedDependency) { | ||
| throw new ScopeMismatchError(`Singleton provider ${formatTokenName(provider.provide)} depends on request-scoped provider ${formatTokenName(requestScopedDependency)}.`, { | ||
| token: provider.provide, | ||
| scope: 'singleton', | ||
| 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.` | ||
| }); | ||
| } | ||
| } | ||
| findRequestScopedDependency(depEntries, visited) { | ||
| for (const depEntry of depEntries) { | ||
| const depToken = this.resolveProviderDependencyToken(depEntry); | ||
| const effectiveProvider = this.resolveEffectiveProvider(depToken); | ||
| if (effectiveProvider?.scope === 'request') { | ||
| throw new ScopeMismatchError(`Singleton provider ${formatTokenName(provider.provide)} depends on request-scoped provider ${formatTokenName(depToken)}.`, { | ||
| token: provider.provide, | ||
| scope: 'singleton', | ||
| hint: `Singleton providers cannot depend on request-scoped providers. Either change ${formatTokenName(depToken)} to singleton/transient scope, or change ${formatTokenName(provider.provide)} to request scope.` | ||
| }); | ||
| if (isOptionalToken(depEntry) && !this.has(depToken)) { | ||
| continue; | ||
| } | ||
| const requestScopedToken = this.findRequestScopedDependencyToken(depToken, visited); | ||
| if (requestScopedToken) { | ||
| return requestScopedToken; | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
| findRequestScopedDependencyToken(token, visited) { | ||
| if (visited.has(token)) { | ||
| return undefined; | ||
| } | ||
| visited.add(token); | ||
| try { | ||
| const provider = this.resolveEffectiveProvider(token); | ||
| if (provider) { | ||
| if (provider.scope === Scope.REQUEST) { | ||
| return provider.provide; | ||
| } | ||
| return this.findRequestScopedDependency(provider.inject, visited); | ||
| } | ||
| if (typeof token !== 'function') { | ||
| return undefined; | ||
| } | ||
| const metadata = getClassDiMetadata(token); | ||
| if (metadata?.scope === Scope.REQUEST) { | ||
| return token; | ||
| } | ||
| return this.findRequestScopedDependency(metadata?.inject ?? [], visited); | ||
| } finally { | ||
| visited.delete(token); | ||
| } | ||
| } | ||
| resolveEffectiveProvider(token, visited = new Set(), chain = []) { | ||
| const cacheable = visited.size === 0 && chain.length === 0; | ||
| if (cacheable) { | ||
| const cached = this.readCachedPlan(this.effectiveProviderPlanCache, token); | ||
| if (cached) { | ||
| return cached.value; | ||
| } | ||
| } | ||
| let currentToken = token; | ||
@@ -780,5 +891,11 @@ while (true) { | ||
| if (!provider) { | ||
| if (cacheable) { | ||
| return this.writePlanCache(this.effectiveProviderPlanCache, token, undefined); | ||
| } | ||
| return undefined; | ||
| } | ||
| if (provider.type !== 'existing' || provider.useExisting === undefined) { | ||
| if (cacheable) { | ||
| return this.writePlanCache(this.effectiveProviderPlanCache, token, provider); | ||
| } | ||
| return provider; | ||
@@ -785,0 +902,0 @@ } |
+2
-2
@@ -12,3 +12,3 @@ { | ||
| ], | ||
| "version": "1.0.0-beta.5", | ||
| "version": "1.0.0-beta.6", | ||
| "private": false, | ||
@@ -40,3 +40,3 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@fluojs/core": "^1.0.0-beta.2" | ||
| "@fluojs/core": "^1.0.0-beta.3" | ||
| }, | ||
@@ -43,0 +43,0 @@ "devDependencies": { |
+8
-5
@@ -79,2 +79,4 @@ # @fluojs/di | ||
| dispose 중에는 루트 컨테이너가 먼저 살아 있는 request scope 자식을 정리한 뒤, 자식 dispose 중 하나 이상이 실패하더라도 루트가 소유한 singleton 정리를 계속 수행합니다. 자식/루트 dispose 실패가 여러 개 발생하면 `dispose()`는 모든 shutdown 실패를 확인할 수 있도록 `AggregateError`로 보고합니다. | ||
| ### request scope 분리 | ||
@@ -95,3 +97,3 @@ | ||
| 순환 의존성을 해결하려면 `forwardRef()`를 사용하여 의존성 토큰의 해석을 지연시키세요. | ||
| 선언 순서 때문에 아직 정의되지 않은 토큰을 참조해야 한다면 `forwardRef()`를 사용하세요. `forwardRef()`는 선언 순서 문제를 위해 토큰 조회를 지연할 뿐이며, 실제 생성자 순환을 해소하지는 않습니다. 그런 순환은 여전히 `CircularDependencyError`로 거부됩니다. | ||
@@ -104,8 +106,9 @@ ```typescript | ||
| class ServiceA { | ||
| constructor(private serviceB: any) {} | ||
| constructor(private readonly serviceB: ServiceB) {} | ||
| } | ||
| @Inject(forwardRef(() => ServiceA)) | ||
| class ServiceB { | ||
| constructor(private serviceA: any) {} | ||
| getStatus() { | ||
| return 'ready'; | ||
| } | ||
| } | ||
@@ -137,3 +140,3 @@ ``` | ||
| ### CircularDependencyError | ||
| 의존성 그래프에서 순환이 감지될 때 발생합니다. 생성자 주입 항목을 확인하고 필요한 경우 `forwardRef()`를 사용하여 순환을 끊으세요. | ||
| 의존성 그래프에서 순환이 감지될 때 발생합니다. 생성자 주입 항목을 확인하고 공유 상태 추출, 중재자 도입, 수명 주기 경계 변경 등으로 순환을 제거하세요. `forwardRef()`는 선언 순서 문제를 위해 토큰 조회만 지연하며, 실제 생성자 순환을 끊지는 않습니다. | ||
@@ -140,0 +143,0 @@ ### 토큰을 찾을 수 없음 (Token Not Found) |
+8
-5
@@ -78,2 +78,4 @@ # @fluojs/di | ||
| 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. | ||
| ### Request Scoping | ||
@@ -95,3 +97,3 @@ Isolated containers can be created to handle per-request state without polluting the root container. | ||
| To resolve a circular dependency, use `forwardRef()` to defer the resolution of the dependent token. | ||
| 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`. | ||
@@ -104,8 +106,9 @@ ```typescript | ||
| class ServiceA { | ||
| constructor(private serviceB: any) {} | ||
| constructor(private readonly serviceB: ServiceB) {} | ||
| } | ||
| @Inject(forwardRef(() => ServiceA)) | ||
| class ServiceB { | ||
| constructor(private serviceA: any) {} | ||
| getStatus() { | ||
| return 'ready'; | ||
| } | ||
| } | ||
@@ -137,3 +140,3 @@ ``` | ||
| ### CircularDependencyError | ||
| Thrown when the container detects a cycle in the dependency graph. Check your constructor injections and use `forwardRef()` where necessary to break the cycle. | ||
| 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. | ||
@@ -140,0 +143,0 @@ ### Token Not Found |
81090
8.47%1528
9.3%164
1.86%Updated