Skip to content

Multi-tenancy

Nwire partitions every store, every projection, and every actor by tenant when the app declares a tenant model. One process can serve N tenants with no bleed.

Declare on the app

ts
import { createApp } from "@nwire/app"
import { forgePlugins } from "@nwire/forge"

const app = createApp({
  appName: "submissions",
  handlers: [/* … */],
  plugins: [...forgePlugins({ /* actors, projections, … */ })],
})

There's no tenantModel setting — the envelope carries the tenant. Configure the inbound transport with the header name; everything downstream reads envelope.tenant. See the Multi-tenancy guide for the full picture.

What happens

  • Every actor store op is keyed by (tenant, aggregateId).
  • Every projection store op is keyed by (tenant, projectionKey).
  • Every action's envelope.tenant is populated from the request header.
  • Handlers and reactions can read ctx.envelope.tenant and use it as-needed.

HTTP wire

httpKoa() reads the x-tenant header by default and stamps envelope.tenant. Handlers can read the tenant from ctx.envelope.tenant. For tenant-required dispatches, validate inside the action handler and throw a typed error.

bash
curl -X POST http://localhost:3000/submissions \
  -H 'x-tenant: acme' \
  -H 'Content-Type: application/json' \
  -d '{"studentId":"avi","answer":"א"}'

Queue + bus

Tenant flows through automatically. The envelope is serialized on dispatch and deserialized on receive — no manual passing.

Excluding a route

Some routes (sign-in, health, signup) run before a tenant exists. Mark the action tenant: "none" to opt out of the requirement.

See also

MIT licensed.