Skip to Content
ErrorsNEXUS_LIFETIME_VIOLATION

This page documents the 0.4 release candidates of @nexusdi/core. The newest on npm is 0.4.0-rc.1. The documentation for 0.3.2, the current release, is at nexus.js.org. Try the release candidate.

NEXUS_LIFETIME_VIOLATION

NEXUS_LIFETIME_VIOLATION means a singleton depends, directly or through transient providers or aliases, on a scoped provider or on REQUEST. A lazy() edge counts as well. A singleton lasts as long as the container, longer than any scope, so it would keep the first scope’s value. Nexus.create reports it inside NEXUS_BLUEPRINT_INVALID.

Reproduce NEXUS_LIFETIME_VIOLATION

The region binds FlightLog as a singleton that depends on Mission, a scoped factory over REQUEST. Without a plugin, core prints a one-line message that starts with the code and ends with the address of this page. With errors() from @nexusdi/errors registered (npm install @nexusdi/errors@next), the same error carries the full text and its fix.

import { Nexus, REQUEST, Token, defineModule } from '@nexusdi/core'; import { isNexusError } from '@nexusdi/core'; import { provide } from '@nexusdi/core'; import { errors } from '@nexusdi/errors'; interface Mission { readonly id: string; readonly target: string; } interface IFlightLog { readonly mission: Mission; } const MISSION = new Token<Mission>('Mission'); const FLIGHT_LOG = new Token<IFlightLog>('FlightLog'); class ShuttleFlightLog implements IFlightLog { static deps = [MISSION] as const; constructor(readonly mission: Mission) {} } const Tactical = defineModule({ name: 'Tactical', providers: [ provide(MISSION, { useFactory: (request) => request.mission, deps: [REQUEST], lifetime: 'scoped', }), provide(FLIGHT_LOG, { useClass: ShuttleFlightLog }), ], }); const thin = await Nexus.create(Tactical).catch((error: unknown) => error); if (!isNexusError(thin, 'NEXUS_BLUEPRINT_INVALID')) throw thin; const line = thin.errors[0]?.message; // -> '[NEXUS_LIFETIME_VIOLATION] path=FlightLog,Mission lifetimes=singleton,scoped. https://nexus.js.org/errors/NEXUS_LIFETIME_VIOLATION' console.log(line); const full = await Nexus.create(Tactical, { plugins: [errors()] }).catch( (error: unknown) => error, ); if (!isNexusError(full, 'NEXUS_BLUEPRINT_INVALID')) throw full; const text = full.errors[0]?.message.split('\n'); // -> ['[NEXUS_LIFETIME_VIOLATION] FlightLog is a singleton and captures a scoped provider: FlightLog (singleton) → Mission (scoped). A singleton outlives every scope.', ' Fix: make FlightLog scoped, or move the scoped dependency out of its dependency chain.'] console.log(text);

Try it: save the region as error.ts and run npx tsx error.ts. It prints the one-line message, then the full text as an array of two lines. The lifetimes field pairs each name of path with its lifetime. The regions read request.mission, so save the request.d.ts file from Scopes and the REQUEST Token beside them and tsc agrees. Then save the fix region below as fixed.ts and run npx tsx fixed.ts. It prints survey-7.

Fix NEXUS_LIFETIME_VIOLATION

NEXUS_LIFETIME_VIOLATION is fixed by making the consumer scoped, so each scope builds its own, or by moving the scoped dependency out of the singleton’s chain. Here FlightLog is scoped and each shuttle gets its own. Scopes and the REQUEST Token covers the rule.

import { Nexus, REQUEST, Token, defineModule, provide } from '@nexusdi/core'; interface Mission { readonly id: string; readonly target: string; } interface IFlightLog { readonly mission: Mission; } const MISSION = new Token<Mission>('Mission'); const FLIGHT_LOG = new Token<IFlightLog>('FlightLog'); class ShuttleFlightLog implements IFlightLog { static deps = [MISSION] as const; constructor(readonly mission: Mission) {} } const Tactical = defineModule({ name: 'Tactical', providers: [ provide(MISSION, { useFactory: (request) => request.mission, deps: [REQUEST], lifetime: 'scoped', }), provide(FLIGHT_LOG, { useClass: ShuttleFlightLog, lifetime: 'scoped' }), ], exports: [FLIGHT_LOG], }); await using ship = await Nexus.create(Tactical); await using shuttle = await ship.createScope({ request: { mission: { id: 'survey-7', target: 'Kepler-442b' } }, }); const mission = shuttle.get(FLIGHT_LOG).mission.id; // -> 'survey-7' console.log(mission);

Try it: delete , lifetime: 'scoped' from the FLIGHT_LOG binding in fixed.ts and run it again. Nexus.create rejects with a BlueprintError whose message lists [NEXUS_LIFETIME_VIOLATION] path=FlightLog,Mission lifetimes=singleton,scoped. Put the option back and the program prints survey-7 again.

Fields of LifetimeError

LifetimeError carries these fields:

  • path: the display names from the singleton to the scoped provider.
  • lifetimes: the lifetime of each name in path: singleton, scoped, transient, or null for an alias.