Skip to Content
ErrorsNEXUS_INTERCEPTOR_INVALID

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.create and ship.load() reject and Nexus.check throws NEXUS_BLUEPRINT_INVALID, whose errors list holds one InterceptorError for each fault. The check runs before any constructor does.
  • Class definition. @UseInterceptors throws 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.create builds the container, so create rejects with NEXUS_PROVIDER_FAILED and the InterceptorError is its cause. A fault in the bindings option nests one level deeper, and so does a bad-target fault for a provider whose instance is a frozen function with an intercepted own method: the cause of the provider failure is NEXUS_PLUGIN_FAILED, and the InterceptorError is that error’s cause. Nexus.check builds 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 an onInit hook fails the provider that owns the hook, so create rejects with NEXUS_PROVIDER_FAILED and the InterceptorError is its cause.

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:

reasonThe fault and its fixHow it arrives
optionsAn 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
declarationA 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
declarationA @UseInterceptors argument is not a token. detail[0] describes the value received. Pass the token.Class definition
unknown-methodA 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-methodA 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-formsA 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-decoratorsThe 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-interceptA registered interceptor has no intercept(call, next) method, and token names it. Add the method.Build, as the cause of NEXUS_PROVIDER_FAILED
bad-nextAn 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-depWith 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-exemptWith 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-interceptA 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, or null. It is the interceptor for register-twice, no-intercept, bad-next and self-intercept, and the binding’s token for a malformed binding. For unexempted-dep it is the interceptor that depends on the service.
  • target: the class or provider the fault is on, or null. For unexempted-dep it is the service, and for unused-exempt the exempt token.
  • method: the method name, or null.
  • state: null for this code.
  • detail: a list of strings, empty when the fault names nothing else. It holds the rule and the value received for options, the key at fault for a static interceptors declaration, the value received for a @UseInterceptors argument, the kind for bad-target on a getter, setter, field or accessor, and the provider names for unexempted-dep and unused-exempt.