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.
NexusDI Plugins
The core of NexusDI is designed to be lean. To keep the bundle size small while providing powerful features, optional functionality is delivered via Plugins.
Plugins allow you to hook into the container’s lifecycle, modify how errors are reported, or add entirely new capabilities like graph introspection and method interception. A NexusDI plugin is a plain object with a name and the plugin API version it was written for (apiVersion), and it sets the hooks it uses as functions on that object. Each first-party package exports a function that returns one.
Registering Plugins
Plugins are passed to the container during creation in the plugins array. The order of this array matters: plugins are executed in the order they are listed. The call is Nexus.create(root, { plugins }).
errors() from @nexusdi/errors gives NexusDI errors their full message with a fix line. Install it at the same version as @nexusdi/core:
npm install @nexusdi/errors@nextThis container is missing the NavCharts provider, so Nexus.create rejects, and errors() writes the message:
import { Nexus, Token, defineModule } from '@nexusdi/core';
import { isNexusError, provide } from '@nexusdi/core';
import { errors } from '@nexusdi/errors';
interface INavCharts {
plot(to: string): string;
}
interface IShipComputer {
course(to: string): string;
}
const NAV_CHARTS = new Token<INavCharts>('NavCharts');
const COMPUTER = new Token<IShipComputer>('ShipComputer');
class QuantumComputer implements IShipComputer {
static deps = [NAV_CHARTS] as const;
constructor(private readonly charts: INavCharts) {}
course(to: string) {
return this.charts.plot(to);
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(COMPUTER, { useClass: QuantumComputer })],
});
const error = await Nexus.create(Engineering, { plugins: [errors()] }).catch(
(caught: unknown) => caught,
);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors[0]
: null;
const first = inner?.message ?? '';
const lines = first.split('\n'); // -> ['[NEXUS_MISSING_PROVIDER] ShipComputer (module Engineering) depends on NavCharts, but no provider of NavCharts is visible in Engineering.', ' Fix: provide NavCharts in Engineering or in a module Engineering imports.']
console.log(first);Without a plugin, core writes a one-line message: the code and the error’s fields, then the link to the code’s page.
import { Nexus, Token, defineModule } from '@nexusdi/core';
import { isNexusError, provide } from '@nexusdi/core';
interface INavCharts {
plot(to: string): string;
}
interface IShipComputer {
course(to: string): string;
}
const NAV_CHARTS = new Token<INavCharts>('NavCharts');
const COMPUTER = new Token<IShipComputer>('ShipComputer');
class QuantumComputer implements IShipComputer {
static deps = [NAV_CHARTS] as const;
constructor(private readonly charts: INavCharts) {}
course(to: string) {
return this.charts.plot(to);
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(COMPUTER, { useClass: QuantumComputer })],
});
const error = await Nexus.create(Engineering).catch(
(caught: unknown) => caught,
);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors[0]
: null;
const first = inner?.message; // -> '[NEXUS_MISSING_PROVIDER] token=NavCharts requester=ShipComputer module=Engineering. https://nexus.js.org/errors/NEXUS_MISSING_PROVIDER'
console.log(first);Try it:
- Save the first region as
plugins.tsand runnpx tsx plugins.ts. The output is the full message and itsFix:line. - Delete
errors()from thepluginsarray and run the file again. The output is the one-line message, which ends with the link to NEXUS_MISSING_PROVIDER. - Put
errors()back.
Registering DevTools in Development
devtools() from @nexusdi/devtools enables graph() and the trace option for a container, and it includes the errors engine, so a container with devtools() needs no errors() beside it. Register it in development only, because a production container uses no graph or trace:
import { Nexus, Token, defineModule, provide } from '@nexusdi/core';
import { devtools, graph } from '@nexusdi/devtools';
interface IReactorCore {
readonly output: number;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
class FusionReactor implements IReactorCore {
readonly output = 1.21;
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
});
const development = process.env.NODE_ENV !== 'production';
await using ship = await Nexus.create(Engineering, {
plugins: development ? [devtools()] : [],
});
const providers = graph(ship).providers.filter(
(provider) => !provider.internal,
);
const tokens = providers.map((provider) => provider.token); // -> ['ReactorCore']
console.log(tokens);graph(ship) lists every provider of the container. The filter drops the providers marked internal, such as core’s REQUEST.
Try it: Save the region as dev.ts and run npx tsx dev.ts. It prints [ 'ReactorCore' ]. Run it again with NODE_ENV=production npx tsx dev.ts: the container is created with no plugin, so graph(ship) throws NEXUS_DEVTOOLS_UNREGISTERED. Run it without NODE_ENV and the first output returns.
Official First-Party Plugins
NexusDI provides several official plugins to enhance your development experience:
| Package | Plugin | Purpose |
|---|---|---|
@nexusdi/errors | errors() | Replaces short error codes with full human-readable messages and fix suggestions. |
@nexusdi/devtools | devtools() | Enables the graph() and trace tools for debugging your dependency tree. |
@nexusdi/interceptors | interceptors() | Enables method interception for cross-cutting concerns. |
@nexusdi/federation | federation() | Gives one provider for every copy of a contract token, across bundles. |
@nexusdi/testing | none to register | createTestingContainer() registers its own plugin ahead of yours. |
The application writes the line that registers each one. errors({ text }) takes the text packs you pass. devtools({ text, annotate, trace }) includes the errors engine. interceptors(options) takes the options of Method Interceptors. createTestingContainer() is covered in Testing and Overrides.
Every package must be at the version of @nexusdi/core. Each one declares an exact peer dependency on core, so a package manager reports a mismatch at install.
Plugin Execution Order
Some plugins provide similar functionality. For example, both errors() and a custom internal plugin might want to format error messages.
NexusDI handles this using a “first-win” strategy for formatError and tokenKey: the first plugin in the array that returns a result wins, and later plugins are not asked. Other hooks, such as observe, construct, compile.check and setup, run for every plugin in array order. This application plugin words one code, and errors() after it words the rest:
import { NEXUS_PLUGIN_API, Nexus, Token } from '@nexusdi/core';
import { defineModule, isNexusError, provide } from '@nexusdi/core';
import type { NexusPlugin } from '@nexusdi/core';
import { errors } from '@nexusdi/errors';
interface INavCharts {
plot(to: string): string;
}
interface IShipComputer {
course(to: string): string;
}
const NAV_CHARTS = new Token<INavCharts>('NavCharts');
const COMPUTER = new Token<IShipComputer>('ShipComputer');
class QuantumComputer implements IShipComputer {
static deps = [NAV_CHARTS] as const;
constructor(private readonly charts: INavCharts) {}
course(to: string) {
return this.charts.plot(to);
}
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(COMPUTER, { useClass: QuantumComputer })],
});
const bridgeConsole: NexusPlugin = {
name: 'meridian:bridge-console',
apiVersion: NEXUS_PLUGIN_API,
formatError: (error) =>
error.code === 'NEXUS_MISSING_PROVIDER'
? { message: 'Bridge console: a provider is missing.' }
: undefined,
};
const error = await Nexus.create(Engineering, {
plugins: [bridgeConsole, errors()],
}).catch((caught: unknown) => caught);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors[0]
: null;
const message = inner?.message; // -> '[NEXUS_MISSING_PROVIDER] Bridge console: a provider is missing.'
console.log(message);Try it: Save the region as order.ts and run it. It prints [NEXUS_MISSING_PROVIDER] Bridge console: a provider is missing. Swap the two entries of the plugins array and run it again. errors() now comes first and writes the message for NEXUS_MISSING_PROVIDER, so the output is the full message and its Fix: line, and bridgeConsole is never asked. Swap the entries back and the bridge console’s line returns.
Plugin Validation
To ensure stability, Nexus.create validates all plugins before the graph is compiled. It will reject the startup process if:
- Two plugins share the same
name. - A plugin was written for an incompatible
apiVersion. - A plugin object is invalid, such as one with no
name.
These faults reject as NEXUS_PLUGIN_INVALID and NEXUS_PLUGIN_VERSION. Nexus.create reports each fault as an error inside NEXUS_BLUEPRINT_INVALID.
Two plugins with one name fail with NEXUS_PLUGIN_INVALID:
import { Nexus, Token, defineModule } from '@nexusdi/core';
import { isNexusError, provide } from '@nexusdi/core';
import { errors } from '@nexusdi/errors';
interface IReactorCore {
readonly output: number;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
class FusionReactor implements IReactorCore {
readonly output = 1.21;
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
});
const error = await Nexus.create(Engineering, {
plugins: [errors(), errors()],
}).catch((caught: unknown) => caught);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors[0]
: null;
const code = inner?.code; // -> 'NEXUS_PLUGIN_INVALID'
console.log(code);
const message = inner?.message; // -> '[NEXUS_PLUGIN_INVALID] nexus:errors has the name of an earlier plugin; each plugin needs its own name.'
console.log(message);Try it: Register errors() twice in the plugins.ts of the first region and run it. The container rejects, and the file prints [NEXUS_PLUGIN_INVALID] nexus:errors has the name of an earlier plugin; each plugin needs its own name. The plugin check runs before the graph compiles, so the missing provider is not reported. Remove the second errors() and the full missing-provider message returns.
A plugin written for another plugin API version fails with NEXUS_PLUGIN_VERSION. NEXUS_PLUGIN_API is the version this @nexusdi/core supports:
import { NEXUS_PLUGIN_API, Nexus, Token } from '@nexusdi/core';
import { defineModule, isNexusError, provide } from '@nexusdi/core';
import type { NexusPlugin } from '@nexusdi/core';
interface IReactorCore {
readonly output: number;
}
const REACTOR = new Token<IReactorCore>('ReactorCore');
class FusionReactor implements IReactorCore {
readonly output = 1.21;
}
const Engineering = defineModule({
name: 'Engineering',
providers: [provide(REACTOR, { useClass: FusionReactor })],
});
const api = NEXUS_PLUGIN_API; // -> 1
console.log(api);
const future: NexusPlugin = { name: 'acme:future', apiVersion: 2 };
const error = await Nexus.create(Engineering, { plugins: [future] }).catch(
(caught: unknown) => caught,
);
const inner = isNexusError(error, 'NEXUS_BLUEPRINT_INVALID')
? error.errors[0]
: null;
const message = inner?.message; // -> '[NEXUS_PLUGIN_VERSION] plugin=acme:future apiVersion=2. https://nexus.js.org/errors/NEXUS_PLUGIN_VERSION'
console.log(message);Try it: Set apiVersion to NEXUS_PLUGIN_API and run the region. The plugin is valid, so Nexus.create resolves, and the file prints 1 and then undefined, because there is no error to read. Set it back to 2 and the NEXUS_PLUGIN_VERSION message returns.
Later, a hook that throws rejects with NEXUS_PLUGIN_FAILED, and two plugins that rewrite the same module or provider reject with NEXUS_PLUGIN_CONFLICT. See NEXUS_PLUGIN_FAILED and NEXUS_PLUGIN_CONFLICT.
This ensures that your plugin configuration is proven correct before your application starts processing data. To write a plugin of your own, read Writing NexusDI Plugins.