@nifrajs/cli 2.14.1 → 3.1.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 (76) hide show
  1. package/dist/check.d.ts.map +1 -1
  2. package/dist/check.js +27 -7
  3. package/dist/check.js.map +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +40 -2
  6. package/dist/cli.js.map +1 -1
  7. package/dist/command-catalog.d.ts +6 -0
  8. package/dist/command-catalog.d.ts.map +1 -1
  9. package/dist/command-catalog.js +20 -2
  10. package/dist/command-catalog.js.map +1 -1
  11. package/dist/doctor.d.ts +42 -0
  12. package/dist/doctor.d.ts.map +1 -1
  13. package/dist/doctor.js +64 -7
  14. package/dist/doctor.js.map +1 -1
  15. package/dist/env-file.d.ts +17 -0
  16. package/dist/env-file.d.ts.map +1 -0
  17. package/dist/env-file.js +113 -0
  18. package/dist/env-file.js.map +1 -0
  19. package/dist/fix-recipes.d.ts.map +1 -1
  20. package/dist/fix-recipes.js +91 -15
  21. package/dist/fix-recipes.js.map +1 -1
  22. package/dist/internal/reserved-segment-codemod.d.ts +36 -0
  23. package/dist/internal/reserved-segment-codemod.d.ts.map +1 -0
  24. package/dist/internal/reserved-segment-codemod.js +134 -0
  25. package/dist/internal/reserved-segment-codemod.js.map +1 -0
  26. package/dist/internal/typescript-import.d.ts +1 -1
  27. package/dist/internal/typescript-import.d.ts.map +1 -1
  28. package/dist/internal/typescript-import.js +62 -11
  29. package/dist/internal/typescript-import.js.map +1 -1
  30. package/dist/introspect.d.ts +26 -1
  31. package/dist/introspect.d.ts.map +1 -1
  32. package/dist/introspect.js +74 -24
  33. package/dist/introspect.js.map +1 -1
  34. package/dist/mcp-http.js +1 -1
  35. package/dist/mcp-http.js.map +1 -1
  36. package/dist/mcp.d.ts +8 -0
  37. package/dist/mcp.d.ts.map +1 -1
  38. package/dist/mcp.js +68 -6
  39. package/dist/mcp.js.map +1 -1
  40. package/dist/recipes/3.0.0.d.ts +10 -0
  41. package/dist/recipes/3.0.0.d.ts.map +1 -0
  42. package/dist/recipes/3.0.0.js +23 -0
  43. package/dist/recipes/3.0.0.js.map +1 -0
  44. package/dist/recipes/index.d.ts.map +1 -1
  45. package/dist/recipes/index.js +2 -0
  46. package/dist/recipes/index.js.map +1 -1
  47. package/dist/release-verification.d.ts.map +1 -1
  48. package/dist/release-verification.js +15 -0
  49. package/dist/release-verification.js.map +1 -1
  50. package/dist/rules/routes.d.ts.map +1 -1
  51. package/dist/rules/routes.js +17 -30
  52. package/dist/rules/routes.js.map +1 -1
  53. package/dist/workspace-link.d.ts +37 -0
  54. package/dist/workspace-link.d.ts.map +1 -0
  55. package/dist/workspace-link.js +72 -0
  56. package/dist/workspace-link.js.map +1 -0
  57. package/docs/examples.json +1 -1
  58. package/docs/llms-full.txt +362 -20
  59. package/docs/types.json +933 -46
  60. package/package.json +9 -9
  61. package/src/check.ts +28 -7
  62. package/src/cli.ts +41 -2
  63. package/src/command-catalog.ts +22 -2
  64. package/src/doctor.ts +99 -8
  65. package/src/env-file.ts +121 -0
  66. package/src/fix-recipes.ts +101 -15
  67. package/src/internal/reserved-segment-codemod.ts +150 -0
  68. package/src/internal/typescript-import.ts +61 -11
  69. package/src/introspect.ts +82 -21
  70. package/src/mcp-http.ts +1 -1
  71. package/src/mcp.ts +81 -6
  72. package/src/recipes/3.0.0.ts +24 -0
  73. package/src/recipes/index.ts +2 -0
  74. package/src/release-verification.ts +17 -0
  75. package/src/rules/routes.ts +17 -30
  76. package/src/workspace-link.ts +80 -0
@@ -320,7 +320,7 @@ renders in its place, wrapped by the layouts at/above that segment, at status 50
320
320
  non-hydrated). On the **client** - a render error during navigation/interaction is caught by
321
321
  the nearest boundary, which renders `_error` in place (all five adapters). It
322
322
  receives the serialized error as `{ data: { name, message } }` (never the
323
- stack); a thrown `Response` (e.g. a guard `redirect`) passes through.
323
+ stack); a thrown control-flow value (e.g. a guard `redirect`) passes through.
324
324
 
325
325
  ```ts
326
326
  routes/
@@ -1607,6 +1607,8 @@ Node against the outcome's plain header record on the **direct socket writer**,
1607
1607
  ever materializing Web `Request`/`Response` objects:
1608
1608
 
1609
1609
  ```ts
