
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@fioc/core
Advanced tools
Fluent, type-safe IoC container for JS/TS, simplifying dependency management in React, Next.js, and Node.js projects
FIoC (Fluid Inversion of Control) is a lightweight, reflection-free dependency injection (DI) library for TypeScript and JavaScript. It simplifies dependency management with type safety, immutability, and a fluent builder API — designed for both frontend and backend projects.
FIoC powers the broader ecosystem, including integrations for React, Next.js.
💡 “Fluid” means your dependency graph is built fluently and safely — no decorators, no reflection metadata, no runtime hacks.
Install via npm, yarn, or pnpm:
npm install @fioc/core
# or
yarn add @fioc/core
# or
pnpm add @fioc/core
A minimal “Hello World” example (with inference comments):
import { buildDIContainer, createDIToken } from "@fioc/core";
interface Logger {
log(message: string): void;
}
const LoggerToken = createDIToken<Logger>().as("Logger");
// LoggerToken: DIToken<Logger, "Logger">
const container = buildDIContainer()
.register(LoggerToken, { log: console.log })
.getResult();
// Resolve — inferred return type is Logger
const logger = container.resolve(LoggerToken); // logger: Logger
logger.log("Hello, FIoC!");
Repository<User>).@fioc/react
- @fioc/nextTokens uniquely identify dependencies in the container.
import { createDIToken } from "@fioc/core";
interface ApiService {
getData: () => string;
}
const ApiServiceToken = createDIToken<ApiService>().as("ApiService");
// ApiServiceToken: DIToken<ApiService, "ApiService">
import { buildDIContainer } from "@fioc/core";
import { ApiServiceToken } from "./tokens";
const HttpApiService: ApiService = { getData: () => "Hello, World!" };
const container = buildDIContainer()
.register(ApiServiceToken, HttpApiService) // registers ApiService
.getResult();
const api = container.resolve(ApiServiceToken); // api: ApiService
api.getData(); // "Hello, World!"
Implementation note: FIoC containers are immutable; registering returns a new container builder result. You can merge containers by passing its states into a new container builder.
Factories let you register logic that depends on other tokens.
Note: The object literal syntax is recommended if you need to keep your factories pure. It is recommended to use the fluent helper
withDependencies(Option 2) below if you want less boilerplate.
// ... imports and token definitions
const getDataUseCaseFactory = (apiService: ApiService) => () =>
apiService.getData();
const GetDataUseCaseToken =
createFactoryDIToken<typeof getDataUseCaseFactory>().as("GetDataUseCase");
const container = buildDIContainer()
.register(ApiServiceToken, HttpApiService)
.registerFactory(GetDataUseCaseToken, {
dependencies: [ApiServiceToken],
factory: getDataUseCaseFactory,
})
.getResult();
const useCase = container.resolve(GetDataUseCaseToken);
useCase();
import {
withDependencies,
createFactoryDIToken,
buildDIContainer,
} from "@fioc/core";
const getDataUseCaseFactory = withDependencies(ApiServiceToken).defineFactory(
(apiService /* inferred as ApiService */) => () => apiService.getData()
);
const GetDataUseCaseToken =
createFactoryDIToken<typeof getDataUseCaseFactory>().as("GetDataUseCase");
const container = buildDIContainer()
.register(ApiServiceToken, HttpApiService)
.registerFactory(GetDataUseCaseToken, getDataUseCaseFactory)
.getResult();
const useCase = container.resolve(GetDataUseCaseToken); // useCase: () => string
useCase();
import {
constructorToFactory,
withDependencies,
buildDIContainer,
createDIToken,
} from "@fioc/core";
class GetDataUseCase {
constructor(private apiService: ApiService) {}
execute = () => this.apiService.getData();
}
const GetDataUseCaseToken =
createDIToken<GetDataUseCase>().as("GetDataUseCase");
const container = buildDIContainer()
.register(ApiServiceToken, HttpApiService)
.registerFactory(
GetDataUseCaseToken,
withDependencies(ApiServiceToken).defineFactory(
constructorToFactory(GetDataUseCase)
) // Will get type error if dependencies don't match with constructor arguments
)
.getResult();
// resolve and inferred type is GetDataUseCase
const instance = container.resolve(GetDataUseCaseToken); // instance: GetDataUseCase
instance.execute();
Clarified semantics and examples — with inference comments.
Definitions
transient (default): a new value/factory result is produced every time the token is resolved.singleton: the first time the token is resolved in a given container, its value is created and then cached for all subsequent resolves on that container.scoped: the token's value is cached per scope. Scopes are short-lived resolution contexts created from a container; each scope gets its own cache for scoped tokens.const container = buildDIContainer()
.registerFactory(MyToken, myFactory, "scoped")
.getResult();
let resolvedA;
let resolvedB;
// Use createScope with a synchronous callback (returns immediately)
container.createScope((scopedContainer) => {
// scopedContainer has the same API as the main container
resolvedA = scopedContainer.resolve(MyToken); // resolvedA: inferred type
resolvedB = scopedContainer.resolve(MyToken); // cached inside this scope
resolvedA === resolvedB; // true (same scope)
});
// Use createScope with an asynchronous callback (waits for resolution)
// If the callback is async, ensure you await createScope
await container.createScope(async (scopedContainer) => {
await someAsyncOperation();
const resolvedC = scopedContainer.resolve(MyToken);
resolvedA === resolvedC; // false (different scope)
});
When to use which
singleton for heavy or long-lived services (database connections, caches).transient for stateless factories or values where fresh instances are required.scoped for per-request or per-job resources that should be reused inside a single operation but isolated across operations.FIoC allows you to register tokens with metadata (implements and generics) to look up implementations of a generic interface at runtime. This mimics the functionality of runtime reflection without sacrificing tree-shakeability.
// 1. Define base and generic tokens
interface Repository<T> {
findOne(): T;
}
const RepositoryToken = createDIToken<Repository<any>>().as("Repository");
interface User {
id: number;
}
const UserToken = createDIToken<User>().as("User");
// 2. Register the implementation with metadata
const UserRepositoryImpl: Repository<User> = { findOne: () => ({ id: 1 }) };
const UserRepositoryToken = createDIToken<typeof UserRepositoryImpl>().as(
"UserRepository",
{
implements: [RepositoryToken], // Implements Repository
generics: [UserToken], // Generic type is User
}
);
const container = buildDIContainer()
.register(UserRepositoryToken, UserRepositoryImpl)
.getResult();
// 3. Find and Resolve by Metadata
// resolveByMetadata returns all resolved instances that match the base and generic tokens
const userRepos = container.resolveByMetadata(RepositoryToken, [UserToken]);
// userRepos: Array<Repository<User>>
const user = userRepos[0].findOne(); // user: User
| Method | Description |
|---|---|
findImplementationTokens | Returns a list of matching DITokens. |
resolveByMetadata | Returns a list of resolved instances of the matching tokens. |
You can create isolated containers as modules and merge them together into a single container:
import { buildDIContainer } from "@fioc/core";
const containerA = buildDIContainer()
.register(ApiServiceToken, HttpApiService)
.getResult();
const containerB = buildDIContainer()
.register(ApiServiceToken, HttpApiService)
.getResult();
const container = buildDIContainer()
.merge(containerA.getState())
.merge(containerB.getState())
.getResult();
container.resolve(ApiServiceToken); // HttpApiService
Switch between environments or test setups seamlessly:
import { buildDIManager } from "@fioc/core";
const manager = buildDIManager()
.registerContainer(productionContainer, "prod")
.registerContainer(testContainer, "test")
.getResult()
.setDefaultContainer(process.env.APP_ENV || "prod");
const container = manager.getContainer();
Use cases:
createScope function.reflect-metadata without the overhead.The FIoC ecosystem provides specialized libraries for various environments:
@fioc/react: Hooks and context-based DI for React.@fioc/next: Type-safe DI for Next.js Server Components and Actions.Contributions are welcome! Feel free to open issues or submit pull requests on GitHub. Please include tests for behavioral changes and keep changes small and focused.
Licensed under the MIT License.
FAQs
Fluent, type-safe IoC container for JS/TS, simplifying dependency management in React, Next.js, and Node.js projects
We found that @fioc/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers 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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.