# @nifrajs/core

The Bun-native, contract-first HTTP framework at the heart of [nifra](../../README.md):
a radix router, a fully type-inferred server, versionable contracts, lifecycle
middleware, and production hardening.

The Web-standard app can be wrapped for Vercel Edge with `toVercelHandler`, Netlify Functions with
`toNetlifyHandler`, or API Gateway v1/v2 with `toLambdaHandler` from `@nifrajs/core/server`. These are
thin event-envelope adapters; application policy, persistence, and platform credentials stay outside
the core.

```sh
bun add @nifrajs/core
```

```ts
import { server } from "@nifrajs/core/server"

const app = server()
  .get("/users/:id", (c) => ({ id: c.params.id }))
  .post("/users", { body: nameSchema }, (c) => ({ created: c.body.name }))
  .listen(3000)

export type App = typeof app // hand this to @nifrajs/client for end-to-end types
```

`@nifrajs/core` and `@nifrajs/core/server` expose the same lean common runtime. Optional systems are
available only from their explicit subpaths, so an ordinary HTTP server never evaluates them:

```ts
import { defineContract, implement } from "@nifrajs/core/contract"
import { startCausality } from "@nifrajs/core/causality"
import { defineAssurancePolicy } from "@nifrajs/core/assurance"
import { createDataPort, defineDataContract, diffDataContract } from "@nifrajs/core/data"
import { defineChannel, memoryChannelHub } from "@nifrajs/core/channel"
```

- **Inline or contract-first.** Write routes inline (types inferred from the
  builder), or `defineContract(...)` + `implement(...)` for a decoupled, versionable
  surface - handlers lift over unchanged.

### Scaling route tables

Fluent chains are the best fit for a small route surface. TypeScript's type-instantiation budget
usually becomes the limiting factor around 95-100 chained routes because every call both infers its
handler context and re-threads the growing typed registry. The runtime router is not the limit, and
the routes remain fully typed when you compose them as short domain groups:

```ts
const listings = server()
  .get("/listings", () => ({ ok: true }))
  .get("/listings/:id", (c) => ({ id: c.params.id }))
const agents = server().get("/agents/:id", (c) => ({ id: c.params.id }))

const app = server().get("/health", () => ({ ok: true })).merge(listings).merge(agents)
```

Each group's routes keep the middleware and assurance captured when that group was defined. For a
contract-owned surface, `defineContract(...)` + `implement(...)` is the other escape hatch: the
registry is declared as one object type, so it does not grow one fluent-instantiation level per
route. Split groups before the compiler reports `TS2589`, rather than weakening the route types.
- **Validation at the boundary.** Per-route `body`/`query` is any
  [Standard Schema](https://standardschema.dev) (zod/valibot/arktype, or `@nifrajs/schema`'s
  `t`); invalid input is rejected with a structured `422` before the handler runs.
- **Lifecycle middleware.** `derive`/`decorate` extend the typed context;
  `onRequest`/`beforeHandle`/`afterHandle`/`onResponse`/`onError` run around handlers;
  `use(middleware)` applies a bundle.
- **Portable response observation.** The header/body/raw observer methods are opt-in so ordinary
  servers stay lean: add `responseObserver()` from `@nifrajs/core/response-observer` before calling
  `onResponseHeaders`, `onResponseBody`, or `onResponseRaw`. Official middleware that uses these
  tiers installs the compatibility runtime automatically.
- **Hardening built in.** `stop({ drainMs })` graceful shutdown (+ opt-in SIGTERM/
  SIGINT), `requestTimeoutMs` (+ `ctx.signal` and `ctx.budget`), a streaming body-size cap, and a
  redacting structured `Logger`.
- **One request budget.** `ctx.budget` carries the admitted absolute deadline and monotonic
  `remaining()` time. An inbound `x-nifra-deadline` can only shorten `requestTimeoutMs`/
  `maxInboundDeadlineMs`; malformed and expired values fail before the handler. `ctx.signal`
  remains the cancellation primitive and aborts at that same effective deadline.
- **Route assurance.** Official auth, CSRF, body-limit, rate-limit, idempotency,
  IP-restriction, and security-header modules publish reflection-safe enforcement evidence.
  An ordered `AssurancePolicy` classifies every route and fails closed on missing or forbidden
  evidence without adding work to the request path.
