🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@fluojs/di

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluojs/di - npm Package Compare versions

Comparing version
1.1.0
to
2.0.0
+3
dist/internal.d.ts
export { validateProviderInputs } from './provider-normalization.js';
export type { Provider } from './types.js';
//# sourceMappingURL=internal.d.ts.map
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
export { validateProviderInputs } from './provider-normalization.js';
import type { NormalizedProvider, Provider } from './types.js';
/**
* Validates and snapshots a public provider declaration for container registration.
*
* @param provider Provider declaration crossing the container registration boundary.
* @returns An immutable provider record with normalized injection and scope fields.
*/
export declare function normalizeProvider(provider: Provider): NormalizedProvider;
/**
* Validates provider declarations through the same normalization path used by `Container` registration.
*
* @param providers Provider declarations crossing an internal framework integration boundary.
* @returns The original provider list after every declaration has passed canonical normalization.
* @internal
*/
export declare function validateProviderInputs(providers: Provider[]): Provider[];
//# sourceMappingURL=provider-normalization.d.ts.map
{"version":3,"file":"provider-normalization.d.ts","sourceRoot":"","sources":["../src/provider-normalization.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAA2B,kBAAkB,EAAiB,QAAQ,EAAE,MAAM,YAAY,CAAC;AA6JvG;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,kBAAkB,CAiFxE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAMxE"}
import { getClassDiMetadata } from '@fluojs/core/internal';
import { InvalidProviderError } from './errors.js';
import { Scope } from './types.js';
function isClassConstructor(value) {
return isConstructableFunction(value);
}
function isProviderObject(value) {
return typeof value === 'object' && value !== null;
}
function isClassType(value) {
return isConstructableFunction(value);
}
function isFactoryFunction(value) {
return typeof value === 'function';
}
function isTokenResolver(value) {
return typeof value === 'function';
}
function isConstructableFunction(value) {
if (typeof value !== 'function') {
return false;
}
try {
Reflect.construct(Object, [], value);
return true;
} catch (error) {
if (error instanceof TypeError) {
return false;
}
throw error;
}
}
function isToken(value) {
return typeof value === 'string' || typeof value === 'symbol' || isConstructableFunction(value);
}
function isScope(value) {
return value === 'singleton' || value === 'request' || value === 'transient';
}
function assertProviderToken(provider) {
if (!('provide' in provider) || provider.provide == null) {
throw new InvalidProviderError('Provider object must include a non-null provide token.');
}
}
function assertProviderStrategy(provider) {
const strategyCount = Number('useValue' in provider) + Number('useFactory' in provider) + Number('useClass' in provider) + Number('useExisting' in provider);
if (strategyCount !== 1) {
throw new InvalidProviderError('Provider object must declare exactly one of useValue, useFactory, useClass, or useExisting.');
}
}
function assertObjectProvider(provider) {
assertProviderToken(provider);
assertProviderStrategy(provider);
}
function normalizeProviderScope(scope, providerToken) {
if (scope === undefined) {
return undefined;
}
if (isScope(scope)) {
return scope;
}
throw new InvalidProviderError('Provider scope must be one of singleton, request, or transient.', {
token: providerToken,
scope: String(scope),
hint: 'Use Scope.DEFAULT, Scope.REQUEST, Scope.TRANSIENT, or the matching string literal.'
});
}
function normalizeInjectToken(token, providerToken, index) {
if (typeof token === 'object' && token !== null && '__forwardRef__' in token && token.__forwardRef__ === true) {
if (!('forwardRef' in token) || !isTokenResolver(token.forwardRef)) {
throw new InvalidProviderError(`Provider inject forwardRef wrapper at index ${String(index)} must expose a callable forwardRef function.`, {
token: providerToken
});
}
return Object.freeze({
__forwardRef__: true,
forwardRef: token.forwardRef
});
}
if (typeof token === 'object' && token !== null && '__optional__' in token && token.__optional__ === true) {
if (!('token' in token) || !isToken(token.token)) {
throw new InvalidProviderError(`Provider inject optional wrapper at index ${String(index)} must contain a valid token.`, {
token: providerToken
});
}
return Object.freeze({
__optional__: true,
token: token.token
});
}
if (isToken(token)) {
return token;
}
throw new InvalidProviderError(`Provider inject entry at index ${String(index)} must be a string, symbol, class, forwardRef(), or optional() token wrapper.`, {
token: providerToken,
hint: 'Check that every dependency token is defined, and use forwardRef() for declaration-order cycles.'
});
}
function normalizeInject(inject, providerToken) {
if (inject === undefined) {
return [];
}
if (!Array.isArray(inject)) {
throw new InvalidProviderError('Provider inject must be an array.', {
token: providerToken,
hint: 'Pass dependency tokens as inject: [DependencyA, DependencyB].'
});
}
return inject.map((token, index) => normalizeInjectToken(token, providerToken, index));
}
function freezeNormalizedProvider(provider) {
return Object.freeze({
...provider,
inject: Object.freeze([...provider.inject])
});
}
/**
* Validates and snapshots a public provider declaration for container registration.
*
* @param provider Provider declaration crossing the container registration boundary.
* @returns An immutable provider record with normalized injection and scope fields.
*/
export function normalizeProvider(provider) {
if (isClassConstructor(provider)) {
const metadata = getClassDiMetadata(provider);
return freezeNormalizedProvider({
inject: normalizeInject(metadata?.inject, provider),
provide: provider,
scope: normalizeProviderScope(metadata?.scope, provider) ?? Scope.DEFAULT,
type: 'class',
useClass: provider
});
}
if (!isProviderObject(provider)) {
throw new InvalidProviderError('Unsupported provider type.');
}
const objectProvider = provider;
assertObjectProvider(objectProvider);
const explicitScope = normalizeProviderScope(objectProvider.scope, objectProvider.provide);
if ('useValue' in objectProvider) {
return freezeNormalizedProvider({
inject: [],
multi: objectProvider.multi,
provide: objectProvider.provide,
scope: Scope.DEFAULT,
type: 'value',
useValue: objectProvider.useValue
});
}
if ('useFactory' in objectProvider) {
if (!isFactoryFunction(objectProvider.useFactory)) {
throw new InvalidProviderError('Factory provider useFactory must be a function.', {
token: objectProvider.provide
});
}
const metadata = objectProvider.resolverClass ? getClassDiMetadata(objectProvider.resolverClass) : undefined;
return freezeNormalizedProvider({
inject: normalizeInject(objectProvider.inject, objectProvider.provide),
multi: objectProvider.multi,
provide: objectProvider.provide,
scope: explicitScope ?? normalizeProviderScope(metadata?.scope, objectProvider.provide) ?? Scope.DEFAULT,
type: 'factory',
useFactory: objectProvider.useFactory
});
}
if ('useClass' in objectProvider) {
if (!isClassType(objectProvider.useClass)) {
throw new InvalidProviderError('Class provider useClass must be a constructor.', {
token: objectProvider.provide
});
}
const metadata = getClassDiMetadata(objectProvider.useClass);
return freezeNormalizedProvider({
inject: normalizeInject(objectProvider.inject === undefined ? metadata?.inject : objectProvider.inject, objectProvider.provide),
multi: objectProvider.multi,
provide: objectProvider.provide,
scope: explicitScope ?? normalizeProviderScope(metadata?.scope, objectProvider.provide) ?? Scope.DEFAULT,
type: 'class',
useClass: objectProvider.useClass
});
}
if ('useExisting' in objectProvider) {
if (objectProvider.useExisting == null) {
throw new InvalidProviderError('Alias provider useExisting must be a non-null token.', {
token: objectProvider.provide
});
}
return freezeNormalizedProvider({
inject: [],
provide: objectProvider.provide,
scope: Scope.DEFAULT,
type: 'existing',
useExisting: objectProvider.useExisting
});
}
throw new InvalidProviderError('Provider object must declare exactly one of useValue, useFactory, useClass, or useExisting.');
}
/**
* Validates provider declarations through the same normalization path used by `Container` registration.
*
* @param providers Provider declarations crossing an internal framework integration boundary.
* @returns The original provider list after every declaration has passed canonical normalization.
* @internal
*/
export function validateProviderInputs(providers) {
for (const provider of providers) {
normalizeProvider(provider);
}
return providers;
}
+40
-12
import { type Token } from '@fluojs/core';
import type { NormalizedProvider, Provider } from './types.js';
/**
* Public read/write seam for framework-owned testing and tooling that need to
* Factory provider resolution mode recorded after a factory returns either synchronously or through a promise.
*/
export type FactoryResolutionKind = 'async' | 'sync';
/**
* Controlled cache adoption seam for framework-owned testing and tooling that
* need synchronous helpers to preserve container-owned singleton disposal.
*/
export interface ContainerResolutionCacheOwner {
readonly deleteMultiSingleton: (provider: NormalizedProvider) => void;
readonly deleteSingleton: (token: Token) => void;
readonly recordFactoryResolution: (provider: NormalizedProvider, kind: FactoryResolutionKind) => void;
readonly setMultiSingleton: (provider: NormalizedProvider, promise: Promise<unknown>) => void;
readonly setSingleton: (token: Token, promise: Promise<unknown>) => void;
}
/**
* Read-only factory resolution diagnostics recorded by container-owned factory
* instantiation paths.
*/
export interface ContainerFactoryResolutionState {
readonly get: (provider: NormalizedProvider) => FactoryResolutionKind | undefined;
readonly has: (provider: NormalizedProvider) => boolean;
}
/**
* Public read-only seam for framework-owned testing and tooling that need to
* inspect a container's resolved provider graph without depending on private

@@ -9,8 +32,10 @@ * field names or structural casts.

export interface ContainerResolutionState {
readonly cacheOwner: ContainerResolutionCacheOwner;
readonly factoryResolutionKinds: ContainerFactoryResolutionState;
readonly parent?: ContainerResolutionState;
readonly registrations: Map<Token, NormalizedProvider>;
readonly multiRegistrations: Map<Token, NormalizedProvider[]>;
readonly multiSingletonCache: Map<NormalizedProvider, Promise<unknown>>;
readonly registrations: ReadonlyMap<Token, NormalizedProvider>;
readonly multiRegistrations: ReadonlyMap<Token, readonly NormalizedProvider[]>;
readonly multiSingletonCache: ReadonlyMap<NormalizedProvider, Promise<unknown>>;
readonly requestScopeEnabled: boolean;
readonly singletonCache: Map<Token, Promise<unknown>>;
readonly singletonCache: ReadonlyMap<Token, Promise<unknown>>;
}

@@ -30,5 +55,5 @@ /**

private readonly staleDisposalTasks;
private readonly staleDisposalErrors;
private readonly singletonCache;
private readonly forwardRefTokenCache;
private readonly factoryResolutionKinds;
private readonly providerLookupPlanCache;

@@ -41,3 +66,3 @@ private readonly multiProviderPlanCache;

private disposed;
private trackedByRoot;
private trackedByParent;
private graphRevision;

@@ -83,8 +108,12 @@ constructor(parent?: Container | undefined, requestScopeEnabled?: boolean, singletonCache?: Map<Token, Promise<unknown>>);

* `@fluojs/testing`; callers should prefer ordinary `has(...)` and
* `resolve(...)` unless they need to preserve container cache ownership while
* implementing a framework-level helper.
* `resolve(...)` unless they need read-only graph/cache visibility while
* implementing a framework-level helper. Cache adoption for synchronous
* helpers goes through `cacheOwner`; the returned maps are not mutable
* container internals.
*
* @returns Provider registrations and resolution caches for this container scope.
* @returns Read-only provider registrations and resolution caches for this container scope.
*/
inspectResolutionState(): ContainerResolutionState;
private createCacheOwner;
private createFactoryResolutionState;
/**

@@ -178,3 +207,3 @@ * Returns whether resolving a token may require a request-scope container.

private clearResolutionPlanCaches;
private waitForStaleDisposalTasks;
private assertStaleDisposalsSettled;
private scheduleStaleDisposal;

@@ -193,3 +222,2 @@ private throwDisposalErrors;

private invalidateAffectedCachedEntriesInHierarchy;
private isAncestorOf;
private invalidateAffectedCachedEntries;

@@ -196,0 +224,0 @@ private shouldInvalidateCachedToken;

@@ -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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"}

@@ -1,8 +0,23 @@

import { InvariantError, formatTokenName } from '@fluojs/core';
import { formatTokenName, InvariantError } from '@fluojs/core';
import { getClassDiMetadata } from '@fluojs/core/internal';
import { CircularDependencyError, ContainerResolutionError, DuplicateProviderError, InvalidProviderError, RequestScopeResolutionError, ScopeMismatchError } from './errors.js';
import { Scope, isForwardRef, isOptionalToken } from './types.js';
import { CircularDependencyError, ContainerResolutionError, DuplicateProviderError, RequestScopeResolutionError, ScopeMismatchError } from './errors.js';
import { normalizeProvider } from './provider-normalization.js';
import { isForwardRef, isOptionalToken, Scope } from './types.js';
/**
* Public read/write seam for framework-owned testing and tooling that need to
* Factory provider resolution mode recorded after a factory returns either synchronously or through a promise.
*/
/**
* Controlled cache adoption seam for framework-owned testing and tooling that
* need synchronous helpers to preserve container-owned singleton disposal.
*/
/**
* Read-only factory resolution diagnostics recorded by container-owned factory
* instantiation paths.
*/
/**
* Public read-only seam for framework-owned testing and tooling that need to
* inspect a container's resolved provider graph without depending on private

@@ -12,111 +27,76 @@ * field names or structural casts.

function isClassConstructor(value) {
return typeof value === 'function';
}
function isValueProvider(value) {
return typeof value === 'object' && value !== null && 'useValue' in value;
}
function isFactoryProvider(value) {
return typeof value === 'object' && value !== null && 'useFactory' in value;
}
function isClassProvider(value) {
return typeof value === 'object' && value !== null && 'useClass' in value;
}
function isExistingProvider(value) {
return typeof value === 'object' && value !== null && 'useExisting' in value;
}
function assertProviderToken(provider) {
if (!('provide' in provider) || provider.provide == null) {
throw new InvalidProviderError('Provider object must include a non-null provide token.');
class ReadonlyMapView {
#source;
[Symbol.toStringTag] = 'Map';
constructor(source) {
this.#source = source;
}
}
function assertProviderStrategy(provider) {
const strategyCount = Number('useValue' in provider) + Number('useFactory' in provider) + Number('useClass' in provider) + Number('useExisting' in provider);
if (strategyCount !== 1) {
throw new InvalidProviderError('Provider object must declare exactly one of useValue, useFactory, useClass, or useExisting.');
get size() {
return this.#source.size;
}
}
function assertObjectProvider(provider) {
assertProviderToken(provider);
assertProviderStrategy(provider);
}
function normalizeInjectToken(token) {
if (token == null) {
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()).');
entries() {
return this.#source.entries();
}
return token;
forEach(callbackfn, thisArg) {
for (const [key, value] of this.#source) {
callbackfn.call(thisArg, value, key, this);
}
}
get(key) {
return this.#source.get(key);
}
has(key) {
return this.#source.has(key);
}
keys() {
return this.#source.keys();
}
values() {
return this.#source.values();
}
[Symbol.iterator]() {
return this.entries();
}
}
function normalizeProvider(provider) {
if (isClassConstructor(provider)) {
const metadata = getClassDiMetadata(provider);
return {
inject: (metadata?.inject ?? []).map(normalizeInjectToken),
provide: provider,
scope: metadata?.scope ?? Scope.DEFAULT,
type: 'class',
useClass: provider
};
class ReadonlyMultiRegistrationMapView {
#source;
[Symbol.toStringTag] = 'Map';
constructor(source) {
this.#source = source;
}
if (isValueProvider(provider)) {
assertObjectProvider(provider);
return {
inject: [],
multi: provider.multi,
provide: provider.provide,
scope: Scope.DEFAULT,
type: 'value',
useValue: provider.useValue
};
get size() {
return this.#source.size;
}
if (isFactoryProvider(provider)) {
assertObjectProvider(provider);
if (typeof provider.useFactory !== 'function') {
throw new InvalidProviderError('Factory provider useFactory must be a function.', {
token: provider.provide
});
*entries() {
for (const [token, providers] of this.#source) {
yield [token, Object.freeze([...providers])];
}
const metadata = provider.resolverClass ? getClassDiMetadata(provider.resolverClass) : undefined;
return {
inject: (provider.inject ?? []).map(normalizeInjectToken),
multi: provider.multi,
provide: provider.provide,
scope: provider.scope ?? metadata?.scope ?? Scope.DEFAULT,
type: 'factory',
useFactory: provider.useFactory
};
}
if (isClassProvider(provider)) {
assertObjectProvider(provider);
if (typeof provider.useClass !== 'function') {
throw new InvalidProviderError('Class provider useClass must be a constructor.', {
token: provider.provide
});
forEach(callbackfn, thisArg) {
for (const [key, value] of this.entries()) {
callbackfn.call(thisArg, value, key, this);
}
const metadata = getClassDiMetadata(provider.useClass);
return {
inject: (provider.inject ?? metadata?.inject ?? []).map(normalizeInjectToken),
multi: provider.multi,
provide: provider.provide,
scope: provider.scope ?? metadata?.scope ?? Scope.DEFAULT,
type: 'class',
useClass: provider.useClass
};
}
if (isExistingProvider(provider)) {
assertObjectProvider(provider);
if (provider.useExisting == null) {
throw new InvalidProviderError('Alias provider useExisting must be a non-null token.', {
token: provider.provide
});
get(key) {
const providers = this.#source.get(key);
return providers ? Object.freeze([...providers]) : undefined;
}
has(key) {
return this.#source.has(key);
}
keys() {
return this.#source.keys();
}
*values() {
for (const providers of this.#source.values()) {
yield Object.freeze([...providers]);
}
return {
inject: [],
provide: provider.provide,
scope: Scope.DEFAULT,
type: 'existing',
useExisting: provider.useExisting
};
}
throw new InvalidProviderError('Unsupported provider type.');
[Symbol.iterator]() {
return this.entries();
}
}
function isPromiseLike(value) {
return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof value.then === 'function';
}

@@ -134,5 +114,5 @@ /**

staleDisposalTasks = new Set();
staleDisposalErrors = [];
singletonCache;
forwardRefTokenCache = new WeakMap();
factoryResolutionKinds = new WeakMap();
providerLookupPlanCache = new Map();

@@ -145,3 +125,3 @@ multiProviderPlanCache = new Map();

disposed = false;
trackedByRoot = false;
trackedByParent = false;
graphRevision = 0;

@@ -269,17 +249,55 @@ constructor(parent, requestScopeEnabled = false, singletonCache) {

* `@fluojs/testing`; callers should prefer ordinary `has(...)` and
* `resolve(...)` unless they need to preserve container cache ownership while
* implementing a framework-level helper.
* `resolve(...)` unless they need read-only graph/cache visibility while
* implementing a framework-level helper. Cache adoption for synchronous
* helpers goes through `cacheOwner`; the returned maps are not mutable
* container internals.
*
* @returns Provider registrations and resolution caches for this container scope.
* @returns Read-only provider registrations and resolution caches for this container scope.
*/
inspectResolutionState() {
const registrations = new Map(this.registrations);
const multiRegistrations = new Map(Array.from(this.multiRegistrations, ([token, providers]) => [token, Object.freeze([...providers])]));
const multiSingletonCache = new Map(this.multiSingletonCache);
const singletonCache = new Map(this.singletonCache);
return {
cacheOwner: this.createCacheOwner(singletonCache, multiSingletonCache),
factoryResolutionKinds: this.createFactoryResolutionState(),
parent: this.parent?.inspectResolutionState(),
registrations: this.registrations,
multiRegistrations: this.multiRegistrations,
multiSingletonCache: this.multiSingletonCache,
registrations: new ReadonlyMapView(registrations),
multiRegistrations: new ReadonlyMultiRegistrationMapView(multiRegistrations),
multiSingletonCache: new ReadonlyMapView(multiSingletonCache),
requestScopeEnabled: this.requestScopeEnabled,
singletonCache: this.singletonCache
singletonCache: new ReadonlyMapView(singletonCache)
};
}
createCacheOwner(singletonCacheSnapshot, multiSingletonCacheSnapshot) {
return Object.freeze({
deleteMultiSingleton: provider => {
this.multiSingletonCache.delete(provider);
multiSingletonCacheSnapshot.delete(provider);
},
deleteSingleton: token => {
this.singletonCache.delete(token);
singletonCacheSnapshot.delete(token);
},
recordFactoryResolution: (provider, kind) => {
this.root().factoryResolutionKinds.set(provider, kind);
},
setMultiSingleton: (provider, promise) => {
this.multiSingletonCache.set(provider, promise);
multiSingletonCacheSnapshot.set(provider, promise);
},
setSingleton: (token, promise) => {
this.singletonCache.set(token, promise);
singletonCacheSnapshot.set(token, promise);
}
});
}
createFactoryResolutionState() {
const root = this.root();
return Object.freeze({
get: provider => root.factoryResolutionKinds.get(provider),
has: provider => root.factoryResolutionKinds.has(provider)
});
}

@@ -332,2 +350,3 @@ /**

}
await this.assertStaleDisposalsSettled();
return this.resolveWithChain(token, [], new Set());

@@ -360,4 +379,4 @@ }

try {
// Dispose all live request-scope children first (root only)
if (!this.parent && this.childScopes && this.childScopes.size > 0) {
// Dispose all live request-scope children before tearing down this scope's cache.
if (this.childScopes && this.childScopes.size > 0) {
const childResults = await Promise.allSettled(Array.from(this.childScopes).map(child => child.dispose()));

@@ -378,5 +397,5 @@ for (const result of childResults) {

} finally {
if (this.parent && this.trackedByRoot) {
this.root().childScopes?.delete(this);
this.trackedByRoot = false;
if (this.parent && this.trackedByParent) {
this.parent.childScopes?.delete(this);
this.trackedByParent = false;
}

@@ -627,9 +646,9 @@ }

ensureTrackedRequestScope() {
if (!this.requestScopeEnabled || !this.parent || this.trackedByRoot) {
if (!this.requestScopeEnabled || !this.parent || this.trackedByParent) {
return;
}
const root = this.root();
root.childScopes ??= new Set();
root.childScopes.add(this);
this.trackedByRoot = true;
this.parent.ensureTrackedRequestScope();
this.parent.childScopes ??= new Set();
this.parent.childScopes.add(this);
this.trackedByParent = true;
}

@@ -717,8 +736,13 @@ requestCacheForWrite() {

async disposeCache(entries) {
await this.waitForStaleDisposalTasks();
const errors = [];
try {
await this.assertStaleDisposalsSettled();
} catch (error) {
this.collectDisposalError(error, errors);
}
const {
disposables,
errors
errors: resolutionErrors
} = await this.collectDisposableInstances(entries);
errors.push(...this.staleDisposalErrors.splice(0, this.staleDisposalErrors.length));
errors.push(...resolutionErrors);
errors.push(...(await this.disposeInstancesInReverseOrder(disposables)));

@@ -799,10 +823,26 @@ this.clearDisposalCaches();

}
async waitForStaleDisposalTasks() {
async assertStaleDisposalsSettled() {
const errors = [];
while (this.staleDisposalTasks.size > 0) {
await Promise.all(Array.from(this.staleDisposalTasks));
const tasks = Array.from(this.staleDisposalTasks);
await Promise.all(tasks.map(task => task.promise));
for (const task of tasks) {
this.staleDisposalTasks.delete(task);
if (task.failed && !task.errorConsumed) {
task.errorConsumed = true;
errors.push(task.error);
}
}
}
this.throwDisposalErrors(errors);
}
scheduleStaleDisposal(instancePromise) {
let task;
task = (async () => {
scheduleStaleDisposal(instancePromise, staleDisposalOwner) {
const observers = staleDisposalOwner === this ? [this] : [this, staleDisposalOwner];
const task = {
error: undefined,
errorConsumed: false,
failed: false,
promise: Promise.resolve()
};
task.promise = (async () => {
try {

@@ -814,8 +854,15 @@ const instance = await instancePromise;

} catch (error) {
this.staleDisposalErrors.push(error);
task.error = error;
task.failed = true;
}
})().finally(() => {
this.staleDisposalTasks.delete(task);
if (!task.failed) {
for (const observer of observers) {
observer.staleDisposalTasks.delete(task);
}
}
});
this.staleDisposalTasks.add(task);
for (const observer of observers) {
observer.staleDisposalTasks.add(task);
}
}

@@ -853,3 +900,5 @@ throwDisposalErrors(errors) {

const deps = await this.resolveProviderDeps(provider, chain, activeTokens);
return provider.useFactory(...deps);
const value = provider.useFactory(...deps);
this.root().factoryResolutionKinds.set(provider, isPromiseLike(value) ? 'async' : 'sync');
return value;
}

@@ -974,25 +1023,9 @@ case 'class':

}
invalidateAffectedCachedEntriesInHierarchy(token) {
this.invalidateAffectedCachedEntries(token);
const childScopes = this.root().childScopes;
if (!childScopes) {
return;
invalidateAffectedCachedEntriesInHierarchy(token, staleDisposalOwner = this) {
this.invalidateAffectedCachedEntries(token, staleDisposalOwner);
for (const childScope of this.childScopes ?? []) {
childScope.invalidateAffectedCachedEntriesInHierarchy(token, staleDisposalOwner);
}
for (const childScope of childScopes) {
if (this.isAncestorOf(childScope)) {
childScope.invalidateAffectedCachedEntries(token);
}
}
}
isAncestorOf(container) {
let current = container.parent;
while (current) {
if (current === this) {
return true;
}
current = current.parent;
}
return false;
}
invalidateAffectedCachedEntries(token) {
invalidateAffectedCachedEntries(token, staleDisposalOwner) {
for (const [cachedToken, cached] of this.requestCache?.entries() ?? []) {

@@ -1002,3 +1035,3 @@ if (!this.shouldInvalidateCachedToken(cachedToken, token)) {

}
this.scheduleStaleDisposal(cached);
this.scheduleStaleDisposal(cached, staleDisposalOwner);
this.requestCache?.delete(cachedToken);

@@ -1011,3 +1044,3 @@ }

}
this.scheduleStaleDisposal(cached);
this.scheduleStaleDisposal(cached, staleDisposalOwner);
this.singletonCache.delete(cachedToken);

@@ -1021,3 +1054,3 @@ }

}
this.scheduleStaleDisposal(cached);
this.scheduleStaleDisposal(cached, staleDisposalOwner);
this.multiSingletonCache.delete(provider);

@@ -1034,3 +1067,3 @@ }

}
this.scheduleStaleDisposal(cached);
this.scheduleStaleDisposal(cached, staleDisposalOwner);
multiRequestCache.delete(provider);

@@ -1037,0 +1070,0 @@ }

@@ -46,2 +46,3 @@ import type { Constructor, ForwardRefToken, InjectionToken, MaybePromise, OptionalInjectToken, Token } from '@fluojs/core';

multi?: boolean;
/** Class metadata source used when the factory should inherit `@Scope(...)` metadata. */
resolverClass?: ClassType;

@@ -98,11 +99,11 @@ }

