@nwire/endpoint
@nwire/endpoint
Process lifecycle — boot an app under one or more transports, drain in-flight work on SIGTERM, surface K8s probes. The only Nwire layer that talks to the operating system.
pnpm add @nwire/endpointQuick example
import { createApp } from "@nwire/app";
import { endpoint } from "@nwire/endpoint";
import { httpKoa } from "@nwire/koa";
const app = createApp({ appName: "api" });
// ... app.wire(...) wires here ...
await endpoint("api", { port: 3000 }).use(httpKoa()).mount(app).run();.use(adopter) installs a transport runtime — httpKoa, expressAdapter,
queueInMemory, bullmqAdapter, mcpAdapter, cronAdapter. Each adopter
consumes the wires whose binding matches its kind. Stack as many as you
want on one endpoint; the same wires run under all of them.
Multi-transport example
import { createApp } from "@nwire/app";
import { endpoint } from "@nwire/endpoint";
import { httpKoa } from "@nwire/koa";
import { queueInMemory } from "@nwire/queue";
import { mcpAdapter } from "@nwire/mcp";
await endpoint("svc", { port: 3000 })
.use(httpKoa({ prefix: "/api" }))
.use(queueInMemory())
.use(mcpAdapter())
.mount(app)
.run();Adopter contract
interface Adapter {
readonly $kind: "adapter";
readonly kind: string; // "http", "queue", "mcp", ...
boot(ctx: AdapterBootContext): Promise<void>;
shutdown(reason?: string): Promise<void>;
}
interface AdapterBootContext {
readonly wires: ReadonlyArray<Wire>;
containerOf(wire: Wire): Container | undefined;
readonly logger: Logger;
addCheck(check: HealthCheck): void;
}Each adopter receives the full wire collection at boot, picks the ones it
serves (binding.$adapter === <its kind>), and dispatches handlers
through the runtime when forge is in play, or directly otherwise.
Lifecycle
| Stage | What runs |
|---|---|
boot |
App plugins boot; adopters consume wires; probes open. |
serve |
Adopters listen / subscribe; readiness flips green. |
drain |
SIGTERM/SIGINT: stop accepting new work, finish open. |
close |
Adopters shut down in reverse order; probes close. |
endpoint() returns a RunningEndpoint from .run(). Tests call
running.shutdown("test") to skip the SIGTERM dance.
Config
endpoint("api", {
banner: true, // boot banner on stdout
probes: { port: 9_400, enabled: true },
shutdown: { drainTimeout: 30_000, hardTimeout: 5_000 },
exitOnShutdown: true, // tests pass false
});Probes
When probes.enabled is true, a separate HTTP server on
probes.port (default 9400) serves:
GET /live— liveness; 200 if the process is upGET /ready— readiness; 200 once boot completes and alladdCheck()contributors pass
Both are intentionally on a separate port from the app — Kubernetes can poll them without touching the app's traffic.
Related
@nwire/app— produces the App that adopters mount@nwire/wires— the binding kinds adopters consume@nwire/koa— canonical HTTP adopter@nwire/queue— in-memory queue adopter (@nwire/bullmqfor production)@nwire/mcp— Model Context Protocol adopter