npm.io
0.15.1 • Published 3d ago

@nwire/endpoint

Licence
MIT
Version
0.15.1
Deps
9
Size
86 kB
Vulns
0
Weekly
1.5K

@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/endpoint

Quick 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 up
  • GET /ready — readiness; 200 once boot completes and all addCheck() contributors pass

Both are intentionally on a separate port from the app — Kubernetes can poll them without touching the app's traffic.

Keywords