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_INTERCEPTOR_NOT_READY
NEXUS_INTERCEPTOR_NOT_READY means a method of an intercepted service was called while its container holds no interceptors to run. The state field names the case. 'building' means the plugin has not built the interceptors yet, which happens when a constructor or factory calls a method of one of its dependencies during create. 'disposed' means the container is already disposed, which happens when code keeps a service past ship[Symbol.asyncDispose](), or past a create that failed and disposed what it had built.
The error is raised at the call, and how it arrives depends on the call. A method declared without async throws it after disposal. An async method returns a promise that rejects with it after disposal. A method called from a constructor or factory during create fails the build: Nexus.create rejects with NEXUS_PROVIDER_FAILED, whose cause is this error with state: 'building'.
@nexusdi/interceptors raises this error where no container formats it, so the message carries its full text with a text pack or without one. The message for 'building' ends with a fix line: Fix: call it from onInit, or inject it with lazy().
Reproduce NEXUS_INTERCEPTOR_NOT_READY
The region disposes ship and then calls output(), which throws, and diagnose(), an async method that returns a rejected promise.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { isNexusError } from '@nexusdi/core';
import { interceptor, interceptors } from '@nexusdi/interceptors';
import type { CallContext, Interceptor, Next } from '@nexusdi/interceptors';
interface IReactorCore {
output(): number;
diagnose(): Promise<string>;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const AUDIT = new Token<Interceptor>('Audit');
class AuditInterceptor implements Interceptor {
intercept(_call: CallContext, next: Next) {
return next();
}
}
class FusionReactor implements IReactorCore {
output() {
return 1.21;
}
async diagnose() {
return 'nominal';
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
exports: [REACTOR],
});
const ship = await Nexus.create(Engineering, {
plugins: [
interceptors({
register: [interceptor(AUDIT, { useClass: AuditInterceptor })],
global: [AUDIT],
}),
],
});
const reactor = ship.get(REACTOR);
await ship[Symbol.asyncDispose]();
// A method that is not async throws; an async method returns a rejected promise.
let thrown: unknown;
try {
reactor.output();
} catch (caught) {
thrown = caught;
}
const rejected = await reactor.diagnose().catch((caught: unknown) => caught);
const narrowed = isNexusError(thrown, 'NEXUS_INTERCEPTOR_NOT_READY')
? thrown
: undefined;
const state = narrowed?.state; // -> 'disposed'
console.log(state);
const message = narrowed?.message; // -> '[NEXUS_INTERCEPTOR_NOT_READY] ReactorCore.output was called after its container was disposed.'
console.log(message);
const arrived = isNexusError(rejected) ? rejected.code : null; // -> 'NEXUS_INTERCEPTOR_NOT_READY'
console.log(arrived);Try it: run npm install @nexusdi/core@next @nexusdi/interceptors@next, save the region as not-ready.ts and run npx tsx not-ready.ts. It prints the state disposed, then the message [NEXUS_INTERCEPTOR_NOT_READY] ReactorCore.output was called after its container was disposed., then the code NEXUS_INTERCEPTOR_NOT_READY of the rejected promise. Delete the line await ship[Symbol.asyncDispose](); and run it again. Both calls succeed, so each of the three claims has nothing to read: it prints undefined twice and then null. Put the line back and the first output returns.
The building case needs a constructor that calls a dependency. In this region PowerLog calls reactor.output() in its constructor, before the plugin has built the interceptors.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { isNexusError } from '@nexusdi/core';
import { interceptor, interceptors } from '@nexusdi/interceptors';
import type { CallContext, Interceptor, Next } from '@nexusdi/interceptors';
interface IReactorCore {
output(): number;
}
interface IPowerLog {
readonly reading: number;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const POWER_LOG = new Token<IPowerLog>('PowerLog');
const AUDIT = new Token<Interceptor>('Audit');
class AuditInterceptor implements Interceptor {
intercept(_call: CallContext, next: Next) {
return next();
}
}
class FusionReactor implements IReactorCore {
output() {
return 1.21;
}
}
class PowerLog implements IPowerLog {
static deps = [REACTOR] as const;
readonly reading: number;
constructor(reactor: IReactorCore) {
// The interceptors are not built yet, so this call fails.
this.reading = reactor.output();
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [
provide(REACTOR, { useClass: FusionReactor }),
provide(POWER_LOG, { useClass: PowerLog }),
],
exports: [POWER_LOG],
});
const error = await Nexus.create(Engineering, {
plugins: [
interceptors({
register: [interceptor(AUDIT, { useClass: AuditInterceptor })],
global: [AUDIT],
}),
],
}).catch((caught: unknown) => caught);
// the code of an error, or null
const codeOf = (e: unknown) => (isNexusError(e) ? e.code : null);
const cause = isNexusError(error, 'NEXUS_PROVIDER_FAILED')
? error.cause
: undefined;
const codes = [error, cause].map(codeOf); // -> ['NEXUS_PROVIDER_FAILED', 'NEXUS_INTERCEPTOR_NOT_READY']
console.log(codes);
const lines = isNexusError(cause) ? cause.message.split('\n') : null; // -> ['[NEXUS_INTERCEPTOR_NOT_READY] ReactorCore.output was called before its interceptors were built.', ' Fix: call it from onInit, or inject it with lazy().']
console.log(lines);Try it: save the region as building.ts and run npx tsx building.ts. It prints the chain of codes as an array, [ 'NEXUS_PROVIDER_FAILED', 'NEXUS_INTERCEPTOR_NOT_READY' ], then the message of the cause as an array of two lines: [NEXUS_INTERCEPTOR_NOT_READY] ReactorCore.output was called before its interceptors were built. and Fix: call it from onInit, or inject it with lazy().
Fix NEXUS_INTERCEPTOR_NOT_READY
NEXUS_INTERCEPTOR_NOT_READY goes away when every intercepted call runs while the container is open. With state: 'disposed', keep the container open with await using for as long as the code calls its services. With state: 'building', move the call out of the constructor into onInit, or inject the dependency with lazy() and call the thunk from a method later.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { interceptor, interceptors } from '@nexusdi/interceptors';
import type { CallContext, Interceptor, Next } from '@nexusdi/interceptors';
interface IReactorCore {
output(): number;
diagnose(): Promise<string>;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const AUDIT = new Token<Interceptor>('Audit');
class AuditInterceptor implements Interceptor {
intercept(_call: CallContext, next: Next) {
return next();
}
}
class FusionReactor implements IReactorCore {
output() {
return 1.21;
}
async diagnose() {
return 'nominal';
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
exports: [REACTOR],
});
// The container stays open until the program ends.
await using ship = await Nexus.create(Engineering, {
plugins: [
interceptors({
register: [interceptor(AUDIT, { useClass: AuditInterceptor })],
global: [AUDIT],
}),
],
});
const reactor = ship.get(REACTOR);
const output = reactor.output(); // -> 1.21
console.log(output);
const diagnosis = await reactor.diagnose(); // -> 'nominal'
console.log(diagnosis);The disposed case is fixed by keeping the container open. The fix region differs from the reproduce region in how the container ends. It declares ship with await using, which disposes it when the program ends, and it calls both methods first.
Try it: save the region as fixed.ts and run npx tsx fixed.ts. It prints 1.21 and nominal. Add the line await ship[Symbol.asyncDispose](); above the line const output = reactor.output(); and run it again. npx tsc accepts the file, and npx tsx fixed.ts throws an uncaught InterceptorError and prints a stack trace headed InterceptorError: [NEXUS_INTERCEPTOR_NOT_READY] ReactorCore.output was called after its container was disposed. Remove the line and the program prints its two lines again.
The building case is fixed by moving the call into onInit, which runs once the interceptors are built.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { interceptor, interceptors } from '@nexusdi/interceptors';
import type { CallContext, Interceptor, Next } from '@nexusdi/interceptors';
interface IReactorCore {
output(): number;
}
interface IPowerLog {
readonly reading: number;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const POWER_LOG = new Token<IPowerLog>('PowerLog');
const AUDIT = new Token<Interceptor>('Audit');
class AuditInterceptor implements Interceptor {
intercept(_call: CallContext, next: Next) {
return next();
}
}
class FusionReactor implements IReactorCore {
output() {
return 1.21;
}
}
class PowerLog implements IPowerLog {
static deps = [REACTOR] as const;
reading = 0;
constructor(private readonly reactor: IReactorCore) {}
// onInit runs once the interceptors are built.
onInit() {
this.reading = this.reactor.output();
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [
provide(REACTOR, { useClass: FusionReactor }),
provide(POWER_LOG, { useClass: PowerLog }),
],
exports: [POWER_LOG],
});
await using ship = await Nexus.create(Engineering, {
plugins: [
interceptors({
register: [interceptor(AUDIT, { useClass: AuditInterceptor })],
global: [AUDIT],
}),
],
});
const reading = ship.get(POWER_LOG).reading; // -> 1.21
console.log(reading);Try it: save the region as building-fixed.ts and run npx tsx building-fixed.ts. It prints 1.21. Change the constructor to constructor(private readonly reactor: IReactorCore) { this.reactor.output(); } and run it again. npx tsc accepts the file, and npx tsx building-fixed.ts throws an uncaught ProviderError and prints a stack trace headed ProviderError: [NEXUS_PROVIDER_FAILED] token=PowerLog module=Engineering path=PowerLog. and the address of this page, whose [cause] is the InterceptorError for ReactorCore.output. Put the constructor back and it prints 1.21 again.
Method Interceptors covers the container’s lifetime, and Lazy Dependencies and Cycles covers lazy().
Fields of InterceptorError
code:'NEXUS_INTERCEPTOR_NOT_READY'.reason:nullfor this code.token:nullfor this code.target: the name of the intercepted provider.method: the name of the method that was called.state:'building'before the interceptors are built,'disposed'after disposal.detail: an empty list for this code.