Skip to content

API card ​

Every runtime call of di-bag: its summary, the codes it can raise, and an example that type-checks. An example without an import line uses import { DiBag } from 'di-bag';. The rules are in AGENTS.md; each code links to its section in errors.md.

One way per task ​

TaskCall
Start a graphDiBag.createBuilder()
Register a servicebuilder.register(more)
Register a synchronous factory for a browser or workerDiBag.fromSyncFactory(callback, options)
Register an async factory for a browser or workerDiBag.fromAsyncFactory(callback, options)
Attach cleanupDiBag.withDisposal(create, dispose)
Choose a lifetimeDiBag.withLifetime(registration, lifetime)
Seal a modulebuilder.buildModule(keys, options?)
Install a modulebuilder.installModule(module)
Check the graph on its own linebuilder.verifyGraph()
Build a bagbuilder.build()
Replace for a testbag.fork(keys, overrides)
Open a scopebag.createScope()
Resolvebag.resolve(token)
Closebag.close(options?)

DiBag facade ​

DiBag.withConfiguration(options) ​

Return a facade with inherited runtime settings and appended observers. Throws: DI_BAG_INVALID_CONFIGURATION.

ts
const Observed = DiBag.withConfiguration({
  observers: [{ onEvent: event => console.log(event.kind), onError: failure => console.error(failure.error) }],
});

DiBag.fromFactory(callback, options) ​

Describe a named-dependency factory with an explicit acquisition mode or the acquisition's abort signal. Throws: DI_BAG_INVALID_FACTORY, DI_BAG_INVALID_ACQUISITION_MODE.

ts
type Query = { then(done: (rows: string[]) => void): void };
const query = DiBag.fromFactory((): Query => ({ then: done => done([]) }), { acquisitionMode: 'raw' });

DiBag.fromSyncFactory(callback, options) ​

Describe a synchronous factory that runs on every host: the exact return value is the service and then is never read. Throws: DI_BAG_INVALID_FACTORY.

ts
const config = DiBag.fromSyncFactory(() => ({ url: 'memory:' }));

DiBag.fromAsyncFactory(callback, options) ​

Describe an asynchronous factory that runs on every host: the service is the returned native Promise and withDisposal receives its fulfilled value. Throws: DI_BAG_INVALID_FACTORY.

ts
const db = DiBag.withDisposal(
  DiBag.fromAsyncFactory(async ({ config }: { config: { url: string } }) => ({ url: config.url, end: async () => {} })),
  db => db.end(),
);

DiBag.token(key) ​

Create a typed token from a unique symbol; .of<Service>() fixes its service type. Throws: DI_BAG_INVALID_TOKEN.

ts
const clockKey = Symbol('clock');
const clock = DiBag.token(clockKey).of<{ now(): number }>();

DiBag.optional(token) ​

Create a positional dependency that yields undefined only when the token is unregistered. Throws: DI_BAG_INVALID_TOKEN.

ts
const clockKey = Symbol('clock');
const clock = DiBag.token(clockKey).of<{ now(): number }>();
const stamp = DiBag.fromFunction([DiBag.optional(clock)], source => source?.now() ?? 0);

DiBag.lazy(token) ​

Create a positional dependency supplied as a function that resolves the token when called. Throws: DI_BAG_INVALID_TOKEN.

ts
const clockKey = Symbol('clock');
const clock = DiBag.token(clockKey).of<{ now(): number }>();
const stamp = DiBag.fromFunction([DiBag.lazy(clock)], getClock => () => getClock().now());

DiBag.all(token) ​

Create a positional dependency containing every contribution to a collection token, in order. Throws: DI_BAG_INVALID_TOKEN.

ts
const toolsKey = Symbol('tools');
const tools = DiBag.token(toolsKey).of<string>();
const menu = DiBag.fromFunction([DiBag.all(tools)], names => names.join(', '));

DiBag.fromPlugin(dependencies, plugin, options) ​

Validate an unknown plugin descriptor now and its acquired output at acquisition. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_INVALID_PLUGIN_OPTIONS, DI_BAG_PLUGIN_VALIDATION.

ts
declare const descriptor: unknown;
const greeter = DiBag.fromPlugin([], descriptor, {
  acquisitionMode: 'raw',
  validate: (value): value is () => string => typeof value === 'function',
});

