
Research
/Security News
11 Malicious NuGet Tools Pose as Game Cheats to Drop a Windows Host-Surveillance Payload
11 malicious NuGet tools pose as game cheats to deploy Windows payloads, track hosts, and use Google Sheets for telemetry and control.
@fluojs/di
Advanced tools
Minimal token-based dependency injection container powering every Fluo application.
English 한국어
Minimal token-based dependency injection container powering every fluo application.
npm install @fluojs/di
Use this package when you need to:
The container resolves tokens into instances based on their registered providers.
import { Container } from '@fluojs/di';
import { Inject, Scope } from '@fluojs/core';
class Logger {
log(msg: string) { console.log(msg); }
}
@Inject(Logger)
@Scope('singleton')
class UserService {
constructor(private logger: Logger) {}
async getStatus() {
this.logger.log('Checking status...');
return { status: 'active' };
}
}
const container = new Container();
container.register(Logger, UserService);
const service = await container.resolve(UserService);
const result = await service.getStatus();
fluo DI supports four provider shapes:
container.register(MyService) or { provide: MyToken, useClass: MyService }.{ provide: 'API_URL', useValue: 'https://api.example.com' }.{ 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.{ provide: ILogger, useExisting: PinoLogger } allows mapping one token to another existing provider.createRequestScope() call.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.
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.
Isolated containers can be created to handle per-request state without polluting the root container.
const requestContainer = container.createRequestScope();
const scopedService = await requestContainer.resolve(RequestScopedService);
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().
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.
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.
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.
import { forwardRef } from '@fluojs/di';
import { Inject } from '@fluojs/core';
@Inject(forwardRef(() => ServiceB))
class ServiceA {
constructor(private readonly serviceB: ServiceB) {}
}
class ServiceB {
getStatus() {
return 'ready';
}
}
forwardRef(...) and optional(...) are token wrappers used inside the class-level @Inject(...) token list or provider-level inject arrays. They are not decorators and do not attach to constructor parameters.
import { optional } from '@fluojs/di';
import { Inject } from '@fluojs/core';
@Inject(optional(AuditLogger))
class ServiceWithOptionalLogger {
constructor(private readonly auditLogger: AuditLogger | undefined) {}
}
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.
import { Inject } from '@fluojs/core';
import { Container } from '@fluojs/di';
import { expect, it, vi } from 'vitest';
class Database {
async query(): Promise<readonly string[]> {
return ['real row'];
}
}
@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();
});
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.
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.
| 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. | |
isForwardRef(value) | Type guard for values produced by forwardRef(...); useful when integrating custom provider tooling with DI token wrappers. | |
optional(token) | Returns a token wrapper that marks one dependency as optional; missing optional dependencies resolve to undefined. | |
isOptionalToken(value) | Type guard for values produced by optional(...); useful when inspecting provider-level inject arrays. | |
Scope | Exposes DEFAULT, REQUEST, and TRANSIENT scope constants. | |
| Provider types | Provider, ClassProvider, FactoryProvider, ValueProvider, and ExistingProvider describe the public registration shapes accepted by register(...) and override(...). | |
| Token wrapper types | ForwardRefFn and OptionalToken describe the wrapper values returned by forwardRef(...) and optional(...). | |
| Container helper types | ClassType, Disposable, and RequestScopeContainer support typed provider declarations, teardown hooks, and request-scope helper boundaries. | |
| 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. | |
| Error classes | InvalidProviderError, ContainerResolutionError, RequestScopeResolutionError, ScopeMismatchError, CircularDependencyError, DuplicateProviderError. |
Resolving a multi-provider token returns an array of resolved values in registration order.
@fluojs/core: Defines the @Inject() and @Scope() decorators used to annotate classes.@fluojs/runtime: Handles automatic registration of providers during application bootstrap.@fluojs/http: Creates a request scope for every incoming HTTP request.packages/di/src/container.tspackages/di/src/container.test.tsexamples/minimal/src/app.tsFAQs
Minimal token-based dependency injection container powering every Fluo application.
We found that @fluojs/di demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
11 malicious NuGet tools pose as game cheats to deploy Windows payloads, track hosts, and use Google Sheets for telemetry and control.

Research
/Security News
4 compromised asyncapi packages deliver miasma botnet loader on macOS, Linux and Windows.

Research
/Security News
A compromised jscrambler npm release added a malicious preinstall hook that runs hidden native binaries on Linux, macOS, and Windows.