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_INVALID
@nexusdi/interceptors raises NEXUS_INTERCEPTOR_INVALID for a fault in how interceptors are set up or called, and the error’s reason field names the rule the fault breaks. The same code reaches the program in four ways, and the table under Fix NEXUS_INTERCEPTOR_INVALID gives the way for each reason:
- Compile. The plugin’s check finds the fault with the graph’s other errors, and
Nexus.createandship.load()reject andNexus.checkthrows NEXUS_BLUEPRINT_INVALID, whoseerrorslist holds oneInterceptorErrorfor each fault. The check runs before any constructor does. - Class definition.
@UseInterceptorsthrows the error bare while the class is defined, which ends the import of the module that declares the class. No container exists yet, so no container wraps it. - Build. A provider’s constructor or factory throws while
Nexus.createbuilds the container, socreaterejects with NEXUS_PROVIDER_FAILED and theInterceptorErroris itscause. A fault in thebindingsoption nests one level deeper, and so does abad-targetfault for a provider whose instance is a frozen function with an intercepted own method: thecauseof the provider failure is NEXUS_PLUGIN_FAILED, and theInterceptorErroris that error’scause.Nexus.checkbuilds nothing, so it never sees these. - Call. An intercepted method throws the error to its caller, or returns a rejected promise when the method is declared
async. A call made from anonInithook fails the provider that owns the hook, socreaterejects withNEXUS_PROVIDER_FAILEDand theInterceptorErroris itscause.
Reproduce NEXUS_INTERCEPTOR_INVALID
interceptors() reports NEXUS_INTERCEPTOR_INVALID with reason set to options when register holds the same interceptor token twice. Without a text pack, the message is one line that starts with the code and ends with the address of this page.
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;
}
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;
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
exports: [REACTOR],
});
// register lists AUDIT twice.
const error = await Nexus.create(Engineering, {
plugins: [
interceptors({
register: [
interceptor(AUDIT, { useClass: AuditInterceptor }),
interceptor(AUDIT, { useClass: AuditInterceptor }),
],
}),
],
}).catch((caught: unknown) => caught);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors[0]
: undefined;
const message = inner?.message; // -> '[NEXUS_INTERCEPTOR_INVALID] reason=options token=Audit detail=register-twice. https://nexus.js.org/errors/NEXUS_INTERCEPTOR_INVALID'
console.log(message);Try it: run npm install @nexusdi/core@next @nexusdi/interceptors@next, save the region as invalid.ts and run npx tsx invalid.ts. It prints the one-line message: [NEXUS_INTERCEPTOR_INVALID] reason=options token=Audit detail=register-twice. and the address of this page. Delete one interceptor(AUDIT, { useClass: AuditInterceptor }), entry from the register list and run it again. The container builds, and the region prints undefined because there is no error to read. Put the entry back and the message returns.
Full text of NEXUS_INTERCEPTOR_INVALID with errors()
NEXUS_INTERCEPTOR_INVALID carries its full text when interceptorsText from @nexusdi/interceptors/text is passed to errors() from @nexusdi/errors, as errors({ text: [interceptorsText] }) in the plugins of Nexus.create. devtools({ text: [interceptorsText] }) words the error the same way. The pack covers the errors a container formats: those found at compile and the bad-next fault raised by a call after setup. The errors thrown where no container formats them carry their full text without the pack: those thrown at class definition and at build, and those raised by a call before setup.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { isNexusError } from '@nexusdi/core';
import { errors } from '@nexusdi/errors';
import { interceptor, interceptors } from '@nexusdi/interceptors';
import type { CallContext, Interceptor, Next } from '@nexusdi/interceptors';
import { interceptorsText } from '@nexusdi/interceptors/text';
interface IReactorCore {
output(): number;
}
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;
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
exports: [REACTOR],
});
const error = await Nexus.create(Engineering, {
plugins: [
errors({ text: [interceptorsText] }),
interceptors({
register: [
interceptor(AUDIT, { useClass: AuditInterceptor }),
interceptor(AUDIT, { useClass: AuditInterceptor }),
],
}),
],
}).catch((caught: unknown) => caught);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors[0]
: undefined;
const message = inner?.message; // -> '[NEXUS_INTERCEPTOR_INVALID] interceptors(): an interceptor is registered twice (Audit).'
console.log(message);Try it: run npm install @nexusdi/errors@next, save the region as full.ts and run npx tsx full.ts. It prints [NEXUS_INTERCEPTOR_INVALID] interceptors(): an interceptor is registered twice (Audit). Delete errors({ text: [interceptorsText] }) from the plugins, together with the errors and interceptorsText imports, and run it again. The message is the one-line form of the previous region. Put it back and the sentence returns.
Fix NEXUS_INTERCEPTOR_INVALID
NEXUS_INTERCEPTOR_INVALID goes away when the declaration follows the rule its reason names:
reason | The fault and its fix | How it arrives |
|---|---|---|
options | An interceptors() option has the wrong shape. detail[0] names the rule, such as register-empty, register-twice, global-entry or exempt-entry, and detail[1] the value received. | Compile |
declaration | A class’s static interceptors is not { class?, methods? }. detail[0] is the key at fault. Use only those two keys, each holding a list of tokens. | Compile |
declaration | A @UseInterceptors argument is not a token. detail[0] describes the value received. Pass the token. | Class definition |
unknown-method | A static interceptors method list names a method that is not on the class’s prototype chain, such as an arrow field. Correct the name, or declare the field as a method. | Compile |
unknown-method | A bindings entry names a method that the instance its provider built lacks. Correct the name in bindings. | Build, as the cause of NEXUS_PLUGIN_FAILED |
two-forms | A class uses both static interceptors and @UseInterceptors. Keep one form. | Compile |
private-method | @UseInterceptors decorates a private method, and method holds its name, such as #reload. Decorate a public method. | Class definition |
static-method | @UseInterceptors decorates a static method. Decorate an instance method. | Class definition |
bad-target | @UseInterceptors decorates a getter, a setter, a field or an accessor, and detail[0] names the kind. It can also decorate onInit or then, which are never intercepted. At build, a provider’s instance is a frozen function with an intercepted own method, and method names it. Decorate a public instance method, or provide an object or class instance. | Class definition, or Build, as the cause of NEXUS_PLUGIN_FAILED |
legacy-decorators | The program runs under experimentalDecorators, which calls a standard decorator the legacy way. Remove the setting, or declare the interceptors with static interceptors. | Class definition |
no-intercept | A registered interceptor has no intercept(call, next) method, and token names it. Add the method. | Build, as the cause of NEXUS_PROVIDER_FAILED |
bad-next | An interceptor called next() with an argument that is not an array. Pass the new argument list as an array, or call next() with no argument. | Call |
unexempted-dep | With global entries, an interceptor depends on a service that exempt does not list. detail holds the providers the entries would skip. Add the service to exempt. | Compile |
unused-exempt | With global entries, exempt lists a service that no interceptor depends on directly. detail names the exempt services that already cover it. Remove it from exempt. | Compile |
self-intercept | A class list, on the class or in a binding, names an interceptor on a service that the interceptor depends on, so the interceptor would run inside itself. List it only on methods the interceptor never calls. | Compile |
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;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const AUDIT = new Token<Interceptor>('Audit');
const audited: string[] = [];
class AuditInterceptor implements Interceptor {
intercept(call: CallContext, next: Next) {
audited.push(String(call.method));
return next();
}
}
class FusionReactor implements IReactorCore {
output() {
return 1.21;
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
exports: [REACTOR],
});
await using ship = await Nexus.create(Engineering, {
plugins: [
interceptors({
register: [interceptor(AUDIT, { useClass: AuditInterceptor })],
global: [AUDIT],
}),
],
});
const output = ship.get(REACTOR).output(); // -> 1.21
console.log(output);
const entries = audited; // -> ['output']
console.log(entries);The fix region differs from the reproduce region in the register list, which names AUDIT once, and in what follows: it calls output() on the container it keeps and prints what the interceptor saw.
Try it: save the region as fixed.ts and run npx tsx fixed.ts. It prints 1.21 and [ 'output' ]. Put the interceptor(AUDIT, { useClass: AuditInterceptor }) entry in the register list a second time and run it again. npx tsc accepts the file, and npx tsx fixed.ts throws an uncaught BlueprintError and prints a stack trace headed BlueprintError: [NEXUS_BLUEPRINT_INVALID] 1 error., whose message goes on with the line [NEXUS_INTERCEPTOR_INVALID] reason=options token=Audit detail=register-twice. Delete the second entry and the program prints 1.21 again.
A decorator fault surfaces when the class is defined. The decorator on a static method below throws before any container exists:
import { Token, isNexusError } from '@nexusdi/core';
import { UseInterceptors } from '@nexusdi/interceptors';
import type { Interceptor } from '@nexusdi/interceptors';
interface IReactorCore {
output(): number;
}
const AUDIT = new Token<Interceptor>('Audit');
// A decorator on a static method fails when the class is defined.
const define = () => {
class FusionReactor implements IReactorCore {
output() {
return 1.21;
}
@UseInterceptors(AUDIT)
static refit() {
return 'refitted';
}
}
return FusionReactor;
};
const error = (() => {
try {
return define();
} catch (caught: unknown) {
return caught;
}
})();
const invalid = isNexusError(error, 'NEXUS_INTERCEPTOR_INVALID')
? error
: undefined;
const reason = invalid?.reason; // -> 'static-method'
console.log(reason);
const message = invalid?.message; // -> '[NEXUS_INTERCEPTOR_INVALID] @UseInterceptors cannot wrap the static method refit.'
console.log(message);Try it: save the region as define.ts and run npx tsx define.ts. It prints static-method and [NEXUS_INTERCEPTOR_INVALID] @UseInterceptors cannot wrap the static method refit. Move @UseInterceptors(AUDIT) onto output() and run it again. The class is defined, and the region prints undefined twice. To see the legacy reason, move the decorator back and add "experimentalDecorators": true to tsconfig.json. npx tsc flags the decorator with TS1241, and npx tsx define.ts prints legacy-decorators and then a message of two lines: [NEXUS_INTERCEPTOR_INVALID] @UseInterceptors is a standard decorator, and ran as a legacy one. and Fix: remove experimentalDecorators from tsconfig, or use static interceptors. Remove the setting again. NEXUS_LEGACY_DECORATORS covers the same setting for the decorators in @nexusdi/decorators.
Method Interceptors states each declaration rule with an example.
Fields of InterceptorError
code:'NEXUS_INTERCEPTOR_INVALID'.reason: the rule the fault breaks, one of the reasons in the table above.token: the name of the token the fault is about, ornull. It is the interceptor forregister-twice,no-intercept,bad-nextandself-intercept, and the binding’s token for a malformed binding. Forunexempted-depit is the interceptor that depends on the service.target: the class or provider the fault is on, ornull. Forunexempted-depit is the service, and forunused-exemptthe exempt token.method: the method name, ornull.state:nullfor this code.detail: a list of strings, empty when the fault names nothing else. It holds the rule and the value received foroptions, the key at fault for astatic interceptorsdeclaration, the value received for a@UseInterceptorsargument, the kind forbad-targeton a getter, setter, field or accessor, and the provider names forunexempted-depandunused-exempt.