DiBag.fromFunction(tokens, callback, ...modeOptions) ​

Adapt a positional function whose parameters receive the listed tokens' services. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_INVALID_FUNCTION, DI_BAG_INVALID_ACQUISITION_MODE.

ts
const clockKey = Symbol('clock');
const clock = DiBag.token(clockKey).of<{ now(): number }>();
const stamp = DiBag.fromFunction([clock], source => new Date(source.now()).toISOString());

DiBag.fromClass(tokens, constructor, ...modeOptions) ​

Adapt a class whose constructor parameters receive the listed tokens' services. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_INVALID_CONSTRUCTOR, DI_BAG_INVALID_ACQUISITION_MODE.

ts
class Greeter { constructor(readonly greeting: string) {} }
const greetingKey = Symbol('greeting');
const greeter = DiBag.fromClass([DiBag.token(greetingKey).of<string>()], Greeter);

DiBag.createBuilder() ​

Begin an empty immutable graph; build creates its owning bag, buildModule seals a reusable module.

ts
const bag = DiBag.createBuilder().register({ greeting: () => 'hello' }).build();

DiBag.withDisposal(create, dispose) ​

Make the bag own a factory's value and run dispose on it when the bag closes. Throws: DI_BAG_INVALID_REGISTRATION.

ts
const bag = DiBag.createBuilder()
  .register({ controller: DiBag.withDisposal(() => new AbortController(), controller => controller.abort()) })
  .build();
await bag.close();

DiBag.withLifetime(registration, lifetime) ​

Select root, scoped (the default), or transient caching for a registration. Throws: DI_BAG_INVALID_LIFETIME, DI_BAG_INVALID_REGISTRATION.

ts
const bag = DiBag.createBuilder()
  .register({ cache: DiBag.withLifetime(() => new Map<string, string>(), 'root') })
  .build();

DiBag.withMetadata(registration, options) ​

Attach static registration metadata, or per-acquisition metadata in direct or awaited mode. Throws: DI_BAG_INVALID_METADATA, DI_BAG_DUPLICATE_METADATA, DI_BAG_INVALID_REGISTRATION.

ts
const greeting = DiBag.withMetadata(() => 'hello', { static: { owner: 'greeting' } });

DiBag.transformService(registration, options) ​

Transform the exposed service while retaining dependencies, metadata, lifetime, and existing ownership. Throws: DI_BAG_INVALID_TRANSFORM, DI_BAG_INVALID_ACQUISITION_MODE, DI_BAG_INVALID_REGISTRATION.

ts
const shout = DiBag.transformService(() => 'hello', { mode: 'direct', transform: text => text.toUpperCase() });

Builder ​

builder.register(more) ​

Add new string-named registrations. Throws: DI_BAG_INVALID_REGISTRATION, DI_BAG_DUPLICATE_REGISTRATION, DI_BAG_INVALID_TOKEN.

ts
type Clock = { now(): number };
const builder = DiBag.createBuilder()
  .register({ clock: (): Clock => ({ now: () => Date.now() }) })
  .register({ stamp: ({ clock }: { clock: Clock }) => clock.now() });

builder.alias(destination, target) ​

Add another lookup name or token for an existing service. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_DUPLICATE_REGISTRATION, DI_BAG_INVALID_ALIAS.

ts
const builder = DiBag.createBuilder().register({ clock: () => Date.now() }).alias('now', 'clock');

builder.contribute(token, registration) ​

Append a provider to a typed-token collection. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_INVALID_REGISTRATION.

ts
const toolsKey = Symbol('tools');
const tools = DiBag.token(toolsKey).of<string>();
const builder = DiBag.createBuilder().contribute(tools, () => 'search').contribute(tools, () => 'fetch');

builder.replace(key, registration) ​

Replace an existing string-named registration with a dependency-free factory. Throws: DI_BAG_INVALID_REPLACEMENT, DI_BAG_INVALID_REGISTRATION, DI_BAG_INVALID_TOKEN.

ts
const builder = DiBag.createBuilder().register({ clock: () => Date.now() }).replace('clock', () => 0);

builder.installModule(module) ​

Install a sealed module, allocating fresh private bindings for this installation. Throws: DI_BAG_INVALID_MODULE, DI_BAG_DUPLICATE_REGISTRATION.

