Plugin authoring (definePlugin closure form)
A plugin is a closure that receives a builder and uses it to register bindings, listen for framework events, and run lifecycle hooks. The closure is the only API surface — no decorators, no class hierarchy.
The builder
ts
import { definePlugin } from "@nwire/app"
const myPlugin = definePlugin("my-plugin", ({
provide, // register a container binding
on, // listen for a framework event
before, // sugar for on("XAboutTo...", ..., mode: "series-bail")
after, // sugar for on("XWas...", ..., mode: "parallel")
middleware, // mount onion-style middleware on the action pipeline
actorHooks, // listen for actor lifecycle (loaded / saved / transitioned)
boot, // run once at startup, in declared order
shutdown, // run once at shutdown, in reverse boot order
}) => {
// closure body — register everything you need
})Anatomy of a real plugin
ts
import { token } from "@nwire/container"
import { definePlugin } from "@nwire/app"
const Db = token<{ query(sql: string): Promise<unknown[]>; close(): Promise<void> }>("Db")
export const dbPlugin = definePlugin("db", ({ provide, on, boot, shutdown }) => {
let conn: Awaited<ReturnType<typeof openDb>>
// 1. Provide a binding — but the value isn't ready yet.
provide(Db, () => conn)
// 2. Open the connection at boot.
boot(async () => {
conn = await openDb(process.env.DATABASE_URL!)
})
// 3. Close it at shutdown.
shutdown(async () => {
await conn.close()
})
// 4. Optional — react to lifecycle events.
on("AppBooted", () => console.log("db plugin: connected"))
})Ordering
- Boot order is the order plugins are declared in
createApp({ plugins }). - Shutdown order is the reverse of boot order.
If dbPlugin boots before migrationsPlugin, the database is connected first and migrations run second; on shutdown, migrations stop first and the connection closes last.
Middleware
middleware(fn) mounts a function on the action pipeline. The signature is onion-shaped — call next() to invoke the next layer, do work before/after.
ts
const loggingPlugin = definePlugin("logging", ({ middleware }) => {
middleware(async (ctx, next) => {
const t0 = Date.now()
await next()
console.log(`${ctx.action.name} took ${Date.now() - t0}ms`)
})
})Where examples live
packages/nwire-tracing/src/tracing-plugin.ts— production observability plugin.packages/nwire-rbac/src/rbac-plugin.ts— authorization plugin (middleware + before-dispatch hook).packages/nwire-storage/src/storage-plugin.ts— provides a contract; per-driver packages register implementations.
See also
- Framework events
- Writing an adapter — for the storage / mail / queue / bus pattern