Skip to main content
Lifetimes define how long resolved instances live. Choosing the right lifetime keeps state ownership clear and prevents request-specific dependencies from leaking across users or requests.

Singleton

Use singleton for stateless services or process-wide dependencies.
Good candidates:
  • loggers
  • configuration readers
  • pure policies
  • stateless mappers
Avoid putting request state in singletons.

Scoped

Use scoped for dependencies that should be reused within one request or unit of work.
Good candidates:
  • request context
  • repositories bound to a request transaction
  • DataLoader instances
  • request-local caches

Transient

Use transient when every resolution should create a fresh instance.
Transient is useful for short-lived objects with no shared state requirement.

Request scopes

In web frameworks, a request scope is created when the request starts and disposed when the request finishes.
Framework adapters do this for you where possible.

Child scopes

v2.1+ adds scope.createChildScope() for nested units of work inside one request (for example a GraphQL subscription operation or a sub-task that should share parent scoped services).
Rules:
  • Dispose child scopes before the parent (ScopeHasActiveChildrenError otherwise).
  • Child scopes can read parent set() values and parent scoped instances.
  • New scoped instances created in a child are stored on that child only.

Lifetime validation

Wyrly can validate lifetime relationships. For example, a singleton should not depend on a scoped dependency — directly or transitively — because that would capture request-specific state in a process-wide instance. Validation also warns when register() overrides @Injectable deps or lifetime metadata. Run validation as part of test or CI: