Topology
Deployment shape is what your wire entry file says. Nwire doesn't ship a "topology manifest" primitive. Each deployable is a small TypeScript file under apps/<name>/__wires__/ (or wires/<name>.ts) that calls httpInterface() (or queueInterface(), cronInterface(), …), wires the apps it should host, and calls .run(). Monolith vs split is a question of how many wire files you write — same modules, same code.
Monolith — one wire, all apps
// apps/main/__wires__/all.ts
import { httpInterface } from "@nwire/http"
import { lmsApp } from "@amit/lms"
import { lxApp } from "@amit/lx"
import { competencyApp } from "@amit/competency"
await httpInterface({
port: Number(process.env.PORT ?? 3000),
inspect: true,
openapi: { info: { title: "AMIT", version: "1.0.0" } },
})
.wire(lmsApp)
.wire(lxApp)
.wire(competencyApp)
.run()All three apps share one HTTP server, one bus, one container. The default for nwire dev.
Split — one wire per service
// apps/lms/__wires__/main.ts
import { httpInterface } from "@nwire/http"
import { natsBus } from "@nwire/bus-nats"
import { lmsApp } from "@amit/lms"
await httpInterface({
port: Number(process.env.PORT ?? 3001),
bus: natsBus({ servers: process.env.NATS_URL }),
})
.wire(lmsApp)
.run()// apps/lx/__wires__/main.ts — same shape, different port + app
import { httpInterface } from "@nwire/http"
import { natsBus } from "@nwire/bus-nats"
import { lxApp } from "@amit/lx"
await httpInterface({
port: Number(process.env.PORT ?? 3002),
bus: natsBus({ servers: process.env.NATS_URL }),
})
.wire(lxApp)
.run()Cross-app events go through the bus. Same modules. Deploy each wire as its own container.
Adding a new deployment
Add one file under __wires__/. Wire it in your package.json scripts (or let the CLI auto-discover it). No central manifest to update.
// apps/main/package.json
{
"scripts": {
"dev:all": "vite-node __wires__/all.ts",
"dev:lms": "PORT=3001 vite-node __wires__/lms.ts",
"dev:lx": "PORT=3002 vite-node __wires__/lx.ts"
}
}Provider swapping
The bus, actor store, projection store, logger — everything that ships an adapter — is swapped at the wire layer:
import { mongoActorStore } from "@nwire/store-mongo"
import { mongoProjectionStore } from "@nwire/store-mongo"
import { pinoLogger } from "@nwire/logger-pino"
await httpInterface({
port: 3000,
actorStore: mongoActorStore({ uri: process.env.MONGO_URL! }),
projectionStore: mongoProjectionStore({ uri: process.env.MONGO_URL! }),
logger: pinoLogger({ level: "info" }),
})
.wire(app)
.run()In dev, omit them — InMemory defaults are inline in every contract package.
See also
- Multi-service guide
- Studio-as-runner — Studio spawns wire processes via the kernel supervisor