export interface NormalizedProvider<T = unknown> {
inject: InjectionToken[];
provide: Token<T>;
scope: Scope;
type: 'class' | 'factory' | 'value' | 'existing';
useClass?: ClassType<T>;
useFactory?: (...deps: unknown[]) => MaybePromise<T>;
useValue?: T;
useExisting?: Token;
multi?: boolean;
readonly inject: readonly InjectionToken[];
readonly provide: Token<T>;
readonly scope: Scope;
readonly type: 'class' | 'factory' | 'value' | 'existing';
readonly useClass?: ClassType<T>;
readonly useFactory?: (...deps: unknown[]) => MaybePromise<T>;
readonly useValue?: T;
readonly useExisting?: Token;
readonly multi?: boolean;
}

@@ -109,0 +110,0 @@ /**

@@ -1,1 +0,1 @@

{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE3H;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAE1D;;GAEG;AACH,yBAAiB,KAAK,CAAC;IACrB;;OAEG;IACI,MAAM,OAAO,EAAE,KAAmB,CAAC;IAE1C;;OAEG;IACI,MAAM,OAAO,EAAE,KAAiB,CAAC;IAExC;;OAEG;IACI,MAAM,SAAS,EAAE,KAAmB,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAClB,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yFAAyF;IACzF,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAClB,QAAQ,EAAE,CAAC,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,OAAO;IAC3C,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAClB,WAAW,EAAE,KAAK,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI,eAAe,CAAC,CAAC,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,GAAG,OAAO,IAC5B,SAAS,CAAC,CAAC,CAAC,GACZ,aAAa,CAAC,CAAC,CAAC,GAChB,eAAe,CAAC,CAAC,CAAC,GAClB,aAAa,CAAC,CAAC,CAAC,GAChB,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO;IAC7C,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAElE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEtE"}

@@ -66,6 +66,6 @@ /**

export function forwardRef(fn) {
return {
return Object.freeze({
__forwardRef__: true,
forwardRef: fn
};
});
}

@@ -90,6 +90,6 @@

export function optional(token) {
return {
return Object.freeze({
__optional__: true,
token
};
});
}

@@ -96,0 +96,0 @@

@@ -12,3 +12,3 @@ {

],
"version": "1.1.0",
"version": "2.0.0",
"private": false,

@@ -32,2 +32,6 @@ "license": "MIT",

"import": "./dist/index.js"
},
"./internal": {
"types": "./dist/internal.d.ts",
"import": "./dist/internal.js"
}

@@ -41,3 +45,3 @@ },

"dependencies": {
"@fluojs/core": "^1.0.3"
"@fluojs/core": "^1.1.0"
},

@@ -44,0 +48,0 @@ "devDependencies": {

@@ -70,3 +70,3 @@ # @fluojs/di

- **값 provider**: `{ provide: 'API_URL', useValue: 'https://api.example.com' }`
- **팩토리 provider**: `{ provide, useFactory, inject }`
- **팩토리 provider**: `{ provide, useFactory, inject }`. 팩토리가 참조 클래스의 `@Scope(...)` 같은 DI metadata를 상속해야 하고 provider `scope`를 명시하지 않았다면 `resolverClass`를 함께 지정합니다.
- **별칭(Alias) provider**: `{ provide: ILogger, useExisting: PinoLogger }`를 사용하여 하나의 토큰을 기존에 등록된 다른 provider로 매핑할 수 있습니다.

@@ -80,7 +80,7 @@

dispose 중에는 루트 컨테이너가 먼저 살아 있는 request scope 자식을 정리한 뒤, 자식 dispose 중 하나 이상이 실패하더라도 루트가 소유한 singleton 정리를 계속 수행합니다. 자식/루트 dispose 실패가 여러 개 발생하면 `dispose()`는 모든 shutdown 실패를 확인할 수 있도록 `AggregateError`로 보고합니다.
dispose 중에는 각 컨테이너가 자신이 소유한 살아 있는 request scope 자식을 먼저 재귀적으로 정리하므로, 루트가 아닌 request scope를 dispose해도 중첩 request scope를 닫은 뒤 자신의 request cache를 정리합니다. 이후 루트 dispose는 자식 dispose 중 하나 이상이 실패하더라도 루트가 소유한 singleton 정리를 계속 수행합니다. 자식/루트 dispose 실패가 여러 개 발생하면 `dispose()`는 모든 shutdown 실패를 확인할 수 있도록 `AggregateError`로 보고합니다.
### provider override
테스트나 request-local 경계에서 기존 등록을 의도적으로 교체해야 할 때는 `override(...providers)`를 사용합니다. override는 각 토큰의 현재 provider set을 교체하고 현재 컨테이너와 이미 materialize된 request-scope 자식의 cached instance를 무효화하며, 오래된 instance를 즉시 dispose합니다. multi provider override는 해당 토큰의 전체 multi-provider set을 교체하므로 필요한 replacement provider를 한 번에 모두 전달하세요. 같은 토큰에 single replacement와 multi replacement를 한 override 호출에서 섞으면 모호한 교체로 보고 거부합니다.
테스트나 request-local 경계에서 기존 등록을 의도적으로 교체해야 할 때는 `override(...providers)`를 사용합니다. override는 각 토큰의 현재 provider set을 교체하고 현재 컨테이너와 이미 materialize된 request-scope 자식의 cached instance를 무효화하며, 다음 replacement resolution이 계속되기 전에 오래된 instance의 dispose가 끝나도록 보장합니다. multi provider override는 해당 토큰의 전체 multi-provider set을 교체하므로 필요한 replacement provider를 한 번에 모두 전달하세요. 같은 토큰에 single replacement와 multi replacement를 한 override 호출에서 섞으면 모호한 교체로 보고 거부합니다.

@@ -96,3 +96,3 @@ ### request scope 분리

provider 객체는 등록 시점에 검증됩니다. 모든 객체 provider는 null이 아닌 `provide` 토큰과 정확히 하나의 전략(`useClass`, `useValue`, `useFactory`, `useExisting`)을 포함해야 합니다. 잘못된 provider 형태는 컨테이너 그래프에 영향을 주기 전에 `InvalidProviderError`를 발생시킵니다.
provider 객체는 등록 시점에 검증됩니다. 모든 객체 provider는 null이 아닌 `provide` 토큰과 정확히 하나의 전략(`useClass`, `useValue`, `useFactory`, `useExisting`)을 포함해야 합니다. class provider에서 `inject`를 생략하거나 `undefined`로 지정하면 `useClass`의 `@Inject(...)` 메타데이터로 fallback하며, 그 밖의 명시적 `inject` 값은 유효한 token 또는 올바른 `forwardRef(...)` / `optional(...)` wrapper로 구성된 배열이어야 합니다. 명시적인 `scope` 값은 `singleton`, `request`, `transient` 중 하나여야 합니다. 잘못된 provider 형태는 컨테이너 그래프에 영향을 주기 전에 `InvalidProviderError`를 발생시킵니다.

@@ -135,18 +135,38 @@ ## 순환 의존성 처리

`useValue`를 사용하면 단위 테스트 중에 컨테이너의 provider를 모의 객체(mock)나 스텁(stub)으로 쉽게 교체할 수 있습니다.
먼저 전체 의존성 그래프를 등록한 다음, `override(...)`와 `useValue`를 사용해 기존 provider를 mock이나 stub으로 교체하세요. `register(...)`는 새 provider를 추가하며 중복 토큰을 거부하고, `override(...)`는 지원되는 교체 API입니다.
```typescript
import { Inject } from '@fluojs/core';
import { Container } from '@fluojs/di';
import { expect, it, vi } from 'vitest';
const container = new Container();
const mockDb = { query: vi.fn() };
class Database {
async query(): Promise<readonly string[]> {
return ['real row'];
}
}
// 실제 Database 클래스를 모의 객체 값으로 교체
container.register({
provide: Database,
useValue: mockDb
@Inject(Database)
class DataService {
constructor(private readonly database: Database) {}
async load(): Promise<readonly string[]> {
return this.database.query();
}
}
it('uses a mock database', async () => {
const mockDb = { query: vi.fn().mockResolvedValue(['mock row']) };
const container = new Container().register(Database, DataService);
container.override({
provide: Database,
useValue: mockDb,
});
const service = await container.resolve(DataService);
await expect(service.load()).resolves.toEqual(['mock row']);
expect(mockDb.query).toHaveBeenCalledOnce();
});
const service = await container.resolve(DataService);
// service는 실제 Database 인스턴스 대신 mockDb를 사용합니다.
```

@@ -164,13 +184,13 @@

| Export | 설명 |
|---|---|
| `Container` | 메인 DI 컨테이너 클래스입니다. |
| `register(...providers)` | 하나 이상의 프로바이더를 등록합니다. |
| `override(...providers)` | 기존 provider를 교체하고 cached instance를 무효화하며 오래된 instance를 dispose합니다. |
| `resolve<T>(token)` | 토큰을 인스턴스로 비동기 해석합니다. |
| `inspectResolutionState()` | cache ownership을 보존해야 하는 testing/tooling helper를 위한 지원 대상 framework-owned container introspection seam을 노출합니다. 애플리케이션 코드는 `has(...)`와 `resolve(...)`를 우선 사용하세요. |
| `createRequestScope()` | 요청 스코프 의존성을 위한 자식 컨테이너를 생성합니다. |
| `has(token)` | 컨테이너나 부모에 토큰이 등록되어 있는지 확인합니다. |
| `hasRequestScopedDependency(token)` | 토큰 해석 시 provider 그래프에 request-scoped 의존성이나 순환이 있어 request-scope 컨테이너가 필요할 수 있는지 확인합니다. |
| `dispose()` | request child와 루트가 소유한 singleton instance를 정리합니다. |
| Surface | 종류 | 설명 |
|---|---|---|
| `Container` | Root export | 메인 DI 컨테이너 클래스입니다. |
| `container.register(...providers)` | `Container` instance method | 하나 이상의 프로바이더를 등록합니다. |
| `container.override(...providers)` | `Container` instance method | 기존 provider를 교체하고 cached instance를 무효화하며 다음 replacement resolution이 계속되기 전에 오래된 instance dispose가 settle되도록 보장합니다. |
| `container.resolve<T>(token)` | `Container` instance method | 토큰을 인스턴스로 비동기 해석합니다. |
| `container.inspectResolutionState()` | `Container` instance method | snapshot read-only map view, frozen provider record, controlled cache adoption을 통해 cache ownership을 보존해야 하는 testing/tooling helper를 위한 지원 대상 framework-owned container introspection seam을 노출합니다. 애플리케이션 코드는 `has(...)`와 `resolve(...)`를 우선 사용하세요. |
| `container.createRequestScope()` | `Container` instance method | 요청 스코프 의존성을 위한 자식 컨테이너를 생성합니다. |
| `container.has(token)` | `Container` instance method | 컨테이너나 부모에 토큰이 등록되어 있는지 확인합니다. |
| `container.hasRequestScopedDependency(token)` | `Container` instance method | 토큰 해석 시 provider 그래프에 request-scoped 의존성이나 순환이 있어 request-scope 컨테이너가 필요할 수 있는지 확인합니다. |
| `container.dispose()` | `Container` instance method | request child와 루트가 소유한 singleton instance를 정리합니다. |
| `forwardRef(fn)` | 선언 순서 문제를 위해 조회를 지연하는 토큰 래퍼를 반환합니다. 실제 생성자 순환을 해석 가능하게 만들지는 않습니다. |

@@ -184,4 +204,5 @@ | `isForwardRef(value)` | `forwardRef(...)`가 만든 값인지 확인하는 type guard입니다. 커스텀 provider tooling이 DI token wrapper와 통합될 때 사용할 수 있습니다. |

| Container helper types | `ClassType`, `Disposable`, `RequestScopeContainer`는 typed provider 선언, teardown hook, request-scope helper 경계를 지원합니다. |
| `ContainerResolutionState` | framework testing/tooling integration을 위해 `inspectResolutionState()`가 반환하는 공개 introspection record입니다. |
| Container introspection helper types | `ContainerResolutionState`, `ContainerResolutionCacheOwner`, `ContainerFactoryResolutionState`는 `inspectResolutionState()`가 반환하는 read-only graph/cache view와 controlled cache adoption helper를 설명합니다. |
| `NormalizedProvider` | 컨테이너가 검증한 provider record shape를 위한 compatibility-only 공개 타입입니다. provider를 작성할 때는 `Provider`나 구체 provider interface를 우선 사용하세요. normalized record 생성은 컨테이너가 소유합니다. |
| `@fluojs/di/internal` | sibling fluo package가 자체 순회 전에 컨테이너의 canonical provider validation을 적용할 수 있도록 `validateProviderInputs(...)`를 노출하는 package-integration seam입니다. 애플리케이션 코드는 계속 `Container`를 통해 provider를 등록해야 합니다. |
| `DiErrorContext` | DI error에 붙는 구조화된 context입니다. 로그와 테스트가 token, scope, module, dependency chain, hint를 검사할 수 있게 합니다. |

@@ -188,0 +209,0 @@ | 에러 클래스 | `InvalidProviderError`, `ContainerResolutionError`, `RequestScopeResolutionError`, `ScopeMismatchError`, `CircularDependencyError`, `DuplicateProviderError`. |

+47
-26

@@ -70,3 +70,3 @@ # @fluojs/di

- **Value Providers**: `{ provide: 'API_URL', useValue: 'https://api.example.com' }`.
- **Factory Providers**: `{ provide: 'ASYNC_CONFIG', useFactory: async (db) => await db.load(), inject: [Database] }`.
- **Factory Providers**: `{ provide: 'ASYNC_CONFIG', useFactory: async (db) => await db.load(), inject: [Database] }`. Add `resolverClass` when the factory should inherit the referenced class's DI metadata, such as `@Scope(...)`, unless an explicit provider `scope` is set.
- **Alias Providers**: `{ provide: ILogger, useExisting: PinoLogger }` allows mapping one token to another existing provider.

@@ -79,7 +79,7 @@

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.
During disposal, each container first recursively tears down live request-scope children it owns, so disposing a non-root request scope also closes nested request scopes before its own request cache. Root disposal 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.
### Provider Overrides
Use `override(...providers)` when a test or request-local boundary needs to replace existing registrations deliberately. Overrides replace the current provider set for each token, invalidate cached instances in the current container and already-materialized request-scope descendants, and dispose stale instances immediately. Multi-provider overrides replace the full multi-provider set for that token, so pass every replacement provider together; mixing single and multi replacements for the same token in one override call is rejected as ambiguous.
Use `override(...providers)` when a test or request-local boundary needs to replace existing registrations deliberately. Overrides replace the current provider set for each token, invalidate cached instances in the current container and already-materialized request-scope descendants, and dispose stale instances before the next replacement resolution continues. Multi-provider overrides replace the full multi-provider set for that token, so pass every replacement provider together; mixing single and multi replacements for the same token in one override call is rejected as ambiguous.

@@ -96,3 +96,3 @@ ### Request Scoping

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.
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`). For class providers, an omitted or `undefined` `inject` value falls back to the `useClass` `@Inject(...)` metadata; any other explicit `inject` value must be an array containing valid tokens or well-formed `forwardRef(...)` / `optional(...)` wrappers. Explicit `scope` values must be `singleton`, `request`, or `transient`. Invalid provider shapes throw `InvalidProviderError` before they can affect the container graph.

@@ -135,18 +135,38 @@ ## Circular Dependency Handling

You can easily override providers in the container to use mocks or stubs during unit testing by using `useValue`.
Register the complete dependency graph first, then use `override(...)` with `useValue` to replace an existing provider with a mock or stub. `register(...)` adds new providers and rejects duplicate tokens; `override(...)` is the supported replacement API.
```typescript
import { Inject } from '@fluojs/core';
import { Container } from '@fluojs/di';
import { expect, it, vi } from 'vitest';
const container = new Container();
const mockDb = { query: vi.fn() };
class Database {
async query(): Promise<readonly string[]> {
return ['real row'];
}
}
// Override the real Database class with a mock value
container.register({
provide: Database,
useValue: mockDb
@Inject(Database)
class DataService {
constructor(private readonly database: Database) {}
async load(): Promise<readonly string[]> {
return this.database.query();
}
}
it('uses a mock database', async () => {
const mockDb = { query: vi.fn().mockResolvedValue(['mock row']) };
const container = new Container().register(Database, DataService);
container.override({
provide: Database,
useValue: mockDb,
});
const service = await container.resolve(DataService);
await expect(service.load()).resolves.toEqual(['mock row']);
expect(mockDb.query).toHaveBeenCalledOnce();
});
const service = await container.resolve(DataService);
// service uses mockDb instead of the real Database instance
```

@@ -164,13 +184,13 @@

| Export | Description |
|---|---|
| `Container` | The main DI container class. |
| `register(...providers)` | Registers one or more providers. |
| `override(...providers)` | Replaces existing providers, invalidates cached instances, and disposes stale instances. |
| `resolve<T>(token)` | Asynchronously resolves a token to an instance. |
| `inspectResolutionState()` | Exposes the supported framework-owned container introspection seam for testing/tooling helpers that must preserve cache ownership. Prefer `has(...)` and `resolve(...)` for application code. |
| `createRequestScope()` | Creates a child container for request-scoped dependencies. |
| `has(token)` | Checks if a token is registered in the container or its parents. |
| `hasRequestScopedDependency(token)` | Checks whether resolving a token may require a request-scope container because its provider graph contains request-scoped dependencies or is cyclic. |
| `dispose()` | Disposes request children and root-owned singleton instances. |
| Surface | Kind | Description |
|---|---|---|
| `Container` | Root export | The main DI container class. |
| `container.register(...providers)` | `Container` instance method | Registers one or more providers. |
| `container.override(...providers)` | `Container` instance method | Replaces existing providers, invalidates cached instances, and ensures stale instance disposal settles before the next replacement resolution continues. |
| `container.resolve<T>(token)` | `Container` instance method | Asynchronously resolves a token to an instance. |
| `container.inspectResolutionState()` | `Container` instance method | Exposes the supported framework-owned container introspection seam for testing/tooling helpers that must preserve cache ownership through snapshot read-only map views, frozen provider records, and controlled cache adoption. Prefer `has(...)` and `resolve(...)` for application code. |
| `container.createRequestScope()` | `Container` instance method | Creates a child container for request-scoped dependencies. |
| `container.has(token)` | `Container` instance method | Checks if a token is registered in the container or its parents. |
| `container.hasRequestScopedDependency(token)` | `Container` instance method | Checks whether resolving a token may require a request-scope container because its provider graph contains request-scoped dependencies or is cyclic. |
| `container.dispose()` | `Container` instance method | Disposes request children and root-owned singleton instances. |
| `forwardRef(fn)` | Returns a token wrapper that defers lookup for declaration-order issues; it does not make constructor dependency cycles resolvable. |

@@ -184,4 +204,5 @@ | `isForwardRef(value)` | Type guard for values produced by `forwardRef(...)`; useful when integrating custom provider tooling with DI token wrappers. |

| Container helper types | `ClassType`, `Disposable`, and `RequestScopeContainer` support typed provider declarations, teardown hooks, and request-scope helper boundaries. |
| `ContainerResolutionState` | Public introspection record returned by `inspectResolutionState()` for framework testing/tooling integrations. |
| Container introspection helper types | `ContainerResolutionState`, `ContainerResolutionCacheOwner`, and `ContainerFactoryResolutionState` describe the read-only graph/cache views and controlled cache adoption helpers returned by `inspectResolutionState()`. |
| `NormalizedProvider` | Compatibility-only public type for the container's validated provider record shape. Prefer authoring providers with `Provider` or the specific provider interfaces; the container owns normalized record construction. |
| `@fluojs/di/internal` | Package-integration seam exposing `validateProviderInputs(...)` so sibling fluo packages can apply the container's canonical provider validation before their own traversal. Application code should continue to register providers through `Container`. |
| `DiErrorContext` | Structured context attached to DI errors so logs and tests can inspect tokens, scopes, modules, dependency chains, and hints. |

@@ -188,0 +209,0 @@ | Error classes | `InvalidProviderError`, `ContainerResolutionError`, `RequestScopeResolutionError`, `ScopeMismatchError`, `CircularDependencyError`, `DuplicateProviderError`. |