Forge sub-plugins (à la carte)
The bundled forgePlugins() set is a convenience — one install, every piece in place. Sub-plugins exist for two reasons:
- Smaller surface. Apps that don't use actors don't need them; apps that don't issue queries don't need the runner.
- Swap a single piece. Subbing in a Redis-backed
IdempotencyStoreor a PostgresProjectionStoreis one option swap on the relevant sub-plugin.
Available sub-plugins
| Plugin | What it owns | Bound on container |
|---|---|---|
actorsPlugin() | actor state transitions on LocalDelivery@800 | forge.actorStore, forge.actorChain |
projectionsPlugin() | projection folds on LocalDelivery@600 | forge.projectionStore |
queriesPlugin() | query runner over projections + handlers | forge.queryRunner |
workflowsPlugin() | workflow correlation + fire on LocalDelivery@400 | forge.workflowRunner |
idempotencyPlugin() | atomic dedup gate on LocalDelivery@1000 | forge.idempotencyStore |
dlqPlugin() | dead-letter sink for failed actions | forge.deadLetterSink |
actionsPlugin() | command pipeline + the shared publish path | forge.actionRunner |
Each fold step attaches to the core runtime.hooks.LocalDelivery chain at its fixed priority slot. The order is structural — .with() install order doesn't change runtime order, only the priority does.
The priority chain
1000 — idempotency (short-circuits on duplicate dedupKey)
800 — actors (state transitions)
600 — projections (read-model folds)
400 — workflows (correlation + fire)The bus and outbound sinks are not chain steps — they're the outgoing leg of publish, applied after local delivery for .public() events. See the local delivery chain for the full mental model.
Two compositions
Bundle + override
The most common shape. Install the forgePlugins({...}) set with the store you want swapped — every option is an infrastructure seam (stores, bus, dead-letter sink); primitives are registered by the app, never passed to forge:
import { createApp } from "@nwire/app";
import { forgePlugins } from "@nwire/forge";
import { RedisIdempotencyStore } from "./redis-idempotency-store";
const app = createApp({
appName: "shop",
handlers: [...handlers, ...queries],
plugins: [...forgePlugins({ idempotencyStore: new RedisIdempotencyStore(client) })],
});If you want to drop whole concerns instead of swapping a store, build à la carte (below).
À la carte
Build the chain from sub-plugins only. The bundled hook slots have to be materialised first — wrap that in a tiny local plugin:
import { createApp, defineRegistry, type PluginDefinition } from "@nwire/app";
import {
actorsPlugin,
projectionsPlugin,
queriesPlugin,
actionsPlugin,
idempotencyPlugin,
} from "@nwire/forge";
function forgeHookSlotsPlugin(): PluginDefinition {
return {
name: "forge.hook-slots",
setup({ runtime }) {
// Local event delivery rides the core `LocalDelivery` hook (always
// present); only forge's Action*/Event* slots need materialising.
for (const slot of [
"ActionDispatching", "ActionCompleted", "ActionFailed",
"EventRecording", "EventRecorded",
] as const) {
runtime.defineHook(slot);
}
},
};
}
const app = createApp({
appName: "shop",
registry: defineRegistry({ handlers: [placeOrder, listOrders], projections: [Dashboard] }),
})
.with(forgeHookSlotsPlugin())
.with(idempotencyPlugin())
.with(projectionsPlugin())
.with(queriesPlugin())
.with(actionsPlugin());Dispatch action via the action runner:
import { FORGE_ACTION_RUNNER_BINDING } from "@nwire/forge";
const actions = app.container.resolve(FORGE_ACTION_RUNNER_BINDING);
await actions.dispatch(placeOrder, { orderId: "o-1", amount: 100 });See the forge-subplugins example for the full pattern.
Custom stores
Each sub-plugin accepts an option to override its store:
| Sub-plugin | Option |
|---|---|
actorsPlugin({ actorStore }) | ActorStore interface |
projectionsPlugin({ projectionStore }) | ProjectionStore interface |
idempotencyPlugin({ idempotencyStore }) | IdempotencyStore interface |
dlqPlugin({ deadLetterSink }) | DeadLetterSink interface |
The default for each is the in-memory implementation. Production deployments swap them for durable backends (Postgres, Redis, S3).
See also
- Local delivery chain
- Capability
- The
forge-subpluginsexample.