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 services by namebuilder.withServices(providersByName)
Create a collection tokenDiBag.createToken(symbol)
Contribute a collection memberbuilder.withCollectionContribution(options)
Register a portable factoryDiBag.createProvider(factory, options)
Attach disposalDiBag.providerWithDisposal(options)
Choose a lifetimeDiBag.providerWithLifetime(options)
Attach registration metadataDiBag.providerWithRegistrationMetadata(options)
Attach acquisition metadataDiBag.providerWithAcquisitionMetadata(options)
Transform a serviceDiBag.providerWithTransformedService(options)
Seal a modulebuilder.buildModule(options)
Install modulesbuilder.withInstalledModules(modules)
Rename a module requirementmodule.withRenamedRequirement(options)
Check the graph on its own linebuilder.verifyGraphAtCompileTime()
Build a containerbuilder.buildContainer()
Wait for services before accepting workcontainer.ensureServicesReady(serviceKeys, options?)
Replace services for a testcontainer.createIndependentContainer(replacedServiceKeys, replacementProviders)
Open a child containercontainer.createChildContainer(options?)
Resolvecontainer.resolve(token)
Resolve a collectioncontainer.resolveCollection(token)
Closecontainer.close(options?)

DiBag facade ​

DiBag.createProvider(factory, options) ​

Create a provider from a named-dependency factory.

ts
const config = DiBag.createProvider(() => ({ url: 'memory:' }), { factoryReturnKind: 'sync-value' });

DiBag.createProviderFromFunction(options) ​

Create a provider whose factory receives positional dependency values.

ts
const portSymbol = Symbol('port');
const port = DiBag.createToken(portSymbol).forService<number>();
const client = DiBag.createProviderFromFunction({ dependencies: [port], factoryFunction: value => ({ port: value }) });

DiBag.createProviderFromClass(options) ​

Create a provider that constructs a class from positional dependencies.

ts
const portSymbol = Symbol('port');
const port = DiBag.createToken(portSymbol).forService<number>();
class Client { constructor(readonly port: number) {} }
const client = DiBag.createProviderFromClass({ dependencies: [port], serviceClass: Client });

DiBag.createProviderFromPlugin(options) ​

Create a provider from a versioned plugin descriptor.

ts
const pluginDescriptor = { apiVersion: 1 as const, create: () => ({ run() {} }) };
const plugin = DiBag.createProviderFromPlugin({ dependencies: [], pluginDescriptor, factoryReturnKind: 'uninspected', isValidPluginOutput: (value): value is { run(): void } => typeof value === 'object' && value !== null });

DiBag.createToken(symbol) ​

Create a nominal token from a symbol.

ts
const clockSymbol = Symbol('clock');
const clock = DiBag.createToken(clockSymbol).forService<{ now(): number }>();

DiBag.withConfiguration(options) ​

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

ts
const Observed = DiBag.withConfiguration({
  lifecycleObservers: [{ onLifecycleEvent: event => console.log(event.kind), onObserverFailure: failure => console.error(failure.error) }],
});

DiBag.optional(token) ​

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

