New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@fioc/core

Package Overview
Dependencies
Maintainers
2
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fioc/core

Fluent, type-safe IoC container for JS/TS, simplifying dependency management in React, Next.js, and Node.js projects

latest
Source
npmnpm
Version
1.7.4
Version published
Maintainers
2
Created
Source

@fioc/core

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.

🚀 Quick Start

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!");

✨ Features

  • 🪶 Lightweight & Tree-Shakeable — zero reflection, minimal dependencies.
  • 🎯 Type-Safe Resolution — no casting, all types inferred automatically.
  • 🧱 Fluent Builder Pattern — chainable, immutable container configuration.
  • 🔄 Immutable by Default — safe for concurrent or multithreaded use; supports scoped and singleton overrides.
  • 🧬 Generic Type Metadata — Supports registering and resolving implementations based on interface tokens and their generic arguments (e.g., Repository<User>).
  • 🔌 Universal — works in Node.js, browser, Deno, Bun, and serverless environments.
  • 🧩 Flexible Factory System — register values, factories, or class constructors.
  • ⚙️ Composable Containers — merge configurations or swap environments dynamically.
  • 🔗 Ecosystem Foundation — powers:   - @fioc/react   - @fioc/next

📘 Table of Contents

🪄 Creating Tokens

Tokens 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">

⚙️ Registering & Resolving

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

Factories let you register logic that depends on other tokens.

Option 1 — Manual Configuration

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();

🧱 Class Factories

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();

🌀 Scopes

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.

Scoped example (Single Function for Sync/Async)

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

  • Use singleton for heavy or long-lived services (database connections, caches).
  • Use transient for stateless factories or values where fresh instances are required.
  • Use scoped for per-request or per-job resources that should be reused inside a single operation but isolated across operations.

🧬 Advanced: Generic Type Metadata

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.

Example: Resolving a Repository by Generic Type

// 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
MethodDescription
findImplementationTokensReturns a list of matching DITokens.
resolveByMetadataReturns a list of resolved instances of the matching tokens.

🔀 Merge Containers

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

🧩 Container Manager

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:

  • Environment-specific containers
  • Online/offline or mock/live switching
  • Testing without global mutations

🧩 Why FIoC

Pros

  • Reflection-free & decorator-free: Works without reflect-metadata, decorators, or runtime hacks → fully compatible with Deno, Bun, Node, and browsers.
  • Tree-shakeable: Due to explicit dependency declaration and no reliance on reflection, only imported symbols are included in the final bundle → minimal footprint for frontend projects.
  • Immutable container state: Safe for concurrent applications, serverless functions, and multi-threaded environments.
  • Scoped lifecycles: Supports transient, singleton, and scoped instances with a single, reliable createScope function.
  • Generic Type Metadata: Unique ability to resolve dependencies based on generic type constraints, replacing a key feature of reflect-metadata without the overhead.
  • Strong TypeScript inference: Minimal boilerplate; dependencies are automatically type-checked and inferred.
  • Fluent builder API: Chainable, readable syntax for container registration and composition.
  • Modular & composable: Merge containers or swap configurations easily → ideal for testing or multi-environment setups.

Cons

  • No automatic decorators: Users coming from decorator-based DI libraries may need to adjust patterns.
  • Requires explicit token management: Every dependency needs a DIToken or factory token → slightly more verbose than reflection-based DI.

🌐 FIoC Ecosystem

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.

🤝 Contributing

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.

📜 License

Licensed under the MIT License.

Keywords

dependency injection

FAQs

Package last updated on 20 Dec 2025

Did you know?

Socket

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.

Install

Related posts