Class: DIContainer<S>
Defined in: src/container.ts:130
A dependency injection container: registers services with explicit dependency keys and resolves them with full types. The container is the composition root: it can also add middlewares, listen to events, fork scopes and dispose what it created. Hand narrower views to the rest of the code: ServiceRegistry to modules, ServiceProvider to consumers.
Example
import { DIContainer } from 'injecute';
class UserRepository {
constructor(readonly dbUrl: string) {}
}
const app = new DIContainer()
.addInstance('dbUrl', 'postgres://localhost/app')
.addSingleton('users', UserRepository, ['dbUrl']);
app.get('users'); // UserRepositoryType Parameters
S
S extends object = { }
Implements
ServiceRegistry<{ },S>
Constructors
Constructor
new DIContainer<
S>():DIContainer<S>
Returns
DIContainer<S>
Accessors
keys
Get Signature
get keys(): readonly
ServiceKey[]
Defined in: src/container.ts:269
Keys visible from this container, including its parents' (each once).
Returns
readonly ServiceKey[]
Keys visible from this container, including its parents'.
Implementation of
ownKeys
Get Signature
get ownKeys(): readonly
ServiceKey[]
Defined in: src/container.ts:276
Keys registered in this container.
Returns
readonly ServiceKey[]
Keys registered in this container.
Implementation of
Methods
[asyncDispose]()
[asyncDispose]():
Promise<void>
Defined in: src/container.ts:731
Same as dispose; enables await using container = ….
Returns
Promise<void>
addAlias()
addAlias<
K,T,R>(key,target,options?):DIContainer<Registered<S,{ [P in ServiceKey]: ResolveDependency<T, {}, S> },R>>
Defined in: src/container.ts:404
Makes key resolve to the service registered under target. Pass replace: true to replace an existing registration of key, here or in a parent.
Type Parameters
K
K extends ServiceKey
T
T extends string | number | symbol
R
R extends boolean = false
Parameters
key
K
target
T
options?
AliasOptions<R>
Returns
DIContainer<Registered<S, { [P in ServiceKey]: ResolveDependency<T, {}, S> }, R>>
Throws
INJECUTE_ALREADY_REGISTERED (see addSingleton).
Example
app.addAlias('logger', config.production ? 'jsonLogger' : 'prettyLogger');
app.fork().addAlias('logger', 'prettyLogger', { replace: true }); // in this fork onlyImplementation of
addEventListener()
addEventListener<
E>(event,handler):this
Defined in: src/container.ts:603
Subscribes to container events: add, replace, reset, get, produce, dispose.
Type Parameters
E
E extends keyof ContainerEvents
Parameters
event
E
handler
(event) => void
Returns
this
Example
app.addEventListener('produce', ({ key }) => console.debug(`created ${String(key)}`));addInstance()
addInstance<
K,T,R>(key,value,options?):DIContainer<Registered<S,{ [P in ServiceKey]: T },R>>
Defined in: src/container.ts:373
Registers an existing value. The container does not dispose it unless dispose is set.
Type Parameters
K
K extends ServiceKey
T
T extends unknown
R
R extends boolean = false
Parameters
key
K
value
T
options?
InstanceOptions<Awaited<T>, R>
Returns
DIContainer<Registered<S, { [P in ServiceKey]: T }, R>>
Example
app.addInstance('config', loadConfig());Implementation of
addSingleton()
addSingleton<
K,F,D,R>(key,factory,dependencies?):DIContainer<Registered<S,{ [P in ServiceKey]: Produced<F> },R>>
Defined in: src/container.ts:324
Registers a service that is created once, on first use, and then cached.
factory is a function or a class. It receives the resolved dependencies in order; a class is called with new. A dependency on key itself receives the previous definition (decoration).
A factory may return a promise: the promise is cached, a rejected one is dropped (the next resolution tries again), and dispose() releases the resolved value.
Type Parameters
K
K extends ServiceKey
F
F extends Factory<D, { }, S, ExpectedService<K>>
D
D extends Dependency<{ }, S>[] = []
R
R extends boolean = false
Parameters
key
K
factory
F
dependencies?
[...D[]] | SingletonFactoryOptions<[...D[]], F, R>
Returns
DIContainer<Registered<S, { [P in ServiceKey]: Produced<F> }, R>>
Remarks
Classes compiled to ES5 and bound classes cannot be detected; register them with construct.
Throws
INJECUTE_ALREADY_REGISTERED when key is registered in this container or a parent (pass replace: true; in a plain fork the override reaches only services resolved in the fork), INJECUTE_CIRCULAR_DEPENDENCY when the dependencies form a cycle.
Example
app
.addSingleton('db', () => createPool(process.env.DATABASE_URL))
.addSingleton('cache', async () => connectRedis()) // Promise<Redis>, disposed once resolved
.addSingleton('users', UserRepository, ['db'])
.addSingleton('mailer', createMailer, { dependencies: ['config'], dispose: (m) => m.close() });Implementation of
addTransient()
addTransient<
K,F,D,R>(key,factory,dependencies?):DIContainer<Registered<S,{ [P in ServiceKey]: Produced<F> },R>>
Defined in: src/container.ts:351
Registers a service that is created again on every resolution. The container does not keep (or dispose) transient instances.
Type Parameters
K
K extends ServiceKey
F
F extends Factory<D, { }, S, ExpectedService<K>>
D
D extends Dependency<{ }, S>[] = []
R
R extends boolean = false
Parameters
key
K
factory
F
dependencies?
[...D[]] | RegistrationOptions<[...D[]], R>
Returns
DIContainer<Registered<S, { [P in ServiceKey]: Produced<F> }, R>>
Remarks
Classes compiled to ES5 and bound classes cannot be detected; register them with construct.
Throws
INJECUTE_ALREADY_REGISTERED, INJECUTE_CIRCULAR_DEPENDENCY.
Example
app.addTransient('requestId', () => crypto.randomUUID());Implementation of
bind()
bind<
D,R>(dependencies,factory): () =>R
Defined in: src/container.ts:551
Returns a function that runs factory with freshly resolved dependencies on every call.
Type Parameters
D
D extends Dependency<{ }, S>[]
R
R
Parameters
dependencies
[...D[]]
factory
((...args) => R) | (new (...args) => R)
Returns
() => R
Example
const sendReport = app.bind(['mailer', 'reports'], (mailer, reports) => mailer.send(reports.weekly()));
sendReport();Implementation of
call()
call<
K>(key,args,thisArg?):ReturnType<Extract<ServiceType<S,K>, (...args) =>any>>
Defined in: src/container.ts:253
Resolves a function service and calls it.
Type Parameters
K
K extends string | number | symbol
Parameters
key
K | TaggedKey<any, K>
args
Parameters<Extract<ServiceType<S, K>, (...args) => any>>
thisArg?
unknown = undefined
Returns
ReturnType<Extract<ServiceType<S, K>, (...args) => any>>
Example
app.addInstance('greet', (name: string) => `Hello ${name}`);
app.call('greet', ['Ada']); // "Hello Ada"Implementation of
createResolver()
createResolver<
K>(key): () =>ServiceType<S,K>
Defined in: src/container.ts:238
Returns a function that resolves key when called.
Type Parameters
K
K extends string | number | symbol
Parameters
key
K | TaggedKey<any, K>
Returns
() => ServiceType<S, K>
Implementation of
ServiceRegistry.createResolver
dispose()
dispose():
Promise<void>
Defined in: src/container.ts:725
Disposes every instance this container owns, dependents first (reverse creation order), then marks the container as disposed: later get() and registrations throw.
Owned instances are the singletons this container created, and addInstance values registered with dispose. Each is disposed with its registration's dispose function, or with [Symbol.asyncDispose] / [Symbol.dispose] when it has one. Namespace containers are disposed too; forks are not (dispose each fork you create, e.g. with await using).
Calling it again returns the same promise. If some disposers fail, the others still run and the promise rejects with an AggregateError.
Returns
Promise<void>
Example
await using scope = app.fork().addSingleton('tx', (db) => db.begin(), ['db']);
// ... scope and its singletons (the transaction) are disposed at the end of the blockextend()
extend<
EA,Req>(extension):DIContainer<S& { [K in string | number | symbol]: EA[K] }>
Defined in: src/container.ts:499
Applies a module: a function that registers services and returns the registry it was given (or a fork of it). A module can declare only the services it needs; extend checks that they exist.
Type Parameters
EA
EA extends object
Req
Req extends object = S
Parameters
extension
[S] extends [Req] ? (registry) => ServiceRegistry<any, EA> : object
Returns
DIContainer<S & { [K in string | number | symbol]: EA[K] }>
Example
const addBilling = (c: ServiceRegistry<{ db: Database }>) =>
c.addSingleton('invoices', InvoiceRepository, ['db']);
const app = new DIContainer().addSingleton('db', createDb).extend(addBilling);Implementation of
fork()
fork(
options?):DIContainer<S>
Defined in: src/container.ts:674
Creates a child container. The child sees every service of this container (and later additions to it); services added to the child stay in the child.
- By default a service registered in this container runs here and is shared by all forks, so overriding one of its dependencies in a fork does not affect it.
- With
isolated: truethe fork runs and caches every service it resolves, including ones registered here. Overrides in the fork then reach the whole graph, and nothing leaks back. Use it for tests and per-tenant variants.
Services registered here can't depend on what a fork adds. A fork for one unit of work (a session, a request, a transaction, a job) is a scope: register its services in it with a scope module, and dispose it with await using. The "Scoped state" guide compares scopes with patterns that keep every service here (an argument, a factory with using, a context accessor).
Parameters
options?
Returns
DIContainer<S>
Example
await using requestScope = app.fork().addSingleton('tx', (db) => db.begin(), ['db']);
const testContainer = app.fork({ isolated: true }).addInstance('db', fakeDb, { replace: true });
testContainer.get('users'); // built with fakeDb; app is untouchedget()
get<
K,O>(key,options?):Oextendsobject?ServiceType<S,K> |undefined:ServiceType<S,K>
Defined in: src/container.ts:220
Resolves a service: returns the cached singleton, creates it, or creates a new transient. Looks in parent containers when the key is not registered here. Throws when nothing is registered under key, unless { optional: true } is passed.
Type Parameters
K
K extends string | number | symbol
O
O extends GetOptions = { }
Parameters
key
K | TaggedKey<any, K>
options?
O
Returns
O extends object ? ServiceType<S, K> | undefined : ServiceType<S, K>
Throws
INJECUTE_NOT_REGISTERED (with "did you mean" suggestions), INJECUTE_RESOLUTION_FAILED when a factory throws (the original error is cause), INJECUTE_CLASS_NOT_CONSTRUCTED, INJECUTE_DISPOSED.
Example
const users = app.get('users');
const cache = app.get('cache', { optional: true }); // Cache | undefinedImplementation of
getParent()
getParent():
ServiceProvider<{ }> |undefined
Defined in: src/container.ts:281
The parent container as a read-only view, if any.
Returns
ServiceProvider<{ }> | undefined
Implementation of
getRegistration()
getRegistration(
key):RegistrationInfo|undefined
Defined in: src/container.ts:286
Read-only metadata of the registration visible under key, or undefined.
Parameters
key
Returns
RegistrationInfo | undefined
Implementation of
ServiceRegistry.getRegistration
has()
has(
key,options?):boolean
Defined in: src/container.ts:232
true when a service is registered under key, here or (unless local: true) in a parent.
Parameters
key
options?
Returns
boolean
Implementation of
injecute()
injecute<
D,R>(factory,dependencies):R
Defined in: src/container.ts:531
Runs a function (or constructs a class) with resolved dependencies, without registering it.
Type Parameters
D
D extends Dependency<{ }, S>[]
R
R
Parameters
factory
((...args) => R) | (new (...args) => R)
dependencies
[...D[]]
Returns
R
Example
const handler = app.injecute((users, logger) => createHandler(users, logger), ['users', 'logger']);Implementation of
namespace()
namespace<
N,NA,Req>(name,extension):DIContainer<S& { [K in string | number | symbol]: NamespacedServices<N, NA>[K] }>
Defined in: src/container.ts:445
Registers the services added by extension under a prefix: name.key for each of them, and name for a read-only provider of the namespace. The extension receives a fork of this container, so it can use every service registered here; its own registrations stay in the namespace.
In the service map, name is a Namespace marker; get(name) is typed as a ServiceProvider of the namespace's services.
Type Parameters
N
N extends string
NA
NA extends object
Req
Req extends object = S
Parameters
name
N
extension
[S] extends [Req] ? (registry) => ServiceRegistry<any, NA> : object
Returns
DIContainer<S & { [K in string | number | symbol]: NamespacedServices<N, NA>[K] }>
Example
app.namespace('Billing', (billing) =>
billing.addSingleton('invoices', InvoiceRepository, ['db']),
);
app.get('Billing.invoices');Implementation of
removeEventListener()
removeEventListener<
E>(event,handler):this
Defined in: src/container.ts:613
Removes a listener added with addEventListener.
Type Parameters
E
E extends keyof ContainerEvents
Parameters
event
E
handler
(event) => void
Returns
this
reset()
reset(
options?):this
Defined in: src/container.ts:686
Clears cached instances (all, or keys), so the next resolution creates them again. Instances are not disposed here; dispose() still releases them later.
Parameters
options?
ResetOptions<S> = {}
Returns
this
seal()
seal():
SealedDIContainer<{ [K in string | number | symbol]: S[K] }>
Defined in: src/container.ts:645
Seals the container: no more services can be registered in it (add*, namespace and extend throw INJECUTE_SEALED); resolving, middlewares, events, reset() and dispose() keep working, and forks of a sealed container are open for registrations. Call it once the composition root is complete, then fork it per request or test.
The returned type has no registration methods, and its service map is one object type instead of an intersection of every registration, so code built on it (forks, request scopes) typechecks faster and shows readable types.
Returns
SealedDIContainer<{ [K in string | number | symbol]: S[K] }>
Throws
INJECUTE_DISPOSED.
Example
export const app = new DIContainer()
.addSingleton('db', createDb)
.addSingleton('users', UserRepository, ['db'])
.seal();
app.get('users');
const scope = app.fork().addInstance('requestId', id); // forks stay openunuse()
unuse(
middleware):this
Defined in: src/container.ts:586
Removes a middleware added with use.
Parameters
middleware
Returns
this
use()
use(
middleware):this
Defined in: src/container.ts:579
Adds a middleware around every resolution started from this container and its forks.
A middleware receives the key, a next function that continues the resolution (optionally with another key) and a context with the resolution path. The last added middleware runs first. Middlewares added to a parent later still apply to existing forks.
Parameters
middleware
Returns
this
Example
app.use((key, next, { depth }) => {
const start = performance.now();
const value = next();
console.debug(`${' '.repeat(depth)}${String(key)}: ${performance.now() - start}ms`);
return value;
});