ts
const greeting = DiBag.createBuilder()
  .register({ greet: ({ name }: { name: string }) => `hello, ${name}` })
  .buildModule(['greet']);
const bag = DiBag.createBuilder().installModule(greeting).register({ name: () => 'Ada' }).build();

builder.verifyGraph() ​

Report at the type level why this graph would not build; the runtime call does nothing.

ts
const builder = DiBag.createBuilder().register({ greeting: () => 'hello' });
builder.verifyGraph() satisfies void;

builder.buildModule(keys, options?) ​

Seal this graph as a reusable module and select its public names and typed tokens. Throws: DI_BAG_INVALID_EXPORT, DI_BAG_INVALID_TOKEN.

ts
const orders = DiBag.createBuilder()
  .register({ repository: () => new Map<string, number>() })
  .register({ placeOrder: ({ repository }: { repository: Map<string, number> }) => (id: string) => repository.set(id, 1) })
  .buildModule(['placeOrder'], { label: 'orders' });
// Errors and inspectGraph() name the private binding 'orders/repository'.
const app = DiBag.createBuilder().installModule(orders).build();

builder.build() ​

Finish a complete graph as a lazy bag. Throws: DI_BAG_CLASSIFIER_REQUIRED.

ts
const bag = DiBag.createBuilder().register({ greeting: () => 'hello' }).build();
await bag.close();

builder.buildAndStart(keys, options?) ​

Create a fresh bag and acquire selected services before returning it. Throws: DI_BAG_STARTUP_FAILED, DI_BAG_STARTUP_CANCELLED, DI_BAG_INVALID_STARTUP, DI_BAG_INVALID_TOKEN, DI_BAG_CLASSIFIER_REQUIRED.

ts
const bag = await DiBag.createBuilder()
  .register({ db: async () => ({ ping: () => true }) })
  .buildAndStart(['db'], { timeoutMs: 5_000 });

Bag ​

bag.resolve(token) ​

Resolve a named or typed-token service, acquiring it lazily when needed. Throws: DI_BAG_CLOSING, DI_BAG_CLOSED, DI_BAG_INVALID_TOKEN, DI_BAG_MISSING_REGISTRATION, DI_BAG_MISSING_DEPENDENCY, DI_BAG_CYCLE, DI_BAG_LIFETIME_DEPENDENCY, DI_BAG_INVALID_DEPENDENCY_ACCESS, DI_BAG_STRUCTURAL_THENABLE, DI_BAG_INVALID_CLASSIFIER_RESULT, DI_BAG_INVALID_METADATA, DI_BAG_PLUGIN_VALIDATION.

ts
const bag = DiBag.createBuilder().register({ greeting: () => 'hello' }).build();
const greeting: string = bag.resolve('greeting');

bag.resolveAll(token) ​

Resolve every contribution for a typed token in declaration and installation order. Throws: DI_BAG_CLOSING, DI_BAG_CLOSED, DI_BAG_INVALID_TOKEN.

ts
const toolsKey = Symbol('tools');
const tools = DiBag.token(toolsKey).of<string>();
const bag = DiBag.createBuilder().contribute(tools, () => 'search').contribute(tools, () => 'fetch').build();
const names: readonly string[] = bag.resolveAll(tools);

bag.inspectAll(token) ​

Inspect every contribution for a token without running its factories. Throws: DI_BAG_INVALID_TOKEN.

ts
const toolsKey = Symbol('tools');
const tools = DiBag.token(toolsKey).of<string>();
const bag = DiBag.createBuilder().contribute(tools, () => 'search').build();
const labels = bag.inspectAll(tools).map(snapshot => snapshot.label);

bag.inspect(token) ​

Inspect static metadata and copied acquisition state without resolving a service. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_MISSING_REGISTRATION, DI_BAG_CYCLE.

ts
const bag = DiBag.createBuilder().register({ greeting: () => 'hello' }).build();
const acquired = bag.inspect('greeting').acquisitions.length;

bag.inspectGraph() ​

Describe every binding this bag can resolve and the dependency edges observed so far.

ts
const bag = DiBag.createBuilder().register({ greeting: () => 'hello' }).build();
const labels = bag.inspectGraph().bindings.map(binding => binding.label);

bag.createScope() ​

