@nwire/forge
v0.7.1
Published
Nwire — the framework's core primitives. defineAction, defineEvent, defineHandler, defineActor, defineProjection, defineQuery, defineWorkflow, defineModule, defineApp, definePlugin, createApp. MessageEnvelope with correlation/causation. The runtime is the
Readme
@nwire/forge
The framework's domain primitives — one import for actions, actors, events, workflows, projections, modules, and the runtime that ties them together.
What it does
Provides the define* surface that domain code uses every day, plus the
runtime that fires them. Forge composes lower packages (@nwire/messages,
@nwire/handler, @nwire/app) into one ergonomic API; you can also use
the lower packages directly when you want a narrower surface.
Install
pnpm add @nwire/forge zodQuick start
import { z } from "zod";
import { defineEvent } from "@nwire/messages";
import { defineAction, defineActor, defineModule, createApp } from "@nwire/forge";
// 1. A past-tense fact the domain cares about.
export const StudentWasEnrolled = defineEvent({
name: "enrollments.student-was-enrolled",
schema: z.object({ studentId: z.string(), courseId: z.string() }),
});
// 2. An aggregate that holds state + invariants.
export const Student = defineActor("Student", {
schema: z.object({
studentId: z.string(),
enrolments: z.array(z.string()),
}),
initial: (id: string) => ({ studentId: id, enrolments: [] }),
methods: {
enrol(state, courseId: string) {
if (state.enrolments.includes(courseId)) return state;
return { ...state, enrolments: [...state.enrolments, courseId] };
},
},
});
// 3. A user-visible command that emits the event.
export const enrolStudent = defineAction({
name: "enrollments.enrol-student",
schema: z.object({ studentId: z.string(), courseId: z.string() }),
emits: [StudentWasEnrolled],
handler: async (input, { use }) => {
const student = await use(Student, input.studentId);
student.enrol(input.courseId);
return StudentWasEnrolled({
studentId: input.studentId,
courseId: input.courseId,
});
},
});
// 4. A module bundles the bounded context.
export const enrollmentsModule = defineModule("enrollments", {
events: [StudentWasEnrolled],
actors: [Student],
actions: [enrolStudent.public()],
});
// 5. createApp wires the modules into a runnable app.
export const app = createApp({ modules: [enrollmentsModule] });
await app.start();
await app.runtime.dispatch(enrolStudent, { studentId: "avi", courseId: "heb-1" });API surface
| Primitive | Purpose |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| defineAction | User-visible command — validated input, emits events |
| defineEvent | Past-tense fact (re-exported from @nwire/messages) |
| defineHandler | Operation primitive — transport-agnostic |
| defineActor | Aggregate with state, methods, optional state machine |
| defineSchema | Data shape + lifecycle states + storage hints |
| defineProjection | Read-model fold over events |
| defineQuery | Projection-backed read function |
| defineWorkflow | Reaction + saga unified — stateful or stateless |
| defineModule | Bundle of actors + actions + events + projections + queries |
| defineApp | App declaration (multi-wire instantiation) |
| createApp | App runtime — boots modules, owns the bus |
| definePlugin | Cross-cutting: provide bindings, hook lifecycle, intercept dispatch |
| defineResource | Public response shape (field allowlist + OpenAPI schema) |
| defineError | Typed throwable with status code |
| defineMiddleware | Reusable handler middleware step |
| defineCron, defineInbox, defineOutbox, defineExternalCall, defineInboundWebhook | Orchestrator primitives |
| runCli(app, argv) | Argv dispatcher — operator CLI without HTTP |
Forge also re-exports MessageEnvelope, seedEnvelope, deriveEnvelope
from @nwire/envelope and the Logger contract from @nwire/logger.
When to use forge
When you want the full framework DX in one import. If you only need typed
handlers without the actor/event machinery, pull @nwire/handler instead;
if you only need lifecycle + plugins, pull @nwire/app. Forge is the
default for app code; the smaller packages are the standalone path.
