Scopes & Lifetime Hierarchy
Understand scope inheritance rules, captive dependencies, and compile-time lifetime validation.
In dependency injection systems, one of the most dangerous bugs is the captive dependency: when a service with a longer lifetime (like a singleton) holds a reference to a service with a shorter lifetime (like a request or transient scope).
Nooh enforces lifetime rules at the TypeScript compiler level, giving you instant feedback in your IDE before you even run your code.
The Scope Hierarchy
Nooh defines a strict hierarchy of dependency scopes:
Compatibility Matrix
| Parent Scope | May Depend On singleton |
May Depend On request |
May Depend On transient |
May Depend On value |
|---|---|---|---|---|
singleton |
Yes | No | No | Yes |
request |
Yes | Yes | Yes | Yes |
transient |
Yes | Yes | Yes | Yes |
value |
No | No | No | No |
Compile-Time Error Example
If you accidentally inject a request service into a singleton:
import { container, singleton, request } from "@nooh-ts/nooh";
export const services = container({
userSession: request(() => new UserSession()),
// ❌ Type Error: A singleton dependency cannot depend on a request dependency.
auditLogger: singleton({
deps: [services.userSession],
factory: ({ userSession }) => new AuditLogger(userSession),
}),
});
TypeScript will mark deps with the type error:
Type 'DependencyReference<"userSession", UserSession, "request", []> & InvalidDependencyScope<"singleton", "request">' is not assignable...
// "__nooh_dependency_error__": "A singleton dependency cannot depend on a request dependency."
How to Fix Scope Violations
If you encounter a scope error, you have two idiomatic solutions:
- Promote the consumer’s scope: Change
auditLoggerfromsingletontorequest. - Pass dynamic parameters at call time: Keep
auditLoggeras asingleton, but passuserSessionas an argument to its methods rather than injecting it in the constructor.
export const services = container({
userSession: request(() => new UserSession()),
// Solution 1: Change to request scope
auditLogger: request({
deps: [services.userSession],
factory: ({ userSession }) => new AuditLogger(userSession),
}),
});