- **Owned effect execution.** `executeCapability()` correlates intent and terminal evidence with an
  opaque `effectId`, records outcomes automatically, and forwards request cancellation. Add
  order-scoped `aroundCapability()` policies for async approval/admission; they receive token-only
  metadata, have bounded timeouts, and must call `next()` exactly once before the effect can run.
- **Durable workflows (opt in).** `@nifrajs/core/durable-execution` provides tenant/principal-bound,
  signed single-use approval resumes; a durable effect journal + reconciliation scanner; and a typed
  saga state machine with reverse compensation, retry/backoff, and ambiguous-crash detection. Production
  constructors reject stores that do not declare `durability: "durable"`. Operational scans use bounded
  cursor pages through `reconcileEffectsPage()` / `reconcileSagasPage()`. Provider-confirmed manual
  review uses effect-ID-bound `resolveAmbiguity()`, followed by `resume()` or `compensate()`.
- **Production durable adapters.** `@nifrajs/core/durable-adapters` supplies
  `PostgresDurableExecutionAdapter`, `SQLiteDurableExecutionAdapter`, and
  `DurableObjectExecutionAdapter`. Each exposes compatible `effects`, `approvals`, `sagas`, and
  `leases` stores. Run `runDurableExecutionAdapterConformance()` against the deployment backend.
- **Bounded reconciliation workers.** `@nifrajs/core/reconciliation-worker` runs effect or saga
  scans under an atomic lease with durable cursor checkpoints, a finite page budget, bounded handler
  concurrency, filters, cancellation, and token-only metrics. A worker invocation always terminates.
- **Rich wire values (opt in).** `@nifrajs/core/wire` round-trips dates, bigints, maps, sets, binary,
  shared references, and cycles through JSON transports. Decoding validates every reachable shape,
  preserves owned `__proto__` keys without prototype mutation, and enforces configurable node, depth,
  collection-entry, and decoded-byte limits.
- **Versioned transport codecs (opt in).** Add `.use(transportCodecs(registry))` from
  `@nifrajs/core/transport-plugin` and configure the typed
  client's `transport` option with the same registry. `@nifrajs/core/transport-codec` negotiates
  bounded HTTP representations and supplies the same frame/loader adapters for WebSockets and
  deferred data. Import `richWireCodec()` from `@nifrajs/core/transport-codec-rich`; the separate
  subpath keeps rich-wire code out of plain JSON bundles.
- **Typed data seam (opt in).** `@nifrajs/core/data` defines token-only operation contracts,
  `db.read`/`db.write` capability names, an opaque request-local `RlsScope`, typed adapter requests,
  drift snapshots, and `createDataPort(contract, adapter, { beacon: useCapability })`, which emits the
  operation's capability evidence - derived from the contract, never from the request - before the
  private adapter runs. It contains no database driver, tenant identity, policy, row values, or durable
  store; those belong in the adapter layer.
- **Typed channels (opt in).** `@nifrajs/core/channel` defines typed message contracts, bounded
  subscriptions, cancellation, per-channel resume cursors, bounded local replay, and a process-local
  in-memory hub for tests. Durable replay, presence, rooms, and multi-instance fan-out remain adapter
  concerns.

```ts
import { defineAssurancePolicy, evaluateRouteAssurance, NIFRA_ASSURANCE } from "@nifrajs/core/assurance"

const policy = defineAssurancePolicy({
  rules: [
    { name: "health", match: { paths: ["/health"] }, require: [] },
    { name: "mutation", match: { methods: ["POST", "PUT", "PATCH", "DELETE"] },
      require: [NIFRA_ASSURANCE.AUTHENTICATED, NIFRA_ASSURANCE.CSRF] },
    { name: "read", match: { methods: ["GET", "HEAD"] },
      require: [NIFRA_ASSURANCE.AUTHENTICATED] },
  ],
})

evaluateRouteAssurance(app, policy).ok // pure reflection-time evaluation
```

ESM-only; requires Bun at runtime. MIT.

## For AI agents

Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
one cheap read instead of the whole corpus. For the wider framework: the repo's
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
done-gate, or `nifra mcp` to give the agent live project tools.