ts
const clockKey = Symbol('clock');
const clock = DiBag.createToken(clockKey).forService<{ now(): number }>();
const stamp = DiBag.createProviderFromFunction({ dependencies: [DiBag.optional(clock)], factoryFunction: 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.createToken(clockKey).forService<{ now(): number }>();
const stamp = DiBag.createProviderFromFunction({ dependencies: [DiBag.lazy(clock)], factoryFunction: getClock => () => getClock().now() });

DiBag.createBuilder() ​

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

ts
const container = DiBag.createBuilder().withServices({ greeting: () => 'hello' }).buildContainer();

DiBag.providerWithDisposal(options) ​

Add an ownership stage to a provider input. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_PROVIDER.

ts
const owned = DiBag.providerWithDisposal({ provider: () => ({ close() {} }), disposeService: service => service.close() });

DiBag.providerWithLifetime(options) ​

Return a provider with singleton, scoped, or transient caching. Providers are scoped per container by default; mark shared clients singleton when none of their dependencies are scoped. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_PROVIDER.

ts
const createClient = () => ({ close() {} });
const client = DiBag.providerWithLifetime({ provider: DiBag.createProvider(() => createClient()), lifetime: 'singleton:one-per-container-tree' });

DiBag.providerWithRegistrationMetadata(options) ​

Add noncolliding registration metadata without acquiring the service. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_DUPLICATE_METADATA_KEY, DI_BAG_INVALID_PROVIDER.

ts
const registered = DiBag.providerWithRegistrationMetadata({ provider: () => 1, registrationMetadata: { owner: 'platform' } });

DiBag.providerWithAcquisitionMetadata(options) ​

Append one synchronous acquisition-metadata frame using the selected callback input. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_ACQUISITION_METADATA, DI_BAG_INVALID_PROVIDER.

ts
const observed = DiBag.providerWithAcquisitionMetadata({ provider: () => 1, callbackReceives: 'exposed-service', describeAcquisition: value => ({ value }) });

DiBag.providerWithTransformedService(options) ​

Transform the selected callback input while retaining dependencies, metadata, lifetime and ownership stages. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_PROVIDER.

ts
const mapped = DiBag.providerWithTransformedService({ provider: () => 1, callbackReceives: 'exposed-service', transformService: value => String(value) });

Builder ​

builder.withServices(providersByName) ​

Add new string-named services. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_PROVIDER, DI_BAG_DUPLICATE_SERVICE_KEY, DI_BAG_WRONG_TOKEN_KIND.

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

builder.withTokenService(token, provider) ​

Add the single service of a typed token. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND, DI_BAG_DUPLICATE_SERVICE_KEY, DI_BAG_INVALID_PROVIDER.

ts
const clockKey = Symbol('clock');
const clock = DiBag.createToken(clockKey).forService<{ now(): number }>();
const builder = DiBag.createBuilder().withTokenService(clock, () => ({ now: () => Date.now() }));

builder.withServiceAlias(options) ​

Add another lookup name for an existing service. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND, DI_BAG_DUPLICATE_SERVICE_KEY, DI_BAG_UNKNOWN_SERVICE_KEY.

ts
const builder = DiBag.createBuilder().withServices({ clock: () => Date.now() }).withServiceAlias({ aliasKey: 'now', targetServiceKey: 'clock' });

builder.withCollectionContribution(options) ​

Append a provider to the list of a collection token. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND, DI_BAG_INVALID_PROVIDER.

ts
const toolsKey = Symbol('tools');
const tools = DiBag.createToken(toolsKey).forCollectionOf<string>();
const builder = DiBag.createBuilder().withCollectionContribution({ collectionToken: tools, provider: () => 'search' }).withCollectionContribution({ collectionToken: tools, provider: () => 'fetch' });

builder.withReplacedService(serviceKey, provider) ​

Replace an existing binding with a compatible provider, selecting it by name, service token, collection token. Throws: DI_BAG_UNKNOWN_SERVICE_KEY, DI_BAG_INVALID_PROVIDER, DI_BAG_WRONG_TOKEN_KIND.

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

builder.withInstalledModules(modules) ​

Install sealed modules in list order, allocating fresh private bindings for each installation. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_INVALID_MODULE, DI_BAG_DUPLICATE_SERVICE_KEY, DI_BAG_WRONG_TOKEN_KIND.

ts
const greeting = DiBag.createBuilder().withServices({ greet: ({ name }: { name: string }) => `hello, ${name}` }).buildModule({ exportedServiceKeys: ['greet'] });
const app = DiBag.createBuilder().withInstalledModules([greeting]).withServices({ name: () => 'Ada' }).buildContainer();

builder.verifyGraphAtCompileTime() ​

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

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

builder.buildModule(options) ​

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

ts
const orders = DiBag.createBuilder()
  .withServices({ repository: () => new Map<string, number>() })
  .withServices({ placeOrder: ({ repository }: { repository: Map<string, number> }) => (id: string) => repository.set(id, 1) })
  .buildModule({ exportedServiceKeys: ['placeOrder'], moduleLabel: 'orders' });
// Errors and graphSnapshot() name the private binding 'orders/repository'.
const app = DiBag.createBuilder().withInstalledModules([orders]).buildContainer();

builder.buildContainer() ​

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

ts
const app = DiBag.createBuilder().withServices({ greeting: () => 'hello' }).buildContainer();
await app.close();

Module ​

module.withRenamedRequirement(options) ​

Return a module view that asks its host for a requirement under a new name. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_UNKNOWN_SERVICE_KEY, DI_BAG_DUPLICATE_SERVICE_KEY.

ts
const feature = DiBag.createBuilder()
  .withServices({ answer: ({ config }: { config: number }) => config })
  .buildModule({ exportedServiceKeys: ['answer'] });
const app = DiBag.createBuilder()
  .withInstalledModules([feature.withRenamedRequirement({ currentRequirementKey: 'config', newRequirementKey: 'featureConfig' })])
  .withServices({ featureConfig: () => 42 }).buildContainer();
console.log(app.resolve('answer'));
await app.close();

Container ​

container.resolve(token) ​

Resolve a registered service, acquiring it lazily when needed. Throws: DI_BAG_CLOSING, DI_BAG_CLOSED, DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND, DI_BAG_UNKNOWN_SERVICE_KEY, DI_BAG_MISSING_DEPENDENCY, DI_BAG_DEPENDENCY_CYCLE, DI_BAG_LIFETIME_DEPENDENCY, DI_BAG_INVALID_DEPENDENCY_ACCESS, DI_BAG_STRUCTURAL_THENABLE, DI_BAG_INVALID_CLASSIFIER_RESULT, DI_BAG_INVALID_ACQUISITION_METADATA, DI_BAG_PLUGIN_VALIDATION.

ts
const container = DiBag.createBuilder().withServices({ greeting: () => 'hello' }).buildContainer();
const greeting: string = container.resolve('greeting');

container.resolveCollection(token) ​

Resolve every contribution for a collection token as a fresh frozen list. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND, DI_BAG_CLOSING, DI_BAG_CLOSED.

ts
const toolsKey = Symbol('tools');
const tools = DiBag.createToken(toolsKey).forCollectionOf<string>();
const container = DiBag.createBuilder().buildContainer();
const names: readonly string[] = container.resolveCollection(tools);

container.serviceSnapshot(serviceKey) ​

Inspect a service binding through any supported public key without resolving it. Throws: DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND.

ts
const container = DiBag.createBuilder().withServices({ greeting: () => 'hello' }).buildContainer();
const snapshot = container.serviceSnapshot('greeting');
await container.close();

container.graphSnapshot() ​

Describe every resolvable binding and the dependency edges observed so far.

ts
const container = DiBag.createBuilder().withServices({ greeting: () => 'hello' }).buildContainer();
const labels = container.graphSnapshot().bindings.map(binding => binding.bindingLabel);
await container.close();

container.createChildContainer(options?) ​

Create a tracked child container with fresh ownership for unshared services. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_CONFLICTING_SERVICE_SELECTION, DI_BAG_MISSING_REPLACEMENT_PROVIDER, DI_BAG_UNKNOWN_SERVICE_KEY, DI_BAG_SINGLETON_REPLACEMENT, DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND.

ts
const parent = DiBag.createBuilder().withServices({ request: DiBag.providerWithLifetime({
  provider: () => ({ id: 'initial' }), lifetime: 'scoped:one-per-container',
}) }).buildContainer();
const child = parent.createChildContainer(['request'], { request: () => ({ id: 'child' }) });
const request = child.resolve('request');
await child.close();
await parent.close();

container.createIndependentContainer(replacedServiceKeys, replacementProviders) ​

Create an independent container with fresh instances and checked replacements. Throws: DI_BAG_INVALID_ARGUMENT, DI_BAG_MISSING_REPLACEMENT_PROVIDER, DI_BAG_UNKNOWN_SERVICE_KEY, DI_BAG_INVALID_PROVIDER, DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND.

ts
const parent = DiBag.createBuilder().withServices({ clock: () => Date.now() }).buildContainer();
const independent = parent.createIndependentContainer(['clock'], { clock: () => 0 });
const now = independent.resolve('clock');
await independent.close();
await parent.close();

container.ensureServicesReady(serviceKeys, options?) ​

Make the listed services ready before continuing, then resolve to this same container. Throws: DI_BAG_SERVICE_READINESS_FAILED, DI_BAG_SERVICE_READINESS_CANCELLED, DI_BAG_INVALID_ARGUMENT, DI_BAG_UNKNOWN_SERVICE_KEY, DI_BAG_INVALID_TOKEN, DI_BAG_WRONG_TOKEN_KIND, DI_BAG_CLOSING, DI_BAG_CLOSED.

ts
const container = await DiBag.createBuilder()
  .withServices({ db: async () => ({ ping: () => true }) })
  .buildContainer()
  .ensureServicesReady(['db'], { totalTimeoutMs: 5_000 });

container.close(options?) ​

Close this container, drain in-flight work, and dispose owned resources once. Throws: DI_BAG_DISPOSAL_FAILED, DI_BAG_CLOSE_FAILED, DI_BAG_CLOSE_TIMEOUT, DI_BAG_CLOSE_ABORTED, DI_BAG_INVALID_ARGUMENT.

ts
const container = DiBag.createBuilder().withServices({ value: () => 1 }).buildContainer();
await container.close({ waitTimeoutMs: 10_000, abortSignal: 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.createProviderFromPlugin({ dependencies: [], pluginDescriptor: { apiVersion: 2 }, factoryReturnKind: 'uninspected', isValidPluginOutput: (pluginOutput): pluginOutput is string => typeof pluginOutput === 'string' });
} catch (error) {
  if (error instanceof DiBagPluginValidationError) console.error(error.phase, error.reason);
}

DiBagDisposalError ​

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

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

const container = DiBag.createBuilder().withServices({ value: () => 1 }).buildContainer();
await container.close().catch((error: unknown) => {
  if (error instanceof DiBagDisposalError) for (const failure of error.failures) console.error(failure.bindingLabel, failure.error);
});

DiBagServiceReadinessError ​

ensureServicesReady could not make a listed service ready, and this container is now closed. Code: DI_BAG_SERVICE_READINESS_FAILED.

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

const container = DiBag.createBuilder().withServices({ db: async (): Promise<number> => { throw new Error('offline'); } }).buildContainer();
try {
  await container.ensureServicesReady(['db']);
} catch (error) {
  if (error instanceof DiBagServiceReadinessError) console.error(error.cause, error.disposalFailures);
}

DiBagServiceReadinessCancelledError ​

ensureServicesReady stopped waiting on abort or timeout; this container is closing and disposalPromise settles when it has closed. Code: DI_BAG_SERVICE_READINESS_CANCELLED.

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

const container = DiBag.createBuilder().withServices({ db: () => new Promise<number>(() => {}) }).buildContainer();
try {
  await container.ensureServicesReady(['db'], { totalTimeoutMs: 1_000 });
} catch (error) {
  if (error instanceof DiBagServiceReadinessCancelledError) console.error(error.details.acquisitionsStillPending);
}

DiBagCloseCancelledError ​

A close({ waitTimeoutMs, abortSignal }) wait stopped before disposal finished; disposal keeps running. Code: DI_BAG_CLOSE_TIMEOUT, DI_BAG_CLOSE_ABORTED.

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

const container = DiBag.createBuilder().withServices({ value: () => 1 }).buildContainer();
try {
  await container.close({ waitTimeoutMs: 5_000 });
} catch (error) {
  if (error instanceof DiBagCloseCancelledError) console.error(error.details.disposersStillRunning);
  throw error;
}

Ordinary services. Checked composition. Explicit ownership.