1610
+ import { responseObserver } from "@nifrajs/core/response-observer"
1611
+ import { server } from "@nifrajs/core/server"
1610
1612
  import type { Middleware } from "@nifrajs/core/server"
1611
1613
 
1612
1614
  // ONE implementation, fast on every runtime: on Bun/Deno it mutates the
@@ -1619,6 +1621,8 @@ export function serverName(value: string): Middleware {
1619
1621
  },
1620
1622
  }
1621
1623
  }
1624
+
1625
+ const app = server().use(responseObserver()).use(serverName("nifra"))
1622
1626
  ```
1623
1627
 
1624
1628
  Why this matters on Node: a full `onResponse(res: Response)` hook needs real Web
@@ -2799,9 +2803,10 @@ createWebApp({ /* … */ api: sessions }) // inject the manager
2799
2803
 
2800
2804
  ### Guard a route
2801
2805
 
2802
- `requireSession` / `requireUser` throw a `Response` (a 302 to
2803
- `redirectTo`, or a 401) when the session is missing - Nifra returns a thrown Response
2804
- as-is, so the guard short-circuits the loader.
2806
+ `requireSession` / `requireUser` throw a `status(...)` render
2807
+ (a 302 to `redirectTo`, or a 401) when the session is missing - Nifra renders a
2808
+ thrown control-flow value as-is, so the guard short-circuits the loader. It is plain data, not
2809
+ a `Response`: same bytes on the wire, on the lane an ordinary return takes.
2805
2810
 
2806
2811
  ```ts
2807
2812
  // doc-check: skip - fragment: `api` is the session manager createWebApp injected (see setup above).
@@ -3422,6 +3427,28 @@ app.ws("/chat", {
3422
3427
  })
3423
3428
  ```
3424
3429
 
3430
+ ### Optional outbound validation
3431
+
3432
+ `sendSchema` is type-level by default: it documents and types what the client
3433
+ receives with no runtime cost. Set `validateSend: true` to check server-sent JSON
3434
+ text and UTF-8 binary frames before the native socket sends them. Invalid frames and async
3435
+ validators fail closed and route a diagnostic to `error` when present. This is a
3436
+ contract check for your own output, not an inbound security boundary; the flag requires a
3437
+ `sendSchema` and is intentionally off by default.
3438
+
3439
+ ```ts
3440
+ // doc-check: skip - illustrative: outbound contract on an existing app.
3441
+ import { t } from "@nifrajs/schema"
3442
+
3443
+ app.ws("/events", {
3444
+ sendSchema: t.Object({ kind: t.Literal("ready"), id: t.String() }),
3445
+ validateSend: true, // opt in; default remains type-level only
3446
+ open(ws) {
3447
+ ws.send(JSON.stringify({ kind: "ready", id: "evt-1" }))
3448
+ },
3449
+ })
3450
+ ```
3451
+
3425
3452
  ### Pub/sub - app.publish
3426
3453
 
3427
3454
  `ws.subscribe(topic)` joins a topic; `app.publish(topic, data)`
@@ -4654,9 +4681,9 @@ global state; a second copy nulls it out and every hook throws.
4654
4681
 
4655
4682
  Nifra **dedupes React** in both the production build and the Vite dev server, so
4656
4683
  the framework itself won't load two copies. The usual culprit is a
4657
- **`file:`-linked package** (a local component library you
4658
- `bun link` or reference with `file:../lib`) that bundles its own React in
4659
- its own `node_modules`:
4684
+ **`file:`- or `link:`-linked package** (a local component
4685
+ library you `bun link` or reference with `link:../lib`) that bundles its
4686
+ own React in its own `node_modules`. Fix the install first, if you can:
4660
4687
 
4661
4688
  -
4662
4689
  Make React a **peer dependency** of the linked package (not a regular
@@ -4672,6 +4699,70 @@ copy.
4672
4699
  Delete the linked package's nested `node_modules/react` after linking if your
4673
4700
  package manager duplicated it.
4674
4701
 
4702
+ #### When the duplicate can't be installed away: declare it single-copy
4703
+
4704
+ A `link:` dependency on a **separate checkout** is the case none of the
4705
+ above reaches. The linked package's files live in the other repo, so Node and Bun resolve their
4706
+ imports from _that_ repo's real path - and that repo's install owns its
4707
+ `node_modules`. Peer deps don't help (the copy is already installed there),
4708
+ `overrides` don't help (they govern your install, not theirs), and deleting the
4709
+ nested copy is undone by the next install in the sibling repo. Only a resolver can answer this:
4710
+ the walk has to be intercepted, not rearranged.
4711
+
4712
+ Declare the packages in your app's `package.json`. It is a **static**
4713
+ declaration on purpose - `nifra check` reads it without importing your config, so
4714
+ the preflight never executes app code:
4715
+
4716
+ ```ts
4717
+ {
4718
+ "name": "my-app",
4719
+ "dependencies": {
4720
+ "react": "19.2.8",
4721
+ "@example/ui": "link:../design-system/packages/ui"
4722
+ },
4723
+ "nifra": {
4724
+ "singleCopy": ["react", "react-dom", "@nifrajs/*"]
4725
+ }
4726
+ }
4727
+ ```
4728
+
4729
+ `"singleCopy": true` is shorthand for Nifra's built-in identity-sensitive set:
4730
+ `@nifrajs/*`, `react`, `react-dom`, `preact`,
4731
+ `solid-js`, `svelte`, `vue`. Entries may be exact names or a
4732
+ `@scope/*` pattern. `@nifrajs/*` belongs in this set for the same reason
4733
+ React does: two copies of `@nifrajs/core` are two distinct `Server`
4734
+ classes, so `.merge()` stops accepting an app built against the other one.
4735
+
4736
+ **The build honours the declaration on its own** -
4737
+ `buildClient` and `buildServer` inject the resolver, so bundled output
4738
+ already loads one copy. Unbundled phases do not: Bun's _runtime_ resolver never offers a
4739
+ bare specifier to a plugin, so `bun test`, `bun run`, and preloaded
4740
+ scripts still load both copies unless you preload the registrar:
4741
+
4742
+ ```ts
4743
+ # bunfig.toml
4744
+ preload = ["@nifrajs/core/single-copy/register"]
4745
+
4746
+ [test]
4747
+ preload = ["@nifrajs/core/single-copy/register"]
4748
+ ```
4749
+
4750
+ Both sections matter, and they are independent: `preload` covers
4751
+ `bun run`, `[test].preload` covers `bun test`. Declaring
4752
+ without preloading is a real state, not a mistake -
4753
+ `nifra check` reports it and names the phase left uncovered.
4754
+
4755
+ **What it will not do:** redirect across _versions_. If the two copies are
4756
+ `19.2.7` and `19.2.8`, the redirect is skipped and
4757
+ `nifra check` still fails with `version-skew`. Silently collapsing a
4758
+ version difference would trade a loud install problem for a quiet behavioural one; align the
4759
+ ranges instead.
4760
+
4761
+ A declared duplicate is **reported, not suppressed**.
4762
+ `nifra check` keeps printing the copies as a warning and
4763
+ `nifra doctor` lists them under "deduplicated by declaration", so the topology stays
4764
+ visible - it just stops being fatal.
4765
+
4675
4766
  [!NOTE]
4676
4767
 
4677
4768
  This applies to every framework with module-global render state, not just React (Preact,
@@ -5339,9 +5430,9 @@ export const hydrate = false // opt out of full-document hydration (static / i
5339
5430
  export default function User(props) { /* props.data, props.actionData, props.params */ }