Create a tracked child with the same graph and fresh scoped acquisitions. Throws: DI_BAG_INVALID_SCOPE, DI_BAG_INVALID_TOKEN, DI_BAG_CLOSING, DI_BAG_CLOSED, DI_BAG_INVALID_REGISTRATION, DI_BAG_CLASSIFIER_REQUIRED.

ts
const app = DiBag.createBuilder().register({ requestId: () => Math.random() }).build();
const request = app.createScope();
const id: number = request.resolve('requestId');
await request.close();

bag.fork(keys, overrides) ​

Create an independent bag with selected replacements, the way tests substitute dependencies. Throws: DI_BAG_CLOSING, DI_BAG_CLOSED, DI_BAG_INVALID_OVERRIDE, DI_BAG_INVALID_TOKEN, DI_BAG_INVALID_REGISTRATION, DI_BAG_CLASSIFIER_REQUIRED.

ts
type Clock = { now(): number };
const app = DiBag.createBuilder().register({ clock: (): Clock => ({ now: () => Date.now() }) }).build();
const test = app.fork(['clock'], { clock: (): Clock => ({ now: () => 0 }) });
await test.close();

bag.close(options?) ​

Close this bag, drain in-flight work, and dispose owned resources once. Throws: DI_BAG_CLEANUP_FAILED, DI_BAG_CLOSE_FAILED, DI_BAG_CLOSE_TIMEOUT, DI_BAG_CLOSE_ABORTED, DI_BAG_INVALID_CLOSE.

ts
const bag = DiBag.createBuilder().register({ value: () => 1 }).build();
await bag.close({ timeoutMs: 10_000, signal: AbortSignal.timeout(15_000) });

Errors ​

DiBagPluginValidationError ​

A plugin descriptor or its acquired output failed validation at the checked plugin boundary. Code: DI_BAG_PLUGIN_VALIDATION.

ts
import { DiBag, DiBagPluginValidationError } from 'di-bag';

try {
  DiBag.fromPlugin([], { apiVersion: 2 }, { acquisitionMode: 'raw', validate: (value): value is string => typeof value === 'string' });
} catch (error) {
  if (error instanceof DiBagPluginValidationError) console.error(error.phase, error.reason);
}

DiBagCleanupError ​

One or more disposers failed during close(); every cleanup was still attempted. Code: DI_BAG_CLEANUP_FAILED.

ts
import { DiBag, DiBagCleanupError } from 'di-bag';

const bag = DiBag.createBuilder().register({ value: () => 1 }).build();
await bag.close().catch((error: unknown) => {
  if (error instanceof DiBagCleanupError) for (const failure of error.failures) console.error(failure.label, failure.error);
});

DiBagStartupError ​

buildAndStart failed to acquire a selected service; the new bag has already released its resources. Code: DI_BAG_STARTUP_FAILED.

ts
import { DiBag, DiBagStartupError } from 'di-bag';

const builder = DiBag.createBuilder().register({ db: async (): Promise<number> => { throw new Error('offline'); } });
try {
  await builder.buildAndStart(['db']);
} catch (error) {
  if (error instanceof DiBagStartupError) console.error(error.cause, error.cleanupFailures);
}

DiBagStartupCancelledError ​

buildAndStart stopped waiting on abort or timeout; cleanupPromise settles when the partial bag is released. Code: DI_BAG_STARTUP_CANCELLED.

ts
import { DiBag, DiBagStartupCancelledError } from 'di-bag';

const builder = DiBag.createBuilder().register({ db: () => new Promise<number>(() => {}) });
try {
  await builder.buildAndStart(['db'], { timeoutMs: 1_000 });
} catch (error) {
  if (error instanceof DiBagStartupCancelledError) await error.cleanupPromise;
}

DiBagCloseCancelledError ​

A close({ timeoutMs, signal }) wait stopped before cleanup finished; cleanup keeps running. Code: DI_BAG_CLOSE_TIMEOUT, DI_BAG_CLOSE_ABORTED.

ts
import { DiBag, DiBagCloseCancelledError } from 'di-bag';

const bag = DiBag.createBuilder().register({ value: () => 1 }).build();
try {
  await bag.close({ timeoutMs: 5_000 });
} catch (error) {
  if (error instanceof DiBagCloseCancelledError) console.error(error.details.pending);
  throw error;
}

Ordinary services. Checked composition. Explicit ownership.