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.
Errors
Every NexusDI error extends NexusError and carries a stable code, such as NEXUS_MISSING_PROVIDER, plus the fields that describe the failure. Nexus.create compiles the whole graph first and reports every compile error at once. A MultiToken contribution that a module keeps private, from the previous page, is a valid design and raises no error.
Read a NexusDI error’s code with isNexusError()
isNexusError(value, code?) returns true for any error NexusDI raised, and narrows the value to the error class of the code you pass. It checks a brand that every copy of @nexusdi/core writes, so it also recognises an error from a second copy of the package, where instanceof fails. Codes are public API: NexusDI never renames or reuses one, and each has a page, listed on the error codes page.
Read every compile error from one NexusDI BlueprintError
Nexus.create reports every mistake it finds while it compiles the graph in one NEXUS_BLUEPRINT_INVALID error, a BlueprintError. Its errors array holds one error per mistake, and NexusDI builds no instance while the list holds an error.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { isNexusError } from '@nexusdi/core';
interface IReactorCore {
readonly output: number;
}
interface IShipComputer {
readonly reactor: IReactorCore;
}
interface INavCharts {
plot(to: string): string;
}
interface ISurveyDrone {
readonly charts: INavCharts;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const COMPUTER = new Token<IShipComputer>('ShipComputer');
const NAV_CHARTS = new Token<INavCharts>('NavCharts');
const DRONE = new Token<ISurveyDrone>('SurveyDrone');
class QuantumComputer implements IShipComputer {
static deps = [REACTOR] as const;
constructor(readonly reactor: IReactorCore) {}
}
class ScoutDrone implements ISurveyDrone {
static deps = [NAV_CHARTS] as const;
constructor(readonly charts: INavCharts) {}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [
provide(COMPUTER, { useClass: QuantumComputer }),
provide(DRONE, { useClass: ScoutDrone, lifetime: 'transient' }),
],
});
const error = await Nexus.create(Engineering).catch(
(caught: unknown) => caught,
);
const code = isNexusError(error) ? error.code : null; // -> 'NEXUS_BLUEPRINT_INVALID'
console.log(code);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors
: [];
const missing = inner.filter((each) =>
isNexusError(each, 'NEXUS_MISSING_PROVIDER'),
);
const tokens = missing.map((each) => each.token); // -> ['ReactorCore', 'NavCharts']
console.log(tokens);The graph misses two providers, and the BlueprintError lists both: ReactorCore for the computer and NavCharts for the drone. A fix for one does not hide the other.
Try it: save the region as broken.ts and run npx tsx broken.ts. It prints NEXUS_BLUEPRINT_INVALID and [ 'ReactorCore', 'NavCharts' ]. Add provide(REACTOR, { useValue: { output: 1.21 } }) to the providers of Engineering and run it again: it prints [ 'NavCharts' ]. Delete that line and both tokens are back.
Read core’s one-line NexusDI message
Core writes a short message for each error: the code in brackets, then the string and number fields as key=value pairs and a link to the code’s page on this site. The message of a BlueprintError starts with the error count and lists each inner message below it. The full text of each message lives in @nexusdi/core/text, which errors() loads, so an application that registers no plugin carries none of it.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { isNexusError } from '@nexusdi/core';
interface IReactorCore {
readonly output: number;
}
interface IShipComputer {
readonly reactor: IReactorCore;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const COMPUTER = new Token<IShipComputer>('ShipComputer');
class QuantumComputer implements IShipComputer {
static deps = [REACTOR] as const;
constructor(readonly reactor: IReactorCore) {}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(COMPUTER, { useClass: QuantumComputer })],
});
const error = await Nexus.create(Engineering).catch(
(caught: unknown) => caught,
);
if (!isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')) throw error;
const missing = error.errors[0];
const message = missing?.message; // -> '[NEXUS_MISSING_PROVIDER] token=ReactorCore requester=ShipComputer module=Engineering. https://nexus.js.org/errors/NEXUS_MISSING_PROVIDER'
console.log(message);The link ends in the code: https://nexus.js.org/errors/NEXUS_MISSING_PROVIDER opens the page for that code, which explains the failure and its fix.
Try it: save the region as one-line.ts and run it. It prints the line above. Open the link it prints.
Get the full message with errors()
errors() from @nexusdi/errors is a plugin that gives each NexusDI error its full message: a sentence, with hints and a Fix: line where the code has them. Register it in Nexus.create(root, { plugins: [errors()] }). devtools() from @nexusdi/devtools writes the same messages, so a development setup that registers devtools() needs nothing else.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { isNexusError } from '@nexusdi/core';
import { errors } from '@nexusdi/errors';
interface IReactorCore {
readonly output: number;
}
interface IShipComputer {
readonly reactor: IReactorCore;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
const COMPUTER = new Token<IShipComputer>('ShipComputer');
class QuantumComputer implements IShipComputer {
static deps = [REACTOR] as const;
constructor(readonly reactor: IReactorCore) {}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(COMPUTER, { useClass: QuantumComputer })],
});
const error = await Nexus.create(Engineering, { plugins: [errors()] }).catch(
(caught: unknown) => caught,
);
if (!isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')) throw error;
const missing = error.errors[0];
const lines = missing?.message.split('\n'); // -> ['[NEXUS_MISSING_PROVIDER] ShipComputer (module Engineering) depends on ReactorCore, but no provider of ReactorCore is visible in Engineering.', ' Fix: provide ReactorCore in Engineering or in a module Engineering imports.']
console.log(lines?.join('\n'));errors() reads core’s text from @nexusdi/core/text. Another package that raises its own codes publishes its text at its own /text entry, and the text packs page shows how to pass those packs to errors().
Try it: run npm install @nexusdi/errors@next, save the region as full-text.ts and run it. It prints the sentence and the Fix: line. Remove { plugins: [errors()] } and run it again: it prints the one-line form from the previous section. Put the option back and the full text returns.
Read the cause of a NexusDI ProviderError
A constructor, a factory, an onInit hook or an options validation that throws while NexusDI builds the provider fails Nexus.create with NEXUS_PROVIDER_FAILED, a ProviderError. Its token field names the provider, and its standard cause holds the error your code threw.
import { Nexus, Token, isNexusError, provide } from '@nexusdi/core';
interface INavCharts {
plot(to: string): string;
}
const NAV_CHARTS = new Token<INavCharts>('NavCharts');
const error = await Nexus.create([
provide(NAV_CHARTS, {
useFactory: async (): Promise<INavCharts> => {
throw new Error('chart archive unreachable');
},
}),
]).catch((caught: unknown) => caught);
const code = isNexusError(error) ? error.code : null; // -> 'NEXUS_PROVIDER_FAILED'
console.log(code);
const failure = isNexusError(error, 'NEXUS_PROVIDER_FAILED') ? error : null;
const token = failure?.token; // -> 'NavCharts'
console.log(token);
const failed = isNexusError(error) ? error.cause : undefined;
const cause = failed instanceof Error ? failed.message : null; // -> 'chart archive unreachable'
console.log(cause);Try it: save the region as failed.ts and run it. It prints NEXUS_PROVIDER_FAILED, NavCharts and chart archive unreachable. Replace the throw line with return { plot: (to: string) => 'course to ' + to }; and run it again: Nexus.create resolves, error holds the container, and the program prints null, undefined and null. Put the throw back to see the error again.
Log a NexusDI error with its fields
A NexusDI error’s fields are own enumerable properties, so { ...error, code: error.code } holds exactly the fields and the code. Write code after the spread: TypeScript rejects a code key written before ...error. Hand the error to your logger’s error serializer and add the code as a key of its own, so a log search finds every occurrence of one code.
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { isNexusError } from '@nexusdi/core';
interface INavCharts {
plot(to: string): string;
}
interface IShipComputer {
readonly charts: INavCharts;
}
const NAV_CHARTS = new Token<INavCharts>('NavCharts');
const COMPUTER = new Token<IShipComputer>('ShipComputer');
const Tactical = defineModule({
name: 'Tactical',
providers: [
provide(NAV_CHARTS, {
useValue: { plot: (to: string) => `course to ${to}` },
}),
],
});
class QuantumComputer implements IShipComputer {
static deps = [NAV_CHARTS] as const;
constructor(readonly charts: INavCharts) {}
}
const error = await Nexus.create(
defineModule({
name: 'Engineering',
imports: [Tactical],
providers: [provide(COMPUTER, { useClass: QuantumComputer })],
}),
).catch((caught: unknown) => caught);
if (!isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')) throw error;
const missing = error.errors[0];
if (missing === undefined) throw error;
const fields = { ...missing, code: missing.code }; // -> { token: 'NavCharts', requester: 'ShipComputer', module: 'Engineering', entry: null, nearMisses: [], code: 'NEXUS_MISSING_PROVIDER' }
console.log(JSON.stringify(fields));Try it: save the region as log.ts and run it. It prints the fields as JSON, with "nearMisses":[]. Add { plugins: [errors()] } as the second argument of Nexus.create (import errors from @nexusdi/errors) and run it again: nearMisses holds {"kind":"not-exported","module":"Tactical"} and the other fields are the same. Move code: missing.code before ...missing and tsc flags it with 'code' is specified more than once, so this usage will be overwritten. Put it back after the spread.