Skip to content

Containers and scopes

A container owns provider registrations and resolves dependency graphs on demand.

const container = createContainer(providers);
container.register(provideValue(PORT, 3000));
container.has(PORT); // true
container.get(PORT); // 3000

Registering the same token twice throws DuplicateProviderError. For an intentional test or environment override, pass { allowOverride: true }.

container.register(provideValue(API, fakeApi), { allowOverride: true });
Scope Lifetime
singleton One cached instance in the container that owns the provider
scoped One cached instance in each resolving container
transient A fresh instance on every resolution

Use the Scopes constants for autocompletion:

provideFactory(REQUEST_STATE, {
scope: Scopes.Scoped,
deps: { request: REQUEST },
useFactory: ({ request }) => new RequestState(request),
});

A longer-lived service cannot capture a shorter-lived dependency. For example, a singleton cannot depend on a scoped provider. Invalid lifetime graphs fail during resolution with InvalidProviderError.

Child containers inherit providers from a parent while adding or overriding request-local values. Singleton instances remain owned by the provider’s container; scoped instances belong to the child performing resolution.

examples/core/scopes.ts
import {
createChildContainer,
createContainer,
createToken,
provideFactory,
provideValue,
Scopes,
} from "di-craft";
type Request = {
readonly id: string;
};
class RequestState {
readonly request: Request;
constructor(request: Request) {
this.request = request;
}
}
const REQUEST = createToken<Request>("REQUEST");
const REQUEST_STATE = createToken<RequestState>("REQUEST_STATE");
const root = createContainer([
provideFactory(REQUEST_STATE, {
scope: Scopes.Scoped,
deps: { request: REQUEST },
useFactory: ({ request }) => new RequestState(request),
}),
]);
const requestA = createChildContainer(root, [
provideValue(REQUEST, { id: "request-a" }),
]);
const requestB = createChildContainer(root, [
provideValue(REQUEST, { id: "request-b" }),
]);
const firstA = requestA.get(REQUEST_STATE);
const secondA = requestA.get(REQUEST_STATE);
const firstB = requestB.get(REQUEST_STATE);
firstA === secondA; // true
firstA === firstB; // false

This is the core primitive used by the Node.js and Next.js request adapters.

All library errors extend DiError:

Error Meaning
MissingProviderError No provider exists for a requested token
DuplicateProviderError A token was registered more than once
CircularDependencyError The provider graph contains a cycle
InvalidDependencyError A dependency declaration is missing or invalid
InvalidProviderError A scope, disposal hook, or override is invalid

Cached resources are released through deterministic disposal.