@nifrajs/cli 1.12.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +17 -2
  2. package/dist/check.d.ts +17 -6
  3. package/dist/check.d.ts.map +1 -1
  4. package/dist/check.js +256 -15
  5. package/dist/check.js.map +1 -1
  6. package/dist/cli.d.ts +2 -3
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +58 -71
  9. package/dist/cli.js.map +1 -1
  10. package/dist/doctor.d.ts.map +1 -1
  11. package/dist/doctor.js +131 -61
  12. package/dist/doctor.js.map +1 -1
  13. package/dist/mcp-http.js +1 -1
  14. package/dist/mcp-http.js.map +1 -1
  15. package/dist/mcp.d.ts +2 -0
  16. package/dist/mcp.d.ts.map +1 -1
  17. package/dist/mcp.js +42 -0
  18. package/dist/mcp.js.map +1 -1
  19. package/dist/recipes/2.0.0.d.ts +9 -0
  20. package/dist/recipes/2.0.0.d.ts.map +1 -0
  21. package/dist/recipes/2.0.0.js +25 -0
  22. package/dist/recipes/2.0.0.js.map +1 -0
  23. package/dist/recipes/index.d.ts +7 -0
  24. package/dist/recipes/index.d.ts.map +1 -1
  25. package/dist/recipes/index.js +2 -0
  26. package/dist/recipes/index.js.map +1 -1
  27. package/dist/sync-manifest.d.ts +33 -0
  28. package/dist/sync-manifest.d.ts.map +1 -0
  29. package/dist/sync-manifest.js +97 -0
  30. package/dist/sync-manifest.js.map +1 -0
  31. package/dist/upgrade.d.ts +23 -2
  32. package/dist/upgrade.d.ts.map +1 -1
  33. package/dist/upgrade.js +144 -13
  34. package/dist/upgrade.js.map +1 -1
  35. package/docs/examples.json +69 -21
  36. package/docs/llms-full.txt +1065 -104
  37. package/docs/types.json +933 -181
  38. package/package.json +9 -9
  39. package/src/check.ts +251 -18
  40. package/src/cli.ts +57 -73
  41. package/src/doctor.ts +160 -61
  42. package/src/mcp-http.ts +1 -1
  43. package/src/mcp.ts +52 -0
  44. package/src/recipes/2.0.0.ts +26 -0
  45. package/src/recipes/index.ts +10 -0
  46. package/src/sync-manifest.ts +111 -0
  47. package/src/upgrade.ts +163 -11
@@ -39,7 +39,7 @@ production backend (routing, validation, middleware, auth, WebSockets). Run it o
39
39
  `.listen()`:
40
40
 
41
41
  ```ts
42
- import { server } from "@nifrajs/core"
42
+ import { server } from "@nifrajs/core/server"
43
43
 
44
44
  server()
45
45
  .get("/", () => ({ hello: "world" }))
@@ -362,7 +362,7 @@ and `c.query` are the _validated_ types, and a bad request gets a structured
362
362
 
363
363
  ```ts
364
364
  // doc-check: skip — uses the third-party `zod` schema lib (any Standard Schema works); install it to run this.
365
- import { server } from "@nifrajs/core"
365
+ import { server } from "@nifrajs/core/server"
366
366
  import { z } from "zod" // any Standard Schema works: zod, valibot, arktype…
367
367
 
368
368
  export const app = server()
@@ -427,7 +427,7 @@ builder produces.
427
427
 
428
428
  ```ts
429
429
  // doc-check: skip — uses the third-party `zod` schema lib + an illustrative `users` repo; install zod to run this.
430
- import { defineContract, implement } from "@nifrajs/core"
430
+ import { defineContract, implement } from "@nifrajs/core/contract"
431
431
  import { z } from "zod"
432
432
 
433
433
  // 1. Declare the contract — methods, paths, and input schemas, no handlers.
@@ -945,7 +945,8 @@ resolves, you call `stream.close()`, or the client disconnects (
945
945
  Node, Deno, and the edge — no `new Function`, no per-runtime API.
946
946
 
947
947
  ```ts
948
- import { server, sse } from "@nifrajs/core"
948
+ import { server } from "@nifrajs/core/server"
949
+ import { sse } from "@nifrajs/core/sse"
949
950
 
950
951
  const app = server()
951
952
  // Your pub-sub of choice — subscribe returns an unsubscribe function.
@@ -1171,7 +1172,7 @@ plugins both depend on it), it wires its hooks once.
1171
1172
 
1172
1173
  ```ts
1173
1174
  // doc-check: skip — fragment: the outer `app`, `verify`, and `db` are your application's.
1174
- import { definePlugin } from "@nifrajs/core"
1175
+ import { definePlugin } from "@nifrajs/core/server"
1175
1176
 
1176
1177
  // A plugin is just (app) => app — call use/derive/decorate or register routes.
1177
1178
  // definePlugin adds a name so applying it twice (even transitively) is a no-op.
@@ -1206,7 +1207,7 @@ client — to `any`, with **no type error and no runtime error**.
1206
1207
  `server().use(betterAuth(auth)).get(...)` keeps every route typed.
1207
1208
 
1208
1209
  ```ts
1209
- import { defineRouterPlugin, server } from "@nifrajs/core"
1210
+ import { defineRouterPlugin, server } from "@nifrajs/core/server"
1210
1211
 
1211
1212
  // A plugin that mounts routes/hooks but adds NO context type: defineRouterPlugin (the clearer name for
1212
1213
  // defineIdentityPlugin) keeps app.use()'s return type EXACTLY the caller's server, so routes added after
@@ -1228,7 +1229,7 @@ short-circuit), `beforeHandle`/`afterHandle` (around the handler),
1228
1229
  error, 404). Hardening middleware uses the same hook model:
1229
1230
 
1230
1231
  ```ts
1231
- import { server } from "@nifrajs/core"
1232
+ import { server } from "@nifrajs/core/server"
1232
1233
  import { cors, rateLimit, securityHeaders, MemoryStore } from "@nifrajs/middleware"
1233
1234
 
1234
1235
  // Hardening middleware is a hook bundle (context-agnostic) — same app.use():
@@ -1244,7 +1245,7 @@ const app = server()
1244
1245
  `@nifrajs/middleware` seeds a few `definePlugin` plugins to build on:
1245
1246
 
1246
1247
  ```ts
1247
- import { server } from "@nifrajs/core"
1248
+ import { server } from "@nifrajs/core/server"
1248
1249
  import { requestId, logger, etag } from "@nifrajs/middleware"
1249
1250
 
1250
1251
  const app = server()
@@ -1309,7 +1310,7 @@ does custom, typed verification.
1309
1310
  clobbering one a handler already set.
1310
1311
 
1311
1312
  ```ts
1312
- import { server } from "@nifrajs/core"
1313
+ import { server } from "@nifrajs/core/server"
1313
1314
  import { compression, cacheControl } from "@nifrajs/middleware"
1314
1315
 
1315
1316
  const app = server()
@@ -1356,7 +1357,7 @@ connection. It short-circuits before the handler; a concurrent retry gets a `409
1356
1357
  guidance (shared store + DB constraint), and the `Set-Cookie` rule.
1357
1358
 
1358
1359
  ```ts
1359
- import { server } from "@nifrajs/core"
1360
+ import { server } from "@nifrajs/core/server"
1360
1361
  import { idempotency, MemoryIdempotencyStore } from "@nifrajs/middleware"
1361
1362
 
1362
1363
  // A retried POST with the same Idempotency-Key replays the first response instead of
@@ -1376,7 +1377,7 @@ Read the typed claims off the returned plugin — `auth.requireClaims(c.req)` (t
1376
1377
  compares static credentials in **constant time** (or takes a `verify` callback).
1377
1378
 
1378
1379
  ```ts
1379
- import { server } from "@nifrajs/core"
1380
+ import { server } from "@nifrajs/core/server"
1380
1381
  import { jwt, jwks, basicAuth } from "@nifrajs/middleware"
1381
1382
 
1382
1383
  // JWT (WebCrypto): an explicit algorithm allowlist is REQUIRED; alg:none and RSA/HMAC confusion are rejected.
@@ -1400,7 +1401,7 @@ user's response to another. `MemoryResponseCache` is per-instance and refuses
1400
1401
  pretty-prints JSON responses (capped, with an optional query toggle).
1401
1402
 
1402
1403
  ```ts
1403
- import { server } from "@nifrajs/core"
1404
+ import { server } from "@nifrajs/core/server"
1404
1405
  import { cache, MemoryResponseCache, prettyJson } from "@nifrajs/middleware"
1405
1406
 
1406
1407
  // Full response cache: pluggable store, Vary-aware keys, byte cap. Bypasses Set-Cookie and respects
@@ -1422,7 +1423,7 @@ no open redirect); `language` negotiates `Accept-Language` into
1422
1423
  is opt-in; `combine` bundles several middleware into one.
1423
1424
 
1424
1425
  ```ts
1425
- import { server } from "@nifrajs/core"
1426
+ import { server } from "@nifrajs/core/server"
1426
1427
  import { methodOverride, trimTrailingSlash, language, timing, poweredBy, combine } from "@nifrajs/middleware"
1427
1428
 
1428
1429
  const app = server()
@@ -1475,7 +1476,7 @@ bypasses that read path. `c.boundedBody(maxBytes?)` and
1475
1476
  `c.boundedJson<T>(maxBytes?)` extend the **same** cap to those routes.
1476
1477
 
1477
1478
  ```ts
1478
- import { server } from "@nifrajs/core"
1479
+ import { server } from "@nifrajs/core/server"
1479
1480
 
1480
1481
  const app = server()
1481
1482
 
@@ -1566,7 +1567,7 @@ the verified text for you to parse with your own schema.
1566
1567
 
1567
1568
  ```ts
1568
1569
  // doc-check: skip — fragment: `app`, `env`, `StripeEvent`, and the rotation keys are your application's.
1569
- import { verifyWebhook } from "@nifrajs/core"
1570
+ import { verifyWebhook } from "@nifrajs/core/webhook"
1570
1571
 
