Skip to content

App

A bounded context made runnable: a container + plugins + wires that adopters mount.

The App is the foundation primitive. One repo can declare multiple apps that compose into a monolith via appCompose(...), or deploy separately as their own services — same domain code either way.

There is no separate Module primitive. An App is the bounded context. Multi-BC systems compose multiple apps; they don't nest modules.

Shape

An App is structure: a name, its registry, and its plugins. It reads no environment and binds no port — those are deployment concerns supplied at boot.

ts
import { createApp } from "@nwire/app";
import { forgePlugins } from "@nwire/forge";
import { registry } from "virtual:nwire-registry";

export const submissionsApp = createApp({
  appName: "submissions",
  registry,                      // every define* under the app, registered
  plugins: [...forgePlugins()],  // the forge battery — reads the runtime by kind
});

One registration channel

You write your primitives — submitAnswer, gradeAnswer, autoGrade, submissionsByStudent — as define* exports under the app's source tree, and they are registered for you through the registry. The @nwire/scan unplugin generates virtual:nwire-registry from that source; nothing is hand-listed, and no primitive is ever passed to a plugin. A battery like forge then scans the runtime by kind for the actors, projections, and workflows it drives.

Where there's no bundler — a test, an edge target — build the registry by hand with the same shape:

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

export const submissionsApp = createApp({
  appName: "submissions",
  registry: defineRegistry({
    handlers: [submitAnswer, gradeAnswer, listSubmissions],
    actors: [Submission],
    projections: [submissionsByStudent],
    workflows: [autoGrade],
  }),
  plugins: [...forgePlugins()],
});

The App is an unbooted value. The entry binds a port and supplies config at boot — config is the deployment's values, kept apart from the app's structure:

ts
import { config } from "virtual:nwire-config";

await endpoint("submissions", config.app.endpoint)
  .use(httpKoa(config.http))
  .mount(submissionsApp, config)   // binds config; handlers read ctx.config
  .run();

Multi-app composition

Each bounded context is its own App with its own registry — under a multi-app layout, each reads its scoped module (virtual:nwire-registry/submissions). One entry composes them onto a single endpoint:

ts
import { appCompose } from "@nwire/app";
import { config } from "virtual:nwire-config";
import { submissionsApp } from "./apps/submissions/app";
import { enrollmentsApp } from "./apps/enrollments/app";

const monolith = appCompose(submissionsApp, enrollmentsApp);

await endpoint("monolith", config.app.endpoint)
  .use(httpKoa(config.http))
  .mount(monolith, config)
  .run();

appCompose merges wire collections; the adopter dispatches each request through the source App's container per wire. Each App keeps its own DI scope. Split a context out to its own process later and only this composition line changes.

What an App owns

  • Registry — every primitive, registered by kind (handlers dispatch; actors/projections/workflows/commands/external-calls land in byKind for plugins to read)
  • Containerapp.container (with per-request scoping via container.createScope()), where the resolved config is bound at boot
  • Wire collectionapp.interface.wires (read by adopters at boot)
  • Plugins — boot/dispose ordering, framework hooks
  • Runtime — handlers, dispatch chain, telemetry stream
  • Framework events bus — typed lifecycle events (AppBooting, AppBooted, PluginBooting, etc.)

Multi-tenancy

Tenant scoping lives on the envelope (envelope.tenant) which threads through every dispatch. HTTP adopters read x-tenant-id (or x-tenant) by default; queue adopters carry it on every job; bus adopters preserve it across services.

Apps don't declare a tenant model — handlers + projections key on envelope.tenant directly. See Multi-tenancy for the patterns.

See also

MIT licensed.