Skip to content

Lifetime Guards

InferDI has three lifetimes:

KindCreatedCached onDisposed by container
singletononce per owning containerowner containeryes
scopedonce per child scopechild scopeyes
transientevery resolveneverno

With strict: true, resolving a scoped key from the root throws Scoped "key" cannot be resolved from the root container. Use createScope(). Call createScope(), then resolve scoped services from its result.

The Lifetime Rule

A singleton cannot directly depend on a scoped or transient service. A singleton is created once and shared across every request, so if it captures a scoped value — the current request's context, user, or transaction — that one request's state silently bleeds into all the others. InferDI makes that edge unrepresentable in the type system instead of leaving it to code review.

ts
new Container()
  .registerClass('request', RequestContext, [], 'scoped')
  .registerClass('users', UserService, ['request'], 'singleton')

That registration is rejected by TypeScript. In strict mode, the same shape is rejected at runtime if a cast bypasses the type system.

Strict Mode

strict: true is the default. It catches:

  • direct resolution of a scoped key from the root container
  • singleton-to-scoped or singleton-to-transient violations introduced by casts
  • captured outer-container factory leaks
  • synchronous singleton cycles
  • synchronous transient cycles
  • dynamic-key misuse that bypasses static checking
ts
const root = new Container({ strict: true })

Fast Mode

Use strict: false only after tests prove the graph shape:

ts
const root = new Container({ strict: false })

Fast mode removes runtime cycle and lifetime bookkeeping from the resolve path. Scopes read the immutable root registry directly instead of walking the parent chain, and delegated singletons are mirrored into the scope cache. Strict scopes walk their exact parent chain on every local miss, so mutations remain observable without retained lookup metadata. Owned-instance identity de-duplication runs during disposal in both modes. Fast mode does not change the type-level contract, but it cannot defend against dishonest casts, captured outer containers, or cycles. It also skips the root-scoped guard. Application code must resolve scoped keys from child scopes.

Recommended workflow: develop and test in strict mode, register each runtime key once through one linear fluent chain, and complete registration before the first resolve or scope. Then switch only audited, immutable production graphs and dispose child scopes before their ancestors.