1571
1572
  app.post("/webhooks/stripe", async (c) => {
1572
1573
  // Reads the raw body BOUNDED (DoS guard), verifies the HMAC CONSTANT-TIME, and only
@@ -1646,7 +1647,7 @@ Transient `5xx` aren't cached, so a failed call stays retryable.
1646
1647
  Every one is constant-time where it compares secrets and **fails closed** by default.
1647
1648
 
1648
1649
  ```ts
1649
- import { server } from "@nifrajs/core"
1650
+ import { server } from "@nifrajs/core/server"
1650
1651
  import { jwt, csrf, ipRestriction, bodyLimit } from "@nifrajs/middleware"
1651
1652
 
1652
1653
  const app = server()
@@ -1782,7 +1783,7 @@ embeds a token), pass opt-in `valuePatterns` — `commonSecretPatterns` covers
1782
1783
  bearer tokens, JWTs, emails, and a few well-known key formats, or supply your own:
1783
1784
 
1784
1785
  ```ts
1785
- import { server, jsonLogger, commonSecretPatterns } from "@nifrajs/core"
1786
+ import { server, jsonLogger, commonSecretPatterns } from "@nifrajs/core/server"
1786
1787
 
1787
1788
  // Key-name redaction is always on; valuePatterns adds opt-in value + message scanning.
1788
1789
  const app = server({
@@ -2342,6 +2343,13 @@ Standard Schema
2342
2343
  , you can also bring Zod, Valibot, or ArkType (all eval-free) — only `t` also
2343
2344
  emits OpenAPI from the same definition.
2344
2345
 
2346
+ Size is the other edge axis. `t` is TypeBox-backed - it carries the whole JSON
2347
+ Schema type system - so a validated app on `t` is heavier than one on a lean
2348
+ validator. Validation is per route, so reach for `t` where you want the contract
2349
+ (OpenAPI/MCP for free) and Valibot where the Workers cold-start bundle is what matters: a
2350
+ Valibot-validated Nifra app measures **~16 KB gzipped**. Both are first-class, and you can
2351
+ mix them in one app.
2352
+
2345
2353
  ### Scheduled (cron)
2346
2354
 
2347
2355
  Pass `scheduled` to `toFetchHandler` to also export a Workers cron
@@ -2350,7 +2358,7 @@ typed `env` and `waitUntil` as your request handlers.
2350
2358
 
2351
2359
  ```ts
2352
2360
  // doc-check: skip — `env.KV` is typed by your app's `Env` (the ./app import), supplied by the reader.
2353
- import { toFetchHandler } from "@nifrajs/core"
2361
+ import { toFetchHandler } from "@nifrajs/core/server"
2354
2362
  import { app } from "./app"
2355
2363
 
2356
2364
  // Export fetch + a cron handler. Wire the schedule in wrangler.toml: [triggers] crons = ["0 * * * *"]
@@ -2369,15 +2377,18 @@ export default toFetchHandler(app, {
2369
2377
  through a generic — and runs on every runtime nifra serves: Bun, Deno, Node, and Cloudflare
2370
2378
  Workers.
2371
2379
 
2372
- The WebSocket runtime ships as a subpath, so apps that never use it don’t bundle it. Import
2373
- it once at your server entry; `app.ws()` without it fails loud at boot with
2374
- `WS_RUNTIME_MISSING`. (`@nifrajs/workers` imports it for you.)
2380
+ The WebSocket runtime ships as an opt-in plugin, so apps that never use it don’t bundle it.
2381
+ Enable it with `.use(websocket())` from `@nifrajs/core/ws`;
2382
+ `app.ws()` without it fails loud at registration with
2383
+ `WS_RUNTIME_MISSING`. It installs on that server instance only — the same
2384
+ `.use()` opt-in as `mcp()` and `streaming()`.
2375
2385
 
2376
2386
  ```ts
2377
- import { server } from "@nifrajs/core"
2378
- import "@nifrajs/core/ws" // registers the WebSocket runtime app.ws() needs
2387
+ import { server } from "@nifrajs/core/server"
2388
+ import { websocket } from "@nifrajs/core/ws" // .use(websocket()) enables app.ws()
2379
2389
 
2380
2390
  const app = server()
2391
+ .use(websocket())
2381
2392
  .get("/", () => ({ ok: true }))
2382
2393
  .ws("/echo", {
2383
2394
  open: (ws) => ws.send("welcome"),
@@ -2665,7 +2676,7 @@ SSR verified on real workerd — **this very site runs there**.
2665
2676
 
2666
2677
  ```ts
2667
2678
  // _worker.ts — the edge SSR entry
2668
- import { toFetchHandler } from "@nifrajs/core"
2679
+ import { toFetchHandler } from "@nifrajs/core/server"
2669
2680
  import { createWebApp } from "@nifrajs/web"
2670
2681
  import { reactAdapter } from "@nifrajs/web-react"
2671
2682
  import { clientEntry, manifest } from "./server-manifest" // generated by buildServer
@@ -2682,7 +2693,7 @@ Deploy: `wrangler pages deploy dist` (Pages) or `wrangler deploy`
2682
2693
 
2683
2694
  ## Nifra — CLI
2684
2695
 
2685
- > The zero-config Nifra CLI (`nifra dev`, `nifra build`, `nifra start`): true HMR, hashed client builds, and SSR serve.
2696
+ > The zero-config Nifra CLI (`nifra dev`, `nifra build`, `nifra start`): true HMR and complete target-specific deploy builds.
2686
2697
 
2687
2698
  `Nifra` is zero-config: it reads `routes/`, `framework.ts`, and
2688
2699
  (optionally) `backend.ts` from your project and wires the right
@@ -2691,57 +2702,62 @@ Deploy: `wrangler pages deploy dist` (Pages) or `wrangler deploy`
2691
2702
 
2692
2703
  ```ts
2693
2704
  nifra dev # true-HMR dev server (Vite middleware + nifra SSR) — http://localhost:4321
2694
- nifra build # bundle the client (content-hashed, code-split) + write dist/manifest.json (incl. CSS)
2695
- nifra start # serve the built client + SSR on Bun (assets, <link> stylesheets, matched-route preload)
2705
+ nifra build # full Bun deploy → dist/server.js + content-hashed dist/assets/ (default target: bun)
2706
+ nifra start # run dist/server.js on Bun
2707
+ nifra build --target cf-pages # also: node | deno | vercel | static; add --report for chunk sizes
2696
2708
 
2697
2709
  # dev + start share the default port 4321. Override per run: --port <n> (alias -p) or the PORT env var.
2698
- # flags: --port <n> (dev/start) · --out <dir> (build/start) · --poll (dev; containers/sandboxes)
2710
+ # flags: --port <n> (dev/start) · --out <dir> (build/start) · --target <t> (build) · --poll (dev)
2699
2711
  ```
2700
2712
 
2701
2713
  ### The conventions
2702
2714
 
2703
- Three files at the project root. `nifra dev` runs the Vite-backed
2715
+ Four conventions at the project root. `nifra dev` runs the Vite-backed
2704
2716
  [HMR dev server](/docs/dev); `nifra build` runs the Bun-native production
2705
- build (content-hashed, code-split, CSS bundled); `nifra start` serves the built
2706
- client and SSRs on Bun — assets with immutable caching + correct MIME, the matched route's
2707
- chunks preloaded, and the CSS bundle linked in every `<head>`.
2717
+ build (content-hashed client assets plus a target-specific server); `nifra start`
2718
+ runs the default Bun output. The generated server serves assets and SSR with matched-route
2719
+ chunks preloaded and route CSS linked in each `<head>`.
2708
2720
 
2709
2721
  ```ts
2710
2722
  my-app/
2711
2723
  routes/ # file-based routes (index.tsx, _layout.tsx, [id].tsx, …)
2712
- framework.ts # adapter + clientModule + plugins (above)
2724
+ framework.ts # deploy-safe render adapter
2725
+ nifra.config.ts # CLI-only client module + dev/build plugins
2713
2726
  backend.ts # export const backend = server()... (optional — the typed contract)
2714
2727
  ```
2715
2728
 
2716
2729
  ### framework.ts — naming the framework once
2717
2730
 
2718
- The single framework-specific file. Only `adapter` + `clientModule` are
2719
- required; the plugin/condition fields are extras the compiler frameworks need (React/Preact's
2720
- JSX is Bun-native, so they need none beyond the Vite plugin for dev HMR).
2731
+ Keep the render `adapter` in deploy-safe `framework.ts`. Put
2732
+ `clientModule`, Vite plugins, compiler plugins, conditions, and defines in CLI-only
2733
+ `nifra.config.ts`. This prevents build tooling and native dependencies from entering
2734
+ generated server bundles.
2721
2735
 
2722
2736
  ```ts
2723
- // doc-check: skip — needs the third-party @vitejs/plugin-react; install it to run this.
2724
- // framework.ts — the one place an app names its framework. The CLI reads these:
2725
- import react from "@vitejs/plugin-react"
2737
+ // framework.ts — deploy-safe; generated server entries import this file.
2726
2738
  import { reactAdapter } from "@nifrajs/web-react"
2727
2739
 
2728
2740
  export const adapter = reactAdapter
2741
+
2742
+ // nifra.config.ts — CLI-only build/dev tooling; never imported by a deployed server.
2743
+ // doc-check: skip — needs the third-party @vitejs/plugin-react; install it to run this.
2744
+ import react from "@vitejs/plugin-react"
2745
+ export { adapter } from "./framework"
2729
2746
  export const clientModule = "@nifrajs/web-react/client"
2730
2747
  export const vitePlugins = [react()] // dev HMR (Fast Refresh)
2731
2748
  // Vue/Svelte/Solid also export:
2732
2749
  // clientPlugins = [vueBunPlugin("dom")] // compile routes for the client build
2733
- // serverPlugins = [vueBunPlugin("ssr")] // compile routes for SSR (nifra start registers them)
2750
+ // serverPlugins = [vueBunPlugin("ssr")] // compile routes into the target's server bundle
2734
2751
  // conditions = ["solid"] // Solid: resolve solid-js to its source
2735
2752
  // define = { __VUE_OPTIONS_API__: "true", ... } // Vue feature flags
2736
2753
  ```
2737
2754
 
2738
2755
  ### Scope
2739
2756
 
2740
- `nifra build` + `nifra start` target a Bun (or Node) long-running server —
2741
- the common self-hosted case. For the edge (Cloudflare Workers / Vercel Edge / Deno Deploy),
2742
- use the per-target build entries from `create-nifra`'s `site` template (see
2743
- [Deployment](/docs/deployment)) — `buildServer` bundles a disk-less
2744
- worker those targets need. A `nifra build --target` for edge is a future addition.
2757
+ `nifra build` defaults to a self-hosted Bun server. Use `--target node`,
2758
+ `deno`, `cf-pages`, `vercel`, or `static` for another
2759
+ complete deploy shape (see [Deployment](/docs/deployment)). Run non-Bun outputs with
2760
+ the command printed by the build.
2745
2761
 
2746
2762
  ## Nifra — Dev & HMR
2747
2763
 
@@ -3170,6 +3186,110 @@ params. The error reads out the fix; add a schema to the route:
3170
3186
  `.get("/thing", { query: z.object({ page: z.string() }) }, h)`. Then
3171
3187
  `c.query` is the validated type and the client accepts a typed `query`.
3172
3188
 
3189
+ ###
3190
+ `TS2589` - "Type instantiation is excessively deep and possibly infinite" (one
3191
+ `server()` chain grew past ~95 routes)
3192
+
3193
+ The fluent builder's whole value - end-to-end inference, so `c.params.id` is typed
3194
+ straight from `:id` and the client is derived from `typeof app` - carries
3195
+ an **O(N) type-instantiation cost**. Each `.get(path, handler)` /
3196
+ `.post(...)` does two things at once: it computes the handler's context type from the
3197
+ path, _and_ it returns a server whose registry is one alias level deeper than the last.
3198
+ Neither strains the compiler alone; the **product** - recomputing the handler
3199
+ context while re-threading an ever-larger registry at every step - exhausts TypeScript's
3200
+ per-expression instantiation budget. A single chain hits `TS2589` at
3201
+ **~95-100 routes**.
3202
+
3203
+ [!NOTE]
3204
+
3205
+ This is a **healthy, growing app's wall, not abuse**. It is inherent to any
3206
+ builder that infers handler context _and_ accumulates a typed route registry (Elysia,
3207
+ tRPC, and Hono's typed clients cap the same way), so it is not fixable by reshaping the
3208
+ internal `AddRoute`. The fix is to use a shape that does not form the product.
3209
+
3210
+ **Fix 1 - split into domain groups and `.merge()` them.** Each group is
3211
+ its own short `server()` chain, so no single chain approaches the ceiling; a
3212
+ `.merge()` is one `R & R2` intersection with no per-call context work,
3213
+ so composing groups stays cheap. A 90-route single chain is inside the ceiling; 120 routes as
3214
+ four merged 30-route groups typecheck with full per-route fidelity.
3215
+
3216
+ ```ts
3217
+ import { server } from "@nifrajs/core"
3218
+
3219
+ // Each domain is its OWN short server() chain, kept well under the ~95-route ceiling.
3220
+ const users = server()
3221
+ .get("/users/:id", (c) => ({ id: c.params.id }))
3222
+ .post("/users", () => ({ created: true }))
3223
+
3224
+ const orders = server()
3225
+ .get("/orders/:id", (c) => ({ id: c.params.id }))
3226
+ .post("/orders", () => ({ placed: true }))
3227
+
3228
+ // merge() adds ONE R & R2 intersection per group - no per-call context recompute - so the whole
3229
+ // app stays inside tsc's budget no matter how many groups (or routes) you compose.
3230
+ export const app = server()
3231
+ .get("/health", () => ({ ok: true }))
3232
+ .merge(users)
3233
+ .merge(orders)
3234
+ ```
3235
+
3236
+ **Fix 2 - go contract-first, and stay flat at any route count.**
3237
+ `defineContract(...)` declares the entire registry as **one object type
3238
+ upfront**, and `implement(contract, handlers)` binds handlers to it. Nothing
3239
+ grows a registry per call, so there is **no ceiling at all** - this is the path for
3240
+ an API that will keep adding routes for years. Handlers stay checked against the contract exactly
3241
+ as inline routes are. See [Contract-first](/docs/contract).
3242
+
3243
+ ```ts
3244
+ import { defineContract, implement } from "@nifrajs/core/contract"
3245
+
3246
+ // One object type, declared upfront - NOT an N-deep stack of `Server<AddRoute<…>>` aliases.
3247
+ const contract = defineContract({
3248
+ getUser: { method: "GET", path: "/users/:id" },
3249
+ listUsers: { method: "GET", path: "/users" },
3250
+ createUser: { method: "POST", path: "/users" },
3251
+ // ...hundreds more operations stay flat: the registry is one type, not a growing chain.
3252
+ })
3253
+
3254
+ export const app = implement(contract, {
3255
+ getUser: (c) => ({ id: c.params.id }),
3256
+ listUsers: () => ({ users: [] as string[] }),
3257
+ createUser: () => ({ created: true }),
3258
+ })
3259
+ ```
3260
+
3261
+ ####
3262
+ `TS2345` from an unrelated `.merge()` - budget exhausted "at a distance"
3263
+
3264
+ Because the budget is per-expression and global to a compilation, a type-heavy construct
3265
+ _elsewhere_ in the program can push an otherwise-fine `.merge()` chain over the
3266
+ edge. It surfaces not as `TS2589` but as a `TS2345` assignability error
3267
+ naming an **uninstantiated** `Server` - the shape
3268
+ the server type collapses to when the compiler gives up mid-inference. The usual trigger is a
3269
+ **generic higher-order function that wraps the builder** (a
3270
+ `withX(app)` that threads the `Server` type through its own type
3271
+ parameters); merely having that file in the program can be enough. Treat these as real
3272
+ constraints of an inference-first framework:
3273
+
3274
+ -
3275
+ Keep **service-layer types flat and explicitly annotated** - do not let
3276
+ inference-heavy generics thread the `Server` / registry type through your own code.
3277
+
3278
+ -
3279
+ **Avoid generic HOF wrappers around the builder.** Wrap with a
3280
+ `defineRouterPlugin` (identity plugin) or compose with `.merge()` instead
3281
+ of a `withX(app)` that re-infers the whole server type.
3282
+
3283
+ -
3284
+ Put an explicit `Promise` return annotation on async callbacks that thread
3285
+ framework types, so the compiler stops re-deriving the awaited type at each use.
3286
+
3287
+ [!TIP]
3288
+
3289
+ Both fixes preserve full type fidelity - the client derived from `typeof app` is
3290
+ exactly as precise after a `.merge()` or an `implement()` as it is for an
3291
+ inline route. Splitting or going contract-first costs you nothing at the call site.
3292
+
3173
3293
  ### Still stuck?
3174
3294
 
3175
3295
  Run `nifra check --json` as the done-gate — it surfaces the import-chain leaks,
@@ -3195,7 +3315,7 @@ _over HTTP_. Both run the identical lifecycle — validation, middleware, contra
3195
3315
 
3196
3316
  ```ts
3197
3317
  // backend.ts — a normal @nifrajs/core server. Routes live at the full /api/... path.
3198
- import { server } from "@nifrajs/core"
3318
+ import { server } from "@nifrajs/core/server"
3199
3319
  import { t } from "@nifrajs/schema"
3200
3320
 
3201
3321
  export const backend = server()
@@ -3269,6 +3389,65 @@ Vite-backed dev server) routes every request through that same app's `fetch`. So
3269
3389
  the `/api/*` routes are served identically in development and production — there is
3270
3390
  nothing extra to wire for the dev loop.
3271
3391
 
3392
+ ### Mounting, composition & path semantics
3393
+
3394
+ A useful thing to know up front, because it is the opposite of some frameworks: in Nifra
3395
+ **composition and mounting are prefix-less, and paths stay absolute**. There is no
3396
+ per-mount base path, and nothing rewrites the request URL. Two mechanisms are involved and both
3397
+ leave the path untouched:
3398
+
3399
+ -
3400
+ **
3401
+ `.merge()` (and contract-first `implement()`)
3402
+ **
3403
+ - the composition escape hatch for large apps. `merge()` folds another server's
3404
+ routes into this one _at the exact paths they were defined_, into a single shared path
3405
+ space. It takes no prefix argument; a path+method collision throws a
3406
+ `RouteConfigError` at merge time (fail-closed), so groups never silently shadow
3407
+ each other. Each domain therefore owns its full absolute paths (`/api/listings`,
3408
+ `/api/agents`), exactly as it would standalone.
3409
+
3410
+ -
3411
+ **the `/api/*` auto-mount** - a path _guard_, not a rewrite.
3412
+ `createWebApp` hands the _same_ `Request` (its URL and body
3413
+ intact) to the backend when the pathname is exactly `apiPrefix` or under
3414
+ `apiPrefix + "/"`. The backend's own router then matches on
3415
+ `new URL(c.req.url).pathname` - the full, original path.
3416
+
3417
+ The consequence for a handler: **
3418
+ `c.req.url` always carries the full, original path
3419
+ **
3420
+ - the same URL the caller sent. The `/api` prefix is _not_ stripped before a
3421
+ merged or mounted route sees it, and there is no `pathOf`-style helper that strips
3422
+ it. Whether a route was defined inline, merged in from a group, or reached through the HTTP
3423
+ mount, the pathname it observes is the request's own.
3424
+
3425
+ ```ts
3426
+ // backend.ts - compose domains with .merge(). Each group owns its FULL absolute paths.
3427
+ import { server } from "@nifrajs/core/server"
3428
+
3429
+ const listings = server().get("/api/listings", (c) => {
3430
+ // c.req.url is the ORIGINAL request URL - .merge() and the /api mount never strip the prefix.
3431
+ const path = new URL(c.req.url).pathname // GET /api/listings -> "/api/listings"
3432
+ return { path }
3433
+ })
3434
+
3435
+ const agents = server().get("/api/agents", (c) => ({ ok: new URL(c.req.url).pathname }))
3436
+
3437
+ // merge() folds both groups into ONE flat path space at their defined paths; a path+method clash
3438
+ // throws a RouteConfigError right here at merge time (fail-closed), never a silent shadow at runtime.
3439
+ export const backend = server().merge(listings).merge(agents)
3440
+ ```
3441
+
3442
+ **The one gotcha:** because nothing strips the prefix, a backend that will be
3443
+ mounted (or merged) under `/api` must **declare its routes at the full path**
3444
+ - `server().post("/api/sync", …)`, _not_ `.post("/sync", …)` in the
3445
+ hope that `/api` gets peeled off. Define `/sync` and a browser
3446
+ `POST /api/sync` returns the backend's 404: the router looks up
3447
+ `/api/sync` and finds only `/sync`. `apiPrefix` chooses
3448
+ _which_ requests are handed to the backend; it does not rewrite them, so the route paths
3449
+ and the caller's paths are one and the same.
3450
+
3272
3451
  ### Calling /api/* from the browser
3273
3452
 
3274
3453
  With the routes mounted, a client island or component hits them with the same typed
@@ -3318,7 +3497,7 @@ to local policy, and exposes monotonic remaining time beside the existing cancel
3318
3497
  ### Clamp once at admission
3319
3498
 
3320
3499
  ```ts
3321
- import { server } from "@nifrajs/core"
3500
+ import { server } from "@nifrajs/core/server"
3322
3501
 
3323
3502
  const app = server({
3324
3503
  requestTimeoutMs: 2_000,
@@ -3357,6 +3536,177 @@ Configure a finite `requestTimeoutMs` before handing `c.budget` to a
3357
3536
  production adapter. The no-timeout compatibility path is deliberately unbounded and is not
3358
3537
  written to the wire.
3359
3538
 
3539
+ ## Nifra - Execution causality
3540
+
3541
+ > Carry one bounded, payload-free lineage across the durable seams a trace cannot span: commands, events, workflows, projections, and repairs.
3542
+
3543
+ A trace describes one observation tree. Causality survives the seams a trace cannot: a command
3544
+ commits an event, a workflow resumes tomorrow, a projection consumes that event, and
3545
+ reconciliation repairs the drift. Same execution, four identities that used to stop at the
3546
+ boundary.
3547
+
3548
+ ### Tokens only, by construction
3549
+
3550
+ A causality record has **no payload field**. There is nowhere to put a request
3551
+ body, a tenant identifier, or a URL, and unknown fields are rejected at every nesting level
3552
+ rather than carried along. Identities are bounded tokens, edge relations are bounded tokens,
3553
+ and the lineage stays safe to store and read during an incident.
3554
+
3555
+ ### Start at the boundary
3556
+
3557
+ ```ts
3558
+ import { createMemoryCausalityStore, startCausality } from "@nifrajs/core/causality"
3559
+
3560
+ const recorder = createMemoryCausalityStore()
3561
+ const requestId = crypto.randomUUID()
3562
+
3563
+ // One root node at the ingress boundary. executionId is the graph's identity:
3564
+ // generate it once, then propagate it.
3565
+ const step = startCausality("request", requestId, { executionId: crypto.randomUUID() })
3566
+
3567
+ // step.context propagates; step.record is what a durable adapter appends.
3568
+ await recorder.record(step.record)
3569
+ ```
3570
+
3571
+ ### Continue across seams
3572
+
3573
+ ```ts
3574
+ import { continueCausality, startCausality } from "@nifrajs/core/causality"
3575
+
3576
+ const step = startCausality("request", crypto.randomUUID(), {
3577
+ executionId: crypto.randomUUID(),
3578
+ })
3579
+
3580
+ // The command this request caused. The edge relation is a bounded token.
3581
+ const command = continueCausality(step.context, "command", crypto.randomUUID(), {
3582
+ relation: "caused",
3583
+ })
3584
+
3585
+ // The event that command emitted, inside the same commit.
3586
+ const event = continueCausality(command.context, "event", crypto.randomUUID(), {
3587
+ relation: "emitted",
3588
+ })
3589
+ ```
3590
+
3591
+ `joinCausality` merges several parents into one node, and refuses to join contexts
3592
+ from different executions: a graph that silently spanned two requests would be worse than no
3593
+ graph.
3594
+
3595
+ ### Propagation is a trust boundary
3596
+
3597
+ ```ts
3598
+ import {
3599
+ causalityHeaders,
3600
+ readCausalityHeaders,
3601
+ startCausality,
3602
+ } from "@nifrajs/core/causality"
3603
+
3604
+ const step = startCausality("request", crypto.randomUUID(), {
3605
+ executionId: crypto.randomUUID(),
3606
+ })
3607
+
3608
+ // Outbound: bounded headers only.
3609
+ await fetch("https://billing.internal/charge", {
3610
+ headers: causalityHeaders(step.context),
3611
+ })
3612
+
3613
+ // Inbound: never throws on hostile input.
3614
+ const request = new Request("https://api.example/charge")
3615
+ const parsed = readCausalityHeaders(request.headers)
3616
+ if (!parsed.success) {
3617
+ // "missing" | "incomplete" | "invalid" | "unknown-field" - start a fresh graph.
3618
+ }
3619
+ ```
3620
+
3621
+ Inbound headers are **untrusted by default**. A caller does not get to attach
3622
+ their request to your graph unless you say so, so a service-to-service seam opts in explicitly
3623
+ and anything that fails the check starts a fresh execution instead of being believed.
3624
+
3625
+ ### Storage is yours
3626
+
3627
+ `createMemoryCausalityStore()` is for development and tests. It is bounded, and it
3628
+ refuses to construct under `NODE_ENV=production`: in-memory evidence disappears on
3629
+ restart, which is exactly when an incident timeline matters. Production supplies a durable
3630
+ adapter behind the same `CausalityRecorder` interface, ideally appending in the same
3631
+ transaction as the effect it describes.
3632
+
3633
+ ### Traces still apply
3634
+
3635
+ The two models compose. `@nifrajs/otel` turns the nearest observed ancestor into a
3636
+ span link, so an event consumed tomorrow links back to the request that emitted it rather than
3637
+ inventing a trace identity that never existed.
3638
+
3639
+ ## Nifra - Adapter certification
3640
+
3641
+ > Prove a storage, cache, job, runtime, or event adapter against its interface and emit a hash-verifiable capability matrix.
3642
+
3643
+ An interface is a promise. Certification is how an adapter proves it kept it, in a form someone
3644
+ else can check.
3645
+
3646
+ ### Profiles
3647
+
3648
+ A profile is a named set of checks written against an interface, not an implementation:
3649
+ `cacheStoreCertificationProfile`, `jobStoreCertificationProfile`,
3650
+ `storageAdapterCertificationProfile`, `runtimeAdapterCertificationProfile`
3651
+ , and `eventDeliveryCertificationProfile`. Domain-specific seams define their own
3652
+ with `defineCertificationProfile`, which validates the profile at module load, so a
3653
+ check naming an undeclared capability fails before any adapter runs.
3654
+
3655
+ ### Certify an adapter
3656
+
3657
+ ```ts
3658
+ // doc-check: skip - createRedisCache stands in for the adapter under test
3659
+ import { test } from "bun:test"
3660
+ import {
3661
+ assertAdapterCertification,
3662
+ cacheStoreCertificationProfile,
3663
+ certifyAdapter,
3664
+ } from "@nifrajs/testing/certification"
3665
+
3666
+ test("the Redis cache adapter satisfies the cache contract", async () => {
3667
+ const report = await certifyAdapter({
3668
+ profile: cacheStoreCertificationProfile(),
3669
+ adapterId: "redis-cache",
3670
+ // A FRESH adapter per check, so one failure cannot contaminate the next.
3671
+ createAdapter: () => createRedisCache(url),
3672
+ cleanup: (adapter) => adapter.clear(),
3673
+ })
3674
+
3675
+ assertAdapterCertification(report) // throws, naming every failed check
3676
+ })
3677
+ ```
3678
+
3679
+ Profiles are structural and dependency-free. A third-party adapter can certify itself in its own
3680
+ test suite without adopting anything else, which is the point: trust becomes portable rather
3681
+ than something we vouch for.
3682
+
3683
+ ### The report is the evidence
3684
+
3685
+ ```ts
3686
+ {
3687
+ "schemaVersion": 1,
3688
+ "ok": true,
3689
+ "profile": { "id": "cache-store", "version": 1 },
3690
+ "adapterId": "redis-cache",
3691
+ "capabilities": [
3692
+ { "capability": "read-write", "status": "passed", "checks": ["set-get", "delete"] },
3693
+ { "capability": "tag-invalidation", "status": "passed", "checks": ["invalidate-tag"] }
3694
+ ],
3695
+ "evidenceHash": "9f2c..."
3696
+ }
3697
+ ```
3698
+
3699
+ The capability matrix is the useful artifact. It says what an adapter supports, per capability,
3700
+ with the checks that back each one. `verifyAdapterCertification` recomputes
3701
+ `evidenceHash`, so a stored report can be checked rather than believed.
3702
+
3703
+ ### Failures carry a class, not a message
3704
+
3705
+ A failed check records the error _class_ only. Provider messages routinely carry
3706
+ connection strings, tokens, and payload fragments, and evidence is meant to be shared, so it
3707
+ never becomes a place credentials leak. Cleanup errors cannot turn a failed functional check
3708
+ into a pass.
3709
+
3360
3710
  ## Nifra — Framework contract
3361
3711
 
3362
3712
  > The one-page contract: LoaderContext shape, loader/action/meta/head signatures, the client↔server boundary, and the build-output contract — every signature verified against the source.
@@ -3636,13 +3986,13 @@ or `env`. Keep server-only imports out of a route file's top level.
3636
3986
 
3637
3987
  ### The build-output contract
3638
3988
 
3639
- `nifra build` emits a content-hashed `dist/` plus a
3640
- `manifest.json` you hand to `createWebApp` (`entry`,
3989
+ The programmatic `buildClient()` API emits a content-hashed directory plus a
3990
+ `manifest.json` you can hand to `createWebApp` (`entry`,
3641
3991
  `routes` for per-route preload, `css` + `routeStyles` for
3642
3992
  `<link>` injection).
3643
3993
 
3644
3994
  ```ts
3645
- // `nifra build` → buildClient (Bun.build) writes dist/ + manifest.json. The contract:
3995
+ // buildClient() writes a browser bundle + manifest.json for custom build pipelines:
3646
3996
  {
3647
3997
  "entry": "/assets/_nifra-entry-HASH.js", // the bootstrap module script
3648
3998
  "assets": ["/assets/…"], // every emitted chunk URL
@@ -3727,6 +4077,73 @@ const paths = prerendered.map((p) => p.path)
3727
4077
  await Bun.write("./dist/_routes.json", JSON.stringify(cloudflarePagesRoutes({ prerendered: paths })))
3728
4078
  ```
3729
4079
 
4080
+ ### Publish a reusable router (library code vs app code)
4081
+
4082
+ The fluent `server().get(...).post(...)` builder is **app code**: each
4083
+ call both infers the handler's context from the path _and_ returns a
4084
+ `Server` whose type has one more route intersected in (`AddRoute` /
4085
+ `RouteInfoFor`). That accreting registry type lives happily inside your own program,
4086
+ but it does **not** cleanly survive TypeScript's `.d.ts` declaration
4087
+ emit - for a router you **publish** as a package, the compiler has to serialize
4088
+ the whole inferred chain into your `.d.ts` and chokes (a `TS2883`-class
4089
+ error: the inferred type is too large to name), which forces ugly casts on the boundary. So a
4090
+ distributable router is written **contract-first** instead.
4091
+
4092
+ The recipe: declare the API as a plain `defineContract({…})` object and
4093
+ **export it** (it is the consumer's typed-client source), then ship a factory that
4094
+ returns `implement(contract, handlers)`. The consumer mounts the router with one
4095
+ `.merge(...)` and derives its client straight from the exported contract value -
4096
+ `client(contract, url)`, no `typeof app` import crossing the package
4097
+ boundary. Pass a preamble as the third arg -
4098
+ `implement(contract, handlers, server().use(auth))` - to bake a middleware /
4099
+ `derive` chain into the router's routes (captured at registration).
4100
+
4101
+ ```ts
4102
+ // A PUBLISHABLE router - contract-first, so its types survive `.d.ts` emit for consumers.
4103
+ import { client } from "@nifrajs/client"
4104
+ import { defineContract, implement } from "@nifrajs/core/contract"
4105
+ import { server } from "@nifrajs/core/server"
4106
+ import { t } from "@nifrajs/schema"
4107
+
4108
+ // --- in the package (say @your-org/billing) ---
4109
+ // 1. The CONTRACT is ONE plain declared object - methods, paths, schemas, no handlers.
4110
+ // Export it: it is the single type the package publishes, and a consumer's typed client is
4111
+ // built from THIS value (not from `typeof app`), so no internal server type crosses the seam.
4112
+ export const billingContract = defineContract({
4113
+ getPlan: { method: "GET", path: "/billing/plan" },
4114
+ subscribe: { method: "POST", path: "/billing/subscribe", body: t.object({ plan: t.string() }) },
4115
+ })
4116
+
4117
+ // 2. A FACTORY closes over the host's dependencies and returns a real, mountable router.
4118
+ // `implement` binds handlers to the contract; each handler's context is typed from the contract
4119
+ // alone (`c.body` is { plan: string } below) - no per-route `AddRoute` inference accreting.
4120
+ export interface BillingDeps {
4121
+ charge(plan: string): Promise<{ id: string }>
4122
+ }
4123
+ export function createBillingRouter(deps: BillingDeps) {
4124
+ return implement(billingContract, {
4125
+ getPlan: () => ({ plan: "pro" }),
4126
+ subscribe: (c) => deps.charge(c.body.plan),
4127
+ })
4128
+ }
4129
+
4130
+ // --- in the consuming app ---
4131
+ // 3. Mount the router with ONE `.merge()` - no per-route `.get()/.post()` chain to re-infer.
4132
+ const app = server().merge(createBillingRouter({ charge: async (plan) => ({ id: plan }) }))
4133
+
4134
+ // 4. Build the client from the EXPORTED contract value - decoupled, no `typeof app` import needed.
4135
+ const api = client(billingContract, "https://api.example.com")
4136
+ const { data } = await api.billing.plan.get() // GET /billing/plan
4137
+ await api.billing.subscribe.post({ plan: "pro" }) // POST /billing/subscribe, body typed
4138
+ ```
4139
+
4140
+ Why this survives `.d.ts` emit where the fluent chain doesn't: the contract is a
4141
+ **plain declared object type** (`ContractShape`), fixed up front - there
4142
+ is no grow-the-registry-per-call inference to serialize, so its declaration is small and
4143
+ stable. The consumer still gets full end-to-end types both ways -
4144
+ `.merge()` threads the router's routes into the host app's registry, and
4145
+ `client(contract, url)` types every call from the same exported object.
4146
+
3730
4147
  See [SSG & ISR](/docs/rendering) for prerendering, [Dev & HMR](/docs/dev) for the
3731
4148
  two dev loops and the `--port` flag, and [Loaders & actions](/docs/data) for the
3732
4149
  data layer in depth.
@@ -3767,7 +4184,7 @@ server, and every handler reads it as a typed `c.db`. This is how the scaffold w
3767
4184
  it — and it keeps the client out of each handler's imports.
3768
4185
 
3769
4186
  ```ts
3770
- import { server } from "@nifrajs/core"
4187
+ import { server } from "@nifrajs/core/server"
3771
4188
  import { desc } from "drizzle-orm"
3772
4189
  import { db, notes } from "./db" // your Drizzle client + schema (what the scaffold generates)
3773
4190
 
@@ -3786,7 +4203,7 @@ Node, or `libSQL`/Turso anywhere. Open the connection once and query from your
3786
4203
  routes:
3787
4204
 
3788
4205
  ```ts
3789
- import { server } from "@nifrajs/core"
4206
+ import { server } from "@nifrajs/core/server"
3790
4207
  import { Database } from "bun:sqlite" // Node: better-sqlite3 · edge: Turso/libSQL
3791
4208
 
3792
4209
  // Open the connection ONCE at module scope (the driver pools internally).
@@ -3920,7 +4337,7 @@ directly:
3920
4337
 
3921
4338
  ```ts
3922
4339
  // doc-check: skip — illustrates a third-party driver (postgres + drizzle); install those to run it.
3923
- import { server } from "@nifrajs/core"
4340
+ import { server } from "@nifrajs/core/server"
3924
4341
  import { drizzle } from "drizzle-orm/postgres-js" // or node-postgres / neon-http
3925
4342
  import postgres from "postgres"
3926
4343
  import { eq } from "drizzle-orm"
@@ -3994,6 +4411,98 @@ Complete runnable references — a typed CRUD API you can diff your own against:
3994
4411
  `examples/db-sqlite` (the same API on raw `bun:sqlite`, parameterized
3995
4412
  queries, boundary validation).
3996
4413
 
4414
+ ## Nifra - Failure laboratory
4415
+
4416
+ > Replayable durable-failure schedules on virtual time: crash after commit, duplicate delivery, reordering, budget expiry, and checkpoint contention.
4417
+
4418
+ Durable systems fail on a schedule you never chose: the crash lands between the commit and the
4419
+ publish, the queue delivers twice, the events arrive backwards. The laboratory lets you choose
4420
+ that schedule, then replay it exactly.
4421
+
4422
+ ### Seven failures worth testing
4423
+
4424
+ -
4425
+ `crash` - stop dead at a named durability seam.
4426
+
4427
+ -
4428
+ `duplicate-delivery` - deliver the same batch N times.
4429
+
4430
+ -
4431
+ `reorder-events` - rotate a batch out of order.
4432
+
4433
+ -
4434
+ `delay` - advance virtual time, with no real sleeping.
4435
+
4436
+ -
4437
+ `expire-budget` - report zero remaining at a budget seam.
4438
+
4439
+ -
4440
+ `lose-provider-reply` - run the call, then lose only its reply.
4441
+
4442
+ -
4443
+ `contend-checkpoint` - make a checkpoint conflict.
4444
+
4445
+ ### Place the seams, schedule the failure
4446
+
4447
+ ```ts
4448
+ import { createFailureLab } from "@nifrajs/testing"
4449
+
4450
+ const lab = createFailureLab({
4451
+ seed: 77,
4452
+ schedule: [
4453
+ { kind: "duplicate-delivery", point: "relay.batch", copies: 3 },
4454
+ { kind: "reorder-events", point: "relay.batch" },
4455
+ ],
4456
+ })
4457
+
4458
+ // The adapter under test just names its seams. Same seed, same schedule,
4459
+ // same result - every run.
4460
+ const delivered = lab.deliveries("relay.batch", ["evt-a", "evt-b", "evt-c"])
4461
+ ```
4462
+
4463
+ The controller is an off-hot-path port. Your adapter names its seams; the schedule decides what
4464
+ happens at them. Time is virtual, so a 24-hour delay costs nothing and nothing is flaky.
4465
+
4466
+ ### Assert the invariant, not the output
4467
+
4468
+ ```ts
4469
+ // doc-check: skip - commit/publish/outboxStillPending stand in for the system under test
4470
+ import { runFailureScenario } from "@nifrajs/testing"
4471
+
4472
+ const report = await runFailureScenario(
4473
+ {
4474
+ name: "crash-after-commit",
4475
+ execute: async (lab) => {
4476
+ await commit()
4477
+ lab.checkpoint("outbox.after-commit") // crashes here, per the schedule
4478
+ await publish()
4479
+ return "published"
4480
+ },
4481
+ // The scenario passes only if the invariant survives the failure.
4482
+ verify: async ({ error }) => error !== undefined && (await outboxStillPending()),
4483
+ },
4484
+ { seed: 1, schedule: [{ kind: "crash", point: "outbox.after-commit" }] },
4485
+ )
4486
+
4487
+ report.ok // did the invariant hold?
4488
+ report.replay // { seed, schedule } - reproduces this exact run
4489
+ ```
4490
+
4491
+ A scenario passes only when `verify` returns true after the failure. Losing the
4492
+ provider reply is not a bug; losing the money is. That distinction is the whole point.
4493
+
4494
+ ### Evidence is tokens only
4495
+
4496
+ Injection evidence records the kind, the point, the occurrence, and the virtual time. Never
4497
+ payloads, provider results, exception messages, or stacks. A failing report carries the error
4498
+ _class_ and the exact `replay` inputs, so it reproduces from the report alone
4499
+ without leaking what flowed through the system.
4500
+
4501
+ ### Where it fits
4502
+
4503
+ The same engine backs `nifra levels` L4. Contract invariants fuzz the HTTP surface;
4504
+ the laboratory covers the durable paths underneath, where the interesting failures live.
4505
+
3997
4506
  ## Nifra — Fonts
3998
4507
 
3999
4508
  > Self-host web fonts with zero layout shift: a build-time Google Fonts downloader (the next/font equivalent) plus CLS-safe @font-face + preload primitives.
@@ -4096,6 +4605,140 @@ stylesheet can't redirect the download elsewhere — an SSRF gate), and caps eac
4096
4605
  Filenames are derived only from validated tokens plus a content hash, so there's no path
4097
4606
  traversal.
4098
4607
 
4608
+ ## Nifra 2.0 migration guide — upgrade from 1.x
4609
+
4610
+ > Upgrade a Nifra 1.x application to 2.0: automated package and import moves, opt-in runtime plugins, client and web changes, external auth mounts, and release verification.
4611
+
4612
+ Nifra 2.0 removes the compatibility layer retained through 1.x and makes optional runtime
4613
+ systems explicit, instance-scoped plugins. The upgrade command handles deterministic package
4614
+ and import edits; this guide covers the structural changes it deliberately cannot guess.
4615
+
4616
+ ### 1. Run the executable upgrade
4617
+
4618
+ ```ts
4619
+
4620
+ ```
4621
+
4622
+ The command updates existing `@nifrajs/*`, `nifra`, and
4623
+ `create-nifra` dependency ranges to `2.0.0` while preserving
4624
+ caret/tilde/exact style. It also replaces the removed `@nifrajs/budget` dependency
4625
+ with `@nifrajs/core` and moves its imports to
4626
+ `@nifrajs/core/budget`. Dry-run is the default; `--write` applies and
4627
+ verifies with `nifra check`.
4628
+
4629
+ ### 2. Install optional server systems explicitly
4630
+
4631
+ Idempotency, the effect ledger, MCP declarations, SSE, WebSockets, and Node-direct resolution
4632
+ are no longer enabled by server options, side-effect imports, or process-global state. Install
4633
+ only the systems an app uses with `.use()`; registration fails loudly if a required
4634
+ plugin is missing.
4635
+
4636
+ ```ts
4637
+
4638
+ ```
4639
+
4640
+ The `@nifrajs/node` adapter installs `nodeDirect()` automatically.
4641
+ Only applications calling `app.resolveNode()` directly need to install it.
4642
+
4643
+ ### 3. Use the lean package entry points
4644
+
4645
+ The `@nifrajs/core` and `nifra` roots now expose the lean HTTP server
4646
+ surface. Prefer `@nifrajs/core/server` for `server()`, and import
4647
+ contracts, assurance, capabilities, budgets, manifests, reflection, SSE, WebSockets, and
4648
+ other optional systems from their documented subpaths.
4649
+
4650
+ ```ts
4651
+
4652
+ ```
4653
+
4654
+ Contract-generated adversarial testing lives in `@nifrajs/testing`; the deprecated
4655
+ core invariant runner is removed.
4656
+
4657
+ ### 4. Update full-stack backend mounts
4658
+
4659
+ `createWebApp` now auto-mounts a backend only through the platform-aware,
4660
+ symbol-keyed `BackendMount` interface. The old duck-typed
4661
+ `api.fetch(url, init)` convention is removed. Normal applications should pass the
4662
+ result of `inProcessClient(backend)` or `testClient(backend)`; both
4663
+ already implement the interface and forward `env` and `waitUntil`.
4664
+
4665
+ ```ts
4666
+
4667
+ ```
4668
+
4669
+ ### 5. Declare external library mounts
4670
+
4671
+ Better Auth and similar libraries can own routes such as `/auth/**` outside the
4672
+ Nifra typed contract. A relative fetch to that library is intentional, but otherwise resembles
4673
+ a hand-rolled own-API call. Declare only the exact mounted prefix in
4674
+ `nifra.check.json` so `nifra check` does not keep CI red:
4675
+
4676
+ ```ts
4677
+
4678
+ ```
4679
+
4680
+ Prefixes are segment-anchored: `/auth` covers `/auth` and its children,
4681
+ never `/authors`. Traversal paths are not suppressed. The normalized allowlist is
4682
+ echoed in human, JSON, and MCP check results, so every bypass remains auditable. This option
4683
+ affects only the typed-client lint; it does not mount, authorize, or trust the library.
4684
+
4685
+ ### 6. Narrow typed-client failures by status
4686
+
4687
+ Routes with declared error schemas now return a failure union discriminated by HTTP status.
4688
+ After checking `!result.ok`, also narrow `result.status` before reading
4689
+ typed failure data. Undeclared statuses and transport status `0` remain unknown.
4690
+
4691
+ ```ts
4692
+
4693
+ ```
4694
+
4695
+ ### 7. Apply the remaining web and protocol changes
4696
+
4697
+ -
4698
+ Redirects accept an options object as their second argument.
4699
+
4700
+ ```ts
4701
+
4702
+ ```
4703
+
4704
+ -
4705
+ Replace the removed prerender wrapper with `enumerateStaticRoutes()`.
4706
+
4707
+ - Fragment navigation resolves element IDs only.
4708
+
4709
+ -
4710
+ MCP Apps metadata uses only `_meta.ui.resourceUri`; remove the deprecated flat
4711
+ `ui/resourceUri` key.
4712
+
4713
+ -
4714
+ Telemetry integrations use `ObservationAdapter`; the
4715
+ `AgentSpan`, `AgentSpanExporter`, and `SpanExporter` aliases
4716
+ are removed.
4717
+
4718
+ - Invalid HTTP method overrides always fail closed with 400.
4719
+
4720
+ -
4721
+ `nifra build` always emits a complete deploy directory and defaults to Bun;
4722
+ `nifra start` runs the generated `server.js`.
4723
+
4724
+ ### 8. Run the release gates
4725
+
4726
+ -
4727
+ Run `nifra check --json` and fix every error.
4728
+
4729
+ -
4730
+ If the project has `nifra.assurance.ts`, run `nifra assure --json`.
4731
+
4732
+ -
4733
+ If capabilities are configured, review a new snapshot and run
4734
+ `nifra capabilities check --json`.
4735
+
4736
+ - Run the application test suite and a production build.
4737
+
4738
+ -
4739
+ Exercise authentication, redirects, backend mounting, SSE/WebSocket routes, and each deploy
4740
+ adapter the application uses.
4741
+
4099
4742
  ## Nifra — Migrating from Express, Hono, Fastify & Elysia
4100
4743
 
4101
4744
  > Move a backend to Nifra: routing, middleware, validation, and body parsing map directly from Express, Hono, Fastify, and Elysia — and you gain a typed client, multi-runtime deploy, and optional SSR.
@@ -4438,7 +5081,7 @@ boundary _before_ your handler runs — invalid input is rejected with a 422, so
4438
5081
  handler only ever sees well-formed data.
4439
5082
 
4440
5083
  ```ts
4441
- import { server } from "@nifrajs/core"
5084
+ import { server } from "@nifrajs/core/server"
4442
5085
  import { GetUser } from "./schema"
4443
5086
 
4444
5087
  export const app = server().get("/users/:id", GetUser, (c) => {
@@ -4481,7 +5124,7 @@ routes and their schemas — generated lazily on first request, never hand-writt
4481
5124
  `ui: true` to also serve a Scalar reference page.
4482
5125
 
4483
5126
  ```ts
4484
- import { server } from "@nifrajs/core"
5127
+ import { server } from "@nifrajs/core/server"
4485
5128
  import { openapi } from "@nifrajs/middleware"
4486
5129
  import { GetUser } from "./schema" // the contract defined above
4487
5130
 
@@ -4523,10 +5166,132 @@ coupling — server code never ships to the client.
4523
5166
  The generated OpenAPI document is a structural subset of 3.1 derived from your schemas;
4524
5167
  it reflects exactly what the routes declare, not hand-authored prose.
4525
5168
 
5169
+ ## Nifra - Verification ladder
5170
+
5171
+ > Five cumulative levels, from a typed contract to contract-derived invariant tests, that say what a Nifra project actually proves.
5172
+
5173
+ "It compiles" and "it is secure" are different claims. The ladder makes the difference
5174
+ legible: five levels, each earned, so a project can state what it proves instead of what it
5175
+ hopes.
5176
+
5177
+ ### The five levels
5178
+
5179
+ Levels are **cumulative**. A level only counts when every level below it holds,
5180
+ so `achieved` is the highest rung with unbroken support underneath, and it is
5181
+ `-1` when even L0 fails.
5182
+
5183
+ -
5184
+ **L0 typed contract** - `nifra check` passes: the frontend and
5185
+ backend cannot have silently diverged.
5186
+
5187
+ -
5188
+ **L1 route assurance** - every route is classified, and the enforcement a rule
5189
+ requires is actually installed.
5190
+
5191
+ -
5192
+ **L2 capability lockfile** - declared effects match a reviewed lockfile, so a
5193
+ route that starts writing to the database shows up in review.
5194
+
5195
+ -
5196
+ **L3 route manifest** - the committed trust artifact still matches the routes,
5197
+ evidence, effects, and response classification it was emitted from.
5198
+
5199
+ -
5200
+ **L4 invariant-tested** - contract-derived invariants ran against an isolated
5201
+ executor with nothing skipped.
5202
+
5203
+ ### Run it
5204
+
5205
+ ```ts
5206
+ # What does this project prove right now?
5207
+ nifra levels
5208
+
5209
+ # Fail CI below a level the project already reached.
5210
+ nifra levels --min 2
5211
+
5212
+ # The same report an agent reads over MCP (nifra_levels).
5213
+ nifra levels --json
5214
+ ```
5215
+
5216
+ ```ts
5217
+ L0 typed contract ok
5218
+ L1 route assurance ok
5219
+ L2 capability lockfile ok
5220
+ L3 route manifest FAIL manifest drifted from routes: run `nifra manifest emit`
5221
+ L4 invariant-tested FAIL no isolated invariant executor configured
5222
+
5223
+ achieved: 2
5224
+ ```
5225
+
5226
+ Every level that does not hold explains itself, so the output is a work list rather than a
5227
+ verdict. `--min <n>` is the CI floor: pin it to the level you have reached and
5228
+ the build fails when a change quietly costs you a rung.
5229
+
5230
+ ### Wiring the levels
5231
+
5232
+ L0 needs nothing. L1 through L4 hang off `nifra.assurance.ts`, and each one you
5233
+ configure is a level you can start claiming.
5234
+
5235
+ ```ts
5236
+ // nifra.assurance.ts
5237
+ // doc-check: skip - policy rules and the isolated executor are app-specific
5238
+ import { defineAssuranceConfig } from "@nifrajs/core/assurance"
5239
+ import { backend } from "./backend"
5240
+
5241
+ export default defineAssuranceConfig({
5242
+ source: backend,
5243
+ policy: { rules: [/* ... */] },
5244
+ capabilities: { lockfile: "capabilities.lock.json", policy: { /* ... */ } },
5245
+ manifest: { path: "nifra.manifest.json" },
5246
+ invariants: {
5247
+ // L4 fuzzes the contract, so it needs an app you are willing to throw hostile
5248
+ // input at. Point this at an isolated instance, never a live one.
5249
+ executor: (request) => backend.fetch(request),
5250
+ },
5251
+ })
5252
+ ```
5253
+
5254
+ L4 requires an **explicit** executor. Invariant runs send deliberately hostile
5255
+ input, so nifra will not guess which app to point them at; a level that fuzzed your live
5256
+ server by default would be a footgun, and a skipped route is reported rather than passed over
5257
+ in silence.
5258
+
5259
+ ### For coding agents
5260
+
5261
+ The `nifra_levels` MCP tool returns the same
5262
+ `{ achieved, levels[] }` report. An agent can read what its change proved, and a
5263
+ level that regressed comes back with the reason it broke.
5264
+
4526
5265
  ---
4527
5266
 
4528
5267
  # Package reference (READMEs)
4529
5268
 
5269
+ ## @nifrajs/agent-telemetry
5270
+
5271
+ Child-span telemetry for AI tool calls made through Nifra tool and MCP endpoints.
5272
+
5273
+ ```sh
5274
+ bun add @nifrajs/agent-telemetry @nifrajs/otel
5275
+ ```
5276
+
5277
+ ```ts
5278
+ import { agentTelemetry, consoleAgentExporter } from "@nifrajs/agent-telemetry"
5279
+
5280
+ app.use(agentTelemetry({ exporter: consoleAgentExporter() }))
5281
+ ```
5282
+
5283
+ Requests under `/_nifra/tool/*` and requests to `/mcp` receive tool-name, input-size, output-size,
5284
+ duration, and status observations. When `@nifrajs/otel` owns the request observation, tool spans are
5285
+ attached as children; otherwise the plugin creates a standalone observation. Other routes pass through.
5286
+
5287
+ ## For AI agents
5288
+
5289
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
5290
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
5291
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
5292
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5293
+ done-gate, or `nifra mcp` to give the agent live project tools.
5294
+
4530
5295
  ## @nifrajs/auth
4531
5296
 
4532
5297
  Session primitives for nifra — signed-cookie + server-store sessions, CSRF, and route guards. Bring your own identity (Better Auth / Lucia / OAuth); nifra owns the session.
@@ -4586,7 +5351,7 @@ export const auth = createBetterAuth({
4586
5351
 
4587
5352
  ```ts
4588
5353
  // server.ts
4589
- import { server } from "@nifrajs/core"
5354
+ import { server } from "@nifrajs/core/server"
4590
5355
  import { betterAuth } from "@nifrajs/better-auth"
4591
5356
  import { auth } from "./auth"
4592
5357
 
@@ -4655,38 +5420,6 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
4655
5420
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
4656
5421
  done-gate, or `nifra mcp` to give the agent live project tools.
4657
5422
 
4658
- ## @nifrajs/budget
4659
-
4660
- Compatibility import for the request-deadline mechanics owned by `@nifrajs/core/budget`. Existing
4661
- Nifra, Hono, and Fetch consumers can keep this import unchanged; new core consumers may use the
4662
- subpath directly. Both paths resolve to the same implementation and classes.
4663
-
4664
- ```ts
4665
- import {
4666
- assertBudgetRemaining,
4667
- createRequestBudget,
4668
- withDeadlineHeader,
4669
- } from "@nifrajs/budget"
4670
-
4671
- const budget = createRequestBudget({
4672
- deadline: Date.now() + 2_000,
4673
- signal: request.signal,
4674
- })
4675
-
4676
- assertBudgetRemaining(budget, 100)
4677
- await fetch(url, {
4678
- signal: budget.signal,
4679
- headers: withDeadlineHeader(undefined, budget.child(50)),
4680
- })
4681
- ```
4682
-
4683
- The wire value is an absolute Unix epoch deadline. `remaining()` samples wall time once, then uses a
4684
- monotonic clock so NTP changes cannot extend or shorten admitted work. A budget does not choose retry,
4685
- provider, tenant, or money policy; it only answers how much time remains.
4686
-
4687
- Nifra route handlers receive the same primitive as `c.budget`. `c.signal` remains the cancellation
4688
- signal and aborts at the effective server deadline.
4689
-
4690
5423
  ## @nifrajs/cache
4691
5424
 
4692
5425
  Typed KV cache for nifra — **TTL**, **stale-while-revalidate**, **tag invalidation**, and **single-flight
@@ -4742,6 +5475,14 @@ the Cache API) via a `CacheStore` for anything that should survive across reques
4742
5475
  - `cache.wrap(key, loader, { ttlMs?, swrMs?, tags? })` · `get` · `has` · `set` · `delete` · `invalidateTag` · `clear`.
4743
5476
  - `MemoryCache({ maxEntries?, now? })` — the default store; implement `CacheStore` for your own.
4744
5477
 
5478
+ ## For AI agents
5479
+
5480
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
5481
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
5482
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
5483
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5484
+ done-gate, or `nifra mcp` to give the agent live project tools.
5485
+
4745
5486
  ## @nifrajs/cli
4746
5487
 
4747
5488
  The `nifra` CLI — zero-config dev/build/start for a nifra app, plus the agent surfaces (`nifra context`,
@@ -4750,8 +5491,8 @@ The `nifra` CLI — zero-config dev/build/start for a nifra app, plus the agent
4750
5491
 
4751
5492
  ```
4752
5493
  nifra dev [--port <n>] [--poll] True-HMR dev server (Vite + nifra SSR).
4753
- nifra build [--out <dir>] Content-hashed client bundle + manifest.json.
4754
- nifra start [--port <n>] [--out <dir>] Serve the built app (client + SSR) on Bun.
5494
+ nifra build [--out <dir>] [--target <t>] Complete deploy directory; target defaults to Bun.
5495
+ nifra start [--port <n>] [--out <dir>] Run the generated Bun server at <out>/server.js.
4755
5496
  nifra context Print this project's API + page routes + conventions.
4756
5497
  nifra mcp MCP server (stdio) exposing this project to a coding agent.
4757
5498
  nifra check [--json] Gate: typecheck + lints; --json includes fixes/suggestions.
@@ -4816,6 +5557,17 @@ For hand-rolled own-API `fetch()` calls, `nifra check` stays conservative: simpl
4816
5557
  that match a statically visible Nifra route get an exact typed-client rewrite diff; dynamic URLs, custom
4817
5558
  headers, bodies, query strings, or ambiguous routes fall back to manual steps.
4818
5559
 
5560
+ When a library such as Better Auth intentionally owns a mounted prefix outside the Nifra contract,
5561
+ declare that prefix in `nifra.check.json` so its relative calls do not keep CI red:
5562
+
5563
+ ```json
5564
+ { "externalMounts": ["/auth"] }
5565
+ ```
5566
+
5567
+ The match is segment-anchored (`/auth` never suppresses `/authors`) and traversal paths are rejected.
5568
+ The normalized prefixes are echoed in human, JSON, and MCP results so the lint bypass stays auditable.
5569
+ This suppresses only the typed-client diagnostic; it does not mount or authorize the external handler.
5570
+
4819
5571
  ## For AI coding agents
4820
5572
 
4821
5573
  **`nifra context`** prints this project's actual surface — API routes (from `backend.routes()`), page
@@ -4842,6 +5594,10 @@ an agent can act on the project, not just read about it. Core tools:
4842
5594
  - **`nifra_check`** — run the drift gate and get the structured `{ ok, typecheck, diagnostics[] }`
4843
5595
  result with suggestions, so the agent verifies its own work and fixes each diagnostic before finishing.
4844
5596
  - **`nifra_assure`** — evaluate `nifra.assurance.ts` and return the complete per-route evidence report.
5597
+ - **`nifra_levels`** - compute the cumulative verification ladder (L0 typed contract, L1 route
5598
+ assurance, L2 capability lockfile, L3 trust manifest, L4 contract invariants) and return
5599
+ `{ achieved, levels[] }`, so an agent can see what its change proves and read why a level it lost
5600
+ no longer holds.
4845
5601
  - **`nifra_doctor`** — catch imported packages missing from `package.json`; with `autoFix:true`, write
4846
5602
  safe dependency entries when a version can be inferred locally and return exact `bun add` commands
4847
5603
  for anything that needs a package-manager decision.
@@ -4959,7 +5715,7 @@ bun add @nifrajs/core
4959
5715
  ```
4960
5716
 
4961
5717
  ```ts
4962
- import { server } from "@nifrajs/core"
5718
+ import { server } from "@nifrajs/core/server"
4963
5719
 
4964
5720
  const app = server()
4965
5721
  .get("/users/:id", (c) => ({ id: c.params.id }))
@@ -4969,6 +5725,15 @@ const app = server()
4969
5725
  export type App = typeof app // hand this to @nifrajs/client for end-to-end types
4970
5726
  ```
4971
5727
 
5728
+ `@nifrajs/core` and `@nifrajs/core/server` expose the same lean common runtime. Optional systems are
5729
+ available only from their explicit subpaths, so an ordinary HTTP server never evaluates them:
5730
+
5731
+ ```ts
5732
+ import { defineContract, implement } from "@nifrajs/core/contract"
5733
+ import { startCausality } from "@nifrajs/core/causality"
5734
+ import { defineAssurancePolicy } from "@nifrajs/core/assurance"
5735
+ ```
5736
+
4972
5737
  - **Inline or contract-first.** Write routes inline (types inferred from the
4973
5738
  builder), or `defineContract(...)` + `implement(...)` for a decoupled, versionable
4974
5739
  surface — handlers lift over unchanged.
@@ -5094,7 +5859,7 @@ deno add npm:@nifrajs/deno # or import npm:@nifrajs/deno directly
5094
5859
  ```
5095
5860
 
5096
5861
  ```ts
5097
- import { server } from "@nifrajs/core"
5862
+ import { server } from "@nifrajs/core/server"
5098
5863
  import { serve } from "@nifrajs/deno"
5099
5864
 
5100
5865
  const app = server().get("/users/:id", (c) => ({ id: c.params.id }))
@@ -5129,6 +5894,32 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
5129
5894
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5130
5895
  done-gate, or `nifra mcp` to give the agent live project tools.
5131
5896
 
5897
+ ## @nifrajs/devtools
5898
+
5899
+ Interactive request-trace DevTools for `nifra dev`, with a secured SSE stream and browser overlay.
5900
+
5901
+ ```sh
5902
+ bun add -d @nifrajs/devtools
5903
+ ```
5904
+
5905
+ ```ts
5906
+ import { devtools } from "@nifrajs/devtools"
5907
+
5908
+ app.use(devtools())
5909
+ ```
5910
+
5911
+ The plugin is enabled by default only when `NODE_ENV=development`. It records a bounded event buffer
5912
+ and serves the overlay stream at `/_nifra/devtools`; remote hosts and cross-origin access stay denied
5913
+ unless explicitly allowed. Leave it unregistered in production for zero request-path overhead.
5914
+
5915
+ ## For AI agents
5916
+
5917
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
5918
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
5919
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
5920
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5921
+ done-gate, or `nifra mcp` to give the agent live project tools.
5922
+
5132
5923
  ## @nifrajs/env
5133
5924
 
5134
5925
  Typed, validated environment variables. Define a schema once at startup; get a **frozen, typed**
@@ -5215,6 +6006,14 @@ lineage metadata.
5215
6006
 
5216
6007
  See the [nifra docs](https://github.com/nifrajs/nifra#readme).
5217
6008
 
6009
+ ## For AI agents
6010
+
6011
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6012
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6013
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6014
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6015
+ done-gate, or `nifra mcp` to give the agent live project tools.
6016
+
5218
6017
  ## @nifrajs/i18n
5219
6018
 
5220
6019
  Framework-agnostic i18n for nifra — locale negotiation + a tiny ICU message formatter on the platform Intl. Dependency-free.
@@ -5422,6 +6221,78 @@ export default {
5422
6221
  - `queue.counts()` → `{ pending, active, dead }`. `queue.store` — the underlying store.
5423
6222
  - Stores: `MemoryJobStore`. Backoff: `exponentialBackoff`, `fixedBackoff`, `noBackoff`.
5424
6223
 
6224
+ ## For AI agents
6225
+
6226
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6227
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6228
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6229
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6230
+ done-gate, or `nifra mcp` to give the agent live project tools.
6231
+
6232
+ ## @nifrajs/mcp
6233
+
6234
+ Build transport-agnostic MCP servers and interactive MCP Apps for Nifra applications.
6235
+
6236
+ ```sh
6237
+ bun add @nifrajs/mcp
6238
+ ```
6239
+
6240
+ ```ts
6241
+ import { createMcpServer, defineMcpTool } from "@nifrajs/mcp"
6242
+
6243
+ const tools = [
6244
+ defineMcpTool({
6245
+ name: "health",
6246
+ description: "Report service health",
6247
+ handler: () => ({ text: "ok" }),
6248
+ }),
6249
+ ]
6250
+
6251
+ const mcp = createMcpServer({ name: "orders", version: "1.0.0", tools })
6252
+ ```
6253
+
6254
+ Mount `mcp.fetch` at `POST /mcp`. The package also exposes the JSON-RPC protocol and Streamable HTTP
6255
+ layers directly, plus `defineMcpWidget` and the React adapter for tool results that render UI in MCP
6256
+ hosts.
6257
+
6258
+ ## For AI agents
6259
+
6260
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6261
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6262
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6263
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6264
+ done-gate, or `nifra mcp` to give the agent live project tools.
6265
+
6266
+ ## @nifrajs/mcp-db
6267
+
6268
+ Expose an allowlisted SQLite schema as a fail-closed MCP server, with opt-in read-only queries.
6269
+
6270
+ ```sh
6271
+ bun add @nifrajs/mcp-db
6272
+ ```
6273
+
6274
+ ```ts
6275
+ import { Database } from "bun:sqlite"
6276
+ import { serveDatabaseAsMcp } from "@nifrajs/mcp-db"
6277
+
6278
+ const mcp = serveDatabaseAsMcp(new Database("app.db"), {
6279
+ tables: ["habits", "entries"],
6280
+ })
6281
+ ```
6282
+
6283
+ Only `list_tables` and `describe_table` are exposed by default, restricted to the explicit table
6284
+ allowlist. `run_query` requires an authorization hook and is guarded by SQLite query-only mode,
6285
+ single-statement/read-only checks, query-plan verification, and bounded output. D1 is not supported
6286
+ because it cannot provide the same engine-level read-only guarantee.
6287
+
6288
+ ## For AI agents
6289
+
6290
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6291
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6292
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6293
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6294
+ done-gate, or `nifra mcp` to give the agent live project tools.
6295
+
5425
6296
  ## @nifrajs/middleware
5426
6297
 
5427
6298
  Composable, dependency-light middleware for [nifra](../../README.md) — CORS, security headers, body
@@ -5433,7 +6304,7 @@ bun add @nifrajs/middleware
5433
6304
  ```
5434
6305
 
5435
6306
  ```ts
5436
- import { server } from "@nifrajs/core"
6307
+ import { server } from "@nifrajs/core/server"
5437
6308
  import {
5438
6309
  bodyLimit,
5439
6310
  cors,
@@ -5510,6 +6381,34 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
5510
6381
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5511
6382
  done-gate, or `nifra mcp` to give the agent live project tools.
5512
6383
 
6384
+ ## @nifrajs/mock
6385
+
6386
+ Generate deterministic mock responses from a Nifra application's reflected response contracts.
6387
+
6388
+ ```sh
6389
+ bun add -d @nifrajs/mock
6390
+ ```
6391
+
6392
+ ```ts
6393
+ import { createMockServer } from "@nifrajs/mock"
6394
+ import { app } from "./app"
6395
+
6396
+ const mock = createMockServer(app, { seed: 42 })
6397
+ const response = await mock.fetch(new Request("http://local.test/users/1"))
6398
+ ```
6399
+
6400
+ The mock server matches reflected routes and synthesizes values from supported JSON Schema keywords.
6401
+ Unsupported or unsatisfiable schemas fail explicitly instead of returning known-invalid data. Use a
6402
+ fixed seed when responses must be replayable across local development and CI.
6403
+
6404
+ ## For AI agents
6405
+
6406
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6407
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6408
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6409
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6410
+ done-gate, or `nifra mcp` to give the agent live project tools.
6411
+
5513
6412
  ## @nifrajs/node
5514
6413
 
5515
6414
  Run a [nifra](../../README.md) app on Node's `http` server — nifra's ergonomics, deployed
@@ -5520,7 +6419,7 @@ bun add @nifrajs/node # or: npm add @nifrajs/node
5520
6419
  ```
5521
6420
 
5522
6421
  ```ts
5523
- import { server } from "@nifrajs/core"
6422
+ import { server } from "@nifrajs/core/server"
5524
6423
  import { serve } from "@nifrajs/node"
5525
6424
 
5526
6425
  const app = server().get("/users/:id", (c) => ({ id: c.params.id }))
@@ -5644,8 +6543,7 @@ inventing trace identity.
5644
6543
 
5645
6544
  ## Adapters
5646
6545
 
5647
- Implement `ObservationAdapter` to send spans wherever you collect them. `SpanExporter` remains as a
5648
- backwards-compatible type alias:
6546
+ Implement `ObservationAdapter` to send spans wherever you collect them:
5649
6547
 
5650
6548
  ```ts
5651
6549
  interface ObservationAdapter {
@@ -5678,6 +6576,37 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
5678
6576
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5679
6577
  done-gate, or `nifra mcp` to give the agent live project tools.
5680
6578
 
6579
+ ## @nifrajs/prompt
6580
+
6581
+ Provider-neutral, type-safe prompts with Standard Schema input and validated structured output.
6582
+
6583
+ ```sh
6584
+ bun add @nifrajs/prompt
6585
+ ```
6586
+
6587
+ ```ts
6588
+ import { prompt } from "@nifrajs/prompt"
6589
+ import { t } from "@nifrajs/schema"
6590
+
6591
+ const extract = prompt("Extract the contact from the text.")
6592
+ .input(t.object({ text: t.string() }))
6593
+ .output(t.object({ name: t.string(), email: t.string({ format: "email" }) }))
6594
+
6595
+ const contact = await extract.run({ text }, { complete: callYourProvider })
6596
+ ```
6597
+
6598
+ The package imports no vendor SDK. Supply a `complete` adapter for your provider; inputs are validated
6599
+ before the call and model output is parsed and validated before it reaches application code. An optional
6600
+ `heal` hook can repair and retry invalid structured output.
6601
+
6602
+ ## For AI agents
6603
+
6604
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6605
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6606
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6607
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6608
+ done-gate, or `nifra mcp` to give the agent live project tools.
6609
+
5681
6610
  ## @nifrajs/runner
5682
6611
 
5683
6612
  Run requests through a nifra app and capture **structured results** — the shared engine behind the
@@ -5727,7 +6656,7 @@ bun add @nifrajs/schema
5727
6656
  ```
5728
6657
 
5729
6658
  ```ts
5730
- import { server } from "@nifrajs/core"
6659
+ import { server } from "@nifrajs/core/server"
5731
6660
  import { t, toOpenAPI } from "@nifrajs/schema"
5732
6661
 
5733
6662
  const app = server().post("/users", { body: t.object({ name: t.string(), age: t.integer() }) }, (c) => ({
@@ -5747,6 +6676,22 @@ const openapi = toOpenAPI(app) // OpenAPI 3.1
5747
6676
  `operationId`s); also works on a live app. Routes using a BYO Standard Schema are
5748
6677
  emitted without a detailed schema (Standard Schema exposes no JSON Schema).
5749
6678
 
6679
+ ### `t` vs bring-your-own (bundle size)
6680
+
6681
+ `t` is the batteries-included default because a TypeBox schema *is* a JSON Schema: it gives you
6682
+ OpenAPI + MCP `tools/list` for free and compiles to a fast validator. That completeness has a
6683
+ cost - TypeBox carries the whole JSON Schema type system, so `t` adds meaningful bytes to a bundle.
6684
+
6685
+ nifra validates through **Standard Schema**, so any Standard-Schema validator works on a route with
6686
+ no adapter - `{ body: v.object({ ... }) }` with valibot, for instance. A validated app built on a
6687
+ lean validator like valibot is a fraction of the size (measured ~16 KB gzipped), which matters for
6688
+ edge/Workers cold-starts. The trade is the one noted above: a bring-your-own validator exposes no
6689
+ JSON Schema, so those routes carry no OpenAPI/MCP detail.
6690
+
6691
+ Rule of thumb: reach for `t` when you want the contract (OpenAPI/MCP) for free; bring valibot (or any
6692
+ Standard-Schema validator) when bundle size on the edge is the priority. Both are first-class - the
6693
+ choice is per route, and you can mix them in one app.
6694
+
5750
6695
  ### `toOpenAPI` coverage
5751
6696
 
5752
6697
  It emits `paths`, `parameters` (path + object query), `requestBody`, and `responses`, plus:
@@ -5835,6 +6780,14 @@ import { assertSafeKey, StorageKeyError } from "@nifrajs/storage"
5835
6780
  - `assertStorageAdapterConformance({ createAdapter })` — dependency-free executable adapter contract.
5836
6781
  - Optional interfaces: `PagedStorageAdapter` · `PresignableStorageAdapter` · `MovableStorageAdapter`.
5837
6782
 
6783
+ ## For AI agents
6784
+
6785
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6786
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6787
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6788
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6789
+ done-gate, or `nifra mcp` to give the agent live project tools.
6790
+
5838
6791
  ## @nifrajs/testing
5839
6792
 
5840
6793
  Contract-derived adversarial testing and stateful in-process helpers for Nifra apps.
@@ -6004,6 +6957,14 @@ that every call carries and captures cookies via a shared jar.
6004
6957
 
6005
6958
  For a **stateless** request (no cookies), use `testClient` from `@nifrajs/client` directly.
6006
6959
 
6960
+ ## For AI agents
6961
+
6962
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
6963
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
6964
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
6965
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
6966
+ done-gate, or `nifra mcp` to give the agent live project tools.
6967
+
6007
6968
  ## @nifrajs/uploads
6008
6969
 
6009
6970
  File-upload hardening for nifra — magic-byte MIME detection, size + type validation, short-TTL signed download URLs, and EXIF/metadata stripping. Dependency-free, edge-safe.
@@ -6330,7 +7291,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
6330
7291
 
6331
7292
  ## @nifrajs/agent-telemetry
6332
7293
 
6333
- `AgentSpan`, `AgentSpanExporter`, `agentTelemetry`, `AgentTelemetryOptions`, `consoleAgentExporter`
7294
+ `agentTelemetry`, `AgentTelemetryOptions`, `consoleAgentExporter`
6334
7295
 
6335
7296
  ## @nifrajs/auth
6336
7297
 
@@ -6338,7 +7299,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
6338
7299
 
6339
7300
  ## @nifrajs/better-auth
6340
7301
 
6341
- `betterAuth`, `BetterAuthLike`, `BetterAuthOptions`, `getSession`, `requireSession`, `RequireSessionOptions`, `SessionOf`
7302
+ `authed`, `AuthedOptions`, `betterAuth`, `BetterAuthLike`, `BetterAuthOptions`, `getSession`, `Principal`, `PrincipalFor`, `requirePrincipal`, `requireSession`, `RequireSessionOptions`, `SessionOf`, `SessionUserOf`, `WithPrincipal`
6342
7303
 
6343
7304
  ## @nifrajs/cache
6344
7305
 
@@ -6346,7 +7307,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
6346
7307
 
6347
7308
  ## @nifrajs/client
6348
7309
 
6349
- `ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `FetchFn`, `inProcessClient`, `InProcessClient`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `Result`, `SubscribeOptions`, `Subscription`, `testClient`, `Treaty`, `TreatyFromRegistry`
7310
+ `ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `ClientRetryOptions`, `FetchFn`, `inProcessClient`, `InProcessClient`, `InProcessClientOptions`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `ResponseContractViolation`, `Result`, `SubscribeOptions`, `Subscription`, `testClient`, `Treaty`, `TreatyFromRegistry`, `WsCallOptions`, `WsHandle`
6350
7311
 
6351
7312
  ## @nifrajs/content
6352
7313
 
@@ -6354,7 +7315,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
6354
7315
 
6355
7316
  ## @nifrajs/core
6356
7317
 
6357
- `AdmissionController`, `AdmissionDecision`, `AnyServer`, `attachWebSocket`, `commonSecretPatterns`, `Context`, `ContextForOp`, `ContractShape`, `CookieOptions`, `defineContract`, `defineIdentityPlugin`, `definePlugin`, `defineRouterPlugin`, `DurableObjectNamespaceLike`, `ExecutionContext`, `FRAMEWORK_NAME`, `FrameworkError`, `FrameworkName`, `Handler`, `HandlersFor`, `IdentityPlugin`, `implement`, `InferInput`, `InferOutput`, `jsonLogger`, `LogFields`, `Logger`, `McpPromptDescriptor`, `McpResourceDescriptor`, `Method`, `METHODS`, `Middleware`, `NifraPlugin`, `NifraWebSocket`, `NodeServeOutcome`, `OnRequestResult`, `OperationDef`, `Params`, `parseCookies`, `Platform`, `Prettify`, `PromptArgument`, `PromptMessage`, `redactLogFields`, `RedactOptions`, `Registry`, `RegistryFor`, `RegistryFromImpl`, `ResponseControls`, `ResponseDef`, `ResponseFinalization`, `robots`, `RobotsOptions`, `RobotsRule`, `RouteConfigError`, `RouteConfigErrorCode`, `RouteDescriptor`, `RouteInfo`, `Router`, `RouterMatch`, `RouteSchema`, `RunningServer`, `ScheduledController`, `ScheduledHandler`, `serializeCookie`, `server`, `Server`, `ServerOptions`, `SignatureEncoding`, `signValue`, `silentLogger`, `sitemap`, `SitemapChangeFreq`, `SitemapEntry`, `SitemapOptions`, `sse`, `SSEContext`, `SSEInit`, `SSEMessage`, `SSEStream`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `StandardTypes`, `StandardWebSocket`, `toFetchHandler`, `ToolAnnotations`, `TopicRegistry`, `typedSSEStream`, `TypedSSEStream`, `unsignValue`, `validateStandard`, `ValidationOutcome`, `verifyWebhook`, `VerifyWebhookOptions`, `Version`, `VERSION`, `WebhookFailureReason`, `WebhookProvider`, `WebhookResult`, `WebSocketContext`, `WebSocketData`, `WebSocketHandler`, `WebSocketUpgradeOutcome`
7318
+ `Version`, `VERSION`
6358
7319
 
6359
7320
  ## @nifrajs/cron
6360
7321
 
@@ -6394,7 +7355,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
6394
7355
 
6395
7356
  ## @nifrajs/mcp
6396
7357
 
6397
- `bridgeScript`, `createMcpServer`, `CreateMcpServerOptions`, `defineMcpTool`, `DefineMcpToolOptions`, `defineMcpWidget`, `DefineMcpWidgetOptions`, `McpAppBridge`, `McpHttpOptions`, `McpServer`, `McpToolHandlerResult`, `McpUiIntent`, `McpWidget`, `respondMcpHttp`, `uiResourceMeta`, `widgetDocument`
7358
+ `bridgeScript`, `createMcpServer`, `CreateMcpServerOptions`, `defineMcpTool`, `DefineMcpToolOptions`, `defineMcpWidget`, `DefineMcpWidgetOptions`, `InferOutput`, `McpAppBridge`, `McpHttpOptions`, `McpServer`, `McpToolHandlerResult`, `McpUiIntent`, `McpWidget`, `respondMcpHttp`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `uiResourceMeta`, `widgetDocument`
6398
7359
 
6399
7360
  ## @nifrajs/mcp-db
6400
7361
 
@@ -6414,7 +7375,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
6414
7375
 
6415
7376
  ## @nifrajs/otel
6416
7377
 
6417
- `ActiveObservation`, `AttributeValue`, `causalitySpanLink`, `combineObservationAdapters`, `consoleSpanExporter`, `createObservationLifecycle`, `EndObservation`, `formatTraceparent`, `generateSpanId`, `generateTraceId`, `NifraSpan`, `ObservationAdapter`, `ObservationClock`, `ObservationContext`, `ObservationLifecycle`, `ObservationLifecycleOptions`, `ObservationLink`, `ObservationParent`, `ParsedTraceparent`, `parseTraceparent`, `SpanExporter`, `SpanStatus`, `StartObservation`, `TraceContext`, `traceHeaders`, `tracing`, `TracingOptions`
7378
+ `ActiveObservation`, `AttributeValue`, `causalitySpanLink`, `combineObservationAdapters`, `consoleSpanExporter`, `createObservationLifecycle`, `EndObservation`, `formatTraceparent`, `generateSpanId`, `generateTraceId`, `NifraSpan`, `ObservationAdapter`, `ObservationClock`, `ObservationContext`, `ObservationLifecycle`, `ObservationLifecycleOptions`, `ObservationLink`, `ObservationParent`, `otlpExporter`, `OtlpExporter`, `OtlpExporterOptions`, `ParsedTraceparent`, `parseTraceparent`, `SpanStatus`, `StartObservation`, `TraceContext`, `traceHeaders`, `tracing`, `TracingOptions`
6418
7379
 
6419
7380
  ## @nifrajs/prompt
6420
7381
 
@@ -6442,7 +7403,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
6442
7403
 
6443
7404
  ## @nifrajs/web
6444
7405
 
6445
- `Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `BrowserNavigate`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createMutation`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `DehydratedState`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `enumeratePrerenderedPaths`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `getBrowserNavigate`, `GetStaticPaths`, `hashQueryKey`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaInput`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NavigateOptions`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderedPage`, `renderPage`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `routeStyles`, `ScriptDescriptor`, `serializeData`, `ServerOnly`, `setBrowserNavigate`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `styles`, `Submission`, `SubmitOptions`, `withISR`
7406
+ `Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `BrowserNavigate`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createMutation`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `DehydratedState`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `getBrowserNavigate`, `GetStaticPaths`, `hashQueryKey`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaInput`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NavigateOptions`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderedPage`, `renderPage`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `routeStyles`, `ScriptDescriptor`, `serializeData`, `ServerOnly`, `setBrowserNavigate`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `styles`, `Submission`, `SubmitOptions`, `withISR`
6446
7407
 
6447
7408
  ## @nifrajs/web-preact
6448
7409