Skip to content
conduit logoConduitDI
Typed dependency injection for TypeScript.
v0.0.12.6 KB gzip 0 depsBrowser · Node.js · SSR · Deno
createContainertokenscopeloadModulesresolveOptionalresolveOrDefaulttryResolvetrySyncResolve +12 more →

Why Conduit?

Manual dependency wiring often spreads across modules, making lifetimes and teardown behavior difficult to reason about in larger systems.

ts
// Before — manual wiring, no lifecycle, no type safety
const logger = new ConsoleLogger();
const db = await connectDb(process.env.DATABASE_URL);
const repo = new UserRepo(db, logger);
const service = new UserService(repo, logger);
// cleanup is your problem

// After — explicit tokens, typed resolution, disposal hooks
const container = createContainer();
container.value(Logger, new ConsoleLogger());
container.factory(Db, () => connectDb(process.env.DATABASE_URL), { dispose: (db) => db.close() });
container.factory(UserRepo, async (r) => {
  const [db, logger] = await Promise.all([r.resolve(Db), r.resolve(Logger)]);
  return new UserRepo(db, logger);
});
container.factory(UserService, async (r) => {
  const [repo, logger] = await Promise.all([r.resolve(UserRepo), r.resolve(Logger)]);
  return new UserService(repo, logger);
});

const service = await container.resolve(UserService);
await container.dispose(); // all hooks run automatically
FeatureConduittsyringeInversifyJS
Bundle size2.6 KB~6 kB~45 kB
Typed token ergonomicsPartialPartial
Async-first resolutionPartialPartial
Child container scopes
Explicit disposal lifecyclePartial
Decorator-free usage (decorator-oriented) (commonly decorator-oriented)
Zero dependencies

Use Conduit when you need a compact typed container with explicit scopes and lifecycle control.

Consider decorator-heavy DI frameworks when your project is already standardized around metadata/decorator injection patterns.

Installation

sh
pnpm add @vielzeug/conduit
sh
npm install @vielzeug/conduit
sh
yarn add @vielzeug/conduit

Quick Start

ts
import { createContainer, token } from '@vielzeug/conduit';

const Logger = token<{ log(message: string): void }>('Logger');
const Service = token<{ run(): Promise<void> }>('Service');

const container = createContainer();

container.value(Logger, console);
container.factory(Service, async (r) => {
  const logger = await r.resolve(Logger);
  return { run: async () => logger.log('Running service') };
});

const service = await container.resolve(Service);
await service.run();

await container.dispose();

Features

  • Small core API — token, scope, createContainer, and a focused set of container methods
  • Typed dependency contracts via Symbol tokens with phantom types
  • Factory functions receive a FactoryResolver — dependencies resolved lazily via r.resolve(Token)
  • Named scope tokens via scope() and createScope() for explicit lifecycle isolation
  • Async-first resolution with singleton deduplication for concurrent callers
  • Sync resolution path (resolveSync) for hot paths after warm-up — rethrows cached rejections for failed singletons
  • Free-function helpers: resolveSyncOptional, resolveSyncOrDefault, resolveOptional, resolveOrDefault, tryResolve, trySyncResolve
  • resolveMany() to resolve multiple tokens in parallel with typed tuples
  • resolveAll() to eagerly warm all singletons; pass { includeScoped: true } to also pre-warm named-scope factories
  • InferTokenTypes<T> utility type to infer a typed tuple from a token array
  • Registration existence check (has) without triggering factory execution
  • ContainerModule + loadModules() for grouping and async provider setup
  • freeze() to lock the container after startup; idempotent — safe to call multiple times; declare deps: on factories for static cycle detection at freeze time
  • inspect() to get a serializable dependency graph
  • on() to subscribe to container lifecycle events (each event carries a source field)
  • onResolve() interceptor called after every successful resolution — for telemetry and hot-path observability
  • Dispose hooks on both factory and value registrations; failures warn in dev instead of throwing
  • Named scope containers for request/component/test scope boundaries
  • Explicit disposal lifecycle with Symbol.asyncDispose support

Documentation

See Also

  • Familiar for dependency-managed worker orchestration.
  • Herald for pub/sub coordination in container-managed modules.
  • Ward for injecting authorization services.