5340
5431
  ```
5341
5432
 
5342
- [!NOTE] An `action` may return a `Response` (a `redirect()`,
5343
- say) - it's passed through untouched. Any other return is serialized as
5344
- `actionData`.
5433
+ [!NOTE] An `action` may return a control-flow value - a `redirect()` or a
5434
+ `status(...)` render, or a hand-rolled `Response` - and it's passed
5435
+ through untouched. Any other return is serialized as `actionData`.
5345
5436
 
5346
5437
  ### Two ways to handle a request
5347
5438
 
@@ -6434,6 +6525,94 @@ If capabilities are configured, review a new snapshot and run
6434
6525
  Exercise authentication, redirects, backend mounting, SSE/WebSocket routes, and each deploy
6435
6526
  adapter the application uses.
6436
6527
 
6528
+ ## Nifra 3.0 migration guide - upgrade from 2.x
6529
+
6530
+ > Upgrade a Nifra 2.x application to 3.0: automated dependency pins, the plain-data redirect return value, the frozen reserved typed-client segment keys and their codemod, the Node proxy transport default, and the release gates.
6531
+
6532
+ Nifra 3.0 makes a redirect the same plain-data value an ordinary return is, and freezes the
6533
+ reserved typed-client segment keys into a published contract. The upgrade command handles the
6534
+ deterministic dependency pins; this guide covers the two structural changes it deliberately
6535
+ cannot guess, plus the Node proxy transport default.
6536
+
6537
+ ### 1. Run the executable upgrade
6538
+
6539
+ ```ts
6540
+
6541
+ ```
6542
+
6543
+ The command pins every matching `@nifrajs/*`, `Nifra`, and
6544
+ `create-nifra` dependency to `3.0.0` while preserving
6545
+ caret/tilde/exact style. No package is removed and no import specifier moves this release, so
6546
+ the runner only pins. Dry-run is the default; `--write` applies and verifies with
6547
+ `nifra check`.
6548
+
6549
+ ### 2. Read redirects as plain data
6550
+
6551
+ `redirect(...)` now returns the same plain render value `status(...)`
6552
+ produces, not a Web `Response`. Same bytes on the wire, now with a
6553
+ `content-length` on Node instead of a chunked empty body. It is still returned or
6554
+ thrown from the same places - loader, action, layout gate - and `return redirect()`
6555
+ / `throw redirect()` stay interchangeable, including the client-submit conversion to
6556
+ a 204 with `X-Nifra-Redirect`.
6557
+
6558
+ **Breaking:** the value is no longer a `Response`, so
6559
+ `.status`, `.headers`, and `instanceof Response` are gone from
6560
+ it. Read `.plain`, build one with `toResponse()` when needed, pass headers
6561
+ through the second argument, and assert on `.plain` in tests. A hand-rolled
6562
+ `Response` from a loader or action is untouched - only what `redirect()`
6563
+ itself returns changed.
6564
+
6565
+ ```ts
6566
+
6567
+ ```
6568
+
6569
+ ### 3. Fix reserved typed-client segments
6570
+
6571
+ The reserved proxy keys are now a frozen, published contract. A route whose static path
6572
+ segment spells one of them - an HTTP verb in any casing, or `subscribe`,
6573
+ `ws`, `index`, `then` - is unreachable by property access,
6574
+ because the proxy resolves the reserved key before a path segment. The types now reject that
6575
+ access at compile time instead of silently reaching the wrong node.
6576
+
6577
+ ```ts
6578
+
6579
+ ```
6580
+
6581
+ Run `nifra fix --code NF-C018` to rewrite the affected call sites. It reads them
6582
+ from the compiler rather than a text search, so it finds every one and never mistakes a real
6583
+ `.delete` verb call for a path segment; a site it cannot rewrite confidently
6584
+ (bracket access, a node held in a variable) is reported and left alone.
6585
+ `nifra routes` annotates a colliding route with the spelling that reaches it, in
6586
+ both the table and `--json`. The set is exported as the one place it is written down.
6587
+
6588
+ ```ts
6589
+
6590
+ ```
6591
+
6592
+ ### 4. Review the Node proxy transport (only if you use it)
6593
+
6594
+ On Node, `@nifrajs/proxy` now defaults to the undici transport, shipped from the new
6595
+ `@nifrajs/proxy/undici` subpath. Pass an explicit `transport` to
6596
+ `createProxy` to override it.
6597
+
6598
+ ```ts
6599
+
6600
+ ```
6601
+
6602
+ ### 5. Run the release gates
6603
+
6604
+ -
6605
+ Run `nifra check --json` and fix every error, including any remaining NF-C018.
6606
+
6607
+ -
6608
+ If the project has `nifra.assurance.ts`, run `nifra assure --json`.
6609
+
6610
+ - Run the application test suite and a production build.
6611
+
6612
+ -
6613
+ Exercise every redirect path - loader, action, and layout gate - and, if the app proxies,
6614
+ each deploy adapter it uses.
6615
+
6437
6616
  ## Nifra - Migrating from Express, Hono, Fastify & Elysia
6438
6617
 
6439
6618
  > 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.
@@ -7691,8 +7870,8 @@ app.get("/me", async (c) => {
7691
7870
 
7692
7871
  ## Protect a route
7693
7872
 
7694
- `requireSession(auth, request, options?)` returns the non-null session or **throws a `Response`** -
7695
- nifra returns a thrown `Response` as-is, short-circuiting the handler:
7873
+ `requireSession(auth, request, options?)` returns the non-null session or **throws a Nifra `ResponseResult`** -
7874
+ nifra handles the thrown `ResponseResult` as control flow, short-circuiting the handler:
7696
7875
 
7697
7876
  ```ts
7698
7877
  import { requireSession } from "@nifrajs/better-auth"
@@ -7716,7 +7895,7 @@ export const loader = async ({ request }) => {
7716
7895
  | --- | --- |
7717
7896
  | `betterAuth(auth, options?)` | Plugin that mounts better-auth's handler at `${basePath}/*` for GET + POST. |
7718
7897
  | `getSession(auth, request)` | `Promise<SessionOf<A> \| null>` - typed wrapper over `auth.api.getSession`. |
7719
- | `requireSession(auth, request, options?)` | Returns the session or throws a `Response` (302 `redirectTo` / 401). |
7898
+ | `requireSession(auth, request, options?)` | Returns the session or throws a Nifra `ResponseResult` (302 `redirectTo` / 401). |
7720
7899
  | `SessionOf<A>` | The non-null session payload type inferred from your `auth`. |
7721
7900
  | `BetterAuthLike` | The structural contract a better-auth instance satisfies. |
7722
7901
 
@@ -8013,6 +8192,31 @@ bun add @nifrajs/content
8013
8192
  - Reference: <https://nifra.dev/docs>
8014
8193
  - AI-readable: <https://nifra.dev/llms.txt>
8015
8194
 
8195
+ ## Typed indexes and joins
8196
+
8197
+ Build an equality lookup and deterministic query index from a baked collection. `where` is field
8198
+ equality, `range` supports typed string/number comparisons, sorting is stable (slug is the tie-breaker),
8199
+ and pages use an opaque cursor rather than an offset:
8200
+
8201
+ ```ts
8202
+ const posts = indexCollection(await bakeCollection(blog), {
8203
+ by: "section",
8204
+ sort: { field: "publishedAt", dir: "desc" },
8205
+ })
8206
+
8207
+ const page = posts.query({ where: { section: "news" }, limit: 20 })
8208
+ const next = page.nextCursor === undefined
8209
+ ? undefined
8210
+ : posts.query({ where: { section: "news" }, cursor: page.nextCursor, limit: 20 })
8211
+ ```
8212
+
8213
+ `posts.baked` is JSON-safe and can be serialized into an edge bundle; `fromBakedIndex` rehydrates it
8214
+ without filesystem access. `joinCollections(left, right, "id")` gives one-to-many rows by default.
8215
+ Pass `{ cardinality: "one-to-one" }` to require unique right-side keys; duplicate right keys fail the
8216
+ build loudly. The join key must exist on both frontmatter types with the same TypeScript value type.
8217
+ These APIs index only validated local/baked content; credentialed CMS ingestion and tenant data remain
8218
+ outside this public package.
8219
+
8016
8220
  MIT
8017
8221
 
8018
8222
  ## For AI agents
@@ -8069,6 +8273,10 @@ import { defineChannel, memoryChannelHub } from "@nifrajs/core/channel"
8069
8273
  - **Lifecycle middleware.** `derive`/`decorate` extend the typed context;
8070
8274
  `onRequest`/`beforeHandle`/`afterHandle`/`onResponse`/`onError` run around handlers;
8071
8275
  `use(middleware)` applies a bundle.
8276
+ - **Portable response observation.** The header/body/raw observer methods are opt-in so ordinary
8277
+ servers stay lean: add `responseObserver()` from `@nifrajs/core/response-observer` before calling
8278
+ `onResponseHeaders`, `onResponseBody`, or `onResponseRaw`. Official middleware that uses these
8279
+ tiers installs the compatibility runtime automatically.
8072
8280
  - **Hardening built in.** `stop({ drainMs })` graceful shutdown (+ opt-in SIGTERM/
8073
8281
  SIGINT), `requestTimeoutMs` (+ `ctx.signal` and `ctx.budget`), a streaming body-size cap, and a
8074
8282
  redacting structured `Logger`.
@@ -8283,6 +8491,98 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
8283
8491
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
8284
8492
  done-gate, or `nifra mcp` to give the agent live project tools.
8285
8493
 
8494
+ ## @nifrajs/edge
8495
+
8496
+ Compact fetch-handler server for edge and serverless runtimes - Cloudflare Workers, Vercel Edge, Deno Deploy, Bun. Keeps the `server().get().post()` DX and the full request trust boundary (bounded body read, Content-Length pre-reject, prototype-pollution guard, JSON / urlencoded framing) in a fraction of the bundle. Zero runtime dependencies beyond `@nifrajs/core`.
8497
+
8498
+ Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
8499
+
8500
+ ## Why
8501
+
8502
+ `@nifrajs/core`'s `server()` carries lifecycle hooks, cookies, response contracts, deadlines, WebSockets, and a plugin system. On an edge worker you often want none of that - just typed routing and a safe body. `@nifrajs/edge` is that subset: the same routing DX, the *same* body trust boundary (imported from core, never re-derived, so there is no second copy of the security contract to drift), and nothing else.
8503
+
8504
+ The rejection envelopes are byte-for-byte the full Server's - so an app can start on `@nifrajs/edge` and graduate to `@nifrajs/core`'s `server()` without its clients noticing.
8505
+
8506
+ ## Install
8507
+
8508
+ ```sh
8509
+ bun add @nifrajs/edge @nifrajs/core
8510
+ ```
8511
+
8512
+ ## Use
8513
+
8514
+ ```ts
8515
+ // worker.ts
8516
+ import { server, type StandardSchemaV1 } from "@nifrajs/edge"
8517
+
8518
+ const createUser: StandardSchemaV1<{ name: string; age: number }> = /* zod, valibot, arktype, ... */
8519
+
8520
+ const app = server()
8521
+ .get("/users/:id", (c) => ({ id: c.params.id }))
8522
+ .post("/users", { body: createUser }, (c) => ({ created: c.body.name, age: c.body.age }))
8523
+
8524
+ export default app // { fetch } - a module worker handler
8525
+ ```
8526
+
8527
+ `c.params` is typed from the route pattern. `c.body` is narrowed to the schema's output. Return a value (rendered as JSON) or a `Response` for full control. Throw a `Response` for an early exit; any other throw becomes a flat `500`.
8528
+
8529
+ ## Context
8530
+
8531
+ | Member | What |
8532
+ | --- | --- |
8533
+ | `c.request` | the raw `Request` |
8534
+ | `c.params` | path params, typed from the pattern (`/users/:id` -> `{ id: string }`) |
8535
+ | `c.body` | parsed, schema-validated body (`undefined` on a body-less route) |
8536
+ | `c.query()` | query string parsed to an object, on demand |
8537
+ | `c.header(name)` | a request header (case-insensitive), or `null` |
8538
+
8539
+ ## Options
8540
+
8541
+ ```ts
8542
+ server({ maxBodyBytes: 256_000, protoPoisoning: "strip" })
8543
+ ```
8544
+
8545
+ | Option | Default | What |
8546
+ | --- | --- | --- |
8547
+ | `maxBodyBytes` | `1_000_000` | body-size cap before a `413`, matching core |
8548
+ | `protoPoisoning` | `"reject"` | `__proto__` policy for JSON bodies: `"reject"` \| `"strip"` \| `"ignore"`, matching core |
8549
+
8550
+ ## Security
8551
+
8552
+ The body trust boundary is on by default and imported from `@nifrajs/core` - the same lane the full Server runs, so there is one implementation, never a trimmed copy that could drift. On every body-bearing route it enforces:
8553
+
8554
+ | Guard | Effect |
8555
+ | --- | --- |
8556
+ | Streaming byte cap | a length-less (chunked) body is drained under `maxBodyBytes`; once over, the stream is **cancelled** and rejected `413` - it is never buffered whole |
8557
+ | Content-Length pre-reject | a declared length over `maxBodyBytes` is `413` before a byte is read; a malformed length is `400` |
8558
+ | Prototype-pollution guard | an own `__proto__` key (or a poisoning-shaped `constructor.prototype`) in a JSON body is rejected (`"reject"`, default) or removed (`"strip"`) before it reaches your handler |
8559
+ | Media-type framing | JSON and urlencoded bodies are framed and capped; other content types get `415` |
8560
+
8561
+ Rejection envelopes are byte-for-byte the full Server's.
8562
+
8563
+ **Whether you need this depends on the runtime:**
8564
+
8565
+ - **Stream runtimes** (Cloudflare Workers, Deno Deploy, Bun): the runtime hands your worker a live request stream with no size ceiling below its own memory limit. A single length-less request into an unguarded `.json()` buffers unbounded and can cross the isolate memory limit (~128 MB on Workers) - one request, one killed worker (and on auto-scaling platforms, a metered bill). Here the byte cap is **load-bearing**, not optional.
8566
+ - **Pre-capped serverless** (Vercel Edge ~4.5 MB, AWS API Gateway 6-10 MB, GCP 10-32 MB, Azure ~30 MB default): the platform already bounds the body at its edge, so the crash case is largely its job. The cap still earns its place by tightening that platform ceiling to your app's real intent and returning a consistent `413`. (Platform limits are documented ceilings - check current docs for your plan.)
8567
+
8568
+ The prototype-pollution guard is **platform-independent**: no runtime guards `JSON.parse` for you, and a `__proto__`-bearing object becomes pollution the moment downstream code merges it. The guard stops it at ingress. Both defenses tune via [Options](#options).
8569
+
8570
+ ## Not included
8571
+
8572
+ By design: no lifecycle / `around` hooks, cookies, response contracts, deadlines, WebSockets, or plugins. Reach for `@nifrajs/core`'s `server()` when an app needs those.
8573
+
8574
+ ## License
8575
+
8576
+ MIT
8577
+
8578
+ ## For AI agents
8579
+
8580
+ Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
8581
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
8582
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
8583
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
8584
+ done-gate, or `nifra mcp` to give the agent live project tools.
8585
+
8286
8586
  ## @nifrajs/env
8287
8587
 
8288
8588
  Typed, validated environment variables. Define a schema once at startup; get a **frozen, typed**
@@ -8463,6 +8763,32 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
8463
8763
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
8464
8764
  done-gate, or `nifra mcp` to give the agent live project tools.
8465
8765
 
8766
+ ## @nifrajs/island-trigger
8767
+
8768
+ Framework-neutral, browser-side trigger scheduling shared by Nifra’s two island runtimes:
8769
+ `@nifrajs/web/islands` and `@nifrajs/islets`.
8770
+
8771
+ It has no DOM or UI-framework dependency. The public contract is the small `scheduleTrigger`
8772
+ primitive with these strategies:
8773
+
8774
+ ```ts
8775
+ import { scheduleTrigger } from "@nifrajs/island-trigger"
8776
+
8777
+ const dispose = scheduleTrigger({ media: "(min-width: 768px)" }, () => {
8778
+ // Activate the island.
8779
+ })
8780
+
8781
+ dispose() // cancel before the trigger fires
8782
+ ```
8783
+
8784
+ `load`, `idle`, `visible`, and media-query triggers are one-shot. Idle and visibility strategies
8785
+ fall back safely when the browser capability is unavailable; malformed or oversized media queries
8786
+ remain inert. The package only schedules the callback - each island runtime owns its own markers,
8787
+ registry, signals, and framework integration.
8788
+
8789
+ For AI agents, see [`LLM.md`](./LLM.md). The wider reference is in
8790
+ [`llms-full.txt`](../../llms-full.txt).
8791
+
8466
8792
  ## @nifrajs/islets
8467
8793
 
8468
8794
  Fine-grained signals + declarative DOM bindings for islands - **interactivity in ~1.4 KB gz**, no
@@ -8779,7 +9105,9 @@ Bun GET, byte-identical on the wire). They are defaults - anything the request p
8779
9105
  `securityHeaders` and the default `poweredBy` ship this way.
8780
9106
 
8781
9107
  A middleware whose response hook only reads/writes **headers** but needs the request should use the
8782
- portable `onResponseHeaders` hook - one implementation, fast on every runtime: it mutates the
9108
+ portable `onResponseHeaders` hook - enable the observer surface with
9109
+ `app.use(responseObserver())` from `@nifrajs/core/response-observer` before registering a custom
9110
+ hook. It remains one implementation, fast on every runtime: it mutates the
8783
9111
  response's own `Headers` on Bun/Deno and the outcome record on Node's direct socket writer, never
8784
9112
  materializing Web `Request`/`Response` objects. `cors` (origin reflection), static `cacheControl`,
8785
9113
  `language`, and `poweredBy({ respectExisting: false })` are built on it. Stateful middleware (`rateLimit` with the built-in key derivation,
@@ -8898,7 +9226,13 @@ authority. If neither option is set, treat the host in `request.url` as client-c
8898
9226
 
8899
9227
  The optional `static` root is served before `app.fetch` for GET and HEAD requests. It is a
8900
9228
  public asset directory and must contain nothing private: no secrets, source maps, build
8901
- metadata, or user uploads.
9229
+ metadata, or user uploads. The directory and its ancestor path must be read-only and owned by
9230
+ the service (or otherwise protected from untrusted local writers). Nifra resolves and confines
9231
+ the file before opening it and refuses symlinked final files, but Node does not expose a native
9232
+ per-component `openat` primitive; a process that can mutate the tree concurrently can still
9233
+ create a local TOCTOU race. Mutable or attacker-writable static trees are therefore outside the
9234
+ adapter's guarantee. Store user-controlled files outside the static root and serve them through
9235
+ an application or object-storage path with its own validation policy.
8902
9236
 
8903
9237
  ## Graceful shutdown on signals
8904
9238
 
@@ -9929,11 +10263,11 @@ done-gate, or `nifra mcp` to give the agent live project tools.
9929
10263
 
9930
10264
  ## @nifrajs/client
9931
10265
 
9932
- `ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `ClientRetryOptions`, `FetchFn`, `inProcessClient`, `InProcessClient`, `InProcessClientOptions`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `ResponseContractViolation`, `Result`, `SubscribeOptions`, `Subscription`, `testClient`, `Treaty`, `TreatyFromRegistry`, `WsCallOptions`, `WsHandle`
10266
+ `ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `ClientRetryOptions`, `FetchFn`, `inProcessClient`, `InProcessClient`, `InProcessClientOptions`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `RESERVED_EXACT_KEYS`, `RESERVED_KEY_READOUT`, `RESERVED_VERB_KEYS`, `reservedKeyFor`, `ResponseContractViolation`, `Result`, `SubscribeOptions`, `Subscription`, `testClient`, `Treaty`, `TreatyFromRegistry`, `WsCallOptions`, `WsHandle`
9933
10267
 
9934
10268
  ## @nifrajs/content
9935
10269
 
9936
- `bakeCollection`, `BakedCollection`, `Entry`, `fromBaked`, `InferSchema`, `parseEntry`, `ParseEntryOptions`, `parseFrontmatter`, `StandardSchemaV1`, `StaticCollection`
10270
+ `bakeCollection`, `BakedCollection`, `BakedCollectionIndex`, `BakedCollectionJoin`, `BakedIndexBucket`, `ContentFieldKey`, `Entry`, `fromBaked`, `fromBakedIndex`, `fromBakedJoin`, `indexCollection`, `IndexedCollection`, `IndexedJoin`, `IndexOptions`, `IndexPage`, `IndexQueryOptions`, `IndexRange`, `IndexRanges`, `IndexSort`, `IndexWhere`, `InferSchema`, `JoinCardinality`, `joinCollections`, `JoinedRow`, `parseEntry`, `ParseEntryOptions`, `parseFrontmatter`, `SharedKey`, `StandardSchemaV1`, `StaticCollection`
9937
10271
 
9938
10272
  ## @nifrajs/core
9939
10273
 
@@ -9951,6 +10285,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
9951
10285
 
9952
10286
  `devtools`, `DevToolsClientOptions`, `devtoolsClientScript`, `DevToolsEvent`, `DevToolsOptions`, `DevToolsQuery`, `filterDevToolsEvents`
9953
10287
 
10288
+ ## @nifrajs/edge
10289
+
10290
+ `EdgeContext`, `EdgeHandler`, `EdgeOptions`, `EdgeServer`, `Method`, `Params`, `QueryValue`, `server`, `StandardSchemaV1`, `toFetchHandler`
10291
+
9954
10292
  ## @nifrajs/env
9955
10293
 
9956
10294
  `boolean`, `defineEnv`, `DefineEnvOptions`, `enumValue`, `env`, `EnvResult`, `EnvShape`, `InferOutput`, `number`, `port`, `StandardResult`, `StandardSchemaV1`, `string`, `url`
@@ -9967,9 +10305,13 @@ done-gate, or `nifra mcp` to give the agent live project tools.
9967
10305
 
9968
10306
  `cloudflareLoader`, `CloudflareLoaderOptions`, `createPngRasterizer`, `createResvgRasterizer`, `HtmlImageAttrs`, `identityLoader`, `imageDimensions`, `ImageFormat`, `ImageInfo`, `ImageLoader`, `ImageProps`, `OgImageOptions`, `OgImageRasterized`, `OgImageRasterizer`, `ogImageResponse`, `PngRenderer`, `readImageDimensions`, `renderOgImage`, `ResolvedImage`, `resolveImage`, `ResvgConstructor`, `ResvgRenderer`, `selfHostedLoader`, `SelfHostedLoaderOptions`, `signImageUrl`, `SignImageUrlOptions`, `toHtmlAttrs`
9969
10307
 
10308
+ ## @nifrajs/island-trigger
10309
+
10310
+ `IslandStrategy`, `MAX_MEDIA_QUERY_LENGTH`, `scheduleTrigger`, `TriggerOptions`
10311
+
9970
10312
  ## @nifrajs/islets
9971
10313
 
9972
- `batch`, `BindableElement`, `BindableRoot`, `bindScope`, `computed`, `effect`, `island`, `IslandContext`, `IslandHost`, `IslandScope`, `IslandSetup`, `islandState`, `mountIslands`, `signal`, `Signal`
10314
+ `batch`, `BindableElement`, `BindableRoot`, `bindScope`, `computed`, `effect`, `island`, `IslandContext`, `IslandHost`, `IslandScope`, `IslandSetup`, `islandState`, `IslandStrategy`, `MountIslandOptions`, `mountIslands`, `signal`, `Signal`
9973
10315
 
9974
10316
  ## @nifrajs/jobs
9975
10317
 
@@ -10005,7 +10347,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
10005
10347
 
10006
10348
  ## @nifrajs/proxy
10007
10349
 
10008
- `createProxy`, `ProxyContext`, `ProxyHandler`, `ProxyOptions`
10350
+ `createProxy`, `fetchTransport`, `FetchTransportOptions`, `NativeProxyTransport`, `NativeProxyTransportRequest`, `NativeProxyUpstreamResponse`, `NodeNativeProxyRequest`, `NodeNativeProxyResponse`, `ProxyContext`, `ProxyHandler`, `ProxyOptions`, `ProxyTransport`, `ProxyUpstreamRequest`, `ProxyUpstreamResponse`
10009
10351
 
10010
10352
  ## @nifrajs/runner
10011
10353
 
@@ -10033,7 +10375,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
10033
10375
 
10034
10376
  ## @nifrajs/web
10035
10377
 
10036
- `Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `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`, `gate`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateRouteSearchTypes`, `GenerateRouteSearchTypesOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `getBrowserNavigate`, `GetStaticPaths`, `gone`, `hashQueryKey`, `IDLE_BLOCKER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaDescriptor`, `MetaInput`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `notFound`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `previewEndpoint`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `registerBlocker`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderedPage`, `renderPage`, `RenderPageInput`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `RETAIN_HEADER`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `RouteSearch`, `routeStyles`, `ScriptDescriptor`, `searchOf`, `SearchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `ServePublicDirOptions`, `SERVER_FN_MODULE`, `SERVER_ONLY_MODULE`, `ServerOnly`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `SsrModuleLoader`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `STATUS_HEADER`, `statusPage`, `StatusPageOptions`, `styles`, `Submission`, `SubmitOptions`, `unsafeInlineScript`, `UnsafeScriptDescriptor`, `withISR`
10378
+ `Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `assertStaticBoundaryImports`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `Boundary`, `BOUNDARY_GLOBAL`, `BoundaryDescriptor`, `boundaryDescriptors`, `BoundaryError`, `BoundaryMode`, `boundaryModeKey`, `BoundaryRegistration`, `BoundaryRequestCtx`, `BoundaryState`, `BoundaryStates`, `BoundaryStatus`, `BrowserNavigate`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `ClientAction`, `ClientActionArgs`, `ClientActionResult`, `clientEntry`, `ClientLoader`, `ClientLoaderArgs`, `ClientRequestBody`, `ClientRouteHooks`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createMutation`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `DehydratedState`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `DynamicBoundary`, `DynamicBoundaryBatch`, `enableDraft`, `EnableDraftOptions`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `gate`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateRouteSearchTypes`, `GenerateRouteSearchTypesOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `getBrowserNavigate`, `GetStaticPaths`, `gone`, `hashQueryKey`, `IDLE_BLOCKER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `InterceptBoundary`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `MemoryStaticBoundaryCache`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaDescriptor`, `MetaInput`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `notFound`, `openGraph`, `OpenGraphInput`, `PendingBoundary`, `PRE_HYDRATION_GUARD`, `previewEndpoint`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `registerBlocker`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderedPage`, `renderPage`, `RenderPageInput`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveDynamicBoundaries`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `resolveStaticBoundaries`, `RETAIN_HEADER`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `RouteSearch`, `routeStyles`, `ScriptDescriptor`, `searchOf`, `SearchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `ServePublicDirOptions`, `SERVER_FN_MODULE`, `SERVER_ONLY_MODULE`, `ServerOnly`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `SsrModuleLoader`, `startDynamicBoundaries`, `StaticBoundary`, `StaticBoundaryCache`, `StaticBoundaryImportEdge`, `StaticBoundaryRoot`, `StaticCtx`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `STATUS_HEADER`, `statusPage`, `StatusPageOptions`, `styles`, `Submission`, `SubmitOptions`, `unsafeInlineScript`, `UnsafeScriptDescriptor`, `withISR`
10037
10379
 
10038
10380
  ## @nifrajs/web-preact
10039
10381