@nifrajs/cli 4.0.2 → 5.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.
- package/README.md +3 -0
- package/dist/agent-files.d.ts +107 -0
- package/dist/agent-files.d.ts.map +1 -0
- package/dist/agent-files.js +162 -0
- package/dist/agent-files.js.map +1 -0
- package/dist/app-files.d.ts +6 -0
- package/dist/app-files.d.ts.map +1 -1
- package/dist/app-files.js +27 -2
- package/dist/app-files.js.map +1 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +7 -2
- package/dist/assure-hydration.js.map +1 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +13 -3
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check-diagnostics.d.ts +20 -2
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +54 -7
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +75 -22
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +1475 -310
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +2 -2
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +192 -65
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +112 -5
- package/dist/cli.js.map +1 -1
- package/dist/client-module.d.ts +18 -0
- package/dist/client-module.d.ts.map +1 -0
- package/dist/client-module.js +49 -0
- package/dist/client-module.js.map +1 -0
- package/dist/command-catalog.d.ts +16 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +133 -29
- package/dist/command-catalog.js.map +1 -1
- package/dist/component-check.d.ts +13 -0
- package/dist/component-check.d.ts.map +1 -0
- package/dist/component-check.js +59 -0
- package/dist/component-check.js.map +1 -0
- package/dist/contract-proof.d.ts +1 -1
- package/dist/contract-proof.d.ts.map +1 -1
- package/dist/contract-proof.js +2 -24
- package/dist/contract-proof.js.map +1 -1
- package/dist/contracts.d.ts +19 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +50 -3
- package/dist/contracts.js.map +1 -1
- package/dist/db-tool.d.ts.map +1 -1
- package/dist/db-tool.js +4 -3
- package/dist/db-tool.js.map +1 -1
- package/dist/dev-bun-config.d.ts +5 -1
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +44 -11
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/diagnostics.d.ts +3 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +9 -8
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +28 -24
- package/dist/diff-tool.js.map +1 -1
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +23 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +6 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +6 -0
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +21 -2
- package/dist/fix-recipes.js.map +1 -1
- package/dist/hand-built-manifest.d.ts +27 -0
- package/dist/hand-built-manifest.d.ts.map +1 -0
- package/dist/hand-built-manifest.js +124 -0
- package/dist/hand-built-manifest.js.map +1 -0
- package/dist/hooks-report.d.ts +23 -0
- package/dist/hooks-report.d.ts.map +1 -0
- package/dist/hooks-report.js +104 -0
- package/dist/hooks-report.js.map +1 -0
- package/dist/init-agents.d.ts +2 -2
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +17 -11
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/nifra-project.d.ts +41 -0
- package/dist/internal/nifra-project.d.ts.map +1 -0
- package/dist/internal/nifra-project.js +183 -0
- package/dist/internal/nifra-project.js.map +1 -0
- package/dist/internal/project-specifiers.d.ts +16 -0
- package/dist/internal/project-specifiers.d.ts.map +1 -0
- package/dist/internal/project-specifiers.js +396 -0
- package/dist/internal/project-specifiers.js.map +1 -0
- package/dist/internal/script-kind.d.ts.map +1 -1
- package/dist/internal/script-kind.js +2 -1
- package/dist/internal/script-kind.js.map +1 -1
- package/dist/internal/source-facts.d.ts +2 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +11 -0
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +1 -1
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +5 -3
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -6
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +55 -24
- package/dist/introspect.js.map +1 -1
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +93 -15
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +31 -8
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +37 -7
- package/dist/load.js.map +1 -1
- package/dist/mcp-context.d.ts +5 -5
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +2 -2
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-exec.d.ts +12 -4
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +50 -13
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-io.d.ts +17 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +40 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts.map +1 -1
- package/dist/mcp-isolate.js +3 -2
- package/dist/mcp-isolate.js.map +1 -1
- package/dist/mcp-project-child.js +1 -1
- package/dist/mcp-project-child.js.map +1 -1
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +50 -10
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-ws.d.ts.map +1 -1
- package/dist/mcp-ws.js +3 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -2
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +7 -0
- package/dist/migrate-layout.d.ts.map +1 -1
- package/dist/migrate-layout.js +778 -64
- package/dist/migrate-layout.js.map +1 -1
- package/dist/output-schemas.d.ts +94 -0
- package/dist/output-schemas.d.ts.map +1 -0
- package/dist/output-schemas.js +864 -0
- package/dist/output-schemas.js.map +1 -0
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +3 -2
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +15 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +1 -0
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/5.0.0.d.ts +11 -0
- package/dist/recipes/5.0.0.d.ts.map +1 -0
- package/dist/recipes/5.0.0.js +34 -0
- package/dist/recipes/5.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts +7 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +12 -3
- package/dist/replay.js.map +1 -1
- package/dist/review.d.ts +3 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +38 -7
- package/dist/review.js.map +1 -1
- package/dist/rules/codes.d.ts +10 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +10 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/comment-markers.d.ts +16 -7
- package/dist/rules/comment-markers.d.ts.map +1 -1
- package/dist/rules/comment-markers.js +58 -19
- package/dist/rules/comment-markers.js.map +1 -1
- package/dist/rules/contract-visibility.d.ts +3 -0
- package/dist/rules/contract-visibility.d.ts.map +1 -0
- package/dist/rules/contract-visibility.js +337 -0
- package/dist/rules/contract-visibility.js.map +1 -0
- package/dist/rules/data-guard.d.ts +7 -0
- package/dist/rules/data-guard.d.ts.map +1 -1
- package/dist/rules/data-guard.js +319 -37
- package/dist/rules/data-guard.js.map +1 -1
- package/dist/rules/layout.d.ts +4 -0
- package/dist/rules/layout.d.ts.map +1 -0
- package/dist/rules/layout.js +19 -0
- package/dist/rules/layout.js.map +1 -0
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +158 -43
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/pages.d.ts +24 -0
- package/dist/rules/pages.d.ts.map +1 -1
- package/dist/rules/pages.js +221 -3
- package/dist/rules/pages.js.map +1 -1
- package/dist/rules/routes.d.ts +6 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +111 -16
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.js +2 -2
- package/dist/rules/secrets.js.map +1 -1
- package/dist/rules/security.d.ts +12 -1
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +281 -68
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/zones.d.ts +13 -0
- package/dist/rules/zones.d.ts.map +1 -1
- package/dist/rules/zones.js +192 -40
- package/dist/rules/zones.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +6 -4
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +14 -5
- package/dist/sdk.js.map +1 -1
- package/dist/stylex-migrate.d.ts.map +1 -1
- package/dist/stylex-migrate.js +163 -7
- package/dist/stylex-migrate.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +12 -3
- package/dist/test-tool.js.map +1 -1
- package/dist/typecheck-coverage.d.ts +20 -0
- package/dist/typecheck-coverage.d.ts.map +1 -0
- package/dist/typecheck-coverage.js +129 -0
- package/dist/typecheck-coverage.js.map +1 -0
- package/dist/upgrade.d.ts +69 -0
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +420 -77
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +11 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/version-drift.d.ts +22 -0
- package/dist/version-drift.d.ts.map +1 -0
- package/dist/version-drift.js +42 -0
- package/dist/version-drift.js.map +1 -0
- package/dist/web-app-options.d.ts +1 -1
- package/dist/web-app-options.d.ts.map +1 -1
- package/dist/web-app-options.js +1 -1
- package/dist/web-app-options.js.map +1 -1
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +20 -8
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +54 -6
- package/docs/llms-full.txt +1539 -138
- package/docs/types.json +1456 -206
- package/package.json +11 -12
- package/src/agent-files.ts +203 -0
- package/src/app-files.ts +34 -4
- package/src/assure-hydration.ts +15 -2
- package/src/capabilities-tool.ts +15 -3
- package/src/check-diagnostics.ts +73 -9
- package/src/check-scan.ts +1773 -381
- package/src/check.ts +237 -76
- package/src/cli.ts +141 -5
- package/src/client-module.ts +61 -0
- package/src/command-catalog.ts +166 -37
- package/src/component-check.ts +79 -0
- package/src/contract-proof.ts +3 -25
- package/src/contracts.ts +60 -3
- package/src/db-tool.ts +5 -2
- package/src/dev-bun-config.ts +51 -10
- package/src/diagnostics.ts +3 -0
- package/src/diff-tool.ts +33 -26
- package/src/doctor.ts +43 -21
- package/src/env-file.ts +7 -0
- package/src/fix-recipes.ts +25 -2
- package/src/hand-built-manifest.ts +144 -0
- package/src/hooks-report.ts +135 -0
- package/src/init-agents.ts +17 -11
- package/src/internal/nifra-project.ts +191 -0
- package/src/internal/project-specifiers.ts +424 -0
- package/src/internal/script-kind.ts +2 -1
- package/src/internal/source-facts.ts +25 -0
- package/src/internal/typescript-import.ts +10 -3
- package/src/introspect.ts +65 -27
- package/src/levels-tool.ts +117 -20
- package/src/load.ts +78 -14
- package/src/mcp-context.ts +7 -7
- package/src/mcp-exec.ts +60 -17
- package/src/mcp-http.ts +1 -1
- package/src/mcp-io.ts +41 -0
- package/src/mcp-isolate.ts +9 -2
- package/src/mcp-project-child.ts +1 -1
- package/src/mcp-root.ts +58 -8
- package/src/mcp-ws.ts +4 -2
- package/src/mcp.ts +8 -3
- package/src/migrate-layout.ts +885 -63
- package/src/output-schemas.ts +1084 -0
- package/src/port.ts +3 -2
- package/src/project-facts.ts +17 -3
- package/src/recipes/5.0.0.ts +35 -0
- package/src/recipes/index.ts +10 -0
- package/src/replay.ts +14 -3
- package/src/review.ts +39 -10
- package/src/rules/codes.ts +10 -0
- package/src/rules/comment-markers.ts +57 -21
- package/src/rules/contract-visibility.ts +383 -0
- package/src/rules/data-guard.ts +351 -42
- package/src/rules/layout.ts +22 -0
- package/src/rules/legacy.ts +184 -51
- package/src/rules/pages.ts +238 -3
- package/src/rules/routes.ts +124 -16
- package/src/rules/secrets.ts +2 -2
- package/src/rules/security.ts +352 -71
- package/src/rules/zones.ts +205 -38
- package/src/scaffold.ts +6 -4
- package/src/sdk.ts +14 -5
- package/src/stylex-migrate.ts +134 -7
- package/src/test-tool.ts +18 -3
- package/src/typecheck-coverage.ts +134 -0
- package/src/upgrade.ts +487 -71
- package/src/verification-plan.ts +24 -0
- package/src/version-drift.ts +67 -0
- package/src/web-app-options.ts +2 -2
- package/src/work-graph.ts +17 -12
package/docs/llms-full.txt
CHANGED
|
@@ -12,6 +12,7 @@ nifra is a Bun-native, contract-first, framework-agnostic full-stack TypeScript
|
|
|
12
12
|
- **Secure by default.** Body-size cap, `requestTimeoutMs` + `c.signal`, graceful shutdown, redacting logger, same-origin `redirect()`, constant-time secret comparison, fail-closed middleware.
|
|
13
13
|
- **Money** in integer minor units; **time** parsed to absolute UTC at the boundary.
|
|
14
14
|
- Throwing a `Response` anywhere in the lifecycle is control flow (returned as-is), not an error.
|
|
15
|
+
Optional server components (post-4.0): React, Vue, Svelte and Solid share owner-GET admission, closed input/public-props schemas and private/no-store output. React uses native RSC/Flight. Use *.component.server.ts(x), defineServerComponent, an explicit componentPipeline named config export, native outlets and bindComponentRouter. Await invalidateComponentScope on identity/tenant/permission changes. Component GETs cannot acquire domain write privileges; use validated serverFn/actions for mutations. Native use-server actions are unsupported. Pin the certified compiler tuple documented in /docs/server-components.
|
|
15
16
|
|
|
16
17
|
---
|
|
17
18
|
|
|
@@ -212,24 +213,19 @@ The data model (loaders, actions, `defer`, progressive enhancement, fetchers,
|
|
|
212
213
|
revalidation) is closest to **Remix / React Router** - delivered across five UI libraries instead
|
|
213
214
|
of one.
|
|
214
215
|
|
|
215
|
-
### Server code
|
|
216
|
-
|
|
217
|
-
Nifra
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Svelte, and Preact can't be built on it. (Nuxt, SvelteKit, and Remix don't ship RSC either -
|
|
229
|
-
this is _Nifra vs. Next App Router specifically_.) So the call is simple: if your app is
|
|
230
|
-
architected around RSC itself, **Next.js App Router is the right tool**. For everything RSC
|
|
231
|
-
is usually reached for - server-side data, secrets off the client, less JS - loaders,
|
|
232
|
-
`defer()`, islands, and server functions cover it here.
|
|
216
|
+
### Server code and server-only views
|
|
217
|
+
|
|
218
|
+
Nifra provides typed loaders and actions, streaming SSR, islands and typed
|
|
219
|
+
[server functions](/docs/server-functions). Server functions keep their
|
|
220
|
+
implementation, secrets and database handles off the browser and use the same validated
|
|
221
|
+
mutation API across the supported UI libraries.
|
|
222
|
+
|
|
223
|
+
The optional [server-components pipeline](/docs/server-components) adds
|
|
224
|
+
server-only views and state-preserving refresh. React uses native RSC/Flight; Vue, Svelte
|
|
225
|
+
and Solid use their native islands in validated server-rendered ranges. The feature is
|
|
226
|
+
intended for the release after 4.0.0 and requires all four adapters to pass certification.
|
|
227
|
+
Server functions remain the mutation API; native React `"use server"` actions
|
|
228
|
+
are unsupported.
|
|
233
229
|
|
|
234
230
|
### Also a standalone backend (vs. Hono & Elysia)
|
|
235
231
|
|
|
@@ -347,10 +343,10 @@ version-checked examples, which no other framework ships.
|
|
|
347
343
|
**To start lean and grow** - begin as a typed API, graduate to full-stack on the same core,
|
|
348
344
|
no rewrite.
|
|
349
345
|
|
|
350
|
-
|
|
351
|
-
[server functions](/docs/server-functions)
|
|
352
|
-
|
|
353
|
-
|
|
346
|
+
Nifra provides streaming SSR, islands and typed
|
|
347
|
+
[server functions](/docs/server-functions). The optional
|
|
348
|
+
[server-components pipeline](/docs/server-components) adds native React
|
|
349
|
+
RSC/Flight and corresponding Vue, Svelte and Solid views under a shared execution contract.
|
|
354
350
|
|
|
355
351
|
## Nifra - Routing
|
|
356
352
|
|
|
@@ -396,7 +392,8 @@ reaches the browser - see [Project structure](/docs/structure).
|
|
|
396
392
|
|
|
397
393
|
-
|
|
398
394
|
`_404.tsx` renders unmatched paths, and whatever a loader answers with
|
|
399
|
-
`notFound()`. At the routes root it renders
|
|
395
|
+
`notFound()`. At the routes root it renders inside the root layout, as below. In
|
|
396
|
+
a directory below (
|
|
400
397
|
`admin/_404.tsx`) it answers for that part of the app: the unmatched URLs under
|
|
401
398
|
`/admin` and `notFound()` from the routes beneath it, the nearest one
|
|
402
399
|
winning, rendered inside the layouts at or above it with their loader data. Those layouts run
|
|
@@ -408,6 +405,21 @@ with a `_404` at one URL prefix are a boot error unless a directory containing
|
|
|
408
405
|
both has one too. `gone()`, `statusPage()` and the
|
|
409
406
|
`_410.tsx`-style pages stay at the root, as does the 404 of a static export.
|
|
410
407
|
|
|
408
|
+
-
|
|
409
|
+
A root status page (`_404.tsx`, `_503.tsx`, ...) or an
|
|
410
|
+
`_error.tsx` renders inside the layouts above it with their loader data, and the
|
|
411
|
+
server puts its `meta` in the head. For a request no route ran (an unmatched
|
|
412
|
+
URL) the root middleware and layout loaders run first; otherwise the page reuses the data
|
|
413
|
+
the request already loaded. When those layouts cannot render it - a loader throws, a
|
|
414
|
+
`gate` or the middleware refuses - a root status page renders alone and an
|
|
415
|
+
`_error` inside its layouts without their data, with the same status, so a
|
|
416
|
+
status page never fails into a second error. The response is
|
|
417
|
+
`private, no-store` once a layout loaded data, and a navigation that lands on
|
|
418
|
+
one loads the URL as a document. `export const inLayouts = false` always renders
|
|
419
|
+
the page that way and runs no layout loader for it. A root status page that wraps the root
|
|
420
|
+
layout itself would render it twice; `nifra check` reports one as
|
|
421
|
+
`NF-C034`.
|
|
422
|
+
|
|
411
423
|
-
|
|
412
424
|
`_loading.tsx` is what the page slot shows while a client navigation loads. A
|
|
413
425
|
navigation still waiting on its data after about 120 ms swaps the page for the target
|
|
@@ -429,8 +441,9 @@ renders in its place, wrapped by the layouts at/above that segment, at status 50
|
|
|
429
441
|
non-hydrated). On the **client** - a render error during navigation/interaction is caught by
|
|
430
442
|
the nearest boundary, which renders `_error` in place (all five adapters). It
|
|
431
443
|
receives the serialized error as `{ data: { name, message } }` (never the
|
|
432
|
-
stack; on the server
|
|
433
|
-
|
|
444
|
+
stack; on the server, a generic `Internal Server Error` rather than the error's
|
|
445
|
+
own text unless `NODE_ENV` is `development` or `test`); a
|
|
446
|
+
thrown control-flow value (e.g. a guard
|
|
434
447
|
`redirect`) passes through.
|
|
435
448
|
|
|
436
449
|
-
|
|
@@ -467,6 +480,13 @@ Each page default-exports a component; an optional `meta` export drives
|
|
|
467
480
|
`loader` in its `.backend.ts` half - see
|
|
468
481
|
[Loaders & actions](/docs/data).
|
|
469
482
|
|
|
483
|
+
A layout's `meta` receives its own loader's data as `data`, the value its
|
|
484
|
+
component renders, or `null` when the layout has no loader. The page's data is
|
|
485
|
+
`pageData`, for a layout title template. A layout that loads the locale can set
|
|
486
|
+
`lang` from its data, and a client navigation keeps it. `nifra check`
|
|
487
|
+
reports a layout with no loader whose `meta` reads `data` as
|
|
488
|
+
`NF-C040`.
|
|
489
|
+
|
|
470
490
|
```ts
|
|
471
491
|
// routes/users/[id].tsx
|
|
472
492
|
import type { Route } from "./+types/[id]"
|
|
@@ -855,7 +875,7 @@ answers `422`.
|
|
|
855
875
|
|
|
856
876
|
Other param modifiers (`:id+`, `:id*`, `:id(\\d+)`) are not
|
|
857
877
|
part of the path grammar and match as literal text. Where that text is intended, put
|
|
858
|
-
`// nifra-expect
|
|
878
|
+
`// nifra-expect NF-C026: ` above the registration.
|
|
859
879
|
|
|
860
880
|
### Param constraints
|
|
861
881
|
|
|
@@ -933,7 +953,7 @@ not look for another split.
|
|
|
933
953
|
`/users/:id{[0-9]+}` serve no request in common, so `nifra check`
|
|
934
954
|
(`NF-C024`) has nothing to report. A constrained route beside a bare
|
|
935
955
|
`:param` route still overlaps and is still reported; mark the pair with
|
|
936
|
-
`// nifra-expect
|
|
956
|
+
`// nifra-expect NF-C024: ` where it is intended.
|
|
937
957
|
|
|
938
958
|
-
|
|
939
959
|
**OpenAPI and the typed client use the bare name.** The path is
|
|
@@ -993,9 +1013,11 @@ in the same call keep both. Call a custom one with
|
|
|
993
1013
|
`fetch(url, { method: "PURGE" })`.
|
|
994
1014
|
|
|
995
1015
|
-
|
|
996
|
-
**An assurance
|
|
997
|
-
never matches a custom-method route
|
|
998
|
-
|
|
1016
|
+
**An assurance rule names standard methods only.** A rule with `methods`
|
|
1017
|
+
never matches a custom-method route; one with `exceptMethods` does, unless the
|
|
1018
|
+
route is under a method it lists. `securityBaseline()` holds a custom method to
|
|
1019
|
+
its mutation rules, CSRF included, and `csrf()` publishes its evidence for one.
|
|
1020
|
+
Left unmatched, a route is reported as `unclassified-route`.
|
|
999
1021
|
|
|
1000
1022
|
Whether a custom method reaches the app is up to the runtime's HTTP parser.
|
|
1001
1023
|
`PROPFIND`, `REPORT`, `PURGE` and `QUERY` arrive on
|
|
@@ -1033,6 +1055,14 @@ return makes the typed client infer `data: never`, so you silently lose drift
|
|
|
1033
1055
|
detection for that route (`nifra check` flags it). `c.set` keeps your
|
|
1034
1056
|
plain-object return fully typed.
|
|
1035
1057
|
|
|
1058
|
+
A `Response` a handler or hook returns or throws still gets the
|
|
1059
|
+
`c.set.headers` it lacks and the queued cookies, so a guard's
|
|
1060
|
+
`cache-control: no-store` reaches it. Its own headers win, `content-type`, `content-length`,
|
|
1061
|
+
`content-encoding`, `content-range` and `transfer-encoding`
|
|
1062
|
+
come from it alone, and `c.set.status` does not change its status. A
|
|
1063
|
+
`Response` shared across requests is copied first, so one request's headers never
|
|
1064
|
+
reach another.
|
|
1065
|
+
|
|
1036
1066
|
When you genuinely want a `Response` - an **error short-circuit** from a
|
|
1037
1067
|
`derive` / `beforeHandle` (auth, rate limits) - `c.json(body, status?)`
|
|
1038
1068
|
and `c.text(body, status?)` build one in a line:
|
|
@@ -1046,6 +1076,42 @@ The request is on `c.req`, also available as `c.request` - the same name a
|
|
|
1046
1076
|
page loader/action receives (which in turn also accepts `ctx.req`), so one name works
|
|
1047
1077
|
in both places.
|
|
1048
1078
|
|
|
1079
|
+
### Typed error responses
|
|
1080
|
+
|
|
1081
|
+
Declare a route's error bodies in `errors`, keyed by status, and answer with
|
|
1082
|
+
`status(code, body, { headers })` from `@nifrajs/core/server`. It
|
|
1083
|
+
ends the request from a handler or any hook, returned or thrown, without building a
|
|
1084
|
+
`Response`: the body is serialized like a plain return, the headers ride with it,
|
|
1085
|
+
and queued cookies still apply. On the typed client, checking `res.status === 409`
|
|
1086
|
+
narrows `res.data` to the declared 409 body; a status the route does not declare
|
|
1087
|
+
falls into one arm whose `data` is `unknown`.
|
|
1088
|
+
|
|
1089
|
+
```ts
|
|
1090
|
+
import { client } from "@nifrajs/client"
|
|
1091
|
+
import { server, status } from "@nifrajs/core/server"
|
|
1092
|
+
import { t } from "@nifrajs/schema"
|
|
1093
|
+
|
|
1094
|
+
const conflict = t.object({ code: t.literal("conflict"), retryAfter: t.number() })
|
|
1095
|
+
const locked = new Set(["7"])
|
|
1096
|
+
|
|
1097
|
+
export const app = server().post("/orders/:id/pay", { errors: { 409: conflict } }, (c) => {
|
|
1098
|
+
if (locked.has(c.params.id))
|
|
1099
|
+
// The body is the typed contract; the header serves proxies and plain HTTP clients.
|
|
1100
|
+
return status(409, { code: "conflict", retryAfter: 5 }, { headers: { "retry-after": "5" } })
|
|
1101
|
+
return { paid: true }
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
const api = client<typeof app>("https://api.example.com")
|
|
1105
|
+
const res = await api.orders({ id: "7" }).pay.post()
|
|
1106
|
+
if (!res.ok && res.status === 409) {
|
|
1107
|
+
const wait: number = res.data.retryAfter // narrowed to the declared 409 body
|
|
1108
|
+
}
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
The client's `Result` carries the body, not the response headers. A value the
|
|
1112
|
+
caller acts on, such as how long to wait, belongs in the declared body; set the matching
|
|
1113
|
+
header too when proxies or plain HTTP clients read it.
|
|
1114
|
+
|
|
1049
1115
|
### Typed request cookies (schema.cookies)
|
|
1050
1116
|
|
|
1051
1117
|
`c.cookies` is the request's `Cookie` header as a name-to-value record,
|
|
@@ -1227,7 +1293,7 @@ same call params use, accepting exactly the colliding segment names. The client
|
|
|
1227
1293
|
the dot access at compile time with that guidance, and `nifra check` reports the
|
|
1228
1294
|
collision (`NF-C018`, advisory). Prefer a verb-free segment
|
|
1229
1295
|
(e.g. `/api/remove`) when you control the path; mark a route served only to
|
|
1230
|
-
non-typed-client consumers with `// nifra-expect
|
|
1296
|
+
non-typed-client consumers with `// nifra-expect NF-C018: ` above its
|
|
1231
1297
|
registration.
|
|
1232
1298
|
|
|
1233
1299
|
### Results never throw
|
|
@@ -1253,9 +1319,11 @@ if (res.ok) {
|
|
|
1253
1319
|
const { data, error } = await api.users.post({ name: "" }) // 422 if the schema rejects it
|
|
1254
1320
|
```
|
|
1255
1321
|
|
|
1256
|
-
A call that gets no HTTP answer has `status: 0` and one of
|
|
1257
|
-
`network_error`, `timeout`, `response_too_large`,
|
|
1258
|
-
`
|
|
1322
|
+
A call that gets no HTTP answer it can read has `status: 0` and one of five
|
|
1323
|
+
codes: `network_error`, `timeout`, `response_too_large`,
|
|
1324
|
+
`malformed_response`, or `invalid_path`.
|
|
1325
|
+
`malformed_response` means the body did not decode as its content type says - a
|
|
1326
|
+
proxy's HTML error page labelled JSON, say. `invalid_path` means a param value was `.`
|
|
1259
1327
|
or `..`: a URL reads those as steps to another path, in every encoding, so the
|
|
1260
1328
|
client sends nothing rather than reach a route the call does not name.
|
|
1261
1329
|
`.subscribe()` reports it through `onError` and closes;
|
|
@@ -1279,8 +1347,11 @@ the client never throws, it returns `{ data, error }`.
|
|
|
1279
1347
|
A route's `loader` runs on the server and calls your backend in-process during
|
|
1280
1348
|
SSR - no network hop. Its `loaderOutput` schema is the contract with the page:
|
|
1281
1349
|
the page receives that shape as `data`, and nothing else the loader returns
|
|
1282
|
-
leaves the server.
|
|
1283
|
-
|
|
1350
|
+
leaves the server. Declare a timestamp as `t.date()` and the page gets a
|
|
1351
|
+
`Date` in the server render and in the browser alike; under
|
|
1352
|
+
`t.string()` it travels as its ISO string. `t.json()` declares a
|
|
1353
|
+
field that is any JSON value, passed through whole - a tool-call payload or a settings blob
|
|
1354
|
+
the page does not pick apart. A loader without a schema is flagged by
|
|
1284
1355
|
`nifra check`. See
|
|
1285
1356
|
[Project structure](/docs/structure) for the route pair.
|
|
1286
1357
|
|
|
@@ -1294,7 +1365,8 @@ import type { Route } from "./+types/[id]"
|
|
|
1294
1365
|
// The page receives exactly this shape. Anything else the loader returns stays on the server.
|
|
1295
1366
|
export const loaderOutput = t.object({ id: t.string(), name: t.string() })
|
|
1296
1367
|
|
|
1297
|
-
|
|
1368
|
+
// Route.Loader checks what this returns against loaderOutput at compile time.
|
|
1369
|
+
export const loader: Route.Loader = async ({ params, api }) => {
|
|
1298
1370
|
const res = await api.users({ id: params.id }).get()
|
|
1299
1371
|
if (!res.ok) throw notFound()
|
|
1300
1372
|
return res.data
|
|
@@ -1310,6 +1382,30 @@ export default function User({ data }: Route.ComponentProps) {
|
|
|
1310
1382
|
}
|
|
1311
1383
|
```
|
|
1312
1384
|
|
|
1385
|
+
Annotating the loader with `Route.Loader` from the generated
|
|
1386
|
+
`./+types/<name>` checks what it returns against `loaderOutput`
|
|
1387
|
+
at compile time: a field of the wrong type, a `defer(...)` whose value does not
|
|
1388
|
+
match its `t.deferred(...)` field, or any data from a loader with no
|
|
1389
|
+
`loaderOutput` is a type error. A redirect, a `Response` and returning
|
|
1390
|
+
nothing stay allowed. `Route.Action` does the same against
|
|
1391
|
+
`actionOutput`, `revalidate(paths, data)` included. A field the schema
|
|
1392
|
+
does not declare is not a type error: the guard drops it before it ships. The annotation
|
|
1393
|
+
makes the loader's type the schema's, so `nifra schemas` cannot read what an
|
|
1394
|
+
annotated loader returns. It reports a generated schema over one as unchecked: annotate a
|
|
1395
|
+
loader whose schema you write, and leave a loader whose schema is generated unannotated.
|
|
1396
|
+
|
|
1397
|
+
`nifra schemas` drafts the schemas from your code for review: it reads each
|
|
1398
|
+
loader's and action's TypeScript return type, and with `--write` puts a schema
|
|
1399
|
+
above every one that has none. It leaves out a field named like a credential and anything
|
|
1400
|
+
that is not JSON, says so in a comment, and marks what it wrote with a
|
|
1401
|
+
`nifra:generated` line. While that line stays, a later
|
|
1402
|
+
`nifra schemas --write` keeps the schema in step with the return type, and
|
|
1403
|
+
`nifra check` fails (NF-C033) when the two have drifted apart, or when the schema
|
|
1404
|
+
under the line was edited (reformatting it is not an edit); delete the line to own the
|
|
1405
|
+
schema by hand. A hand-written schema is never rewritten: the report lists the
|
|
1406
|
+
returned fields it drops, by path. Nothing is derived at request time - the schema in the
|
|
1407
|
+
file is the one the guard enforces.
|
|
1408
|
+
|
|
1313
1409
|
A field named like a credential (`password`, `token`,
|
|
1314
1410
|
`apiKey`) fails the route when it loads unless its schema is wrapped in
|
|
1315
1411
|
`t.declassified("why it may reach the browser", ...)`; a schema library that
|
|
@@ -1333,7 +1429,7 @@ import type { Route } from "./+types/new"
|
|
|
1333
1429
|
|
|
1334
1430
|
export const actionOutput = t.object({ ok: t.boolean() })
|
|
1335
1431
|
|
|
1336
|
-
export async
|
|
1432
|
+
export const action: Route.Action = async ({ api, request }) => {
|
|
1337
1433
|
const form = await request.formData()
|
|
1338
1434
|
await api.users.post({ name: String(form.get("name")) })
|
|
1339
1435
|
return { ok: true } // the page's loaders revalidate automatically
|
|
@@ -1608,6 +1704,10 @@ Nothing here touches the kernel or the request path. An app that mounts no serve
|
|
|
1608
1704
|
pays exactly nothing for the feature - which is the reason it is built as a plugin over the
|
|
1609
1705
|
public registration API rather than as a bespoke dispatcher.
|
|
1610
1706
|
|
|
1707
|
+
## Nifra - Server components
|
|
1708
|
+
|
|
1709
|
+
> Server-only views with schema-guarded props, owner-route authorization and native React, Vue, Svelte, Solid and Preact hydration.
|
|
1710
|
+
|
|
1611
1711
|
## Nifra - Optimistic UI & fetchers
|
|
1612
1712
|
|
|
1613
1713
|
> Optimistic updates and concurrent fetchers in Nifra: instant feedback, row-level mutations.
|
|
@@ -1838,7 +1938,9 @@ wouldn't propagate across instances. In production use a shared, durable store.
|
|
|
1838
1938
|
`KVCacheStore` wraps a Workers KV namespace; any backend that fits the small
|
|
1839
1939
|
`CacheStore` interface (Redis, the Cache API) works too. Every read is validated
|
|
1840
1940
|
before it's trusted, so a corrupt or version-skewed entry is treated as a miss, not served
|
|
1841
|
-
broken.
|
|
1941
|
+
broken. A store that also implements `fence()` keeps a purge that lands while a
|
|
1942
|
+
page renders from being undone when that render stores the page it built from the old data;
|
|
1943
|
+
both built-in stores do.
|
|
1842
1944
|
|
|
1843
1945
|
```ts
|
|
1844
1946
|
// doc-check: skip - Workers entry: `Env`/`ExecutionContext` globals + your `app` from server.ts.
|
|
@@ -1858,6 +1960,13 @@ export default {
|
|
|
1858
1960
|
}
|
|
1859
1961
|
```
|
|
1860
1962
|
|
|
1963
|
+
A store outage never fails a page. A read that throws, rejects, or outlives
|
|
1964
|
+
`storeTimeoutMs` is a miss and the page renders; a failed write still returns the
|
|
1965
|
+
rendered page; a failed background regeneration keeps the stale entry. Each failure goes to
|
|
1966
|
+
`onStoreError(error, { op })`, which by default logs one line per operation
|
|
1967
|
+
per minute without the cache key. Set `storeTimeoutMs` for a network store, so a
|
|
1968
|
+
hung connection costs a miss rather than the request.
|
|
1969
|
+
|
|
1861
1970
|
### On-demand revalidation
|
|
1862
1971
|
|
|
1863
1972
|
Purge a path the moment its data changes (a CMS webhook, an admin action) with
|
|
@@ -1872,6 +1981,21 @@ curl -X POST 'https://example.com/__nifra/revalidate?path=/posts/hello' \
|
|
|
1872
1981
|
# → { "revalidated": "/posts/hello" } (the token is checked in constant time)
|
|
1873
1982
|
```
|
|
1874
1983
|
|
|
1984
|
+
A per-request CSP nonce makes a page uncacheable. For a strict policy under ISR, give
|
|
1985
|
+
`createWebApp` a `csp` from `createCspPolicy` instead of a
|
|
1986
|
+
`nonce`: constant scripts are allowed by hash, so only a page that needs a nonce
|
|
1987
|
+
carries one - see [A strict CSP that still caches](/docs/contract#strict-csp).
|
|
1988
|
+
|
|
1989
|
+
A page that must not be cached as a whole, because it is personalized or carries a nonce, can
|
|
1990
|
+
still cache the data its loader reads. Wrap the expensive call in `cache.wrap` from
|
|
1991
|
+
`@nifrajs/cache`, with the same tags the pages use. Concurrent loads of one key run
|
|
1992
|
+
once, and a store outage falls through to the loader. Pass that cache to
|
|
1993
|
+
`revalidateEndpoint` as `dataCaches: [cache]`. A tag purge then
|
|
1994
|
+
invalidates it first, then the page store, then the CDN, so no page is rebuilt from stale
|
|
1995
|
+
data. If a data cache fails, the endpoint answers `500` before it touches the page
|
|
1996
|
+
store, so a retry purges everything again. `createInvalidator` from
|
|
1997
|
+
`@nifrajs/web/cdn` takes the same `dataCaches` for purges from app code.
|
|
1998
|
+
|
|
1875
1999
|
`revalidateTags` names what a purge by tag reaches. A list tags every page of the
|
|
1876
2000
|
route alike; a function of the route's params and URL tags each page on its own. Tags travel to
|
|
1877
2001
|
the CDN and to anyone reading the origin, so build them from params, never from loader data.
|
|
@@ -2570,6 +2694,54 @@ stripped from `event`/`id` to prevent frame injection); multi-line
|
|
|
2570
2694
|
`data` is split into multiple `data:` lines per the spec; and
|
|
2571
2695
|
`keepAlive` emits comment pings so idle proxies don't drop the connection.
|
|
2572
2696
|
|
|
2697
|
+
A client that stops reading is dropped once it has `maxBufferedBytes` (default 1
|
|
2698
|
+
MiB) of events unread: `stream.signal` aborts with `sse_client_too_slow`,
|
|
2699
|
+
the queued frames are released, and later sends do nothing, so one stalled reader cannot hold
|
|
2700
|
+
server memory without limit. Raise it for a burst larger than that sent before the client can
|
|
2701
|
+
read, or pass `Infinity` to turn the limit off.
|
|
2702
|
+
|
|
2703
|
+
### Typed SSE routes
|
|
2704
|
+
|
|
2705
|
+
`app.sse(path, schema, run)` declares the event payload with `sse`, so
|
|
2706
|
+
`stream.send()` is type-checked and the typed client grows
|
|
2707
|
+
`.subscribe(onEvent)` with the same event type. A route whose schema declares a
|
|
2708
|
+
`body` is a POST stream: the body validates as on any POST, and
|
|
2709
|
+
`.subscribe(onEvent, { body })` sends it. A GET stream reconnects after a
|
|
2710
|
+
drop and resumes with `Last-Event-ID`; a POST stream does not reconnect unless you
|
|
2711
|
+
pass `reconnect: true`, since a reconnect sends the body again. The client's
|
|
2712
|
+
`onRequest` hook runs before each connect, so a refreshed token reaches the stream.
|
|
2713
|
+
|
|
2714
|
+
```ts
|
|
2715
|
+
import { client } from "@nifrajs/client"
|
|
2716
|
+
import { server } from "@nifrajs/core/server"
|
|
2717
|
+
import { streaming } from "@nifrajs/core/sse"
|
|
2718
|
+
import { t } from "@nifrajs/schema"
|
|
2719
|
+
|
|
2720
|
+
declare function generate(prompt: string): AsyncIterable<string>
|
|
2721
|
+
|
|
2722
|
+
export const app = server()
|
|
2723
|
+
.use(streaming())
|
|
2724
|
+
// A GET stream: the event payload is contracted by `sse`.
|
|
2725
|
+
.sse("/feed", { sse: t.object({ id: t.integer(), title: t.string() }) }, (_c, stream) => {
|
|
2726
|
+
stream.send({ id: 1, title: "hello" })
|
|
2727
|
+
stream.close()
|
|
2728
|
+
})
|
|
2729
|
+
// A body makes it a POST stream, validated like any POST: a chat that posts its prompt.
|
|
2730
|
+
.sse(
|
|
2731
|
+
"/chat",
|
|
2732
|
+
{ body: t.object({ prompt: t.string() }), sse: t.object({ token: t.string() }) },
|
|
2733
|
+
async (c, stream) => {
|
|
2734
|
+
for await (const token of generate(c.body.prompt)) stream.send({ token })
|
|
2735
|
+
stream.close()
|
|
2736
|
+
},
|
|
2737
|
+
)
|
|
2738
|
+
|
|
2739
|
+
const api = client<typeof app>("https://example.com")
|
|
2740
|
+
const feed = api.feed.subscribe((post) => console.log(post.title))
|
|
2741
|
+
api.chat.subscribe((event) => console.log(event.token), { body: { prompt: "Hi" } })
|
|
2742
|
+
feed.close()
|
|
2743
|
+
```
|
|
2744
|
+
|
|
2573
2745
|
## Nifra - Query cache
|
|
2574
2746
|
|
|
2575
2747
|
> Nifra's keyed query cache: useQuery / createQuery, dedup, staleness, invalidation.
|
|
@@ -2657,7 +2829,7 @@ than small.
|
|
|
2657
2829
|
```ts
|
|
2658
2830
|
import { html, vanillaAdapter } from "@nifrajs/web-vanilla"
|
|
2659
2831
|
|
|
2660
|
-
// routes/hotels.
|
|
2832
|
+
// routes/hotels.tsx - a page keeps a route extension even with no JSX in it.
|
|
2661
2833
|
export default function Hotels({ data }: { data: { hotels: Array<{ name: string }> } }) {
|
|
2662
2834
|
// Interpolated values are escaped; wrap trusted markup in raw() to opt out deliberately.
|
|
2663
2835
|
return html`<ul>${data.hotels.map((h) => html`<li>${h.name}</li>`)}</ul>`
|
|
@@ -2877,6 +3049,48 @@ const app = server()
|
|
|
2877
3049
|
.use(rateLimit({ store: new MemoryStore(), max: 100, windowMs: 60_000 }))
|
|
2878
3050
|
```
|
|
2879
3051
|
|
|
3052
|
+
To refuse a request in `onRequest`, return the same `status()` value a
|
|
3053
|
+
route would: `return status(401, { error: "key required" })`. It takes the
|
|
3054
|
+
normal response path, so the body is serialized with its content-type, and declared
|
|
3055
|
+
`responseHeaders` and every `onResponse` hook apply to it. A hand-built
|
|
3056
|
+
`Response` still works.
|
|
3057
|
+
|
|
3058
|
+
#### Which hooks run where
|
|
3059
|
+
|
|
3060
|
+
Where a hook is registered decides which requests it sees. `nifra routes --hooks`
|
|
3061
|
+
prints, for every backend route and for each prefix's unmatched paths, the hooks that run
|
|
3062
|
+
and the site that registered them (`app`, `group /admin`,
|
|
3063
|
+
`merge #1`, `mount /m`), named by plugin where one registered them:
|
|
3064
|
+
|
|
3065
|
+
-
|
|
3066
|
+
A **group**'s hooks cover its whole prefix, including that prefix's 404 and 405
|
|
3067
|
+
responses.
|
|
3068
|
+
|
|
3069
|
+
-
|
|
3070
|
+
A **merged** server's `onRequest` runs only for its own routes; its
|
|
3071
|
+
response hooks and static headers become app-wide.
|
|
3072
|
+
|
|
3073
|
+
-
|
|
3074
|
+
A **mount** runs the child's full lifecycle inside the parent's request and response
|
|
3075
|
+
hooks.
|
|
3076
|
+
|
|
3077
|
+
-
|
|
3078
|
+
`around`, `derive`, and `beforeHandle` apply only to
|
|
3079
|
+
routes declared after them.
|
|
3080
|
+
|
|
3081
|
+
-
|
|
3082
|
+
A **WebSocket** route, listed as `WS /path`, runs the request hooks, then the
|
|
3083
|
+
authentication stages, `derive`s, `beforeHandle`s and
|
|
3084
|
+
`onError` declared before it. Its handshake runs no `around`,
|
|
3085
|
+
`afterHandle` or response hooks, but a refused handshake carries the
|
|
3086
|
+
app’s `responseHeaders()`.
|
|
3087
|
+
|
|
3088
|
+
`nifra routes --hooks --json` writes the report; `--since <file>`
|
|
3089
|
+
prints what runs differently from a saved one. `nifra upgrade --write` saves the
|
|
3090
|
+
report before it edits anything and prints the scope diff, and `nifra check --json`
|
|
3091
|
+
includes it as `hooks` when `nifra.assurance.ts` names a
|
|
3092
|
+
`source`. Origins are recorded only outside production builds.
|
|
3093
|
+
|
|
2880
3094
|
### Header middleware: one portable hook, fast everywhere
|
|
2881
3095
|
|
|
2882
3096
|
Most response middleware only reads or writes **headers** - security headers, CORS
|
|
@@ -3177,6 +3391,36 @@ const app = server()
|
|
|
3177
3391
|
// poweredBy() is opt-in (Nifra emits no X-Powered-By by default). combine(a, b, c) bundles several into one plugin.
|
|
3178
3392
|
```
|
|
3179
3393
|
|
|
3394
|
+
### Rate-limit tiers
|
|
3395
|
+
|
|
3396
|
+
A `rateLimit()` inside a `group()` still sits under an app-wide one, so a
|
|
3397
|
+
group can only be stricter than the app. `rateLimitTiers()` is one limiter that
|
|
3398
|
+
charges each request to exactly one bucket - the tier with the longest static
|
|
3399
|
+
`prefix` holding the path, else the `floor` - so a public API can allow
|
|
3400
|
+
more than the floor while a login path allows less. It runs before routing, so 404s, 405s and
|
|
3401
|
+
bodies that fail validation are charged too. Prefixes are checked when the limiter is built
|
|
3402
|
+
(static, no trailing slash, no duplicates), and each tier counts in its own keys, so one store
|
|
3403
|
+
serves them all. `skip`, on both limiters, leaves a request uncharged.
|
|
3404
|
+
|
|
3405
|
+
```ts
|
|
3406
|
+
import { server } from "@nifrajs/core/server"
|
|
3407
|
+
import { MemoryStore, rateLimitTiers } from "@nifrajs/middleware"
|
|
3408
|
+
|
|
3409
|
+
// Each request is charged to ONE bucket: the longest matching prefix, else the floor.
|
|
3410
|
+
// A tier may sit above the floor - tiers never stack on it.
|
|
3411
|
+
const app = server().use(
|
|
3412
|
+
rateLimitTiers({
|
|
3413
|
+
store: new MemoryStore(),
|
|
3414
|
+
floor: { max: 120, windowMs: 60_000 },
|
|
3415
|
+
tiers: [
|
|
3416
|
+
{ prefix: "/api/public", max: 600, windowMs: 60_000 },
|
|
3417
|
+
{ prefix: "/auth/login", max: 10, windowMs: 60_000 },
|
|
3418
|
+
],
|
|
3419
|
+
skip: (req) => new URL(req.url).pathname === "/health", // never charged
|
|
3420
|
+
}),
|
|
3421
|
+
)
|
|
3422
|
+
```
|
|
3423
|
+
|
|
3180
3424
|
### Security middleware
|
|
3181
3425
|
|
|
3182
3426
|
The security set - `csrf` (signed double-submit + Origin/Referer), `jwt`,
|
|
@@ -3249,8 +3493,10 @@ keeps the lane out of your bundle rather than shipping a disabled branch to ever
|
|
|
3249
3493
|
serves the payload **unchanged** - so turning it on in staging can never be the
|
|
3250
3494
|
thing that broke production. `"enforce"` serializes the validated value
|
|
3251
3495
|
instead of the raw result. Both cover a returned value, a `status(...)` result and
|
|
3252
|
-
a `c.json(...)` reply alike
|
|
3253
|
-
|
|
3496
|
+
a `c.json(...)` reply alike, whether the handler returns or throws it, a hook
|
|
3497
|
+
returns it early, or `onError` answers with it; only a hand-built
|
|
3498
|
+
`Response` (a redirect, a stream) passes unchecked. Install it before the routes it
|
|
3499
|
+
should cover: like
|
|
3254
3500
|
`idempotency()`, the decision is made per route at registration.
|
|
3255
3501
|
|
|
3256
3502
|
Enforcement follows your schema’s own semantics, because Standard Schema exposes
|
|
@@ -3654,7 +3900,9 @@ Verification is **constant-time** - the provider's signature goes straight into
|
|
|
3654
3900
|
`crypto.subtle.verify`, so a wrong signature can't be discovered byte-by-byte through
|
|
3655
3901
|
timing. Presets cover **Stripe** (parses `t=…,v1=…` and enforces a 5-minute replay
|
|
3656
3902
|
window on the signed timestamp) and **GitHub** (`sha256=…`); the `generic`
|
|
3657
|
-
preset takes an explicit header, encoding
|
|
3903
|
+
preset takes an explicit header, encoding (`"hex"`, `"base64"` or
|
|
3904
|
+
`"base64url"`, padded or not), and prefix for anything else. A signature that does not
|
|
3905
|
+
decode is refused as `malformed_signature`, never thrown. Pass an array of secrets to
|
|
3658
3906
|
accept either during a key rotation.
|
|
3659
3907
|
|
|
3660
3908
|
### Idempotency - `idempotency()` middleware
|
|
@@ -3786,6 +4034,13 @@ group can apply its own configuration over the app's - `admin.use(securityH
|
|
|
3786
4034
|
inside `app.group("/admin", ...)` - and each header it sets replaces the app's
|
|
3787
4035
|
value on the group's routes only.
|
|
3788
4036
|
|
|
4037
|
+
A fixed `Content-Security-Policy` here cannot allow nifra's inline scripts by
|
|
4038
|
+
nonce. For a strict `script-src` on web pages, give `createWebApp` a
|
|
4039
|
+
`csp` from `createCspPolicy`: it allows the framework's constant
|
|
4040
|
+
scripts by hash and adds a nonce only to a page that needs one, so every other page sends the
|
|
4041
|
+
same header and stays cacheable under `withISR` or a CDN. See
|
|
4042
|
+
[A strict CSP that still caches](/docs/contract#strict-csp).
|
|
4043
|
+
|
|
3789
4044
|
### Route assurance - prove every route is guarded
|
|
3790
4045
|
|
|
3791
4046
|
Installing security middleware is not the same as proving it covers every route. Nifra's
|
|
@@ -3806,11 +4061,15 @@ export default defineAssuranceConfig({
|
|
|
3806
4061
|
rules: [
|
|
3807
4062
|
{ name: "health", match: { paths: ["/health"] }, require: [],
|
|
3808
4063
|
forbid: [NIFRA_ASSURANCE.AUTHENTICATED] },
|
|
3809
|
-
|
|
4064
|
+
// Every method but these, so a custom one such as PURGE is a mutation too.
|
|
4065
|
+
{ name: "mutation", match: { exceptMethods: ["GET", "HEAD", "OPTIONS", "WS"] },
|
|
3810
4066
|
require: [NIFRA_ASSURANCE.AUTHENTICATED, NIFRA_ASSURANCE.CSRF,
|
|
3811
4067
|
NIFRA_ASSURANCE.BODY_BOUNDED] },
|
|
3812
4068
|
{ name: "read", match: { methods: ["GET", "HEAD"] },
|
|
3813
4069
|
require: [NIFRA_ASSURANCE.AUTHENTICATED] },
|
|
4070
|
+
// WebSocket routes reflect under the pseudo-method "WS".
|
|
4071
|
+
{ name: "socket", match: { methods: ["WS"] },
|
|
4072
|
+
require: [NIFRA_ASSURANCE.AUTHENTICATED] },
|
|
3814
4073
|
],
|
|
3815
4074
|
},
|
|
3816
4075
|
})
|
|
@@ -3824,6 +4083,14 @@ while authentication and other order-scoped hooks cover only routes registered a
|
|
|
3824
4083
|
Method and path filters prevent a narrow guard from claiming broader coverage. The evaluation
|
|
3825
4084
|
runs only through reflection or `nifra assure`, so requests pay no assurance cost.
|
|
3826
4085
|
|
|
4086
|
+
WebSocket routes are reflected under the pseudo-method `"WS"`, so a policy classifies
|
|
4087
|
+
every socket too. A handshake runs the auth guards registered before the socket and the
|
|
4088
|
+
app's request hooks, so a socket carries authentication and rate-limit evidence from them.
|
|
4089
|
+
It never claims evidence whose enforcement its message handlers bypass, such as a durable
|
|
4090
|
+
command journal. A socket declares its effects with
|
|
4091
|
+
`app.ws(path, { capabilities })`, and capability assurance checks them against
|
|
4092
|
+
the module that registers it.
|
|
4093
|
+
|
|
3827
4094
|
### Effect assurance - declared capability versus provenance
|
|
3828
4095
|
|
|
3829
4096
|
Authentication does not reveal what a route can do. Capability assurance compares an exact
|
|
@@ -3887,7 +4154,11 @@ Approval resumes are HMAC-signed, expire, bind to the tenant, principal, capabil
|
|
|
3887
4154
|
digest, and are consumed atomically once. The effect journal marks execution before the provider
|
|
3888
4155
|
call, so a crash after an external commit remains `ambiguous` for reconciliation instead
|
|
3889
4156
|
of being retried blindly. Typed sagas persist compensation arguments separately from the sealed
|
|
3890
|
-
token-only ledger and compensate committed steps in reverse order with retry/backoff state.
|
|
4157
|
+
token-only ledger and compensate committed steps in reverse order with retry/backoff state. One
|
|
4158
|
+
`resume` or `compensate` call owns a saga at a time through a lease every
|
|
4159
|
+
transition renews (`leaseMs`, default 5 minutes): a second call meanwhile throws
|
|
4160
|
+
`SagaConcurrencyError` and changes nothing, and a call that died is taken over once its
|
|
4161
|
+
lease lapses.
|
|
3891
4162
|
|
|
3892
4163
|
```ts
|
|
3893
4164
|
// doc-check: skip - durable store implementations are deployment-specific.
|
|
@@ -4109,6 +4380,15 @@ there and grants exactly `db.read` - it never reaches the driver underneath. Kee
|
|
|
4109
4380
|
the two halves import-disjoint, at the seam and at the route modules, and every route's
|
|
4110
4381
|
declaration equals its reach.
|
|
4111
4382
|
|
|
4383
|
+
The walk starts at the module that registers the route, which Nifra finds by reading the
|
|
4384
|
+
source without running it. It reads a path written as a string literal, or as a name bound to
|
|
4385
|
+
a module-level `const` string, with or without `as const`. The
|
|
4386
|
+
`const` can live in the same file or be exported from a project file it imports.
|
|
4387
|
+
The same holds for a WebSocket route's `app.ws(path, ...)`. A path built at runtime, such as a template with `${...}` in it, cannot be read
|
|
4388
|
+
this way. `nifra check` names that expression in an NF-C036 finding, because the
|
|
4389
|
+
route then has no module to start from. List the module in
|
|
4390
|
+
`provenance.routeModules` to give it one.
|
|
4391
|
+
|
|
4112
4392
|
### Runtime beacons
|
|
4113
4393
|
|
|
4114
4394
|
Static provenance answers what a _module_ can reach, so its evidence is as broad as the
|
|
@@ -4220,6 +4500,33 @@ $ nifra check
|
|
|
4220
4500
|
• known gap, not capability-analyzed: /api/auth/* - better-auth's own handler; its effects are its own
|
|
4221
4501
|
```
|
|
4222
4502
|
|
|
4503
|
+
A mount can also state the routes it serves and the capabilities each one exercises. Pass an
|
|
4504
|
+
object with the `reason` and a `routes` list. Each path is the full
|
|
4505
|
+
request path and must sit under the mount:
|
|
4506
|
+
|
|
4507
|
+
```ts
|
|
4508
|
+
export const app = server().mount({
|
|
4509
|
+
path: "/api/auth",
|
|
4510
|
+
app: authHandler,
|
|
4511
|
+
opaque: {
|
|
4512
|
+
reason: "better-auth's own handler; its effects are its own",
|
|
4513
|
+
routes: [
|
|
4514
|
+
{ method: "POST", path: "/api/auth/sign-in/email", capabilities: ["session.write"] },
|
|
4515
|
+
{ method: "GET", path: "/api/auth/get-session", capabilities: ["session.read"] },
|
|
4516
|
+
],
|
|
4517
|
+
},
|
|
4518
|
+
})
|
|
4519
|
+
```
|
|
4520
|
+
|
|
4521
|
+
Assurance lists these routes with `analysis: "declared"`, apart from the routes it
|
|
4522
|
+
analyzed. The mount stays a known gap with its reason. A declared capability the policy does
|
|
4523
|
+
not define fails, as it does on a route, and so does a domain write declared on
|
|
4524
|
+
`GET`. Nothing else is checked, because there is no evidence to read. The routes
|
|
4525
|
+
also appear in the capability lockfile, so a changed declaration shows up in review. They
|
|
4526
|
+
appear in the `nifra context` route index too. An L4 run lists them as having no
|
|
4527
|
+
contract to test. A malformed entry, or a path outside the mount, stops the analysis with an
|
|
4528
|
+
error that names it.
|
|
4529
|
+
|
|
4223
4530
|
### The lockfile
|
|
4224
4531
|
|
|
4225
4532
|
Once assurance passes, snapshot the result. The lockfile is a review artifact: a route that
|
|
@@ -4417,6 +4724,102 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|
|
4417
4724
|
}
|
|
4418
4725
|
```
|
|
4419
4726
|
|
|
4727
|
+
Without `redirectTo`, a guard denies with a 401 JSON
|
|
4728
|
+
`{ ok: false, error: "unauthorized" }`. `unauthenticated` sets that
|
|
4729
|
+
response's status (400-599), body and headers instead; the same option exists on
|
|
4730
|
+
`@nifrajs/better-auth`'s `requireSession`, `requirePrincipal`
|
|
4731
|
+
and `authed`, and on `@nifrajs/authjs`'s `requireAuthUser`. A
|
|
4732
|
+
guard given both `redirectTo` and `unauthenticated` throws.
|
|
4733
|
+
|
|
4734
|
+
```ts
|
|
4735
|
+
// doc-check: skip - fragment: `session` is the request's session.
|
|
4736
|
+
requireUser(session, "userId", {
|
|
4737
|
+
unauthenticated: { status: 403, body: { error: "login_required" }, headers: { "www-authenticate": "Session" } },
|
|
4738
|
+
})
|
|
4739
|
+
```
|
|
4740
|
+
|
|
4741
|
+
### One request-scoped context from authentication to the handler
|
|
4742
|
+
|
|
4743
|
+
`around` wraps everything after routing for the routes declared after it:
|
|
4744
|
+
authentication, body validation, `derive` and the handler. An
|
|
4745
|
+
`AsyncLocalStorage` frame or a database session setting opened there stays open
|
|
4746
|
+
until the response is decided. `authenticate` runs inside that frame before
|
|
4747
|
+
validation, resolves the caller once and types it as `c.principal`, so it can write
|
|
4748
|
+
what it resolved into the frame instead of a second lookup in a `derive`. Both are
|
|
4749
|
+
order-scoped, so inside a `group` they cover only its routes.
|
|
4750
|
+
|
|
4751
|
+
```ts
|
|
4752
|
+
// doc-check: skip - fragment: `app`, `readSession`, `Note`, and `db` are your application's.
|
|
4753
|
+
import { AsyncLocalStorage } from "node:async_hooks"
|
|
4754
|
+
import { authenticated, rejected } from "@nifrajs/core"
|
|
4755
|
+
|
|
4756
|
+
const frame = new AsyncLocalStorage<{ userId?: string }>()
|
|
4757
|
+
|
|
4758
|
+
app.group("/api", (api) =>
|
|
4759
|
+
api
|
|
4760
|
+
// Opens before authentication and closes after the response: validation, derive and the
|
|
4761
|
+
// handler all run inside it, and so does a thrown Response.
|
|
4762
|
+
.around((_c, next) => frame.run({}, next))
|
|
4763
|
+
// Resolves the caller once: typed as c.principal, and written into the open frame.
|
|
4764
|
+
.authenticate({
|
|
4765
|
+
id: "session",
|
|
4766
|
+
run: async (input) => {
|
|
4767
|
+
const session = await readSession(input.headers.get("cookie"))
|
|
4768
|
+
if (session === null) return rejected()
|
|
4769
|
+
const store = frame.getStore()
|
|
4770
|
+
if (store !== undefined) store.userId = session.userId
|
|
4771
|
+
return authenticated(session)
|
|
4772
|
+
},
|
|
4773
|
+
})
|
|
4774
|
+
.post("/notes", { body: Note }, (c) => db.notes.insert({ ...c.body, owner: c.principal.userId })),
|
|
4775
|
+
)
|
|
4776
|
+
```
|
|
4777
|
+
|
|
4778
|
+
### Authentication runs first
|
|
4779
|
+
|
|
4780
|
+
`authenticate()` refuses a request before anything reads it: the body is not read,
|
|
4781
|
+
no schema runs, and no `derive` or `beforeHandle` sees it. An
|
|
4782
|
+
unauthenticated caller gets the 401 (or the refusal `run` returns), never a 413 or a
|
|
4783
|
+
422 that describes the route's input. `basicAuth`, `bearer`,
|
|
4784
|
+
`apiKey` and `jwt` from `@nifrajs/middleware` run there. With
|
|
4785
|
+
`optional: true` they never refuse, so validation answers as usual.
|
|
4786
|
+
|
|
4787
|
+
`run` receives the method, pathname, headers, path params, the raw
|
|
4788
|
+
`search` and the parsed `query` (frozen, read by the parser
|
|
4789
|
+
`c.query` uses; a repeated key is an array), the `request` (never read
|
|
4790
|
+
its body), `env`, `clientIp` and an abort `signal`. Every
|
|
4791
|
+
field is the caller's and unvalidated. Read the query only for a short-lived, single-use
|
|
4792
|
+
ticket, such as one for a WebSocket handshake a browser cannot set a header on: a URL lands in
|
|
4793
|
+
proxy and access logs. The dev feed records a query's keys, never its values.
|
|
4794
|
+
|
|
4795
|
+
```ts
|
|
4796
|
+
// doc-check: skip - fragment: `app` and `tickets` (a single-use store) are your application's.
|
|
4797
|
+
import { authenticated, rejected } from "@nifrajs/core"
|
|
4798
|
+
|
|
4799
|
+
app
|
|
4800
|
+
.authenticate({
|
|
4801
|
+
id: "socket-ticket",
|
|
4802
|
+
run: async (input) => {
|
|
4803
|
+
// A browser cannot set a header on a WebSocket handshake, so the ticket rides in the URL.
|
|
4804
|
+
const ticket = input.query.ticket
|
|
4805
|
+
if (typeof ticket !== "string") return rejected()
|
|
4806
|
+
const user = await tickets.take(ticket) // single use, expires in seconds
|
|
4807
|
+
return user === null ? rejected() : authenticated(user)
|
|
4808
|
+
},
|
|
4809
|
+
})
|
|
4810
|
+
.ws("/live", { upgrade: (c) => ({ user: c.principal.id }) })
|
|
4811
|
+
```
|
|
4812
|
+
|
|
4813
|
+
A guard written as a `beforeHandle` runs after validation, so an unauthenticated
|
|
4814
|
+
caller reaches the body parser. When `nifra check` loads
|
|
4815
|
+
`backend/app.ts` (it does when a `contracts.lock.json` exists), NF-S008
|
|
4816
|
+
names each route that claims authentication this way and declares an input schema. Move the
|
|
4817
|
+
check into `authenticate()`, or set
|
|
4818
|
+
`validationOrder: "auth-before-validation"` on the route to run its hooks first. A
|
|
4819
|
+
rate limiter written as a `beforeHandle` behind `authenticate()` does not
|
|
4820
|
+
see refused requests; `rateLimit()` runs in `onRequest`, before routing,
|
|
4821
|
+
and counts them.
|
|
4822
|
+
|
|
4420
4823
|
### Session code stays in the backend
|
|
4421
4824
|
|
|
4422
4825
|
The session manager, an auth instance and their secrets live in `backend/`, and only
|
|
@@ -5215,15 +5618,16 @@ app.listen(3000) // Bun
|
|
|
5215
5618
|
### Lifecycle
|
|
5216
5619
|
|
|
5217
5620
|
Every callback is optional - `{ message }` alone is a valid echo server.
|
|
5218
|
-
`upgrade` is the only
|
|
5219
|
-
|
|
5621
|
+
`upgrade` is the only callback that runs before the socket opens, so it’s where a
|
|
5622
|
+
route rejects a connection or seeds its data.
|
|
5220
5623
|
|
|
5221
5624
|
```ts
|
|
5222
5625
|
// doc-check: skip - illustrative lifecycle (empty close/error bodies, cookie shape).
|
|
5223
5626
|
app.ws<{ user: string }>("/chat", {
|
|
5224
|
-
// upgrade(c) runs in the full HTTP request context, BEFORE the socket opens
|
|
5225
|
-
//
|
|
5226
|
-
//
|
|
5627
|
+
// upgrade(c) runs in the full HTTP request context, BEFORE the socket opens, after the
|
|
5628
|
+
// guards registered before this route. Return the per-connection data (→ ws.data, typed),
|
|
5629
|
+
// or a Response or status(...) to REJECT the upgrade. A thrown Response rejects with it;
|
|
5630
|
+
// any other throw goes to onError, then a 500.
|
|
5227
5631
|
upgrade(c) {
|
|
5228
5632
|
const user = c.cookies.session
|
|
5229
5633
|
if (user === undefined) return new Response("unauthorized", { status: 401 })
|
|
@@ -5236,6 +5640,37 @@ app.ws<{ user: string }>("/chat", {
|
|
|
5236
5640
|
})
|
|
5237
5641
|
```
|
|
5238
5642
|
|
|
5643
|
+
### Guards
|
|
5644
|
+
|
|
5645
|
+
A handshake is a `GET`, and it runs the chain an HTTP `GET` registered
|
|
5646
|
+
at the same point runs: the `authenticate()` stages, `derive`s and
|
|
5647
|
+
`beforeHandle`s added before `app.ws()`, then `upgrade`. So
|
|
5648
|
+
auth middleware such as `basicAuth` or `jwt` guards the sockets
|
|
5649
|
+
registered after it, any guard can refuse the handshake with the response it would send an
|
|
5650
|
+
HTTP request, and `upgrade` reads what the guards derived. The origin check runs
|
|
5651
|
+
first. A socket registered before a guard stays public, like an HTTP route there. A browser
|
|
5652
|
+
cannot set a header on the handshake, so `authenticate()` can read a short-lived,
|
|
5653
|
+
single-use ticket from `input.query` - see
|
|
5654
|
+
[Authentication runs first](/docs/auth#auth-first).
|
|
5655
|
+
|
|
5656
|
+
```ts
|
|
5657
|
+
// doc-check: skip - illustrative: an existing auth guard and session lookup.
|
|
5658
|
+
app
|
|
5659
|
+
.ws("/status", {}) // registered before the guard: public
|
|
5660
|
+
.authenticate(sessionAuth) // or .use(basicAuth(...)), .derive(...), .beforeHandle(...)
|
|
5661
|
+
.get("/me", (c) => c.principal)
|
|
5662
|
+
.ws("/chat", {
|
|
5663
|
+
// Reached only when sessionAuth accepted the handshake, exactly as GET /me is.
|
|
5664
|
+
upgrade: (c) => ({ user: c.principal.id }),
|
|
5665
|
+
})
|
|
5666
|
+
```
|
|
5667
|
+
|
|
5668
|
+
A socket declares the effects its code may reach with `capabilities`, as an HTTP
|
|
5669
|
+
route does. `nifra capabilities check` compares them with what the registering
|
|
5670
|
+
module can reach, and the handshake context carries the runtime guard for
|
|
5671
|
+
`upgrade()`. Reflection lists every socket under the pseudo-method
|
|
5672
|
+
`"WS"`, so `nifra assure` classifies sockets too.
|
|
5673
|
+
|
|
5239
5674
|
### NifraWebSocket
|
|
5240
5675
|
|
|
5241
5676
|
The `ws` handed to each callback is a portable wrapper over the runtime’s native
|
|
@@ -5388,6 +5823,47 @@ it). Install it when you use `app.ws()`; without it a WS upgrade gets a clean
|
|
|
5388
5823
|
npm i ws
|
|
5389
5824
|
```
|
|
5390
5825
|
|
|
5826
|
+
### In a composed app
|
|
5827
|
+
|
|
5828
|
+
`mount({ path, app })` carries WebSocket upgrades as well as HTTP: when the
|
|
5829
|
+
serving adapter sees an upgrade under the mount's path, it asks the child's
|
|
5830
|
+
`resolveWebSocketUpgrade` and upgrades its own socket with the outcome. Any nifra
|
|
5831
|
+
server with `websocket()` installed has one. A child that is not a nifra server can
|
|
5832
|
+
implement it too, building the outcome from `@nifrajs/core/ws`:
|
|
5833
|
+
`attachWebSocket` wires the socket to a handler and a `TopicRegistry`
|
|
5834
|
+
owns its pub/sub. On Bun, install `websocket()` on the parent or on a mounted nifra
|
|
5835
|
+
child, or `listen()` refuses to start with `INVALID_WS_RUNTIME`.
|
|
5836
|
+
|
|
5837
|
+
`mountFetch(path, handler)` and a plain fetch handler carry HTTP only. An upgrade
|
|
5838
|
+
request under such a path reaches the handler as an ordinary request and never becomes a
|
|
5839
|
+
socket. Serve that path's WebSockets from a mounted child, or keep that server beside nifra.
|
|
5840
|
+
|
|
5841
|
+
```ts
|
|
5842
|
+
// server.ts - one listener; the chat child and a non-nifra child both take upgrades.
|
|
5843
|
+
import { server } from "@nifrajs/core/server"
|
|
5844
|
+
import { attachWebSocket, TopicRegistry, websocket, type WebSocketUpgradeOutcome } from "@nifrajs/core/ws"
|
|
5845
|
+
|
|
5846
|
+
const chat = server()
|
|
5847
|
+
.use(websocket())
|
|
5848
|
+
.ws("/room", { message: (ws, data) => ws.send(data) })
|
|
5849
|
+
|
|
5850
|
+
// Not a nifra server: it answers HTTP through fetch and builds its own upgrade outcome.
|
|
5851
|
+
const topics = new TopicRegistry()
|
|
5852
|
+
const legacy = {
|
|
5853
|
+
fetch: () => new Response("legacy"),
|
|
5854
|
+
resolveWebSocketUpgrade: (request: Request): WebSocketUpgradeOutcome =>
|
|
5855
|
+
new URL(request.url).pathname === "/feed"
|
|
5856
|
+
? { kind: "upgrade", handler: { open: (ws) => ws.send("hello") }, data: undefined, pubsub: topics, attach: attachWebSocket }
|
|
5857
|
+
: { kind: "pass" },
|
|
5858
|
+
}
|
|
5859
|
+
|
|
5860
|
+
server()
|
|
5861
|
+
.use(websocket()) // Bun: the parent holds the socket table every mounted upgrade runs through
|
|
5862
|
+
.mount({ path: "/chat", app: chat, stripPrefix: true })
|
|
5863
|
+
.mount({ path: "/legacy", app: legacy, stripPrefix: true })
|
|
5864
|
+
.listen(3000)
|
|
5865
|
+
```
|
|
5866
|
+
|
|
5391
5867
|
## Nifra - Deployment
|
|
5392
5868
|
|
|
5393
5869
|
> One Nifra app, one command per target - Bun, Node (Docker), Deno Deploy, Cloudflare Pages, Vercel Edge.
|
|
@@ -5518,6 +5994,11 @@ hand one visitor a response produced for another:
|
|
|
5518
5994
|
**Only content-hashed assets are precached.** A hashed URL names its bytes, so serving it
|
|
5519
5995
|
from cache forever is correct by construction. Unhashed URLs are left to the network.
|
|
5520
5996
|
|
|
5997
|
+
-
|
|
5998
|
+
**Only the build's own URLs are answered, from the build's own cache.** Any other request,
|
|
5999
|
+
an API call included, goes to the network untouched, and a cache the page or another app
|
|
6000
|
+
stored on the origin is never read.
|
|
6001
|
+
|
|
5521
6002
|
-
|
|
5522
6003
|
**Documents are never cached.** Only navigations that FAIL are answered, and only with the
|
|
5523
6004
|
offline page you nominate - which must be static and user-independent, since every visitor
|
|
@@ -5529,8 +6010,9 @@ for another.
|
|
|
5529
6010
|
straight to the network.
|
|
5530
6011
|
|
|
5531
6012
|
-
|
|
5532
|
-
**The cache name carries the build id**, and activation deletes every older cache
|
|
5533
|
-
stale worker cannot pin an old build
|
|
6013
|
+
**The cache name carries the build id**, and activation deletes every older cache named
|
|
6014
|
+
`<cacheName>:<buildId>` - so a stale worker cannot pin an old build
|
|
6015
|
+
indefinitely, and the origin's other caches are left alone.
|
|
5534
6016
|
|
|
5535
6017
|
Omit `offlineUrl` and a failed navigation simply fails, which is the honest default:
|
|
5536
6018
|
an offline page you have not written is not better than a browser error.
|
|
@@ -5572,6 +6054,14 @@ content-typed, with an immutable cache - before the app runs, leaving the SSR fa
|
|
|
5572
6054
|
untouched: `serve(app, {'{ port, static: { dir: new URL("./assets/", import.meta.url) } }'})`.
|
|
5573
6055
|
On Cloudflare/Vercel the platform serves assets, so you don't need it there.
|
|
5574
6056
|
|
|
6057
|
+
Every file under the mount is cached as immutable by default. If the directory also holds
|
|
6058
|
+
files that keep their name across deploys, pass the build manifest's `assets` as
|
|
6059
|
+
`hashedFiles`: only those URL paths stay immutable, and every other file is sent
|
|
6060
|
+
with its validators so the browser revalidates it. The server that
|
|
6061
|
+
`nifra build --target bun` or `node` generates does this already. It
|
|
6062
|
+
caches only the client build's own content-hashed outputs as immutable. A file copied from
|
|
6063
|
+
`public/`, even under `public/assets/`, gets a one-day policy.
|
|
6064
|
+
|
|
5575
6065
|
### HTTPS without a proxy
|
|
5576
6066
|
|
|
5577
6067
|
Most deploys end TLS in front of the app - a load balancer, a CDN, the platform - and the app
|
|
@@ -5766,6 +6256,40 @@ import { stylexVite } from "@nifrajs/web/plugins/stylex"
|
|
|
5766
6256
|
export const vitePlugins = [react(), stylexVite()]
|
|
5767
6257
|
```
|
|
5768
6258
|
|
|
6259
|
+
### Upgrading nifra
|
|
6260
|
+
|
|
6261
|
+
Every `@nifrajs/*` package, `nifra` and `create-nifra` ship
|
|
6262
|
+
together at one version. Move a workspace between releases with
|
|
6263
|
+
`nifra upgrade <version>` rather than by editing pins: it runs every release
|
|
6264
|
+
recipe between the oldest version your `package.json` files pin and the target,
|
|
6265
|
+
oldest first. A recipe moves a removed package to its replacement, rewrites imports that
|
|
6266
|
+
moved, lists comments a release stopped honoring, and pins each group dependency to the
|
|
6267
|
+
target, keeping its `^` or `~` unless you pass `--exact`.
|
|
6268
|
+
The pins in `overrides`, `resolutions` and
|
|
6269
|
+
`pnpm.overrides` move with them, since an override decides what installs.
|
|
6270
|
+
|
|
6271
|
+
```ts
|
|
6272
|
+
nifra upgrade --list # the release targets this CLI knows
|
|
6273
|
+
nifra upgrade 5.0.0 # dry-run: every planned edit, nothing written
|
|
6274
|
+
nifra upgrade 5.0.0 --write # apply, then nifra check verifies (--no-verify skips it)
|
|
6275
|
+
nifra doctor # afterwards: no "mixed nifra versions" advisory left
|
|
6276
|
+
```
|
|
6277
|
+
|
|
6278
|
+
It is a dry-run until `--write`, and the same run twice makes no further edits. An
|
|
6279
|
+
unknown version or a target below what is installed is refused (`--allow-downgrade`
|
|
6280
|
+
when the rollback is intended); a target newer than the running CLI prints the command for
|
|
6281
|
+
that release's CLI. It edits files under the workspace root only: a symlinked package
|
|
6282
|
+
directory or `package.json` is never followed, and a group package installed by a
|
|
6283
|
+
link from outside the root is listed under "Left alone" for you to upgrade in its
|
|
6284
|
+
own repository. So is another version pinned outside `package.json`, with its
|
|
6285
|
+
line: an MCP config such as `.mcp.json`, a Dockerfile or compose file, a CI
|
|
6286
|
+
workflow, and a lockfile other than the workspace's own (an image build's frozen
|
|
6287
|
+
lockfile, say), which you regenerate after installing. With `--write` it also
|
|
6288
|
+
prints how hook scope changed across the
|
|
6289
|
+
upgrade (`--no-hooks` skips that). `nifra doctor` and
|
|
6290
|
+
`nifra check` (NF-D002) point here when a workspace carries more than one version;
|
|
6291
|
+
see [mixed nifra versions](/docs/troubleshooting#mixed-versions).
|
|
6292
|
+
|
|
5769
6293
|
### Scope
|
|
5770
6294
|
|
|
5771
6295
|
`nifra build` defaults to a self-hosted Bun server. Use `--target node`,
|
|
@@ -6310,8 +6834,10 @@ database connection is not the same as handing it a query tool, and this is the
|
|
|
6310
6834
|
`@nifrajs/agent-protocol` is the small versioned session and event contract.
|
|
6311
6835
|
`@nifrajs/coding-agent` provides the standalone `nifra-agent` host with
|
|
6312
6836
|
bounded sessions, checkpoints, workflows, extensions, verification, and authenticated local
|
|
6313
|
-
RPC. `@nifrajs/pi` is an optional Pi backend
|
|
6314
|
-
|
|
6837
|
+
RPC. `@nifrajs/pi` is an optional Pi backend, and `@nifrajs/acp` drives
|
|
6838
|
+
any Agent Client Protocol agent (Claude Code, Codex, Gemini CLI) over stdio; other backends
|
|
6839
|
+
implement the same protocol port and can prove it with `checkAgentBackend` from
|
|
6840
|
+
`@nifrajs/agent-protocol/conformance`.
|
|
6315
6841
|
|
|
6316
6842
|
```ts
|
|
6317
6843
|
bun add @nifrajs/coding-agent @nifrajs/pi
|
|
@@ -6440,6 +6966,14 @@ resume. `@nifrajs/agent-telemetry` converts the same constrained evidence into
|
|
|
6440
6966
|
fail-open OpenTelemetry run and step spans. Providers, credentials, durable storage, retention,
|
|
6441
6967
|
and tenant policy remain application-owned ports.
|
|
6442
6968
|
|
|
6969
|
+
A step that suspends for approval resumes with the decision the caller sends in
|
|
6970
|
+
`resume.approval`. Over A2A, or on an AG-UI or `mountAgent` route that
|
|
6971
|
+
anyone can reach, that caller may be the party asking for the tool. Return
|
|
6972
|
+
`resumeApproval: "port"` from `ports` to keep the configured approval
|
|
6973
|
+
port authoritative: it is asked again on resume, receives the caller's decision as
|
|
6974
|
+
`callerDecision`, and its verdict stands. Without a port, such a resume suspends
|
|
6975
|
+
again.
|
|
6976
|
+
|
|
6443
6977
|
### Where your code and secrets run
|
|
6444
6978
|
|
|
6445
6979
|
The server your client starts runs none of your project's code. Each tool that loads the app,
|
|
@@ -6766,6 +7300,17 @@ with `export const dev = { indicator: false }` in `nifra.config.ts`,
|
|
|
6766
7300
|
or with `indicator: false` on `createDevServer`; the errors still reach
|
|
6767
7301
|
the feed.
|
|
6768
7302
|
|
|
7303
|
+
### Host check
|
|
7304
|
+
|
|
7305
|
+
The Bun dev server listens on 127.0.0.1 and answers only loopback host names:
|
|
7306
|
+
`localhost`, any `*.localhost`, `127.0.0.1` and
|
|
7307
|
+
`[::1]`. Any other `Host` gets a 403, so a page on another site whose
|
|
7308
|
+
name a DNS rebind points at your machine cannot read your pages, loader data or error overlay.
|
|
7309
|
+
To reach the server under another name (a hosts-file entry, a tunnel), list it with
|
|
7310
|
+
`export const dev = { allowedHosts: ["app.test"] }` in
|
|
7311
|
+
`nifra.config.ts` or `allowedHosts` on `createDevServer`.
|
|
7312
|
+
The Vite pipeline applies Vite's own `server.allowedHosts`.
|
|
7313
|
+
|
|
6769
7314
|
### Containers & sandboxes
|
|
6770
7315
|
|
|
6771
7316
|
In Docker, networked volumes, and some sandboxes, pass
|
|
@@ -7217,7 +7762,12 @@ classes with separate request state. The second copy prints
|
|
|
7217
7762
|
|
|
7218
7763
|
**The build honours the declaration on its own** -
|
|
7219
7764
|
`buildClient` and `buildServer` inject the resolver, so bundled output
|
|
7220
|
-
already loads one copy
|
|
7765
|
+
already loads one copy, and so does the browser bundle `nifra dev` serves on Bun.
|
|
7766
|
+
Listing `singleCopyPlugin()` in `clientPlugins` as well is harmless: dev
|
|
7767
|
+
runs that instance in the resolver's place instead of a second one. On Vite, the dev server and
|
|
7768
|
+
both Vite builds add each declared package to `resolve.dedupe`, a
|
|
7769
|
+
`@scope/*` pattern as every package installed under that scope. Unbundled phases do
|
|
7770
|
+
not: Bun's _runtime_ resolver never offers a
|
|
7221
7771
|
bare specifier to a plugin, so `bun test`, `bun run`, and preloaded
|
|
7222
7772
|
scripts still load both copies unless you preload the registrar:
|
|
7223
7773
|
|
|
@@ -7420,6 +7970,23 @@ which the server logs. Read the body in a hook with `c.req.bytes()`,
|
|
|
7420
7970
|
instead: they read it once, within the route's body limit, and every reader after the hook
|
|
7421
7971
|
replays the same bytes.
|
|
7422
7972
|
|
|
7973
|
+
### "mixed nifra versions in this workspace"
|
|
7974
|
+
|
|
7975
|
+
Every `@nifrajs/*` package, `nifra` and `create-nifra` ship
|
|
7976
|
+
together at one version. `nifra doctor` (and `nifra check`, as the
|
|
7977
|
+
`NF-D002` advisory) reads the versions your `package.json` files pin,
|
|
7978
|
+
the versions installed in `node_modules` - followed to their real location, so a
|
|
7979
|
+
linked repository reports its own - and the running CLI. When they disagree it names each
|
|
7980
|
+
version and where it came from. It only reads files, and it never fails the run.
|
|
7981
|
+
|
|
7982
|
+
The fix is [`nifra upgrade <highest>`](/docs/cli#upgrade), not a
|
|
7983
|
+
hand edit of the pins: it runs
|
|
7984
|
+
every release's migration steps between your oldest pin and the target, then re-pins. Run it
|
|
7985
|
+
without `--write` first to see each planned edit. It edits files under the
|
|
7986
|
+
workspace root only. A symlinked package directory or `package.json` is never
|
|
7987
|
+
followed, and a group package installed by a link from outside the root is listed under
|
|
7988
|
+
"Left alone" - upgrade that repository on its own.
|
|
7989
|
+
|
|
7423
7990
|
### Still stuck?
|
|
7424
7991
|
|
|
7425
7992
|
Run `nifra check --json` as the done-gate - it surfaces the import-chain leaks,
|
|
@@ -7429,13 +7996,14 @@ package ships a tight `LLM.md` contract card.
|
|
|
7429
7996
|
|
|
7430
7997
|
## Nifra - Backends & API (dev and prod)
|
|
7431
7998
|
|
|
7432
|
-
> How a Nifra backend reaches your app: inProcessClient feeds ctx.api to loaders, and createWebApp
|
|
7999
|
+
> How a Nifra backend reaches your app: inProcessClient feeds ctx.api to loaders, and createWebApp serves the backend over HTTP at the apiPrefix you set - in `nifra dev` and production alike, no hand-dispatch.
|
|
7433
8000
|
|
|
7434
8001
|
Your `@nifrajs/core` backend reaches a Nifra app two ways from a single wiring.
|
|
7435
8002
|
`inProcessClient(backend)` is fed to every loader and action as
|
|
7436
|
-
`ctx.api` - and `createWebApp`
|
|
7437
|
-
backend over HTTP
|
|
7438
|
-
hand-written `if (pathname.startsWith("/api/"))` branch in your server
|
|
8003
|
+
`ctx.api` - and, given an `apiPrefix`, `createWebApp`
|
|
8004
|
+
**mounts** that backend over HTTP there, so the browser can call the very same
|
|
8005
|
+
routes. No hand-written `if (pathname.startsWith("/api/"))` branch in your server
|
|
8006
|
+
entry.
|
|
7439
8007
|
|
|
7440
8008
|
### One backend, two call paths
|
|
7441
8009
|
|
|
@@ -7475,20 +8043,19 @@ export async function loader({ api }: Route.LoaderArgs) {
|
|
|
7475
8043
|
}
|
|
7476
8044
|
```
|
|
7477
8045
|
|
|
7478
|
-
### The
|
|
8046
|
+
### The mounted /api/*
|
|
7479
8047
|
|
|
7480
|
-
|
|
7481
|
-
backend over HTTP
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
`apiPrefix` (default `/api`) or starts with
|
|
8048
|
+
Without an `apiPrefix`, `inProcessClient` feeds `ctx.api` but
|
|
8049
|
+
the backend is _not_ served over HTTP, so a browser `POST /api/sync` reaches
|
|
8050
|
+
the page router. Set `apiPrefix: "/api"` and `createWebApp` mounts it: a
|
|
8051
|
+
request whose pathname is exactly `apiPrefix` or starts with
|
|
7485
8052
|
`apiPrefix + "/"` is dispatched through Nifra's platform-aware backend mount
|
|
7486
8053
|
interface **before** page routing. The backend receives the same Workers
|
|
7487
8054
|
`env` bindings and `waitUntil` lifetime as the web app, and its
|
|
7488
8055
|
`Response` is returned untouched (the request body is passed through, never pre-read).
|
|
7489
8056
|
|
|
7490
8057
|
```ts
|
|
7491
|
-
// backend/server.ts - a hand-written server: createWebApp serves pages AND
|
|
8058
|
+
// backend/server.ts - a hand-written server: createWebApp serves pages AND mounts the
|
|
7492
8059
|
// backend at /api/*. (`nifra build` emits this wiring for you; write it only for a custom host.)
|
|
7493
8060
|
import { inProcessClient } from "@nifrajs/client"
|
|
7494
8061
|
import { createWebApp } from "@nifrajs/web"
|
|
@@ -7500,8 +8067,8 @@ export const app = createWebApp({
|
|
|
7500
8067
|
adapter: reactAdapter,
|
|
7501
8068
|
manifest,
|
|
7502
8069
|
clientEntry,
|
|
7503
|
-
api: inProcessClient(backend), // → ctx.api in loaders/actions
|
|
7504
|
-
|
|
8070
|
+
api: inProcessClient(backend), // → ctx.api in loaders/actions
|
|
8071
|
+
apiPrefix: "/api", // AND served over HTTP at /api/*; without it the backend is loader-only
|
|
7505
8072
|
})
|
|
7506
8073
|
|
|
7507
8074
|
// Bun: Bun.serve({ fetch: app.fetch }). No `if (pathname.startsWith("/api/")) …` branch needed -
|
|
@@ -7513,8 +8080,16 @@ for **every method** - so `GET`/`POST`/`PUT`/… all
|
|
|
7513
8080
|
reach the backend, and an unknown `/api/…` path returns the _backend's_ 404,
|
|
7514
8081
|
not the page's. A sibling path that merely shares the prefix string (e.g.
|
|
7515
8082
|
`/apidocs`) is **not** captured - only the `/api` boundary
|
|
7516
|
-
is.
|
|
7517
|
-
loader-only client.
|
|
8083
|
+
is. Leave `apiPrefix` unset, or set it to `""`, to keep
|
|
8084
|
+
`ctx.api` as a loader-only client.
|
|
8085
|
+
|
|
8086
|
+
Loader-only is the default because an app that also serves the same backend behind its own
|
|
8087
|
+
server - with that server's rate limits and guards - would otherwise get a second path to it
|
|
8088
|
+
that skips them. `nifra check` reports `NF-C035` for a
|
|
8089
|
+
`backend/framework.ts` that sets no `apiPrefix`, so the choice is
|
|
8090
|
+
written down: `nifra fix --code NF-C035` adds
|
|
8091
|
+
`export const apiPrefix = "/api"`, which is what 4.x mounted by default, and
|
|
8092
|
+
`""` keeps the backend loader-only.
|
|
7518
8093
|
|
|
7519
8094
|
Because the backend answers first and its 404 is final, a page file under the prefix (
|
|
7520
8095
|
`routes/api/report.tsx`) could never render. Nifra refuses it instead of serving a
|
|
@@ -7549,8 +8124,8 @@ export const use = (app: { get(path: string, handler: () => Response): unknown }
|
|
|
7549
8124
|
|
|
7550
8125
|
[!NOTE] The mount lives in `createWebApp`, and `nifra dev` (the
|
|
7551
8126
|
Vite-backed dev server) routes every request through that same app's `fetch`. So
|
|
7552
|
-
the
|
|
7553
|
-
|
|
8127
|
+
the mounted routes are served identically in development and production - there is nothing
|
|
8128
|
+
extra to wire for the dev loop.
|
|
7554
8129
|
|
|
7555
8130
|
### Events that survive the transport
|
|
7556
8131
|
|
|
@@ -7650,6 +8225,7 @@ const agents = server().get("/api/agents", (c) => ({ ok: new URL(c.req.url).path
|
|
|
7650
8225
|
|
|
7651
8226
|
// merge() folds both groups into ONE flat path space at their defined paths; a path+method clash
|
|
7652
8227
|
// throws a RouteConfigError right here at merge time (fail-closed), never a silent shadow at runtime.
|
|
8228
|
+
// A group holding .ws() routes or mount()s is refused the same way: add those on the parent.
|
|
7653
8229
|
export const backend = server().merge(listings).merge(agents)
|
|
7654
8230
|
```
|
|
7655
8231
|
|
|
@@ -7902,11 +8478,12 @@ than something we vouch for.
|
|
|
7902
8478
|
{
|
|
7903
8479
|
"schemaVersion": 1,
|
|
7904
8480
|
"ok": true,
|
|
7905
|
-
"profile": { "id": "cache-store", "version":
|
|
8481
|
+
"profile": { "id": "cache-store", "version": 2 },
|
|
7906
8482
|
"adapterId": "redis-cache",
|
|
7907
8483
|
"capabilities": [
|
|
7908
|
-
{ "capability": "
|
|
7909
|
-
{ "capability": "tag-invalidation", "status": "passed", "checks": ["invalidate-tag"] }
|
|
8484
|
+
{ "capability": "roundtrip", "status": "passed", "checks": ["set-get-delete"] },
|
|
8485
|
+
{ "capability": "tag-invalidation", "status": "passed", "checks": ["invalidate-tag"] },
|
|
8486
|
+
{ "capability": "clear", "status": "passed", "checks": ["clear"] }
|
|
7910
8487
|
],
|
|
7911
8488
|
"evidenceHash": "9f2c..."
|
|
7912
8489
|
}
|
|
@@ -7972,11 +8549,13 @@ A route is two files. The page (`x.tsx`) holds the view and its head:
|
|
|
7972
8549
|
// own backend, so it can't compile standalone (see the typed examples elsewhere).
|
|
7973
8550
|
import type { Route } from "./+types/[id]"
|
|
7974
8551
|
|
|
7975
|
-
// Static OR a function of { data, params, origin }. Static meta is serialized once; function
|
|
7976
|
-
// recomputes per request (its content can vary with loader data). `
|
|
7977
|
-
//
|
|
7978
|
-
//
|
|
7979
|
-
|
|
8552
|
+
// Static OR a function of { data, pageData, params, origin }. Static meta is serialized once; function
|
|
8553
|
+
// meta recomputes per request (its content can vary with loader data). `data` is the exporting
|
|
8554
|
+
// module's own loader data: a layout's meta gets its layout's (null without a loader) and reads the
|
|
8555
|
+
// page's as `pageData`. `origin` is the request's scheme+host (server-resolved, matches the
|
|
8556
|
+
// client's location.origin) - use it for ABSOLUTE canonical / og:url / og:image URLs without threading
|
|
8557
|
+
// siteUrl through loader data.
|
|
8558
|
+
export const meta = ({ data, params, origin }: Route.MetaArgs) => ({
|
|
7980
8559
|
title: `User ${data.name}`,
|
|
7981
8560
|
meta: [{ name: "description", content: data.bio }],
|
|
7982
8561
|
link: [{ rel: "canonical", href: `${origin}/users/${params.id}` }],
|
|
@@ -8241,7 +8820,7 @@ base, a CDN host) belongs in the `loader`, not `meta()`.
|
|
|
8241
8820
|
```ts
|
|
8242
8821
|
// routes/articles/[slug].tsx - a complete SEO head: canonical + Open Graph + Twitter + JSON-LD,
|
|
8243
8822
|
// with ABSOLUTE URLs built from `origin`. The three helpers (canonical/openGraph/jsonLd) are public
|
|
8244
|
-
// exports of @nifrajs/web; `origin` is
|
|
8823
|
+
// exports of @nifrajs/web; `origin` is a MetaArgs field beside data, pageData and params.
|
|
8245
8824
|
import { canonical, jsonLd, openGraph, type MetaArgs } from "@nifrajs/web"
|
|
8246
8825
|
|
|
8247
8826
|
// CAVEAT: meta() runs in BOTH SSR and client navigation, so it has NO server env - never read
|
|
@@ -8329,6 +8908,8 @@ const page = renderPage({
|
|
|
8329
8908
|
})
|
|
8330
8909
|
```
|
|
8331
8910
|
|
|
8911
|
+
#### A strict CSP that still caches
|
|
8912
|
+
|
|
8332
8913
|
A nonce makes every document unique, so a nonce-bearing page is
|
|
8333
8914
|
`private, no-store` and no shared cache - `withISR`, a CDN - may store
|
|
8334
8915
|
it. Page data never needs one: it rides in an inert
|
|
@@ -8386,6 +8967,16 @@ file that reaches it.
|
|
|
8386
8967
|
// same layout-chain head, so sitewide tags persist across navigation - no page-only flash.
|
|
8387
8968
|
```
|
|
8388
8969
|
|
|
8970
|
+
### Optional server components
|
|
8971
|
+
|
|
8972
|
+
The post-4.0 [server-components pipeline](/docs/server-components) supports
|
|
8973
|
+
React, Vue, Svelte and Solid. Declare closed input/public-props schemas and authorization
|
|
8974
|
+
in `*.component.server.ts(x)`, register owner instances and export a named
|
|
8975
|
+
`componentPipeline` configuration. React uses native RSC/Flight; the other three
|
|
8976
|
+
hydrate native islands in validated server ranges. Refresh repeats owner GET admission;
|
|
8977
|
+
HTML/data/refresh are private/no-store. Writes use server functions or actions. Bind the
|
|
8978
|
+
router and await auth-scope invalidation on identity, tenant or permission changes.
|
|
8979
|
+
|
|
8389
8980
|
### The build-output contract
|
|
8390
8981
|
|
|
8391
8982
|
The programmatic `buildClient()` API emits a content-hashed directory plus a
|
|
@@ -8554,6 +9145,399 @@ See [SSG & ISR](/docs/rendering) for prerendering, [Dev & HMR](/docs/dev) for th
|
|
|
8554
9145
|
two dev loops and the `--port` flag, and [Loaders & actions](/docs/data) for the
|
|
8555
9146
|
data layer in depth.
|
|
8556
9147
|
|
|
9148
|
+
## Nifra - Control APIs (one schema, every client)
|
|
9149
|
+
|
|
9150
|
+
> Build an RPC-style control API on core routes: one schema per method gives request validation, a typed TypeScript client, and an OpenAPI 3.2 document whose SSE events let Swift, Kotlin, or Python clients be generated.
|
|
9151
|
+
|
|
9152
|
+
A desktop app, a CLI, or a mobile companion that drives a local service needs a control API:
|
|
9153
|
+
a set of methods, a stream of events, and clients in more than one language. Write each
|
|
9154
|
+
method once as a route with a schema. The schema validates requests, types the TypeScript
|
|
9155
|
+
client, and describes the method in an OpenAPI document that generators turn into clients for
|
|
9156
|
+
other languages.
|
|
9157
|
+
|
|
9158
|
+
### One file, three methods
|
|
9159
|
+
|
|
9160
|
+
Two methods take a JSON body and answer JSON. The third is a typed event stream. This file
|
|
9161
|
+
typechecks against the current packages.
|
|
9162
|
+
|
|
9163
|
+
```ts
|
|
9164
|
+
import { timingSafeEqual } from "node:crypto"
|
|
9165
|
+
import { server } from "@nifrajs/core"
|
|
9166
|
+
import { streaming } from "@nifrajs/core/sse"
|
|
9167
|
+
import { t, toOpenAPI } from "@nifrajs/schema"
|
|
9168
|
+
|
|
9169
|
+
const token = process.env.CONTROL_TOKEN
|
|
9170
|
+
if (token === undefined || token.length < 32) throw new Error("CONTROL_TOKEN must be 32+ characters")
|
|
9171
|
+
const expected = Buffer.from(`Bearer ${token}`)
|
|
9172
|
+
const authorized = (request: Request): boolean => {
|
|
9173
|
+
const given = Buffer.from(request.headers.get("authorization") ?? "")
|
|
9174
|
+
return given.length === expected.length && timingSafeEqual(given, expected)
|
|
9175
|
+
}
|
|
9176
|
+
|
|
9177
|
+
const Session = t.object({
|
|
9178
|
+
id: t.string(),
|
|
9179
|
+
status: t.union([t.literal("idle"), t.literal("running")]),
|
|
9180
|
+
})
|
|
9181
|
+
const TurnEvent = t.object({
|
|
9182
|
+
seq: t.integer({ minimum: 0 }),
|
|
9183
|
+
type: t.string(),
|
|
9184
|
+
text: t.optional(t.string()),
|
|
9185
|
+
})
|
|
9186
|
+
|
|
9187
|
+
export const app = server()
|
|
9188
|
+
.use(streaming())
|
|
9189
|
+
.onRequest((request) =>
|
|
9190
|
+
authorized(request) ? undefined : Response.json({ error: "unauthorized" }, { status: 401 }),
|
|
9191
|
+
)
|
|
9192
|
+
.post(
|
|
9193
|
+
"/session/create",
|
|
9194
|
+
{ body: t.object({ cwd: t.string({ minLength: 1, maxLength: 4096 }) }), response: Session },
|
|
9195
|
+
() => ({ id: crypto.randomUUID(), status: "idle" as const }),
|
|
9196
|
+
)
|
|
9197
|
+
.post(
|
|
9198
|
+
"/turn/send",
|
|
9199
|
+
{
|
|
9200
|
+
body: t.object({
|
|
9201
|
+
sessionId: t.string(),
|
|
9202
|
+
message: t.string({ minLength: 1, maxLength: 256 * 1024 }),
|
|
9203
|
+
}),
|
|
9204
|
+
response: t.object({ turnId: t.string() }),
|
|
9205
|
+
bodyLimit: 512 * 1024,
|
|
9206
|
+
},
|
|
9207
|
+
() => ({ turnId: crypto.randomUUID() }),
|
|
9208
|
+
)
|
|
9209
|
+
.sse(
|
|
9210
|
+
"/turn/events",
|
|
9211
|
+
{ query: t.object({ turnId: t.string() }), sse: TurnEvent },
|
|
9212
|
+
(c, stream) => {
|
|
9213
|
+
const after = Number(c.req.headers.get("last-event-id") ?? -1)
|
|
9214
|
+
for (let seq = after + 1; seq < 3; seq++)
|
|
9215
|
+
stream.send({ seq, type: "assistant.delta", text: `part ${seq}` }, { id: String(seq) })
|
|
9216
|
+
stream.close()
|
|
9217
|
+
},
|
|
9218
|
+
)
|
|
9219
|
+
|
|
9220
|
+
export const spec = toOpenAPI(app, { openapiVersion: "3.2.0", title: "Control API", version: "1.0.0" })
|
|
9221
|
+
```
|
|
9222
|
+
|
|
9223
|
+
-
|
|
9224
|
+
**Methods are routes.** A method name like `session/create` is a path, so a
|
|
9225
|
+
call is `POST /session/create` and the typed client reads
|
|
9226
|
+
`api.session.create.post(body)`. Use `POST` for anything that changes
|
|
9227
|
+
state; one envelope endpoint that dispatches on a method field would hide every method
|
|
9228
|
+
from the schema, the client, and the document.
|
|
9229
|
+
|
|
9230
|
+
-
|
|
9231
|
+
**The schema is the validator.** A body that does not match answers `422`
|
|
9232
|
+
before the handler runs. `maxLength` bounds each field and
|
|
9233
|
+
`bodyLimit` bounds the request as it arrives.
|
|
9234
|
+
|
|
9235
|
+
-
|
|
9236
|
+
**Authorization runs first.** An `onRequest` hook that returns a response
|
|
9237
|
+
ends the request before routing, so no method is reachable without the token. The
|
|
9238
|
+
comparison is constant-time, and the server refuses to start without a long token.
|
|
9239
|
+
|
|
9240
|
+
-
|
|
9241
|
+
**Retries.** A method that must not run twice declares
|
|
9242
|
+
`idempotency` on its route, with `.use(idempotency())` from
|
|
9243
|
+
`@nifrajs/core/idempotency-plugin` and a namespace per caller.
|
|
9244
|
+
|
|
9245
|
+
### Event streams that resume
|
|
9246
|
+
|
|
9247
|
+
`.sse(path, { sse: schema }, run)` declares the type of every event the stream
|
|
9248
|
+
sends. Give each event an `id` (here its sequence number). A browser
|
|
9249
|
+
`EventSource`, or the typed client's `subscribe`, sends the last id it
|
|
9250
|
+
saw as `Last-Event-ID` when it reconnects, so the handler replays only what the
|
|
9251
|
+
client missed. Keep the events a reconnect may need in memory or in your own store: core
|
|
9252
|
+
sends the header, and what to replay is your service's to decide.
|
|
9253
|
+
|
|
9254
|
+
### The TypeScript client
|
|
9255
|
+
|
|
9256
|
+
```ts
|
|
9257
|
+
// doc-check: skip - fragment: app is the control server above, imported from its module.
|
|
9258
|
+
import { client } from "@nifrajs/client"
|
|
9259
|
+
|
|
9260
|
+
const api = client<typeof app>("http://127.0.0.1:4100", {
|
|
9261
|
+
headers: { authorization: `Bearer ${token}` },
|
|
9262
|
+
})
|
|
9263
|
+
|
|
9264
|
+
const session = await api.session.create.post({ cwd: "/work/repo" })
|
|
9265
|
+
if (!session.ok) throw new Error(`create failed: ${session.status}`)
|
|
9266
|
+
|
|
9267
|
+
const subscription = api.turn.events.subscribe((event) => render(event.seq, event.text ?? ""), {
|
|
9268
|
+
query: { turnId },
|
|
9269
|
+
// After a dropped connection the client reconnects and sends Last-Event-ID itself.
|
|
9270
|
+
})
|
|
9271
|
+
// subscription.close() ends it.
|
|
9272
|
+
```
|
|
9273
|
+
|
|
9274
|
+
Every call is typed from the server's routes, with no generated code. A call returns a
|
|
9275
|
+
`Result`: check `ok`, then read `data`, or
|
|
9276
|
+
`status` and `error`.
|
|
9277
|
+
|
|
9278
|
+
### Clients in other languages
|
|
9279
|
+
|
|
9280
|
+
`toOpenAPI(app, { openapiVersion: "3.2.0" })` from `@nifrajs/schema`
|
|
9281
|
+
describes each method's request and response, and each typed stream as
|
|
9282
|
+
`text/event-stream` whose `itemSchema` is the SSE event: a
|
|
9283
|
+
`data` field holding JSON that matches the route's event schema, plus
|
|
9284
|
+
`event`, `id`, and `retry`. The event schema is also a named
|
|
9285
|
+
component (`TurnEventsEvent` here), so a generator emits a type for it. The
|
|
9286
|
+
`openapi()` plugin from `@nifrajs/middleware` takes the same option and
|
|
9287
|
+
serves the document.
|
|
9288
|
+
|
|
9289
|
+
```ts
|
|
9290
|
+
// doc-check: skip - shell commands, not TypeScript.
|
|
9291
|
+
// Write the document at build time, then hand it to any OpenAPI 3.2 generator.
|
|
9292
|
+
bun -e 'import { spec } from "./control.ts"; await Bun.write("openapi.json", JSON.stringify(spec, null, 2))'
|
|
9293
|
+
```
|
|
9294
|
+
|
|
9295
|
+
OpenAPI 3.1 has no field for the items of a stream. Without `openapiVersion` the
|
|
9296
|
+
document stays 3.1, declares the stream as a string, and carries the same item schema as
|
|
9297
|
+
`x-itemSchema`; the named event component is there either way. A generator that
|
|
9298
|
+
reads only 3.1 still produces the event type, and your code decodes each
|
|
9299
|
+
`data` line with it.
|
|
9300
|
+
|
|
9301
|
+
## Nifra - Custom server (createWebApp)
|
|
9302
|
+
|
|
9303
|
+
> Run a page app from your own server entry with createWebApp: static and hashed files, a strict CSP on nifra's nonce, middleware order, loader error logging, graceful shutdown, and nifra check settings.
|
|
9304
|
+
|
|
9305
|
+
The standard project layout gets its server entry from `nifra build`. An app that
|
|
9306
|
+
calls `createWebApp` from its own entry wires the same pieces by hand. Each one is an
|
|
9307
|
+
option or a single call.
|
|
9308
|
+
|
|
9309
|
+
### A complete entry
|
|
9310
|
+
|
|
9311
|
+
Everything on this page in one file. The sections below explain each part. This file
|
|
9312
|
+
typechecks against the current packages.
|
|
9313
|
+
|
|
9314
|
+
```ts
|
|
9315
|
+
import { fileURLToPath } from "node:url"
|
|
9316
|
+
import { createCspPolicy, createWebApp, servePublicDir } from "@nifrajs/web"
|
|
9317
|
+
import type { Manifest, RenderAdapter } from "@nifrajs/web"
|
|
9318
|
+
|
|
9319
|
+
// From your build: the framework adapter, the route manifest, and what buildClient() returned.
|
|
9320
|
+
declare const adapter: RenderAdapter
|
|
9321
|
+
declare const manifest: Manifest
|
|
9322
|
+
declare const build: {
|
|
9323
|
+
readonly entry: string
|
|
9324
|
+
readonly assets: readonly string[]
|
|
9325
|
+
readonly routes: Readonly<Record<string, readonly string[]>>
|
|
9326
|
+
readonly css?: readonly string[]
|
|
9327
|
+
}
|
|
9328
|
+
declare const errors: { capture(error: unknown, tags: Record<string, string>): void }
|
|
9329
|
+
|
|
9330
|
+
// buildClient({ outDir: "dist/assets" }) writes the chunks where their /assets/ URLs point.
|
|
9331
|
+
const bundle = servePublicDir({
|
|
9332
|
+
dir: fileURLToPath(new URL("./dist/", import.meta.url)),
|
|
9333
|
+
hashedFiles: new Set(build.assets),
|
|
9334
|
+
})
|
|
9335
|
+
const publicFiles = servePublicDir({ dir: fileURLToPath(new URL("./public/", import.meta.url)) })
|
|
9336
|
+
|
|
9337
|
+
const csp = createCspPolicy({
|
|
9338
|
+
header: ({ sources }) => `default-src 'self'; script-src 'self' ${sources}; object-src 'none'`,
|
|
9339
|
+
})
|
|
9340
|
+
|
|
9341
|
+
export const app = createWebApp({
|
|
9342
|
+
adapter,
|
|
9343
|
+
manifest,
|
|
9344
|
+
clientEntry: build.entry,
|
|
9345
|
+
routePreload: build.routes,
|
|
9346
|
+
styles: build.css ?? [],
|
|
9347
|
+
csp,
|
|
9348
|
+
server: { requestTimeoutMs: 15_000, gracefulSignals: { drainMs: 8_000 } },
|
|
9349
|
+
use: (app) => {
|
|
9350
|
+
app.onRequest(async (request) => (await bundle(request)) ?? (await publicFiles(request)))
|
|
9351
|
+
},
|
|
9352
|
+
onLoaderError: (error, { route }) => errors.capture(error, { route }),
|
|
9353
|
+
})
|
|
9354
|
+
|
|
9355
|
+
app.listen(3000)
|
|
9356
|
+
```
|
|
9357
|
+
|
|
9358
|
+
### Static and hashed files
|
|
9359
|
+
|
|
9360
|
+
`servePublicDir` from `@nifrajs/web` serves a directory on Bun, Node and
|
|
9361
|
+
Deno. It answers `GET` and `HEAD` only, refuses paths that leave the
|
|
9362
|
+
directory or name a hidden file, sets the content type and `nosniff`, handles range
|
|
9363
|
+
requests, and returns `undefined` on a miss so the request falls through to your
|
|
9364
|
+
routes.
|
|
9365
|
+
|
|
9366
|
+
-
|
|
9367
|
+
**Hashed build output.** Pass `buildClient()`'s `assets` as
|
|
9368
|
+
`hashedFiles`: those files get `immutable` for a year, and anything
|
|
9369
|
+
else in the directory gets one day. Write the build to `outDir: "dist/assets"` so
|
|
9370
|
+
a chunk's `/assets/...` URL is also its path under `dist/`.
|
|
9371
|
+
|
|
9372
|
+
-
|
|
9373
|
+
**Your own files.** A second handler on `public/` serves them at the root,
|
|
9374
|
+
with the one-day policy.
|
|
9375
|
+
|
|
9376
|
+
-
|
|
9377
|
+
**Where it runs.** Register the handlers with `app.onRequest` inside
|
|
9378
|
+
`use`, so a static hit never reaches page rendering.
|
|
9379
|
+
|
|
9380
|
+
On Node, `serve()` from `@nifrajs/node` can serve the bundle itself before
|
|
9381
|
+
the app runs, which keeps the direct request path for everything else:
|
|
9382
|
+
|
|
9383
|
+
```ts
|
|
9384
|
+
// doc-check: skip - fragment: app and build are the createWebApp app and buildClient() output above.
|
|
9385
|
+
import { serve } from "@nifrajs/node"
|
|
9386
|
+
|
|
9387
|
+
await serve(app, {
|
|
9388
|
+
port: 3000,
|
|
9389
|
+
static: { dir: new URL("./dist/assets/", import.meta.url), hashedFiles: build.assets },
|
|
9390
|
+
signals: { drainMs: 8_000 },
|
|
9391
|
+
})
|
|
9392
|
+
```
|
|
9393
|
+
|
|
9394
|
+
### A strict CSP on nifra's nonce
|
|
9395
|
+
|
|
9396
|
+
Pass `createCspPolicy` as `csp` and nifra writes the
|
|
9397
|
+
`Content-Security-Policy` header for each document. You do not stash a nonce or set
|
|
9398
|
+
the header yourself. Your `header` function receives `sources`: a hash
|
|
9399
|
+
for each constant inline script nifra writes, plus a nonce only when the document carries a
|
|
9400
|
+
request-specific script (a `defer()`ed value, or `unsafeInlineScript` in
|
|
9401
|
+
`meta`).
|
|
9402
|
+
|
|
9403
|
+
A document without a nonce gets the same header on every request, so it can still be cached by
|
|
9404
|
+
`withISR` or a CDN. A document with a nonce is sent `private, no-store`.
|
|
9405
|
+
The older `nonce` option puts a nonce on every document, so nothing is cacheable.
|
|
9406
|
+
`createWebApp` refuses `csp` and `nonce` together.
|
|
9407
|
+
|
|
9408
|
+
### Middleware goes in use
|
|
9409
|
+
|
|
9410
|
+
`createWebApp` declares every page route, and the `/*` catch-all, before
|
|
9411
|
+
it returns. `beforeHandle`, `afterHandle`, `around`,
|
|
9412
|
+
`derive`, `decorate` and `onError` are copied into each route
|
|
9413
|
+
when the route is declared. One of those hooks added to the returned app would therefore reach
|
|
9414
|
+
no page. `requestId()` is a `derive`: added late, no page would see
|
|
9415
|
+
`c.requestId`. Outside production, adding one throws a `FrameworkError`
|
|
9416
|
+
with code `HOOK_AFTER_PAGES` that names the call site. Hooks added inside
|
|
9417
|
+
`app.group(prefix, ...)` are still allowed, because a group is its own scope.
|
|
9418
|
+
|
|
9419
|
+
Apply middleware in the `use` option instead. It runs before any page is declared,
|
|
9420
|
+
and ahead of `mounts` and the `api` mount, so an `onRequest`
|
|
9421
|
+
guard also covers a mounted auth handler:
|
|
9422
|
+
|
|
9423
|
+
```ts
|
|
9424
|
+
// doc-check: skip - fragment: the use option of the createWebApp call above; @nifrajs/middleware is not installed in the docs sandbox.
|
|
9425
|
+
import { requestId, securityHeaders } from "@nifrajs/middleware"
|
|
9426
|
+
|
|
9427
|
+
createWebApp({
|
|
9428
|
+
// ...
|
|
9429
|
+
use: (app) => {
|
|
9430
|
+
app.use(securityHeaders())
|
|
9431
|
+
app.use(requestId())
|
|
9432
|
+
},
|
|
9433
|
+
})
|
|
9434
|
+
```
|
|
9435
|
+
|
|
9436
|
+
`onRequest` and `onResponse` are read per request, so those two still
|
|
9437
|
+
work when added late. Putting everything in `use` means you never need to remember
|
|
9438
|
+
which hooks those are.
|
|
9439
|
+
|
|
9440
|
+
A plugin applied in `use` works at runtime but cannot add to the returned app's type.
|
|
9441
|
+
To get `c.requestId` typed on routes you declare afterwards, call
|
|
9442
|
+
`app.use(requestId())` again on the returned app. Named plugins apply once.
|
|
9443
|
+
|
|
9444
|
+
### Loader errors
|
|
9445
|
+
|
|
9446
|
+
When a loader or action throws and an `_error` boundary renders the page, the error
|
|
9447
|
+
is written to the server's `logger`, at the detail set by
|
|
9448
|
+
`server.errorLogDetail`. That is the same log line an unhandled 500 produces, and you
|
|
9449
|
+
do not need to configure anything for it. A soft-navigation data request that fails is logged
|
|
9450
|
+
the same way. A thrown `Response`, such as `redirect()`, is not an
|
|
9451
|
+
error and is not logged.
|
|
9452
|
+
|
|
9453
|
+
`onLoaderError` is for an error reporter. It sees every real throw with the request,
|
|
9454
|
+
params and route id before the boundary renders. If it throws, the error is ignored so rendering
|
|
9455
|
+
continues.
|
|
9456
|
+
|
|
9457
|
+
### Graceful shutdown
|
|
9458
|
+
|
|
9459
|
+
On Bun, `server: { gracefulSignals: { drainMs } }` makes `listen()`
|
|
9460
|
+
handle `SIGTERM` and `SIGINT`. The server gives in-flight requests up
|
|
9461
|
+
to `drainMs` (default 10 000) to finish, then stops, closing any that are still
|
|
9462
|
+
running. On Bun, new connections can still arrive during that window, so the load balancer
|
|
9463
|
+
should stop routing to the instance first, as most platforms do before they send
|
|
9464
|
+
`SIGTERM`. Keep `drainMs` under your platform's kill grace period:
|
|
9465
|
+
`docker stop` waits 10 seconds. `true` uses the default. On Node and
|
|
9466
|
+
Deno, pass
|
|
9467
|
+
`signals: { drainMs }` to `serve()` instead, as above.
|
|
9468
|
+
|
|
9469
|
+
### Letting check and assure read the app
|
|
9470
|
+
|
|
9471
|
+
`nifra check`, `assure`, `routes` and the other commands that
|
|
9472
|
+
read the app import it, so everything the app does at module scope runs. A command that only
|
|
9473
|
+
reads what the app declares sets `NIFRA_REFLECT` to its own name first:
|
|
9474
|
+
`check`, `routes`, `context`, `openapi`,
|
|
9475
|
+
`contracts`, `capabilities`, `manifest`, `doctor`
|
|
9476
|
+
, `diff`, `sync-routes`, `sync-manifest`, and
|
|
9477
|
+
`assure` without `--bundle`, `--strict`,
|
|
9478
|
+
`--hydration`, `--interact` or `--out`. Commands that send the
|
|
9479
|
+
app requests, such as `smoke`, `prove`, `levels` and the
|
|
9480
|
+
`assure` bundle, leave it unset. Check it to skip work a read never needs:
|
|
9481
|
+
|
|
9482
|
+
```ts
|
|
9483
|
+
// doc-check: skip - fragment: an app module's Redis client; ioredis is not installed in the docs sandbox.
|
|
9484
|
+
import Redis from "ioredis"
|
|
9485
|
+
|
|
9486
|
+
// Unset when the app really runs; "check", "routes", "assure", ... while nifra only reads it.
|
|
9487
|
+
export const redis = process.env.NIFRA_REFLECT ? undefined : new Redis(process.env.REDIS_URL!)
|
|
9488
|
+
```
|
|
9489
|
+
|
|
9490
|
+
-
|
|
9491
|
+
**Environment validation.** An app that validates its environment on import still needs
|
|
9492
|
+
values to pass it. Supply them with `--env-file`, for example
|
|
9493
|
+
`nifra check --env-file .env.example`. Variables already set in the shell win.
|
|
9494
|
+
|
|
9495
|
+
-
|
|
9496
|
+
**A process that will not exit.** If something the app opened is still keeping the process
|
|
9497
|
+
alive 2 seconds after the command has answered, the command exits with its own result and
|
|
9498
|
+
says that the app kept it open.
|
|
9499
|
+
|
|
9500
|
+
### nifra check for a custom entry
|
|
9501
|
+
|
|
9502
|
+
-
|
|
9503
|
+
**Routes nifra cannot see.** A mounted handler such as better-auth answers paths that are
|
|
9504
|
+
not in your backend, and no typed client covers them. Declare the mount opaque,
|
|
9505
|
+
`
|
|
9506
|
+
mounts: [{ path: "/api/auth", app: auth, opaque: "better-auth" }]
|
|
9507
|
+
`
|
|
9508
|
+
, and when `nifra.assurance.ts` names this app as its `source`, the
|
|
9509
|
+
check skips a `fetch` into that prefix by itself. Without that, list the prefixes
|
|
9510
|
+
in `nifra.check.json` next to `package.json`:
|
|
9511
|
+
|
|
9512
|
+
```ts
|
|
9513
|
+
{
|
|
9514
|
+
"externalMounts": ["/api/auth"]
|
|
9515
|
+
}
|
|
9516
|
+
```
|
|
9517
|
+
|
|
9518
|
+
-
|
|
9519
|
+
**One call nifra cannot type.** For a single call rather than a whole prefix, such as a
|
|
9520
|
+
static file, put `// nifra-expect NF-C002: ` on the line or in the
|
|
9521
|
+
comment block above it. The finding stays in the report as info, with your reason. A comment
|
|
9522
|
+
with no reason silences nothing.
|
|
9523
|
+
|
|
9524
|
+
-
|
|
9525
|
+
**Two repositories, one runtime.** When a linked sibling repository brings its own install
|
|
9526
|
+
of nifra or React, `nifra check` reports the duplicates. A reinstall in this app
|
|
9527
|
+
cannot remove a copy that another project installed. Declare the packages single-copy and
|
|
9528
|
+
nifra loads this app's copy for every importer:
|
|
9529
|
+
|
|
9530
|
+
```ts
|
|
9531
|
+
{
|
|
9532
|
+
"name": "my-app",
|
|
9533
|
+
"nifra": { "singleCopy": ["@nifrajs/*", "react", "react-dom"] }
|
|
9534
|
+
}
|
|
9535
|
+
```
|
|
9536
|
+
|
|
9537
|
+
Packages duplicated the same way are reported as one finding with one combined
|
|
9538
|
+
`singleCopy` list. The preload this needs, and the version-skew case it cannot
|
|
9539
|
+
cover, are in [Troubleshooting](/docs/troubleshooting).
|
|
9540
|
+
|
|
8557
9541
|
## Nifra - Databases
|
|
8558
9542
|
|
|
8559
9543
|
> Nifra is database-agnostic: use SQLite, Postgres, or any ORM (Drizzle, Prisma, Kysely) from your handlers and loaders, on Bun, Node, Deno, and the edge.
|
|
@@ -8929,7 +9913,10 @@ Route data failed the output guard that stands between loaders/actions and the b
|
|
|
8929
9913
|
|
|
8930
9914
|
**Fix:** declare `loaderOutput`/`actionOutput` as a Standard
|
|
8931
9915
|
Schema that describes exactly the fields the page needs. `nifra check` flags routes
|
|
8932
|
-
without one (NF-C030)
|
|
9916
|
+
without one (NF-C030): an error when the loader's own body returns a literal (an object,
|
|
9917
|
+
array, string, number or boolean), since every request that reaches that return fails, and a
|
|
9918
|
+
warning when only running it shows what it returns. `nifra schemas --write` drafts
|
|
9919
|
+
a schema from the loader's return type for review.
|
|
8933
9920
|
|
|
8934
9921
|
### Rendering and data
|
|
8935
9922
|
|
|
@@ -8992,6 +9979,22 @@ serving the old page until its freshness runs out. The revalidate endpoint answe
|
|
|
8992
9979
|
permission for this zone, project or service; a 5xx or `network_error` is retried on
|
|
8993
9980
|
its own.
|
|
8994
9981
|
|
|
9982
|
+
### Silencing a nifra check finding
|
|
9983
|
+
|
|
9984
|
+
`nifra check` names each finding with an `NF-` code. Where a finding is
|
|
9985
|
+
reviewed and intended, a comment on the flagged line, or anywhere in the comment block directly
|
|
9986
|
+
above it, says so and why:
|
|
9987
|
+
|
|
9988
|
+
The comment answers only the code it names. A finding that fails the check, and every security
|
|
9989
|
+
finding, is silenced only with a reason after the colon; an advisory also accepts the comment
|
|
9990
|
+
without one. A blank line or a line of code ends the block, so a comment never reaches the next
|
|
9991
|
+
statement. A silenced security finding, interpolated SQL (NF-C006) included, stays in the report
|
|
9992
|
+
as information, with the comment and its reason as evidence. Some codes also answer to a name: `sql-dynamic` (NF-C006),
|
|
9993
|
+
`raw-response` (NF-C007), `reserved-segment` (NF-C018),
|
|
9994
|
+
`route-overlap` (NF-C024 and NF-C025) and `param-modifier` (NF-C026).
|
|
9995
|
+
`@nifra-gate-reviewed` silences nothing; a finding under it names the comment to
|
|
9996
|
+
write instead.
|
|
9997
|
+
|
|
8995
9998
|
## Nifra - Failure laboratory
|
|
8996
9999
|
|
|
8997
10000
|
> Replayable durable-failure schedules on virtual time: crash after commit, duplicate delivery, reordering, budget expiry, and checkpoint contention.
|
|
@@ -10431,6 +11434,49 @@ in its `package.json`:
|
|
|
10431
11434
|
`{'"nifra": { "environment": "frontend" | "backend" | "shared" | "library" }'}`.
|
|
10432
11435
|
A file in no zone is a build error, so nothing is ever classified by accident.
|
|
10433
11436
|
|
|
11437
|
+
The app's own `package.json` can set sides too, so a folder that has to stay
|
|
11438
|
+
where it is, or a family of workspace packages, needs no edit of its own:
|
|
11439
|
+
|
|
11440
|
+
```ts
|
|
11441
|
+
{
|
|
11442
|
+
"name": "my-app",
|
|
11443
|
+
"nifra": {
|
|
11444
|
+
"zones": {
|
|
11445
|
+
"packages": { "@shop/*": "library", "@shop/db": "backend" },
|
|
11446
|
+
"backend": ["migrations"]
|
|
11447
|
+
}
|
|
11448
|
+
}
|
|
11449
|
+
}
|
|
11450
|
+
```
|
|
11451
|
+
|
|
11452
|
+
`packages` gives a side to a package that does not declare one, by exact name or
|
|
11453
|
+
by a prefix ending in `*`; a package's own declaration always wins.
|
|
11454
|
+
`backend`, `frontend` and `shared` list app folders that
|
|
11455
|
+
count as that zone folder. Builds, dev servers, `nifra check` and the editor
|
|
11456
|
+
plugin all read this setting.
|
|
11457
|
+
|
|
11458
|
+
A project that serves no pages has no browser, so the zones do not apply to it. Declare that
|
|
11459
|
+
in its `package.json` with `{'"nifra": { "kind": "api" }'}`:
|
|
11460
|
+
`nifra check` then skips the zone checks and the server-only import check there,
|
|
11461
|
+
and treats every file as server code. It is read from the nearest `package.json`
|
|
11462
|
+
and only takes values `"api"` and `"web"`. A directory with web config
|
|
11463
|
+
(`nifra.config.ts` exporting `adapter`, or
|
|
11464
|
+
`backend/framework.ts`) keeps its zone checks under it, and
|
|
11465
|
+
`nifra check` reports the clash (NF-C037); builds and dev servers never read it.
|
|
11466
|
+
|
|
11467
|
+
A directory with `backend/app.ts` and no web config is read as an API-only app
|
|
11468
|
+
whether or not it declares the kind: `nifra routes`, `nifra context`,
|
|
11469
|
+
`nifra openapi` and `nifra mcp` load `backend/app.ts` alone,
|
|
11470
|
+
and the tools, resources and prompts its backend declares reach `nifra mcp`.
|
|
11471
|
+
|
|
11472
|
+
Those commands, the contract lock and the route lints see only what
|
|
11473
|
+
`backend/app.ts` exports. An API whose app is built by a factory such as
|
|
11474
|
+
`createApp(deps)`, with its real dependencies wired in another entry, exports the
|
|
11475
|
+
same app from `backend/app.ts` built with stand-in dependencies, so every operation
|
|
11476
|
+
is visible without a database or secrets; `nifra check` names an implemented
|
|
11477
|
+
operation it does not serve (NF-C039). See
|
|
11478
|
+
[the stand-in entry](/docs/types-first#stub-entry).
|
|
11479
|
+
|
|
10434
11480
|
### A route is two files
|
|
10435
11481
|
|
|
10436
11482
|
The page renders; its backend half loads. A server-only export (`loader`,
|
|
@@ -10461,7 +11507,8 @@ import { notFound } from "@nifrajs/web"
|
|
|
10461
11507
|
import type { Route } from "./+types/[id]"
|
|
10462
11508
|
|
|
10463
11509
|
// What the page receives - and the ONLY fields that leave the server. A field the loader returns
|
|
10464
|
-
// but the schema does not declare is dropped before rendering
|
|
11510
|
+
// but the schema does not declare is dropped before rendering; in development the console names
|
|
11511
|
+
// each dropped field, so a missing declaration never shows up as a silently blank page.
|
|
10465
11512
|
export const loaderOutput = t.object({ name: t.string(), avatarUrl: t.string() })
|
|
10466
11513
|
|
|
10467
11514
|
export async function loader({ params, api }: Route.LoaderArgs) {
|
|
@@ -10529,9 +11576,28 @@ page, `frontend/` or `shared/` fails the build.
|
|
|
10529
11576
|
that contains what looks like a credential fails the build.
|
|
10530
11577
|
|
|
10531
11578
|
The same rules run in `nifra dev` (a refused module is answered with a 403 before
|
|
10532
|
-
its source is sent), in both bundlers, for every framework, and in `nifra check
|
|
10533
|
-
|
|
10534
|
-
|
|
11579
|
+
its source is sent), in both bundlers, for every framework, and in `nifra check`,
|
|
11580
|
+
which follows the imports the bundler follows: relative paths, tsconfig `paths`
|
|
11581
|
+
and `baseUrl`, `package.json` `imports`, and the
|
|
11582
|
+
`clientModule` the browser entry loads. An app on the older layout moves with `nifra migrate layout`: a dry run that
|
|
11583
|
+
reports every move, then `--write`. It follows the same imports, rewrites them
|
|
11584
|
+
and test-runner mocks (an alias through the same alias), moves the config's
|
|
11585
|
+
`clientModule` and component pipeline entries with their modules, adds the zone
|
|
11586
|
+
folders to tsconfig `include` and `.nifra/` to
|
|
11587
|
+
`.gitignore`, and leaves data files and folders zoned in
|
|
11588
|
+
`package.json` where they are. It reports what still needs a hand, such as an
|
|
11589
|
+
alias with no target that covers a moved file, or a specifier that names nothing installed.
|
|
11590
|
+
|
|
11591
|
+
An API-only app, one that declares `{'"nifra": { "kind": "api" }'}` or has no web
|
|
11592
|
+
config, no `routes/` and a `backend.ts` that exports
|
|
11593
|
+
`backend`, migrates as one: only
|
|
11594
|
+
`backend.ts` moves, to `backend/app.ts`, with the imports from it and
|
|
11595
|
+
to it. Nothing is split or zoned. The migration adds `backend` to tsconfig
|
|
11596
|
+
`include`, `.nifra/` to `.gitignore`, and the declaration
|
|
11597
|
+
to `package.json` when it is missing. Web config without `routes/`
|
|
11598
|
+
could be either kind of app, so the migration stops and asks which. Until an app moves,
|
|
11599
|
+
`nifra check` fails with NF-C038 on its root `backend.ts` or
|
|
11600
|
+
`framework.ts`, since no contract gate reads them there.
|
|
10535
11601
|
|
|
10536
11602
|
## Nifra - Contract-derived adversarial testing
|
|
10537
11603
|
|
|
@@ -10617,6 +11683,14 @@ await assertAdversarialContract(app, {
|
|
|
10617
11683
|
Missing, invalid, or unsynthesizable witnesses become explicit coverage gaps. The default is
|
|
10618
11684
|
fail-closed; `requireCoverage: false` makes gaps advisory.
|
|
10619
11685
|
|
|
11686
|
+
Declared `params` and `headers` get witnesses too, validated before any
|
|
11687
|
+
case runs, so a body or query case is rejected by the input it mutates. The harness never
|
|
11688
|
+
invents credentials: `authorization`, `cookie`, `x-api-key`
|
|
11689
|
+
and names that look like a key, token, secret or signature come from the witness and
|
|
11690
|
+
`prepareRequest` only, and are never mutated. A route with a `cookies`
|
|
11691
|
+
schema needs `headers.cookie` in its witness, and a missing credential is a gap that
|
|
11692
|
+
names it.
|
|
11693
|
+
|
|
10620
11694
|
### Auth and tenant context
|
|
10621
11695
|
|
|
10622
11696
|
`prepareRequest` runs for every case and runtime. Use it to attach a test session,
|
|
@@ -10693,6 +11767,41 @@ await assertAdversarialContract(app, {
|
|
|
10693
11767
|
})
|
|
10694
11768
|
```
|
|
10695
11769
|
|
|
11770
|
+
### A web app's routes in a test
|
|
11771
|
+
|
|
11772
|
+
Give the test the same route manifest the app serves. `discoverRoutes(dir)` from
|
|
11773
|
+
`@nifrajs/web/fs` scans a routes directory the way the build does, so every page
|
|
11774
|
+
arrives with its `.backend.ts` half: its loader, action, output schemas and
|
|
11775
|
+
middleware. A built app can import the generated `server-manifest` instead, which
|
|
11776
|
+
bakes the same pairs into static imports.
|
|
11777
|
+
|
|
11778
|
+
```ts
|
|
11779
|
+
// test/routes.test.ts - render the real routes in-process, each page with its backend half.
|
|
11780
|
+
import { expect, test } from "bun:test"
|
|
11781
|
+
import { createWebApp } from "@nifrajs/web"
|
|
11782
|
+
import { discoverRoutes } from "@nifrajs/web/fs"
|
|
11783
|
+
import { reactAdapter } from "@nifrajs/web-react"
|
|
11784
|
+
|
|
11785
|
+
const app = createWebApp({
|
|
11786
|
+
adapter: reactAdapter,
|
|
11787
|
+
// Scans routes/ the way the build does: a page and its .backend.ts half always arrive together.
|
|
11788
|
+
manifest: discoverRoutes(`${import.meta.dir}/../routes`),
|
|
11789
|
+
clientEntry: "/client.js",
|
|
11790
|
+
})
|
|
11791
|
+
|
|
11792
|
+
test("the users page runs its loader", async () => {
|
|
11793
|
+
const res = await app.fetch(new Request("http://localhost/users/7"))
|
|
11794
|
+
expect(res.status).toBe(200)
|
|
11795
|
+
})
|
|
11796
|
+
```
|
|
11797
|
+
|
|
11798
|
+
`buildManifest(files, load)` pairs a page with its backend half only when both are
|
|
11799
|
+
in `files`. It is pure, so it cannot know a half is missing, and a hand-written list
|
|
11800
|
+
that names the page alone serves it without its loader and guards: the test passes against a
|
|
11801
|
+
route production never runs. `nifra doctor` flags a `buildManifest(...)`
|
|
11802
|
+
call whose array literal or `import.meta.glob` patterns reach a page but not the
|
|
11803
|
+
backend half beside it.
|
|
11804
|
+
|
|
10696
11805
|
### Browser end-to-end
|
|
10697
11806
|
|
|
10698
11807
|
API flows stay in-process through `testSession` - typed, cookie-aware, no
|
|
@@ -10826,6 +11935,23 @@ implementation into the client.
|
|
|
10826
11935
|
See [Framework contract](/docs/contract) for the decoupled form and its scaling
|
|
10827
11936
|
guidance.
|
|
10828
11937
|
|
|
11938
|
+
The tooling reads the backend `backend/app.ts` exports. An app that
|
|
11939
|
+
`implement`s a contract somewhere else, such as a `createApp(deps)`
|
|
11940
|
+
factory, is invisible to the contract lock and the route lints until that file serves it.
|
|
11941
|
+
`nifra check` names each implemented operation the backend does not serve
|
|
11942
|
+
(NF-C039), and counts them when there is no `backend/app.ts` at all. Export the
|
|
11943
|
+
same app built with stand-in dependencies:
|
|
11944
|
+
|
|
11945
|
+
```ts
|
|
11946
|
+
// backend/app.ts - the backend nifra's tooling reads
|
|
11947
|
+
import { createApp } from "../src/app"
|
|
11948
|
+
import { stubDeps } from "../src/stub-deps"
|
|
11949
|
+
|
|
11950
|
+
// The real entry builds the app with real dependencies elsewhere. This one builds the same routes with
|
|
11951
|
+
// stand-ins, so the contract lock, the typed client checks and `nifra routes` see every operation.
|
|
11952
|
+
export const backend = createApp(stubDeps())
|
|
11953
|
+
```
|
|
11954
|
+
|
|
10829
11955
|
### OpenAPI
|
|
10830
11956
|
|
|
10831
11957
|
The `openapi()` middleware builds an OpenAPI 3.1 document from your registered
|
|
@@ -10965,6 +12091,47 @@ input, so Nifra will not guess which app to point them at; a level that fuzzed y
|
|
|
10965
12091
|
server by default would be a footgun, and a skipped route is reported rather than passed over
|
|
10966
12092
|
in silence.
|
|
10967
12093
|
|
|
12094
|
+
An app that needs a session, real row ids or signed tokens passes them to the runner instead of
|
|
12095
|
+
rewriting requests inside the executor: `witnesses`, `prepareRequest`,
|
|
12096
|
+
`isRejected`, `include` and `maxMutationsPerInput` on
|
|
12097
|
+
`invariants` reach every L4 run, from `nifra levels` and the
|
|
12098
|
+
`nifra_levels` MCP tool alike. `setup` provisions a throwaway target only
|
|
12099
|
+
when L4 runs: it is called once, before the first L4 request, and its `teardown`
|
|
12100
|
+
always runs after the run, a failing one included. The example keeps its target in memory; a
|
|
12101
|
+
database or container belongs in your own `setup`.
|
|
12102
|
+
|
|
12103
|
+
```ts
|
|
12104
|
+
// nifra.assurance.ts
|
|
12105
|
+
// doc-check: skip - the backend and its seed data are app-specific
|
|
12106
|
+
import { defineAssuranceConfig } from "@nifrajs/core/assurance"
|
|
12107
|
+
import { createBackend } from "./backend"
|
|
12108
|
+
|
|
12109
|
+
export default defineAssuranceConfig({
|
|
12110
|
+
source: createBackend({ rows: new Map() }),
|
|
12111
|
+
policy: { rules: [/* ... */] },
|
|
12112
|
+
invariants: {
|
|
12113
|
+
// Called once, before the first L4 request - never when this file is imported, so
|
|
12114
|
+
// `nifra check` and `nifra assure` provision nothing.
|
|
12115
|
+
setup: async () => {
|
|
12116
|
+
const rows = new Map([["row_1", { id: "row_1", owner: "fixture" }]])
|
|
12117
|
+
const target = createBackend({ rows })
|
|
12118
|
+
return {
|
|
12119
|
+
executor: (request) => target.fetch(request),
|
|
12120
|
+
teardown: () => rows.clear(), // runs after a failing run too
|
|
12121
|
+
}
|
|
12122
|
+
},
|
|
12123
|
+
// Known-good inputs for schemas L4 cannot synthesize (an opaque validator, a real id).
|
|
12124
|
+
witnesses: { "PATCH /rows/:id": { params: { id: "row_1" }, body: { title: "ok" } } },
|
|
12125
|
+
// Attach what the app needs on every case: a session, a signed token.
|
|
12126
|
+
prepareRequest: (request) => {
|
|
12127
|
+
const headers = new Headers(request.headers)
|
|
12128
|
+
headers.set("cookie", "session=fixture")
|
|
12129
|
+
return new Request(request, { headers })
|
|
12130
|
+
},
|
|
12131
|
+
},
|
|
12132
|
+
})
|
|
12133
|
+
```
|
|
12134
|
+
|
|
10968
12135
|
### For coding agents
|
|
10969
12136
|
|
|
10970
12137
|
The `nifra_levels` MCP tool returns the same
|
|
@@ -11149,6 +12316,62 @@ Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one
|
|
|
11149
12316
|
For AI agents, see [`LLM.md`](./LLM.md) and the full corpus
|
|
11150
12317
|
[`../../llms-full.txt`](../../llms-full.txt).
|
|
11151
12318
|
|
|
12319
|
+
## @nifrajs/acp
|
|
12320
|
+
|
|
12321
|
+
An `AgentBackend` for agents that speak the [Agent Client Protocol](https://agentclientprotocol.com)
|
|
12322
|
+
(version 1) over stdio: Claude Code through `claude-code-acp`, Codex through `codex-acp`, Gemini CLI
|
|
12323
|
+
with `--experimental-acp`, or any other ACP agent. A Nifra host, RPC server, or app drives it like any
|
|
12324
|
+
other backend.
|
|
12325
|
+
|
|
12326
|
+
```ts
|
|
12327
|
+
import { AcpBackend } from "@nifrajs/acp"
|
|
12328
|
+
import { CodingAgentHost } from "@nifrajs/coding-agent"
|
|
12329
|
+
|
|
12330
|
+
const backend = new AcpBackend({
|
|
12331
|
+
command: "claude-code-acp",
|
|
12332
|
+
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
|
|
12333
|
+
})
|
|
12334
|
+
const host = new CodingAgentHost({ backend })
|
|
12335
|
+
```
|
|
12336
|
+
|
|
12337
|
+
Each session is one agent process: `initialize` (protocol version 1, refused otherwise), an optional
|
|
12338
|
+
`authenticate` with `authMethod`, then `session/new` in the session's absolute `cwd`. A message is a
|
|
12339
|
+
`session/prompt`; its updates become protocol events:
|
|
12340
|
+
|
|
12341
|
+
| ACP update | Event |
|
|
12342
|
+
| --- | --- |
|
|
12343
|
+
| `agent_message_chunk` | `assistant.delta`, and the whole reply as `assistant.message` before a tool call and at the end |
|
|
12344
|
+
| `agent_thought_chunk` | `reasoning.delta` |
|
|
12345
|
+
| `tool_call`, `tool_call_update` | `tool.started`, then `tool.completed` once the status is `completed` or `failed` |
|
|
12346
|
+
| `plan` | `plan.updated` |
|
|
12347
|
+
| `usage_update` | `context.usage` (`used`, `size`) |
|
|
12348
|
+
| anything else | `x-acp.<sessionUpdate>` with a bounded copy of the update |
|
|
12349
|
+
|
|
12350
|
+
The stop reason ends the turn: `end_turn`, `max_tokens`, and `max_turn_requests` complete it (any but
|
|
12351
|
+
`end_turn` also reported as `x-acp.stop_reason`), `refusal` fails it, and `cancelled` stops it.
|
|
12352
|
+
|
|
12353
|
+
`session/request_permission` surfaces as `approval.required` (`capability` is `acp.<tool kind>`).
|
|
12354
|
+
`resolveApproval` answers with the agent's `allow_once` or `reject_once` option and never an "always"
|
|
12355
|
+
one, so the host's approval policy stays in charge; with no such option it answers `cancelled`. An
|
|
12356
|
+
approval the agent withdraws with `$/cancel_request` is answered and reported as `approval.resolved`
|
|
12357
|
+
with `approved: false`.
|
|
12358
|
+
|
|
12359
|
+
The client offers no filesystem or terminal methods: the agent uses its own tools, and those
|
|
12360
|
+
requests get JSON-RPC `-32601`. `cancel` answers open approvals `cancelled` and sends
|
|
12361
|
+
`session/cancel`; an agent that has not ended the turn after `cancelGraceMs` (default 2 seconds) is
|
|
12362
|
+
stopped, which ends its session.
|
|
12363
|
+
|
|
12364
|
+
The agent runs with `PATH`, `HOME`, locale, terminal, user, shell, and temp variables plus what `env`
|
|
12365
|
+
adds; nothing else from the parent environment passes, so give it its model credentials explicitly.
|
|
12366
|
+
Error text from the agent (a refused request, or the last error-looking stderr line when it exits)
|
|
12367
|
+
is cut to a bounded length with those values and common key shapes masked. stdout carries only
|
|
12368
|
+
JSON-RPC, one message per line, each at most `maxRecordBytes` (default 1 MiB).
|
|
12369
|
+
|
|
12370
|
+
The package passes `@nifrajs/agent-protocol/conformance` against a scripted ACP agent.
|
|
12371
|
+
|
|
12372
|
+
For AI agents, see [`LLM.md`](./LLM.md) and the full corpus
|
|
12373
|
+
[`../../llms-full.txt`](../../llms-full.txt).
|
|
12374
|
+
|
|
11152
12375
|
## @nifrajs/ag-ui
|
|
11153
12376
|
|
|
11154
12377
|
Mount a nifra agent as an [AG-UI](https://docs.ag-ui.com) (Agent-User Interaction protocol) endpoint: one POST accepting `RunAgentInput`, a stream of AG-UI events out over SSE - run lifecycle, step and tool-call events projected from the runner's step evidence, text message events for the output, and a typed continuation for human-in-the-loop resume.
|
|
@@ -11269,6 +12492,11 @@ terminal events - the run is never re-executed. A disconnected run keeps going a
|
|
|
11269
12492
|
real terminal events for that reconnect. Without an `evidenceLog` nothing can rejoin a dropped
|
|
11270
12493
|
stream, so the disconnect aborts `ports.signal` and the turn suspends as `cancelled`.
|
|
11271
12494
|
|
|
12495
|
+
Exceeding `maxOutputBytes` detaches the viewer in the same way, including an oversized initial state
|
|
12496
|
+
snapshot. The response closes immediately, with an `output_limit` error frame when the remaining
|
|
12497
|
+
budget permits it. Without a log the turn is aborted; with a log it continues recording its real
|
|
12498
|
+
terminal result for reconnect rather than storing the viewer's output-limit error as the run result.
|
|
12499
|
+
|
|
11272
12500
|
```ts
|
|
11273
12501
|
import { createMemoryAgentEvidenceLog } from "@nifrajs/agent/events"
|
|
11274
12502
|
|
|
@@ -11367,6 +12595,11 @@ dropped stream, so the disconnect aborts `ports.signal` and the turn suspends as
|
|
|
11367
12595
|
`createMemoryAgentEvidenceLog` (from `@nifrajs/agent/events`) is the single-process dev/test
|
|
11368
12596
|
reference; a durable, multi-process log is an adapter implementing the same `AgentEvidenceLog`
|
|
11369
12597
|
interface.
|
|
12598
|
+
|
|
12599
|
+
One SSE response emits at most `maxOutputBytes` (default 4 MiB), so a client that stops reading
|
|
12600
|
+
cannot make the server queue a whole run. Past the cap the response ends with an `error` frame
|
|
12601
|
+
(`output_limit`): with an `evidenceLog` the run keeps recording and a `Last-Event-ID` reconnect reads
|
|
12602
|
+
on from where the response stopped; without one the run is cancelled.
|
|
11370
12603
|
When more than one caller shares the route, also pass `evidenceOwner: (c) => <the caller's user or
|
|
11371
12604
|
tenant id>`: turns are recorded and replayed under that owner, so another caller's reconnect or
|
|
11372
12605
|
reused `turnId` finds none of them. Without it a turn id works as a bearer capability. The owner
|
|
@@ -11404,6 +12637,11 @@ counts.
|
|
|
11404
12637
|
- **Ordered, deduplicated streaming.** A turn arrives as an ordered stream of `AgentEventView`s.
|
|
11405
12638
|
Out-of-order and redelivered frames are reordered and de-duplicated by sequence; a bounded buffer
|
|
11406
12639
|
skips an unfilled gap rather than stalling.
|
|
12640
|
+
- **Turns that survive a dropped connection.** When a turn's stream breaks mid-turn, `send` follows
|
|
12641
|
+
the turn again with `turn.attach` from the last seq it received (`resumeAttempts`, default 3;
|
|
12642
|
+
`resumeDelayMs`, default 250), so the views continue without a gap or a repeat. The host must keep
|
|
12643
|
+
turns (`resumableTurns` on `CodingAgentRpcServer`); one that cannot resume refuses, and `send`
|
|
12644
|
+
throws the original break.
|
|
11407
12645
|
- **Cursor resume.** A persisted log can be replayed from a cursor. A cursor whose next record was
|
|
11408
12646
|
evicted from the retained window asks the caller to resync.
|
|
11409
12647
|
- **Approvals and handoffs.** Pending approvals list as identifiers; decisions and handoff outcomes go
|
|
@@ -11469,10 +12707,57 @@ const events = createAgentEventStream(256)
|
|
|
11469
12707
|
events.push(/* a versioned AgentEvent */)
|
|
11470
12708
|
```
|
|
11471
12709
|
|
|
12710
|
+
`isAgentEvent` refuses an event past the protocol's bounds: 256 KiB characters of `text`, `prompt`, or
|
|
12711
|
+
error message, and 64 KiB per string inside `input`, `output`, or `details`. A backend keeps its
|
|
12712
|
+
events inside them with `splitAgentText` (a long reply as consecutive events that join back),
|
|
12713
|
+
`boundAgentText` (one field, cut with a marker), and `boundAgentValue` (a display copy of a tool's
|
|
12714
|
+
input or output; the tool itself still gets the whole value). `agentEventProblem` says why an event
|
|
12715
|
+
was refused, naming the field and the limit but never its content.
|
|
12716
|
+
|
|
12717
|
+
Three events carry what most coding agents report besides their reply: `reasoning.delta` (a piece
|
|
12718
|
+
of the model's reasoning, grouped by an optional `itemId`), `plan.updated` (the whole current plan,
|
|
12719
|
+
entries of `content`, `status`, and optional `priority`, each event replacing the last), and
|
|
12720
|
+
`context.usage` (`usedTokens` of the latest request, the model's `windowTokens` when known, and
|
|
12721
|
+
optional input, output, cached, and reasoning counts).
|
|
12722
|
+
|
|
12723
|
+
Any other frame a backend has of its own travels as an extension event:
|
|
12724
|
+
`type` is `x-<vendor>.<name>` (lowercase, at most 64 characters), with an optional `turnId` and a
|
|
12725
|
+
`data` value inside the same payload bounds. Hosts that do not know the type pass it through; a host
|
|
12726
|
+
that does can register a check for it.
|
|
12727
|
+
|
|
12728
|
+
```ts
|
|
12729
|
+
yield { version: 1, sessionId, seq, at: Date.now(), type: "x-codex.plan", turnId, data: { steps } }
|
|
12730
|
+
```
|
|
12731
|
+
|
|
11472
12732
|
Use `@nifrajs/pi` for the Pi backend or implement `AgentBackend` for another
|
|
11473
12733
|
runtime. The protocol is intentionally small so CLI, desktop, mobile, and CI
|
|
11474
12734
|
clients can share it without pulling in the Nifra application framework.
|
|
11475
12735
|
|
|
12736
|
+
## Conformance kit
|
|
12737
|
+
|
|
12738
|
+
`@nifrajs/agent-protocol/conformance` drives an `AgentBackend` through what a host relies on and
|
|
12739
|
+
returns a report, so an adapter for another agent can prove itself in any test runner:
|
|
12740
|
+
|
|
12741
|
+
```ts
|
|
12742
|
+
import { checkAgentBackend } from "@nifrajs/agent-protocol/conformance"
|
|
12743
|
+
|
|
12744
|
+
const report = await checkAgentBackend({
|
|
12745
|
+
backend: () => new MyBackend(),
|
|
12746
|
+
cwd: process.cwd(),
|
|
12747
|
+
prompt: "say hi",
|
|
12748
|
+
slowPrompt: "wait until cancelled", // enables turn.busy and turn.cancel
|
|
12749
|
+
approvalPrompt: "write a file", // enables the approval checks
|
|
12750
|
+
})
|
|
12751
|
+
expect(report.checks.filter((check) => check.status === "failed")).toEqual([])
|
|
12752
|
+
```
|
|
12753
|
+
|
|
12754
|
+
Each scenario gets a fresh backend and session. The checks: a valid session snapshot; a turn whose
|
|
12755
|
+
events are valid, ordered, of one turn, and end with exactly one terminal event; `session.completed`
|
|
12756
|
+
for a plain turn and an idle snapshot after it; a second message refused while a turn runs, without
|
|
12757
|
+
ending it; a cancel that ends with `session.stopped` and leaves the session usable; approvals allowed
|
|
12758
|
+
and denied; `undefined` from `resolveApproval` for an approval it never asked for or whose turn was
|
|
12759
|
+
cancelled; and no turn for a closed session.
|
|
12760
|
+
|
|
11476
12761
|
## Version 1 compatibility
|
|
11477
12762
|
|
|
11478
12763
|
Version 1 is additive. Hosts advertise capabilities and clients use only the negotiated intersection;
|
|
@@ -11846,7 +13131,12 @@ await cache.invalidateTag(`user:${id}`)
|
|
|
11846
13131
|
**immediately** while a **background** refresh runs (deduped). Latency stays flat; data self-heals.
|
|
11847
13132
|
- **Miss/expired**: awaits `loader`. Concurrent misses for the same key share **one** call (no stampede).
|
|
11848
13133
|
- A throwing `loader` is **not** cached (and in the background path goes to `onError`, never rejects the caller).
|
|
11849
|
-
-
|
|
13134
|
+
- **Store outage**: a store `get` that throws or rejects is a miss, so the loader answers; a `set` that
|
|
13135
|
+
fails still returns the loaded value, and a `fence` that fails leaves that load uncached. Each
|
|
13136
|
+
failure goes to `onStoreError(error, { op })`, which by default logs one line per operation per
|
|
13137
|
+
minute without the key. The explicit `get`, `set`, `delete`, `invalidateTag` and `clear` still
|
|
13138
|
+
reject, so a failed invalidation is never silent.
|
|
13139
|
+
- **`set` / `get` / `has` / `delete`**, **`invalidateTag(tag)`**, **`clear()`**. A `delete`, `invalidateTag`, `clear` or newer `set` also stops a `wrap` load or background refresh already running for that key or tag from storing its result, and the next `wrap` loads afresh. A `delete`, `invalidateTag` or `clear` runs after this cache's own async store writes it covers, so a store that applies requests out of order cannot land one after it.
|
|
11850
13140
|
- TTL: `ttlMs` (→ stale) + optional `swrMs` (→ served-stale window) + optional `tags`.
|
|
11851
13141
|
|
|
11852
13142
|
## Stores
|
|
@@ -11860,6 +13150,14 @@ opt-in. For a cache shared across instances, implement `CacheStore`
|
|
|
11860
13150
|
const cache = createCache({ store: new RedisCacheStore(redis) })
|
|
11861
13151
|
```
|
|
11862
13152
|
|
|
13153
|
+
A store several caches or processes share should also implement the optional `fence(key, tags)` and
|
|
13154
|
+
`setIfFence(key, entry, tags, token)`. A `wrap` load takes a fence before it runs and stores its result
|
|
13155
|
+
through `setIfFence`, which must refuse, in one atomic step, when a `set` or `delete` of the key, an
|
|
13156
|
+
`invalidateTag` of one of its tags, or a `clear` ran since that fence, from any cache. Without them, a
|
|
13157
|
+
load running in one process can store a value another process invalidated meanwhile. `MemoryCache`
|
|
13158
|
+
implements both, and `cacheStoreCertificationProfile({ fencing: true })` from
|
|
13159
|
+
`@nifrajs/testing/certification` checks a store's implementation.
|
|
13160
|
+
|
|
11863
13161
|
On **Cloudflare Workers** the in-memory cache is per-isolate and short-lived - back it with **CF KV** (or
|
|
11864
13162
|
the Cache API) via a `CacheStore` for anything that should survive across requests/instances.
|
|
11865
13163
|
|
|
@@ -11867,7 +13165,8 @@ the Cache API) via a `CacheStore` for anything that should survive across reques
|
|
|
11867
13165
|
|
|
11868
13166
|
`observer` receives one event per operation after it settles: `{ op, outcome, startedAt, durationMs,
|
|
11869
13167
|
key, tag, tagCount, context }`. `outcome` is `hit`, `stale` or `miss` for reads, `ok` for writes and a
|
|
11870
|
-
finished background refresh (`op: "revalidate"`), `error` when the store or loader threw.
|
|
13168
|
+
finished background refresh (`op: "revalidate"`), `error` when the store or loader threw. A `wrap`
|
|
13169
|
+
that loaded through a store outage reports `miss`. An observer
|
|
11871
13170
|
that throws is swallowed; it never changes a result. Without an observer the cache reads no extra clock.
|
|
11872
13171
|
|
|
11873
13172
|
`cache.for(c)` binds a request (or job) context, and every event carries it. That is how
|
|
@@ -11890,7 +13189,7 @@ whenever it is set.
|
|
|
11890
13189
|
|
|
11891
13190
|
## API
|
|
11892
13191
|
|
|
11893
|
-
- `createCache(options?)` → `Cache` - `{ store?, defaultTtlMs?, now?, onError?, observer?, beacon?, capabilities? }`.
|
|
13192
|
+
- `createCache(options?)` → `Cache` - `{ store?, defaultTtlMs?, now?, onError?, onStoreError?, observer?, beacon?, capabilities? }`.
|
|
11894
13193
|
- `cache.wrap(key, loader, { ttlMs?, swrMs?, tags? })` · `get` · `has` · `set` · `delete` · `invalidateTag` · `clear`.
|
|
11895
13194
|
- `cache.for(context)` → the same surface bound to a request or job context.
|
|
11896
13195
|
- `MemoryCache({ maxEntries?, now? })` - the default store; implement `CacheStore` for your own.
|
|
@@ -11959,6 +13258,9 @@ the frontend asset `dist/manifest.json`.
|
|
|
11959
13258
|
## `nifra verify` - verification tiers
|
|
11960
13259
|
|
|
11961
13260
|
`nifra check` is the fast project done-gate: TypeScript plus static contract and boundary diagnostics.
|
|
13261
|
+
It reads source without running it, with one exception: when the project has a `contracts.lock.json`,
|
|
13262
|
+
`nifra check` (with `--lints-only` too) imports `backend/app.ts`, running its top-level code, to compare
|
|
13263
|
+
the routes it serves against the lock.
|
|
11962
13264
|
It is not a release claim. `nifra verify` runs the ordered local verification plan; its default mode
|
|
11963
13265
|
is the feedback plan and reports which release gates it omits. CI and release automation should run
|
|
11964
13266
|
`nifra verify --release` (or `bun run check:release`), which includes build, coverage, corpus,
|
|
@@ -12141,8 +13443,11 @@ const { ok, status, data, error } = await api.users({ id: "42" }).get()
|
|
|
12141
13443
|
`api.files("report.json").get()`.
|
|
12142
13444
|
- **Result, never exceptions.** Every call resolves to `{ ok, status, data, error }`,
|
|
12143
13445
|
so the failure path is in the types. Bodies are positional (`api.users.post({ name })`);
|
|
12144
|
-
pass `{ query, headers, signal }` as call options. A
|
|
12145
|
-
|
|
13446
|
+
pass `{ query, headers, signal }` as call options. A DELETE route with a body schema takes
|
|
13447
|
+
its body as an option: `api.items.delete({ body: { ids } })`. A call that gets no HTTP answer it
|
|
13448
|
+
can read has `status: 0` and the code `network_error`, `timeout`, `response_too_large`,
|
|
13449
|
+
`malformed_response` (a body its content type does not decode, like a proxy's HTML error page
|
|
13450
|
+
labelled JSON) or `invalid_path`.
|
|
12146
13451
|
- **A param value is never a path step.** `.` and `..` are resolved by URL parsing in every
|
|
12147
13452
|
encoding, so `api.users({ id: ".." }).delete()` sends nothing and returns `invalid_path`
|
|
12148
13453
|
instead of reaching another route. `.subscribe()` reports it through `onError`; `.ws()` throws.
|
|
@@ -12198,6 +13503,14 @@ loopback debugger, set `exposeErrorStacks: true` (or pass
|
|
|
12198
13503
|
`--expose-error-stacks` to `nifra-agent --rpc`) to include bounded exception
|
|
12199
13504
|
stacks. This option is rejected for remote binding.
|
|
12200
13505
|
|
|
13506
|
+
Each `turn.send` SSE frame carries `id: <seq>`. By default a client that disconnects cancels its
|
|
13507
|
+
turn. With `resumableTurns: true` the turn keeps running: a client that comes back calls
|
|
13508
|
+
`turn.attach` with `{ afterSeq }` (or a `Last-Event-ID` header) to read the frames it missed and
|
|
13509
|
+
follow the rest. A cursor older than the held frames (`replayEvents`, default 1024; `replayBytes`,
|
|
13510
|
+
default 8 MiB) answers `409 resync_required`, so read `session.events` instead; a second
|
|
13511
|
+
`turn.send` while one runs answers `409 turn_running`; and a turn no client has followed for
|
|
13512
|
+
`detachedTimeoutMs` (default 2 minutes) is cancelled.
|
|
13513
|
+
|
|
12201
13514
|
`nifra-agent` loads `.nifra/extensions/**` from `--cwd` only when started with
|
|
12202
13515
|
`--extensions`. An extension module's top-level code runs as soon as it is
|
|
12203
13516
|
imported, before its declared `capabilities` are checked, so pass the flag only
|
|
@@ -12211,7 +13524,10 @@ loading code you do not trust.
|
|
|
12211
13524
|
an injected model implementation and bounded tools without importing a provider
|
|
12212
13525
|
SDK. The conversation it sends the model is capped by `maxHistoryChars` (default
|
|
12213
13526
|
1 MiB): past it, whole earlier turns are dropped, oldest first, and the current
|
|
12214
|
-
turn is always sent in full. `
|
|
13527
|
+
turn is always sent in full. `maxMessageChars` (default 64 KiB) caps the message `send` accepts and
|
|
13528
|
+
`maxReplyChars` (default 1 MiB) the model's reply; a reply longer than one event's text arrives as
|
|
13529
|
+
consecutive `assistant.message` events, and a tool's event shows a bounded copy of its input while
|
|
13530
|
+
the tool runs with all of it. `ReplayBackend` and `readReplayEvents` provide deterministic protocol
|
|
12215
13531
|
replays for CI and Workbench regression tests.
|
|
12216
13532
|
|
|
12217
13533
|
## At-least-once run dispatch
|
|
@@ -12233,7 +13549,8 @@ Legacy `FileSessionStore` files remain supported for explicit local compatibilit
|
|
|
12233
13549
|
`nifra-agent --migrate-session <id> --migrate-from <legacy-dir> --migrate-to <evidence-dir>` to
|
|
12234
13550
|
create a separately validated, evidence-only target. The source is left untouched and the command
|
|
12235
13551
|
does not switch an active configuration pointer: after reviewing its report, point the host at the
|
|
12236
|
-
new directory yourself.
|
|
13552
|
+
new directory yourself. `FileSessionStore` reads the migrated rows as content-free entries whose
|
|
13553
|
+
type is the stable event code, and new appends continue the sequence. To roll back, point it at the legacy directory again and remove the new
|
|
12237
13554
|
target; a failed or interrupted migration leaves the active choice unchanged.
|
|
12238
13555
|
|
|
12239
13556
|
For AI agents, see [`LLM.md`](./LLM.md) and the full corpus
|
|
@@ -12413,7 +13730,9 @@ route. Split groups before the compiler reports `TS2589`, rather than weakening
|
|
|
12413
13730
|
client's `transport` option with the same registry. `@nifrajs/core/transport-codec` negotiates
|
|
12414
13731
|
bounded HTTP representations and supplies the same frame/loader adapters for WebSockets and
|
|
12415
13732
|
deferred data. Import `richWireCodec()` from `@nifrajs/core/transport-codec-rich`; the separate
|
|
12416
|
-
subpath keeps rich-wire code out of plain JSON bundles.
|
|
13733
|
+
subpath keeps rich-wire code out of plain JSON bundles. The plugin decodes a codec body before
|
|
13734
|
+
routing, so a route's `bodyLimit` and its authentication run after the decode; its own `maxBytes`
|
|
13735
|
+
is the bound on that work.
|
|
12417
13736
|
- **Typed data seam (opt in).** `@nifrajs/core/data` defines token-only operation contracts,
|
|
12418
13737
|
`db.read`/`db.write` capability names, an opaque request-local `RlsScope`, typed adapter requests,
|
|
12419
13738
|
drift snapshots, and `createDataPort(contract, adapter, { beacon: useCapability })`, which emits the
|
|
@@ -12451,7 +13770,9 @@ contract-first registry.
|
|
|
12451
13770
|
|
|
12452
13771
|
`response` and `errors` are typed by default but are not runtime enforcement by themselves. Install
|
|
12453
13772
|
`responseContract("enforce")` before the routes it should cover to validate JSON/status payloads and
|
|
12454
|
-
prevent extra fields from reaching the wire. `
|
|
13773
|
+
prevent extra fields from reaching the wire. That covers a `status(...)` or `c.json(...)` reply a
|
|
13774
|
+
handler returns or throws, a hook returns, or `onError` answers with. `warn` preserves the original
|
|
13775
|
+
payload during migration.
|
|
12455
13776
|
Raw `Response`, streams, redirects, and `mountFetch` remain explicit transport escape hatches and
|
|
12456
13777
|
must be governed by assurance/static review.
|
|
12457
13778
|
|
|
@@ -12559,6 +13880,7 @@ a Bun-`listen()`-style graceful `stop()` and opt-in signals.
|
|
|
12559
13880
|
|
|
12560
13881
|
```ts
|
|
12561
13882
|
await serve(app, { port: 3000, signals: true }) // SIGTERM/SIGINT → graceful stop()
|
|
13883
|
+
await serve(app, { port: 3000, signals: { drainMs: 8_000 } }) // the same, with an 8 s drain window
|
|
12562
13884
|
```
|
|
12563
13885
|
|
|
12564
13886
|
## What you get for free
|
|
@@ -12594,6 +13916,13 @@ The plugin is enabled by default only when `NODE_ENV=development`. It records a
|
|
|
12594
13916
|
and serves the overlay stream at `/_nifra/devtools`; remote hosts and cross-origin access stay denied
|
|
12595
13917
|
unless explicitly allowed. Leave it unregistered in production for zero request-path overhead.
|
|
12596
13918
|
|
|
13919
|
+
Each SSE connection also has a finite pending-byte budget (`maxPendingBytes`, default 64 KiB),
|
|
13920
|
+
including keep-alive frames. A slow reader that fills that budget is closed and releases its
|
|
13921
|
+
connection slot. Already queued frames drain before EOF; the overflow frame is dropped. Browser
|
|
13922
|
+
`EventSource` reconnects automatically. Initial delivery includes only the newest history suffix
|
|
13923
|
+
that fits the byte budget. This is a recent trace view, so reconnects can have event-id gaps when
|
|
13924
|
+
older events no longer fit the history or connection budget; it does not guarantee lossless replay.
|
|
13925
|
+
|
|
12597
13926
|
## For AI agents
|
|
12598
13927
|
|
|
12599
13928
|
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
@@ -12780,7 +14109,8 @@ pass the request's bindings: `defineEnv(shape, { source: env })`.
|
|
|
12780
14109
|
## Secret safety
|
|
12781
14110
|
|
|
12782
14111
|
Error messages name the offending variable and the reason - **never its value** (it may be a
|
|
12783
|
-
secret).
|
|
14112
|
+
secret). A BYO validator such as valibot may quote the value in its message; `defineEnv` replaces
|
|
14113
|
+
it with `<redacted>`. `defineEnv` returns a frozen object, so a validated config can't be mutated downstream.
|
|
12784
14114
|
|
|
12785
14115
|
## For AI agents
|
|
12786
14116
|
|
|
@@ -12875,6 +14205,15 @@ Resolvers read it as their third argument.
|
|
|
12875
14205
|
|
|
12876
14206
|
Subscriptions run over the same route via `graphql-transport-ws`. Bring a subscription source - the bundled in-memory `createPubSub` is the reference implementation; swap in a durable bus (Redis, a Durable Object, NATS) that exposes the same `{ publish, subscribe }` shape for multi-instance deploys.
|
|
12877
14207
|
|
|
14208
|
+
Each reference subscriber queues at most 1024 events by default. If publication outruns consumption,
|
|
14209
|
+
the overloaded iterator detaches, clears its queued values, and its next pull rejects with
|
|
14210
|
+
`GraphqlPubSubOverflowError` (`code: "graphql_pubsub_overflow"`). Configure
|
|
14211
|
+
`createPubSub({ maxQueuedEvents: 256, overflow: "error" })` to change the finite limit. Explicit
|
|
14212
|
+
`"drop-oldest"` and `"drop-newest"` policies support consumers that accept missing events. Returning or
|
|
14213
|
+
throwing into an iterator clears its queue and terminates delivery. This is an event-count bound;
|
|
14214
|
+
publishers remain responsible for payload sizes. Synchronous `publish` does not provide producer
|
|
14215
|
+
backpressure or cross-instance delivery.
|
|
14216
|
+
|
|
12878
14217
|
```ts
|
|
12879
14218
|
import { server } from "@nifrajs/core"
|
|
12880
14219
|
import { websocket } from "@nifrajs/core/ws"
|
|
@@ -12897,6 +14236,8 @@ pubsub.publish("count", 1)
|
|
|
12897
14236
|
|
|
12898
14237
|
The route must negotiate the `graphql-transport-ws` subprotocol at the WebSocket layer for a browser `graphql-ws` client to connect.
|
|
12899
14238
|
|
|
14239
|
+
With `subscriptions: true`, a socket is admitted only when `authorize` accepts its upgrade request, and operations over it get the same query limits as HTTP. A browser cannot set headers on a WebSocket upgrade, so cookie auth carries over and bearer-header auth does not; for a token sent in `connection_init`, pass WS options with `onConnect` instead. When `authorize` is set, WS options must have their own `authorize` or `onConnect` (`onConnect: () => true` admits every socket on purpose), or `mountGraphql` throws.
|
|
14240
|
+
|
|
12900
14241
|
## Options
|
|
12901
14242
|
|
|
12902
14243
|
| Option | Default | Purpose |
|
|
@@ -12909,12 +14250,12 @@ The route must negotiate the `graphql-transport-ws` subprotocol at the WebSocket
|
|
|
12909
14250
|
| `allowedOrigins` | reflect `*` | CORS origin allowlist. |
|
|
12910
14251
|
| `authorize` | - | Per-request guard; return `false` for 401. |
|
|
12911
14252
|
| `enableGet` | `true` | Serve `GET /graphql` (queries only). |
|
|
12912
|
-
| `subscriptions` | `false` | `true` (reuse schema
|
|
14253
|
+
| `subscriptions` | `false` | `true` (reuse schema, context, `authorize` and limits) or explicit WS options. |
|
|
12913
14254
|
| `legacyJsonResponse` | `false` | Old always-200 `application/json` shape. |
|
|
12914
14255
|
|
|
12915
14256
|
## Responses
|
|
12916
14257
|
|
|
12917
|
-
By default responses use the `application/graphql-response+json` media type with spec status codes: `200` for an executed result (including field errors in `errors`), `400` for a parse or validation failure, `401` for a rejected `authorize`, `405` for a mutation attempted over GET. Set `legacyJsonResponse: true` for the older always-`200` `application/json` behavior some clients expect.
|
|
14258
|
+
By default responses use the `application/graphql-response+json` media type with spec status codes: `200` for an executed result (including field errors in `errors`), `400` for a parse or validation failure, `401` for a rejected `authorize`, `405` for a mutation attempted over GET, `415` for a POST whose body is not `application/json` (or another `+json` type). The last keeps a cross-site HTML form, which can send only urlencoded, multipart or plain-text bodies, from posting a mutation with the visitor's cookies. Set `legacyJsonResponse: true` for the older always-`200` `application/json` behavior some clients expect.
|
|
12918
14259
|
|
|
12919
14260
|
## License
|
|
12920
14261
|
|
|
@@ -13110,7 +14451,7 @@ framework runtime. The client companion to `@nifrajs/web-vanilla`: the server re
|
|
|
13110
14451
|
(zero framework JS); islands attach behavior to it in place. No VDOM, no hydration re-render -
|
|
13111
14452
|
the markup the server sent IS the initial state.
|
|
13112
14453
|
|
|
13113
|
-
The full island bundle is
|
|
14454
|
+
The full island bundle is about 2 KB gzipped (2,080 B at most), and a size test keeps it there. Use
|
|
13114
14455
|
it for small interactive widgets where a full framework runtime would be overkill.
|
|
13115
14456
|
|
|
13116
14457
|
## Server side (any adapter - `@nifrajs/web-vanilla` shown)
|
|
@@ -13176,7 +14517,10 @@ bind it, for example to show or hide the nested island.
|
|
|
13176
14517
|
|
|
13177
14518
|
`signal(initial)` / `computed(fn)` / `effect(fn) → dispose` / `batch(fn)` - auto-tracking with
|
|
13178
14519
|
per-run re-tracking, `Object.is` skips, synchronous updates; `batch` coalesces multiple writes
|
|
13179
|
-
into one flush.
|
|
14520
|
+
into one flush. A `computed` runs on first read and stops by itself once nothing reads it: the
|
|
14521
|
+
next change to its inputs stops it instead of recomputing, and reading it again computes it afresh.
|
|
14522
|
+
Every effect an island's setup creates stops when `mountIslands`' disposer unmounts it; one created
|
|
14523
|
+
outside an island runs until its disposer is called. Island-scale by design: if a widget outgrows this (lists, ownership trees,
|
|
13180
14524
|
async orchestration), that's the signal to use the Solid adapter for that page - both tiers are
|
|
13181
14525
|
first-class.
|
|
13182
14526
|
|
|
@@ -13246,6 +14590,15 @@ const q = createQueue({ store: new RedisJobStore(redis) })
|
|
|
13246
14590
|
Leasing is at-least-once: a leased job is hidden for `leaseMs`; a worker that dies mid-job releases it
|
|
13247
14591
|
back automatically. Make handlers **idempotent**.
|
|
13248
14592
|
|
|
14593
|
+
A job's `timeoutMs` bounds each attempt: one still running after that many ms fails with a
|
|
14594
|
+
`JobTimeoutError` (then retries or dead-letters), frees its worker slot, and aborts `ctx.signal`. Pass the
|
|
14595
|
+
signal on to `fetch` or your driver, since a handler that ignores it keeps running unobserved. Keep
|
|
14596
|
+
`timeoutMs` below `leaseMs`, after which the job can be leased again.
|
|
14597
|
+
|
|
14598
|
+
```ts
|
|
14599
|
+
q.define("render", { timeoutMs: 10_000, async handler(id, ctx) { await fetch(url(id), { signal: ctx.signal }) } })
|
|
14600
|
+
```
|
|
14601
|
+
|
|
13249
14602
|
`enqueue` may pass an optional `traceparent` (the producer's W3C trace context); a store should persist
|
|
13250
14603
|
it and hand it back on every `lease`. A store that drops it still runs every job - a traced run only
|
|
13251
14604
|
loses its link to the producer. `jobStoreCertificationProfile({ traceparent: true })` from
|
|
@@ -13309,10 +14662,10 @@ the run stays in the producer's trace.
|
|
|
13309
14662
|
## API
|
|
13310
14663
|
|
|
13311
14664
|
- `createQueue(options?)` → `Queue` - `{ store?, onError?, now?, defaultAttempts?, backoff?, beacon?, capabilities?, instrument? }`.
|
|
13312
|
-
- `queue.define(name, { handler, input?, retries? })` → typed `JobHandle` with `.enqueue(payload, { delayMs? | runAt?, traceparent? })` and `.for(context)`.
|
|
14665
|
+
- `queue.define(name, { handler, input?, retries?, timeoutMs? })` → typed `JobHandle` with `.enqueue(payload, { delayMs? | runAt?, traceparent? })` and `.for(context)`.
|
|
13313
14666
|
- `queue.enqueue(name, payload, options?)` - enqueue by name.
|
|
13314
|
-
- `queue.start({ concurrency?, pollIntervalMs?, leaseMs? })` → `Worker` (`.stop()` drains gracefully).
|
|
13315
|
-
- `queue.process()` - run one poll round (for Workers / custom drivers). `queue.drain()` - process until
|
|
14667
|
+
- `queue.start({ concurrency?, pollIntervalMs?, leaseMs? })` → `Worker` (`.stop()` drains gracefully). Each poll fills the slots free at that moment, so one slow job holds only its own slot.
|
|
14668
|
+
- `queue.process()` - run one poll round (for Workers / custom drivers). `queue.drain()` - process until nothing is due or in flight.
|
|
13316
14669
|
- `queue.counts()` → `{ pending, active, dead }`. `queue.store` - the underlying store.
|
|
13317
14670
|
- Stores: `MemoryJobStore`. Backoff: `exponentialBackoff`, `fixedBackoff`, `noBackoff`.
|
|
13318
14671
|
|
|
@@ -13477,8 +14830,8 @@ Each layer refuses on its own, in this order:
|
|
|
13477
14830
|
|
|
13478
14831
|
`explainPostgres` returns the plan (with `analyze`, executed inside the same transaction),
|
|
13479
14832
|
`readPostgresSchema` reads tables, columns, keys and indexes from `pg_catalog`, and `postgresRoleSql`
|
|
13480
|
-
writes the SQL for a read-only role (`
|
|
13481
|
-
|
|
14833
|
+
writes the SQL for a read-only role (`GRANT SELECT` on each allowed schema, with a `REVOKE` for each
|
|
14834
|
+
excluded table and its partitions) without running it.
|
|
13482
14835
|
|
|
13483
14836
|
The live tests run against a throwaway server: set `NIFRA_TEST_POSTGRES_URL` to its superuser URL.
|
|
13484
14837
|
|
|
@@ -13533,6 +14886,9 @@ const app = server()
|
|
|
13533
14886
|
- **`basicAuth(options)`** - Basic Auth plugin with constant-time static credential comparison or
|
|
13534
14887
|
a custom verifier.
|
|
13535
14888
|
- **`bearer(options)` / `apiKey(options)`** - token auth plugins with typed principals.
|
|
14889
|
+
- Required `basicAuth`, `bearer`, `apiKey` and `jwt` refuse in the core `authenticate()` stage:
|
|
14890
|
+
before the body is read or validated and before any `derive` or `beforeHandle`. A rate limiter
|
|
14891
|
+
that must count refused requests belongs in `onRequest`, where `rateLimit()` runs.
|
|
13536
14892
|
- **`csrf({ secret })`** - signed double-submit CSRF protection plus Origin/Referer checking. The
|
|
13537
14893
|
token travels in the `x-csrf-token` header; set `field: "_csrf"` to also accept it from a plain
|
|
13538
14894
|
HTML form's hidden input (urlencoded or multipart, bodies up to `fieldMaxBytes`, default 64 KiB).
|
|
@@ -13636,9 +14992,10 @@ The mock server matches reflected routes and synthesizes values from supported J
|
|
|
13636
14992
|
Unsupported or unsatisfiable schemas fail explicitly instead of returning known-invalid data. Use a
|
|
13637
14993
|
fixed seed when responses must be replayable across local development and CI.
|
|
13638
14994
|
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
14995
|
+
A validator that implements Standard JSON Schema (`~standard.jsonSchema`, zod 4.2+ among them) mocks
|
|
14996
|
+
real data with no wiring, and an older zod 4 is converted with `z.toJSONSchema`. Routes whose response
|
|
14997
|
+
validator exposes neither mock as `{}` by default. Pass `reflectJsonSchema` to derive a schema from such
|
|
14998
|
+
a validator; for zod, the bridge ships as `@nifrajs/testing/zod`:
|
|
13642
14999
|
|
|
13643
15000
|
```ts
|
|
13644
15001
|
import { zodJsonSchema } from "@nifrajs/testing/zod"
|
|
@@ -13709,10 +15066,12 @@ The optional `static` root is served before `app.fetch` for GET and HEAD request
|
|
|
13709
15066
|
public asset directory and must contain nothing private: no secrets, source maps, build
|
|
13710
15067
|
metadata, or user uploads. The directory and its ancestor path must be read-only and owned by
|
|
13711
15068
|
the service (or otherwise protected from untrusted local writers). Nifra resolves and confines
|
|
13712
|
-
the file before opening it and refuses
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
15069
|
+
the file before opening it, and refuses it when its final file or any directory on its path became
|
|
15070
|
+
a symlink in the meantime: on macOS the open itself refuses such a path, and on Linux the opened
|
|
15071
|
+
descriptor's path is checked. On Windows and other platforms no file API can open without following a
|
|
15072
|
+
middle link, so a process that swaps a directory concurrently can still win a local TOCTOU race;
|
|
15073
|
+
the check there only makes it need repeated swaps. Mutable or attacker-writable static trees are
|
|
15074
|
+
therefore outside the adapter's guarantee on those platforms. Store user-controlled files outside the static root and serve them through
|
|
13716
15075
|
an application or object-storage path with its own validation policy.
|
|
13717
15076
|
|
|
13718
15077
|
Every file carries a strong `ETag` (from mtime and size), `Last-Modified`, and
|
|
@@ -13730,6 +15089,13 @@ process signals should be a deliberate choice):
|
|
|
13730
15089
|
await serve(app, { port: 3000, signals: true })
|
|
13731
15090
|
```
|
|
13732
15091
|
|
|
15092
|
+
In-flight requests get 10 s to finish before the stop forces them closed. Set the window to fit
|
|
15093
|
+
the platform's kill grace period:
|
|
15094
|
+
|
|
15095
|
+
```ts
|
|
15096
|
+
await serve(app, { port: 3000, signals: { drainMs: 8_000 } })
|
|
15097
|
+
```
|
|
15098
|
+
|
|
13733
15099
|
## What you get for free
|
|
13734
15100
|
|
|
13735
15101
|
The request timeout (`server({ requestTimeoutMs })` → `503 request_timeout`) and body-size
|
|
@@ -13737,6 +15103,10 @@ cap live **inside `app.fetch`**, not Bun's `listen()` - so they apply through th
|
|
|
13737
15103
|
with no extra wiring. Slow-client / slow-loris protection is Node's built-in
|
|
13738
15104
|
`requestTimeout` (300s) and `headersTimeout` (60s) defaults.
|
|
13739
15105
|
|
|
15106
|
+
A hook that throws past `app.fetch`, or a response Node refuses to write (a header value carrying
|
|
15107
|
+
CR/LF, say), answers `500` and is logged through the app's own `logger`, as Bun's `listen()` logs it. The `req` a hook sees is a lazy stand-in for Node's request: copy it
|
|
15108
|
+
with `req.clone()`, not `new Request(req)`, which Node's `Request` constructor refuses.
|
|
15109
|
+
|
|
13740
15110
|
Works with any `{ fetch(req): Promise<Response> }` handler, not just nifra. ESM-only. MIT.
|
|
13741
15111
|
|
|
13742
15112
|
## For AI agents
|
|
@@ -13773,7 +15143,9 @@ const app = server()
|
|
|
13773
15143
|
the inbound span as the parent. No inbound header → starts a fresh trace.
|
|
13774
15144
|
- **Opens a span** with HTTP semantic-convention attributes (`http.request.method`, `url.path`,
|
|
13775
15145
|
`http.response.status_code`, optional `service.name`), ended on response with duration + status
|
|
13776
|
-
(`error` for 5xx, `ok` otherwise).
|
|
15146
|
+
(`error` for 5xx, `ok` otherwise). A request that matched a route reports the route template as
|
|
15147
|
+
`url.path`, so an id or a token in the path stays out of the trace backend; `rawUrlPath: true`
|
|
15148
|
+
reports the raw path instead.
|
|
13777
15149
|
- **Exposes `c.trace`** (`{ traceId, spanId, parentSpanId?, sampled, traceparent }`) - spread
|
|
13778
15150
|
`traceHeaders(c.trace)` into any downstream `fetch`/`ctx.api` call to continue the trace.
|
|
13779
15151
|
- **Exposes `c.causality`** - a bounded, payload-free request node that survives durable command,
|
|
@@ -14026,7 +15398,7 @@ const upstream = createProxy({ upstream: "http://127.0.0.1:8081" })
|
|
|
14026
15398
|
const app = server().mountFetch("/api", upstream, { stripPrefix: true })
|
|
14027
15399
|
```
|
|
14028
15400
|
|
|
14029
|
-
The upstream is a **bare origin** fixed at construction - the forwarded URL is built by mutating a clone of that origin, never by resolving request-derived strings, so no request input can change which host is dialed. Hop-by-hop and `Connection`-nominated headers are stripped in both directions.
|
|
15401
|
+
The upstream is a **bare origin** fixed at construction - the forwarded URL is built by mutating a clone of that origin, never by resolving request-derived strings, so no request input can change which host is dialed. Hop-by-hop and `Connection`-nominated headers are stripped in both directions. Every inbound `Forwarded` and `X-Forwarded-*` header is dropped; caller IP/protocol metadata is re-added only under `forwardClientIp: true`, and any other forwarding header only as a fixed `headers` entry. The inbound Host is never trusted, and `X-Forwarded-Host` is emitted only from a fixed `forwardedHost`. Upstream redirects are never followed, and TLS verification has no off switch. Unreachable upstreams answer a flat `502`; the deadline (`timeoutMs`, default 30s) answers `504`.
|
|
14030
15402
|
|
|
14031
15403
|
## Docs
|
|
14032
15404
|
|
|
@@ -14296,7 +15668,7 @@ const keys = await storage.list({ prefix: "avatars/" })
|
|
|
14296
15668
|
| Adapter | Use | Notes |
|
|
14297
15669
|
|---|---|---|
|
|
14298
15670
|
| `MemoryStorage` | dev / tests | not durable, not shared across instances |
|
|
14299
|
-
| `FileStorage(root)` | a long-running server (Bun/Node/Deno) | persists content type + custom metadata in
|
|
15671
|
+
| `FileStorage(root)` | a long-running server (Bun/Node/Deno) | writes each object whole (staged in `<root>.nifra-state`, then renamed into place); persists content type + custom metadata in a sidecar tree there; reads older `<root>.nifra-metadata` sidecars; objects with none infer MIME from extension |
|
|
14300
15672
|
| `R2Storage(env.BUCKET)` | the edge (Cloudflare R2) | round-trips `contentType` + metadata; binding typed structurally (no `@cloudflare/workers-types`) |
|
|
14301
15673
|
|
|
14302
15674
|
Implement `StorageAdapter` (`put` / `get` / `delete` / `exists` / `list`) for **S3 / GCS / anything else** -
|
|
@@ -14394,6 +15766,16 @@ The validator still decides which generated mutations are truly invalid; the sup
|
|
|
14394
15766
|
weaken validation. A missing/invalid witness is a coverage gap, not a silent pass. Set
|
|
14395
15767
|
`requireCoverage: false` only when gaps are intentionally advisory.
|
|
14396
15768
|
|
|
15769
|
+
Declared `params` and `headers` get witnesses too, validated before any case runs, so a body or query
|
|
15770
|
+
case is rejected by the input it mutates rather than by a placeholder param or a missing header. A `t`
|
|
15771
|
+
schema synthesizes them, the witness's `params` and `headers` override, and a route param the schema
|
|
15772
|
+
leaves out keeps a placeholder its pattern accepts. The harness never invents credentials:
|
|
15773
|
+
`authorization`, `proxy-authorization`, `cookie`, `x-api-key`, and names ending in `-key` or containing
|
|
15774
|
+
`token`, `secret` or `signature` come from the witness (and `prepareRequest`) only, and are never
|
|
15775
|
+
mutated; a schema that requires one the witness lacks is a `NO_WITNESS` gap that names it. Cookies are
|
|
15776
|
+
never synthesized either: a route with a `cookies` schema needs `headers.cookie` in its witness. A
|
|
15777
|
+
params or header field typed as bare text accepts any text, so it is not a `NO_REJECTED_MUTATION` gap.
|
|
15778
|
+
|
|
14397
15779
|
To exercise adapter parity, pass runtime fetch targets. The reflected `app` remains the one source of
|
|
14398
15780
|
the contract:
|
|
14399
15781
|
|
|
@@ -14415,10 +15797,13 @@ laboratory at production.
|
|
|
14415
15797
|
### Opaque validators (zod / valibot / arktype)
|
|
14416
15798
|
|
|
14417
15799
|
A `t` schema exposes JSON Schema, so witnesses and constraint-driven mutations are synthesized
|
|
14418
|
-
automatically.
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
15800
|
+
automatically. So does any validator that implements Standard JSON Schema (`~standard.jsonSchema`,
|
|
15801
|
+
zod 4.2+ among them): the laboratory calls the schema's own converter, so nothing is resolved from disk
|
|
15802
|
+
and a monorepo run from its root reads an app's zod schemas too. An older zod 4 is converted with
|
|
15803
|
+
`z.toJSONSchema`, resolved from the project directory first, then from this package. A validator with
|
|
15804
|
+
neither validates requests fine, but the laboratory can't inspect it - those routes report `NO_WITNESS`
|
|
15805
|
+
and get only type-confusion mutations, and the gap says why when a conversion was tried and failed.
|
|
15806
|
+
Pass `reflectJsonSchema` to convert any other validator, or to choose the zod bridge explicitly:
|
|
14422
15807
|
|
|
14423
15808
|
```ts
|
|
14424
15809
|
import { zodJsonSchema } from "@nifrajs/testing/zod"
|
|
@@ -14654,6 +16039,17 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
14654
16039
|
|
|
14655
16040
|
## @nifrajs/web
|
|
14656
16041
|
|
|
16042
|
+
Optional server components are implemented for React, Vue, Svelte and Solid in the post-4.0
|
|
16043
|
+
feature branch. React uses native RSC/Flight; the other adapters use native islands in validated
|
|
16044
|
+
server ranges. Definitions require closed input/public-props schemas and authorization. Refresh
|
|
16045
|
+
executes the actual owner GET, preserving admission, read capabilities, budgets and cancellation;
|
|
16046
|
+
output is private/no-store. Mutate through validated server functions or actions.
|
|
16047
|
+
|
|
16048
|
+
Use the `/server-components`, `/server-components/client`, `/server-components/islands` and
|
|
16049
|
+
`/server-components/build` exports with an explicit `componentPipeline` named config export.
|
|
16050
|
+
See [the server-components guide](https://nifra.dev/docs/server-components) and the four runnable
|
|
16051
|
+
repository examples for complete entries, native outlets, exact compiler pins and rollback.
|
|
16052
|
+
|
|
14657
16053
|
Framework-agnostic SSR core for nifra - the render seam + HTML document orchestration.
|
|
14658
16054
|
|
|
14659
16055
|
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`.
|
|
@@ -14885,7 +16281,8 @@ const registration = await registerWebMcpTools([addItem])
|
|
|
14885
16281
|
|
|
14886
16282
|
The returned WebMCP `execute` function runs Nifra's existing input validation, capability, approval,
|
|
14887
16283
|
idempotency, budget, cancellation, output validation, and evidence path. By default it returns a bounded
|
|
14888
|
-
receipt envelope so an agent can verify whether the operation committed.
|
|
16284
|
+
receipt envelope so an agent can verify whether the operation committed. A `signal` the host passes as
|
|
16285
|
+
`execute`'s second argument reaches the executor as `ctx.signal`. The normal human UI remains the
|
|
14889
16286
|
source of page rendering and works when WebMCP is absent.
|
|
14890
16287
|
|
|
14891
16288
|
Receipt output is retained only while its JSON encoding stays within the bounded `maxReceiptBytes`
|
|
@@ -15094,6 +16491,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15094
16491
|
|
|
15095
16492
|
`A2AAgentCard`, `A2AAgentSkill`, `A2ACardInfo`, `A2AMountableApp`, `A2ARouteContext`, `A2ATaskState`, `A2A_ERROR_CODES`, `A2A_PROTOCOL_VERSION`, `AGENT_CARD_WELL_KNOWN_PATH`, `MountA2AOptions`, `agentCard`, `mountA2A`
|
|
15096
16493
|
|
|
16494
|
+
## @nifrajs/acp
|
|
16495
|
+
|
|
16496
|
+
`AcpBackend`, `AcpBackendOptions`, `AcpClientInfo`
|
|
16497
|
+
|
|
15097
16498
|
## @nifrajs/ag-ui
|
|
15098
16499
|
|
|
15099
16500
|
`AgUIMountableApp`, `AgUIRouteContext`, `AgUIRunContext`, `AgUIRunUsage`, `MountAgUIOptions`, `mountAgUI`
|
|
@@ -15104,11 +16505,11 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15104
16505
|
|
|
15105
16506
|
## @nifrajs/agent-app
|
|
15106
16507
|
|
|
15107
|
-
`AGENT_APP_FEATURES`, `AgentAppClient`, `AgentAppClientOptions`, `AgentAppError`, `AgentEventView`, `AgentTransport`, `AgentTransportError`, `AgentTransportRequest`, `ApprovalRequiredView`, `ApprovalResolvedView`, `AssistantChunkView`, `AuthProvider`, `BoundaryDecisionResult`, `BoundaryItemView`, `BoundaryStateView`, `CommandOutcome`, `CreateSessionInput`, `EvalComparisonView`, `EvidenceTimelineView`, `ExtensionReloadedView`, `FaultInjectionView`, `HandoffView`, `HttpAgentTransport`, `HttpAgentTransportOptions`, `MemoryCompactedView`, `OrderedEventBuffer`, `PendingApprovalView`, `RegistryCapabilityView`, `RepairRequiredView`, `ReplayEntryView`, `ReplayResult`, `ResolveHandoffInput`, `ResumeInput`, `ReviewView`, `ReviewViewCheck`, `ReviewViewEvidence`, `ReviewViewFinding`, `ReviewViewStatus`, `RunStudioNodeState`, `RunStudioNodeView`, `RunStudioView`, `RunView`, `SessionFailedView`, `SessionLifecycleView`, `SessionStoppedView`, `SessionView`, `ToolCompletedView`, `ToolDeltaView`, `ToolStartedView`, `TurnStartedView`, `VerificationCompletedView`, `boundaryCommands`, `boundaryIsStale`, `parseEventStream`, `toEvalComparisonView`, `toEventView`, `toEvidenceTimelineView`, `toFaultInjectionViews`, `toHandoffView`, `toRegistryCapabilityView`, `toReviewView`, `toRunStudioView`, `toRunView`, `toSessionView`, `virtualizeEvidenceRows`
|
|
16508
|
+
`AGENT_APP_FEATURES`, `AgentAppClient`, `AgentAppClientOptions`, `AgentAppError`, `AgentEventView`, `AgentTransport`, `AgentTransportError`, `AgentTransportRequest`, `ApprovalRequiredView`, `ApprovalResolvedView`, `AssistantChunkView`, `AuthProvider`, `BoundaryDecisionResult`, `BoundaryItemView`, `BoundaryStateView`, `CommandOutcome`, `ContextUsageView`, `CreateSessionInput`, `EvalComparisonView`, `EvidenceTimelineView`, `ExtensionEventView`, `ExtensionReloadedView`, `FaultInjectionView`, `HandoffView`, `HttpAgentTransport`, `HttpAgentTransportOptions`, `MemoryCompactedView`, `OrderedEventBuffer`, `PendingApprovalView`, `PlanUpdatedView`, `ReasoningDeltaView`, `RegistryCapabilityView`, `RepairRequiredView`, `ReplayEntryView`, `ReplayResult`, `ResolveHandoffInput`, `ResumeInput`, `ReviewView`, `ReviewViewCheck`, `ReviewViewEvidence`, `ReviewViewFinding`, `ReviewViewStatus`, `RunStudioNodeState`, `RunStudioNodeView`, `RunStudioView`, `RunView`, `SessionFailedView`, `SessionLifecycleView`, `SessionStoppedView`, `SessionView`, `ToolCompletedView`, `ToolDeltaView`, `ToolStartedView`, `TurnStartedView`, `VerificationCompletedView`, `boundaryCommands`, `boundaryIsStale`, `parseEventStream`, `toEvalComparisonView`, `toEventView`, `toEvidenceTimelineView`, `toFaultInjectionViews`, `toHandoffView`, `toRegistryCapabilityView`, `toReviewView`, `toRunStudioView`, `toRunView`, `toSessionView`, `virtualizeEvidenceRows`
|
|
15108
16509
|
|
|
15109
16510
|
## @nifrajs/agent-protocol
|
|
15110
16511
|
|
|
15111
|
-
`AGENT_PROTOCOL_VERSION`, `AgentApprovalRequiredEvent`, `AgentApprovalResolvedEvent`, `AgentAssistantDeltaEvent`, `AgentAssistantMessageEvent`, `AgentBackend`, `AgentBackendInfo`, `AgentError`, `AgentEvent`, `AgentEventPayload`, `AgentEventSink`, `AgentEventStream`, `AgentEventType`, `AgentExtensionReloadedEvent`, `AgentMemoryCompactedEvent`, `AgentRepairRequiredEvent`, `AgentRepairTask`, `AgentSessionCheckpoint`, `AgentSessionCompletedEvent`, `AgentSessionEventBase`, `AgentSessionFailedEvent`, `AgentSessionSnapshot`, `AgentSessionStartedEvent`, `AgentSessionStatus`, `AgentSessionStoppedEvent`, `AgentSessionUpdatedEvent`, `AgentToolCompletedEvent`, `AgentToolDeltaEvent`, `AgentToolStartedEvent`, `AgentTurnStartedEvent`, `AgentVerificationCompletedEvent`, `CreateSessionInput`, `ForkSessionInput`, `ForkSessionResult`, `ReloadResult`, `SendMessageInput`, `agentError`, `createAgentEventStream`, `isAgentEvent`
|
|
16512
|
+
`AGENT_MAX_EVENT_TEXT`, `AGENT_MAX_PAYLOAD_STRING`, `AGENT_PROTOCOL_VERSION`, `AgentApprovalRequiredEvent`, `AgentApprovalResolvedEvent`, `AgentAssistantDeltaEvent`, `AgentAssistantMessageEvent`, `AgentBackend`, `AgentBackendInfo`, `AgentContextUsageEvent`, `AgentError`, `AgentEvent`, `AgentEventPayload`, `AgentEventSink`, `AgentEventStream`, `AgentEventType`, `AgentExtensionEvent`, `AgentExtensionEventType`, `AgentExtensionReloadedEvent`, `AgentMemoryCompactedEvent`, `AgentPlanEntry`, `AgentPlanEntryStatus`, `AgentPlanUpdatedEvent`, `AgentReasoningDeltaEvent`, `AgentRepairRequiredEvent`, `AgentRepairTask`, `AgentSessionCheckpoint`, `AgentSessionCompletedEvent`, `AgentSessionEventBase`, `AgentSessionFailedEvent`, `AgentSessionSnapshot`, `AgentSessionStartedEvent`, `AgentSessionStatus`, `AgentSessionStoppedEvent`, `AgentSessionUpdatedEvent`, `AgentToolCompletedEvent`, `AgentToolDeltaEvent`, `AgentToolStartedEvent`, `AgentTurnStartedEvent`, `AgentVerificationCompletedEvent`, `CreateSessionInput`, `ForkSessionInput`, `ForkSessionResult`, `ReloadResult`, `SendMessageInput`, `agentError`, `agentEventProblem`, `boundAgentText`, `boundAgentValue`, `createAgentEventStream`, `isAgentEvent`, `isAgentExtensionEventType`, `splitAgentText`
|
|
15112
16513
|
|
|
15113
16514
|
## @nifrajs/agent-review
|
|
15114
16515
|
|
|
@@ -15120,11 +16521,11 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15120
16521
|
|
|
15121
16522
|
## @nifrajs/auth
|
|
15122
16523
|
|
|
15123
|
-
`AuthorizationRequest`, `Authorizer`, `CsrfOptions`, `GuardOptions`, `KVNamespaceLike`, `KVSessionStore`, `MemorySessionStore`, `MemorySessionStoreOptions`, `Session`, `SessionContext`, `SessionCookieOptions`, `SessionManager`, `SessionOptions`, `SessionRecord`, `SessionStore`, `createSessions`, `csrf`, `isAuthorized`, `requireAuthorization`, `requireSession`, `requireUser`
|
|
16524
|
+
`AuthorizationRequest`, `Authorizer`, `CsrfOptions`, `GuardOptions`, `KVNamespaceLike`, `KVSessionStore`, `MemorySessionStore`, `MemorySessionStoreOptions`, `Session`, `SessionContext`, `SessionCookieOptions`, `SessionManager`, `SessionOptions`, `SessionRecord`, `SessionStore`, `UnauthenticatedResponse`, `createSessions`, `csrf`, `isAuthorized`, `requireAuthorization`, `requireSession`, `requireUser`
|
|
15124
16525
|
|
|
15125
16526
|
## @nifrajs/authjs
|
|
15126
16527
|
|
|
15127
|
-
`AuthGuardOptions`, `AuthJSConfig`, `AuthJSOptions`, `Session`, `app`, `authjs`, `getSession`, `requireAuthUser`
|
|
16528
|
+
`AuthGuardOptions`, `AuthJSConfig`, `AuthJSOptions`, `Session`, `UnauthenticatedResponse`, `app`, `authjs`, `getSession`, `requireAuthUser`
|
|
15128
16529
|
|
|
15129
16530
|
## @nifrajs/aws-lambda
|
|
15130
16531
|
|
|
@@ -15132,19 +16533,19 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15132
16533
|
|
|
15133
16534
|
## @nifrajs/better-auth
|
|
15134
16535
|
|
|
15135
|
-
`AuthedOptions`, `BetterAuthLike`, `BetterAuthOptions`, `Principal`, `PrincipalFor`, `RequireSessionOptions`, `SessionOf`, `SessionUserOf`, `WithPrincipal`, `authed`, `betterAuth`, `getSession`, `requirePrincipal`, `requireSession`
|
|
16536
|
+
`AuthedOptions`, `BetterAuthLike`, `BetterAuthOptions`, `Principal`, `PrincipalFor`, `RequireSessionOptions`, `SessionOf`, `SessionUserOf`, `UnauthenticatedResponse`, `WithPrincipal`, `authed`, `betterAuth`, `getSession`, `requirePrincipal`, `requireSession`
|
|
15136
16537
|
|
|
15137
16538
|
## @nifrajs/cache
|
|
15138
16539
|
|
|
15139
|
-
`Cache`, `CacheCapabilities`, `CacheEvent`, `CacheObserver`, `CacheOperation`, `CacheOptions`, `CacheOutcome`, `CacheStore`, `CapabilityBeacon`, `MemoryCache`, `MemoryCacheOptions`, `SetOptions`, `StoredEntry`, `WrapOptions`, `createCache`
|
|
16540
|
+
`Cache`, `CacheCapabilities`, `CacheEvent`, `CacheObserver`, `CacheOperation`, `CacheOptions`, `CacheOutcome`, `CacheStore`, `CacheStoreOp`, `CapabilityBeacon`, `MemoryCache`, `MemoryCacheOptions`, `SetOptions`, `StoredEntry`, `WrapOptions`, `createCache`
|
|
15140
16541
|
|
|
15141
16542
|
## @nifrajs/client
|
|
15142
16543
|
|
|
15143
|
-
`ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `ClientOptions`, `ClientRetryOptions`, `FetchFn`, `InProcessClient`, `InProcessClientOptions`, `Jsonify`, `LoaderArgs`, `LoaderData`, `LoaderResponseControls`, `OutputOf`, `RESERVED_EXACT_KEYS`, `RESERVED_KEY_READOUT`, `RESERVED_VERB_KEYS`, `Register`, `RegisteredBackend`, `RegisteredEnv`, `RegistryOf`, `ResponseContractViolation`, `Result`, `RouteLoaderArgs`, `SearchSchemaOf`, `SubscribeOptions`, `Subscription`, `TestClientOptions`, `Treaty`, `TreatyFromRegistry`, `WsCallOptions`, `WsHandle`, `client`, `inProcessClient`, `reservedKeyFor`, `testClient`
|
|
16544
|
+
`ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `ClientOptions`, `ClientRetryOptions`, `FetchFn`, `InProcessClient`, `InProcessClientOptions`, `InputOf`, `Jsonify`, `LoaderArgs`, `LoaderData`, `LoaderResponseControls`, `OutputOf`, `RESERVED_EXACT_KEYS`, `RESERVED_KEY_READOUT`, `RESERVED_VERB_KEYS`, `Register`, `RegisteredBackend`, `RegisteredEnv`, `RegistryOf`, `ResponseContractViolation`, `Result`, `RouteActionReturn`, `RouteLoaderArgs`, `RouteLoaderReturn`, `SearchSchemaOf`, `SubscribeOptions`, `Subscription`, `TestClientOptions`, `Treaty`, `TreatyFromRegistry`, `WsCallOptions`, `WsHandle`, `client`, `inProcessClient`, `reservedKeyFor`, `testClient`
|
|
15144
16545
|
|
|
15145
16546
|
## @nifrajs/coding-agent
|
|
15146
16547
|
|
|
15147
|
-
`AGENT_CAPABILITY_MANIFEST_VERSION`, `AGENT_PRESETS`, `AgentCapability`, `AgentCapabilityManifest`, `AgentPlan`, `AgentPreset`, `AgentPresetName`, `ApprovalDecision`, `ApprovalManager`, `ApprovalManagerOptions`, `ApprovalMatchRejection`, `ApprovalMatchResult`, `ApprovalRequest`, `BoundedSubagentRunner`, `BoundedText`, `CapabilityDescriptor`, `CiDeploymentAdapter`, `CodingAgentExtension`, `CodingAgentHost`, `CodingAgentHostOptions`, `CodingAgentRpcServer`, `CodingAgentRpcServerHandle`, `CodingAgentRpcServerOptions`, `CompactionReport`, `ContextRecord`, `ContextWindow`, `ContextWindowOptions`, `DEPLOYMENT_REFERENCE_PROFILES`, `ExtensionCommand`, `ExtensionContext`, `ExtensionDescriptorOptions`, `ExtensionDescriptorSource`, `ExtensionEventHandler`, `ExtensionHost`, `ExtensionHostOptions`, `ExtensionProvider`, `ExtensionReloadResult`, `ExtensionSubagent`, `ExtensionTool`, `FileSessionStore`, `FileSessionStoreOptions`, `HandoffCoordinator`, `HandoffCoordinatorOptions`, `HandoffDecision`, `HandoffError`, `HandoffRejection`, `HandoffView`, `HealingEvent`, `HealingOptions`, `HealingResult`, `IsolatedExtensionSnapshot`, `IsolatedExtensionTool`, `IsolatedExtensionWorker`, `IsolatedExtensionWorkerOptions`, `LocalProcessDeploymentAdapter`, `MigrateLegacySessionOptions`, `NIFRA_AGENT_INSTRUCTIONS`, `NativeApprovalPort`, `NativeGatewayModelPortOptions`, `NativeMessage`, `NativeModelChunk`, `NativeModelPort`, `NativeModelRequest`, `NativeModelResponse`, `NativeTool`, `NifraAgentTool`, `NifraBackend`, `NifraBackendOptions`, `NifraContextOptions`, `NifraContextResult`, `OpenHandoffInput`, `PiBackend`, `PiBackendOptions`, `PlanEvent`, `PlanPhase`, `PlanResult`, `PlanRunner`, `PlanRunnerOptions`, `ProjectDiffOptions`, `ProjectDiffResult`, `RepairProposal`, `ReplayBackend`, `ReplayBackendOptions`, `ReplayDeploymentAdapter`, `ReviewExecutionErrorCode`, `ReviewExecutionResult`, `ReviewOptions`, `SESSION_EVIDENCE_VERSION`, `SelfHealingController`, `SessionEvidenceRecord`, `SessionLogEntry`, `SessionMigrationError`, `SessionMigrationReport`, `SessionStore`, `SubagentAbandonment`, `SubagentAbandonmentLedger`, `SubagentExecutor`, `SubagentResult`, `SubagentRunnerOptions`, `SubagentSpec`, `UiExtensionHost`, `UiExtensionHostOptions`, `UiExtensionManifest`, `UiExtensionSlot`, `UiReloadResult`, `UiStatusWidget`, `UiThemeDescriptor`, `VerificationOptions`, `VerificationRepairTask`, `VerificationResult`, `WorkflowContext`, `WorkflowEvent`, `WorkflowResult`, `WorkflowRunner`, `WorkflowRunnerOptions`, `WorkflowStep`, `approvalRequestFromEvent`, `approvalResolvedEvent`, `createCapabilityManifest`, `createCiDeploymentAdapter`, `createLocalProcessDeploymentAdapter`, `createNativeGatewayModelPort`, `createNifraTools`, `createPresetSpec`, `createReplayDeploymentAdapter`, `createVerificationRepairTask`, `deniedCapabilities`, `discoverExtensions`, `extensionDescriptor`, `extensionDescriptors`, `getAgentPreset`, `isSafeReviewDiff`, `migrateLegacySession`, `parseCapabilityManifest`, `parseSessionEvidenceRecord`, `readBoundedText`, `readProjectDiff`, `readReplayEvents`, `runNifraContext`, `runNifraReview`, `runNifraVerification`, `stableSessionEventCode`, `validateExtensionModule`
|
|
16548
|
+
`AGENT_CAPABILITY_MANIFEST_VERSION`, `AGENT_PRESETS`, `AgentCapability`, `AgentCapabilityManifest`, `AgentPlan`, `AgentPreset`, `AgentPresetName`, `ApprovalDecision`, `ApprovalManager`, `ApprovalManagerOptions`, `ApprovalMatchRejection`, `ApprovalMatchResult`, `ApprovalRequest`, `BoundedSubagentRunner`, `BoundedText`, `CapabilityDescriptor`, `CiDeploymentAdapter`, `CodingAgentExtension`, `CodingAgentHost`, `CodingAgentHostOptions`, `CodingAgentRpcServer`, `CodingAgentRpcServerHandle`, `CodingAgentRpcServerOptions`, `CompactionReport`, `ContextRecord`, `ContextWindow`, `ContextWindowOptions`, `DEPLOYMENT_REFERENCE_PROFILES`, `ExtensionCommand`, `ExtensionContext`, `ExtensionDescriptorOptions`, `ExtensionDescriptorSource`, `ExtensionEventHandler`, `ExtensionHost`, `ExtensionHostOptions`, `ExtensionProvider`, `ExtensionReloadResult`, `ExtensionSubagent`, `ExtensionTool`, `FileSessionStore`, `FileSessionStoreOptions`, `HandoffCoordinator`, `HandoffCoordinatorOptions`, `HandoffDecision`, `HandoffError`, `HandoffRejection`, `HandoffView`, `HealingEvent`, `HealingOptions`, `HealingResult`, `IsolatedExtensionSnapshot`, `IsolatedExtensionTool`, `IsolatedExtensionWorker`, `IsolatedExtensionWorkerOptions`, `LocalProcessDeploymentAdapter`, `MigrateLegacySessionOptions`, `NIFRA_AGENT_INSTRUCTIONS`, `NativeApprovalPort`, `NativeGatewayModelPortOptions`, `NativeMessage`, `NativeModelChunk`, `NativeModelPort`, `NativeModelRequest`, `NativeModelResponse`, `NativeTool`, `NifraAgentTool`, `NifraBackend`, `NifraBackendOptions`, `NifraContextOptions`, `NifraContextResult`, `OpenHandoffInput`, `PiBackend`, `PiBackendOptions`, `PlanEvent`, `PlanPhase`, `PlanResult`, `PlanRunner`, `PlanRunnerOptions`, `ProjectDiffOptions`, `ProjectDiffResult`, `RepairProposal`, `ReplayBackend`, `ReplayBackendOptions`, `ReplayDeploymentAdapter`, `ReviewExecutionErrorCode`, `ReviewExecutionResult`, `ReviewOptions`, `SESSION_EVIDENCE_VERSION`, `SelfHealingController`, `SessionEvidenceRecord`, `SessionLogEntry`, `SessionMigrationError`, `SessionMigrationReport`, `SessionStore`, `SubagentAbandonment`, `SubagentAbandonmentLedger`, `SubagentExecutor`, `SubagentResult`, `SubagentRunnerOptions`, `SubagentSpec`, `UiExtensionHost`, `UiExtensionHostOptions`, `UiExtensionManifest`, `UiExtensionSlot`, `UiReloadResult`, `UiStatusWidget`, `UiThemeDescriptor`, `VerificationOptions`, `VerificationRepairTask`, `VerificationResult`, `WorkflowContext`, `WorkflowEvent`, `WorkflowResult`, `WorkflowRunner`, `WorkflowRunnerOptions`, `WorkflowStep`, `approvalRequestFromEvent`, `approvalResolvedEvent`, `createCapabilityManifest`, `createCiDeploymentAdapter`, `createLocalProcessDeploymentAdapter`, `createNativeGatewayModelPort`, `createNifraTools`, `createPresetSpec`, `createReplayDeploymentAdapter`, `createVerificationRepairTask`, `deniedCapabilities`, `discoverExtensions`, `extensionDescriptor`, `extensionDescriptors`, `getAgentPreset`, `isSafeReviewDiff`, `isVerificationAvailable`, `migrateLegacySession`, `parseCapabilityManifest`, `parseSessionEvidenceRecord`, `readBoundedText`, `readProjectDiff`, `readReplayEvents`, `runNifraContext`, `runNifraReview`, `runNifraVerification`, `stableSessionEventCode`, `validateExtensionModule`
|
|
15148
16549
|
|
|
15149
16550
|
## @nifrajs/content
|
|
15150
16551
|
|
|
@@ -15152,7 +16553,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15152
16553
|
|
|
15153
16554
|
## @nifrajs/core
|
|
15154
16555
|
|
|
15155
|
-
`AdmissionController`, `AdmissionDecision`, `AnyServer`, `AuthHeaders`, `AuthMaybePromise`, `AuthenticationFailure`, `AuthenticationFailureReason`, `AuthenticationInput`, `AuthenticationResult`, `AuthenticationStage`, `AuthenticationSuccess`, `Context`, `ContextPlugin`, `CookieOptions`, `CookieSecret`, `DefinePluginResult`, `DurableObjectNamespaceLike`, `ExecutionContext`, `FRAMEWORK_NAME`, `FetchApp`, `FrameworkError`, `FrameworkName`, `Handler`, `IdentityPlugin`, `InferInput`, `InferOutput`, `JoinRoutePath`, `LambdaEvent`, `LambdaHandler`, `LambdaResponse`, `LambdaV1Event`, `LambdaV2Event`, `ListenTlsOptions`, `LogFields`, `Logger`, `METHODS`, `McpPromptDescriptor`, `McpResourceDescriptor`, `Method`, `Middleware`, `MountFetchOptions`, `MountOptions`, `MountableApp`, `NetlifyEvent`, `NetlifyHandler`, `NifraFeatureVersion`, `NifraPlugin`, `NifraWebSocket`, `NodeRequestContext`, `NodeRequestHook`, `NodeResponseBodyHook`, `NodeResponseContext`, `NodeResponseHook`, `NodeServeOutcome`, `OnRequestResult`, `Params`, `PlainRender`, `Platform`, `PlatformResponse`, `PluginTypeCollapsed`, `PrefixRegistry`, `Prettify`, `PromptArgument`, `PromptMessage`, `ROUTE_PATTERN_OVERLAP_MAX_STATES`, `RedactOptions`, `Registry`, `RequestPath`, `ResponseBodyHook`, `ResponseBodyReplacement`, `ResponseControls`, `ResponseFinalization`, `ResponseHeadersHook`, `ResponseHeadersView`, `ResponseMapFor`, `ResponseResult`, `RouteConfigError`, `RouteConfigErrorCode`, `RouteDescriptor`, `RouteInfo`, `RouteInfoFor`, `RouteMethod`, `RoutePatternOverlapLimitError`, `RouteSchema`, `Router`, `RouterMatch`, `RunningServer`, `SSEContext`, `SSEInit`, `SSEMessage`, `SSEStream`, `ScheduledController`, `ScheduledHandler`, `Server`, `ServerOptions`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `StandardTypes`, `StandardWebSocket`, `StatusResponse`, `ToolAnnotations`, `TypedSSEStream`, `UrlParts`, `VERSION`, `ValidationOutcome`, `VercelHandler`, `Version`, `WebSocketContext`, `WebSocketData`, `WebSocketHandler`, `WebSocketUpgradeOutcome`, `authenticated`, `commonSecretPatterns`, `cookieNamePrefix`, `defineContextPlugin`, `defineIdentityPlugin`, `definePlugin`, `defineRouterPlugin`, `hasDuplicateCookie`, `isSameOriginPath`, `isSameOriginRequest`, `jsonLogger`, `parseCookies`, `pathnameOf`, `redactLogFields`, `rejected`, `replacedRequestOf`, `routePatternOverlap`, `serializeCookie`, `server`, `signValue`, `silentLogger`, `status`, `toFetchHandler`, `toLambdaHandler`, `toNetlifyHandler`, `toVercelHandler`, `unsignValue`, `urlPartsOf`
|
|
16556
|
+
`AdmissionController`, `AdmissionDecision`, `AnyServer`, `AuthHeaders`, `AuthMaybePromise`, `AuthenticationFailure`, `AuthenticationFailureReason`, `AuthenticationInput`, `AuthenticationResult`, `AuthenticationStage`, `AuthenticationSuccess`, `Context`, `ContextPlugin`, `CookieOptions`, `CookieSecret`, `DeclaredMountRoute`, `DefinePluginResult`, `DurableObjectNamespaceLike`, `ExecutionContext`, `FRAMEWORK_NAME`, `FetchApp`, `FrameworkError`, `FrameworkName`, `Handler`, `IdentityPlugin`, `InferInput`, `InferOutput`, `JoinRoutePath`, `LambdaEvent`, `LambdaHandler`, `LambdaResponse`, `LambdaV1Event`, `LambdaV2Event`, `ListenTlsOptions`, `LogFields`, `Logger`, `METHODS`, `McpPromptDescriptor`, `McpResourceDescriptor`, `Method`, `Middleware`, `MountFetchOptions`, `MountOptions`, `MountableApp`, `NetlifyEvent`, `NetlifyHandler`, `NifraFeatureVersion`, `NifraPlugin`, `NifraWebSocket`, `NodeRequestContext`, `NodeRequestHook`, `NodeResponseBodyHook`, `NodeResponseContext`, `NodeResponseHook`, `NodeServeOutcome`, `OnRequestResult`, `OpaqueMountDeclaration`, `Params`, `PlainRender`, `Platform`, `PlatformResponse`, `PluginTypeCollapsed`, `PrefixRegistry`, `Prettify`, `PromptArgument`, `PromptMessage`, `ROUTE_PATTERN_OVERLAP_MAX_STATES`, `RedactOptions`, `Registry`, `RequestPath`, `ResponseBodyHook`, `ResponseBodyReplacement`, `ResponseControls`, `ResponseFinalization`, `ResponseHeadersHook`, `ResponseHeadersView`, `ResponseMapFor`, `ResponseResult`, `RouteConfigError`, `RouteConfigErrorCode`, `RouteDescriptor`, `RouteInfo`, `RouteInfoFor`, `RouteMethod`, `RoutePatternOverlapLimitError`, `RouteSchema`, `Router`, `RouterMatch`, `RunningServer`, `SSEContext`, `SSEInit`, `SSEMessage`, `SSEStream`, `ScheduledController`, `ScheduledHandler`, `Server`, `ServerOptions`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `StandardTypes`, `StandardWebSocket`, `StatusResponse`, `ToolAnnotations`, `TypedSSEStream`, `UrlParts`, `VERSION`, `ValidationOutcome`, `VercelHandler`, `Version`, `WebSocketContext`, `WebSocketData`, `WebSocketHandler`, `WebSocketUpgradeOutcome`, `authenticated`, `commonSecretPatterns`, `cookieNamePrefix`, `defineContextPlugin`, `defineIdentityPlugin`, `definePlugin`, `defineRouterPlugin`, `hasDuplicateCookie`, `isAuthenticatedRequest`, `isSameOriginPath`, `isSameOriginRequest`, `jsonLogger`, `markAuthenticatedRequest`, `parseCookies`, `pathnameOf`, `redactLogFields`, `rejected`, `replacedRequestOf`, `routePatternOverlap`, `serializeCookie`, `server`, `signValue`, `silentLogger`, `status`, `toFetchHandler`, `toLambdaHandler`, `toNetlifyHandler`, `toVercelHandler`, `unsignValue`, `urlPartsOf`, `writableResponse`
|
|
15156
16557
|
|
|
15157
16558
|
## @nifrajs/cron
|
|
15158
16559
|
|
|
@@ -15180,7 +16581,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15180
16581
|
|
|
15181
16582
|
## @nifrajs/graphql
|
|
15182
16583
|
|
|
15183
|
-
`GraphqlContextBuilder`, `GraphqlContextInput`, `GraphqlHttpOptions`, `GraphqlPubSub`, `GraphqlWsOptions`, `MountGraphqlOptions`, `NifraContextLike`, `buildContext`, `createPubSub`, `graphqlWebSocket`, `mountGraphql`, `respondGraphql`
|
|
16584
|
+
`GraphqlContextBuilder`, `GraphqlContextInput`, `GraphqlHttpOptions`, `GraphqlPubSub`, `GraphqlPubSubOptions`, `GraphqlPubSubOverflowError`, `GraphqlWsOptions`, `MountGraphqlOptions`, `NifraContextLike`, `buildContext`, `createPubSub`, `graphqlWebSocket`, `mountGraphql`, `respondGraphql`
|
|
15184
16585
|
|
|
15185
16586
|
## @nifrajs/i18n
|
|
15186
16587
|
|
|
@@ -15196,11 +16597,11 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15196
16597
|
|
|
15197
16598
|
## @nifrajs/islets
|
|
15198
16599
|
|
|
15199
|
-
`BindableElement`, `BindableRoot`, `IslandContext`, `IslandHost`, `IslandScope`, `IslandSetup`, `IslandStrategy`, `MountIslandOptions`, `Signal`, `batch`, `bindScope`, `computed`, `effect`, `island`, `islandState`, `mountIslands`, `signal`
|
|
16600
|
+
`BindableElement`, `BindableRoot`, `Computed`, `IslandContext`, `IslandHost`, `IslandScope`, `IslandSetup`, `IslandStrategy`, `MountIslandOptions`, `Signal`, `batch`, `bindScope`, `computed`, `effect`, `island`, `islandState`, `mountIslands`, `signal`
|
|
15200
16601
|
|
|
15201
16602
|
## @nifrajs/jobs
|
|
15202
16603
|
|
|
15203
|
-
`Backoff`, `CapabilityBeacon`, `DeadLetterRecord`, `DeadLetterView`, `EnqueueOptions`, `ExponentialOptions`, `JobContext`, `JobCounts`, `JobDefinition`, `JobEnqueueInfo`, `JobError`, `JobHandle`, `JobHandler`, `JobRunInfo`, `JobRunOutcome`, `JobStore`, `JobTraceContext`, `JobValidationError`, `MemoryJobStore`, `Queue`, `QueueHealth`, `QueueInstrument`, `QueueOptions`, `RetryPolicy`, `StandardResult`, `StandardSchemaV1`, `StoredJob`, `Worker`, `WorkerOptions`, `createQueue`, `exponentialBackoff`, `fixedBackoff`, `noBackoff`, `toDeadLetterView`, `toQueueHealth`
|
|
16604
|
+
`Backoff`, `CapabilityBeacon`, `DeadLetterRecord`, `DeadLetterView`, `EnqueueOptions`, `ExponentialOptions`, `JobContext`, `JobCounts`, `JobDefinition`, `JobEnqueueInfo`, `JobError`, `JobHandle`, `JobHandler`, `JobRunInfo`, `JobRunOutcome`, `JobStore`, `JobTimeoutError`, `JobTraceContext`, `JobValidationError`, `MemoryJobStore`, `Queue`, `QueueHealth`, `QueueInstrument`, `QueueOptions`, `RetryPolicy`, `StandardResult`, `StandardSchemaV1`, `StoredJob`, `Worker`, `WorkerOptions`, `createQueue`, `exponentialBackoff`, `fixedBackoff`, `noBackoff`, `toDeadLetterView`, `toQueueHealth`
|
|
15204
16605
|
|
|
15205
16606
|
## @nifrajs/mcp
|
|
15206
16607
|
|
|
@@ -15212,11 +16613,11 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15212
16613
|
|
|
15213
16614
|
## @nifrajs/middleware
|
|
15214
16615
|
|
|
15215
|
-
`AdmissionControllerHandle`, `AdmissionEvidence`, `AdmissionOptions`, `AdmissionPolicy`, `AdmissionSnapshot`, `ApiKeyStaticOptions`, `ApiKeyVerifyOptions`, `AuthPlugin`, `BasicAuthPlugin`, `BasicAuthStaticOptions`, `BasicAuthVerifyOptions`, `BearerOptions`, `BodyLimitOptions`, `ByteRange`, `ByteRangeResult`, `CacheControlOptions`, `CacheOptions`, `CachedResponse`, `Composable`, `CompressionOptions`, `ConditionalResponseOptions`, `ContentPreference`, `CorsOptions`, `CsrfOptions`, `DurableCommandOptions`, `ETagOptions`, `HealthcheckOptions`, `IdempotencyClaim`, `IdempotencyOptions`, `IdempotencyRecord`, `IdempotencyStore`, `IpMatcher`, `IpRestrictionOptions`, `JwkKey`, `JwksOptions`, `JwtAlgorithm`, `JwtClaims`, `JwtHeader`, `JwtKeyResolver`, `JwtOptions`, `JwtPlugin`, `JwtVerificationKey`, `LanguageMatch`, `LanguageOptions`, `LoggerOptions`, `LoopDelayHistogram`, `LoopDelayMonitor`, `MemoryIdempotencyStore`, `MemoryIdempotencyStoreOptions`, `MemoryResponseCache`, `MemoryResponseCacheOptions`, `MemoryStore`, `MemoryStoreOptions`, `MethodOverrideOptions`, `MultipartBody`, `MultipartHeadersInit`, `MultipartPart`, `MultipartResponseOptions`, `OpenApiInfo`, `OpenApiOptions`, `OpenApiServer`, `OpenApiTag`, `OpenApiUiOptions`, `PoweredByOptions`, `PrettyJsonOptions`, `ProblemDetailsOptions`, `RangeResponseOptions`, `RateLimitOptions`, `RateLimitResult`, `RateLimitStore`, `RequestIdOptions`, `RequestLogFields`, `ResponseCacheStore`, `RouteLike`, `SecurityHeadersOptions`, `SecurityRequirement`, `ShedReason`, `TimingControls`, `TimingMetric`, `TimingOptions`, `TrailingSlashOptions`, `VerifiedJwt`, `VerifyJwtOptions`, `VerifyJwtResult`, `apiKey`, `appendTrailingSlash`, `basicAuth`, `bearer`, `bodyLimit`, `buildOpenApiDocument`, `cache`, `cacheControl`, `combine`, `compression`, `conditionalResponse`, `cors`, `createAdmissionController`, `createCsrfToken`, `createEventLoopLagSampler`, `csrf`, `durableCommand`, `etag`, `healthcheck`, `idempotency`, `ipRestriction`, `jwk`, `jwks`, `jwt`, `language`, `logger`, `methodOverride`, `multipartResponse`, `namedCombine`, `negotiateContentType`, `openapi`, `parseAcceptHeader`, `parseByteRange`, `pickLanguage`, `poweredBy`, `prettyJson`, `problemDetails`, `rangeResponse`, `rateLimit`, `requestId`, `responseCache`, `securityHeaders`, `timing`, `trimTrailingSlash`, `tryVerifyJwt`, `verifyCsrfToken`, `verifyJwt`
|
|
16616
|
+
`AdmissionControllerHandle`, `AdmissionEvidence`, `AdmissionOptions`, `AdmissionPolicy`, `AdmissionSnapshot`, `ApiKeyStaticOptions`, `ApiKeyVerifyOptions`, `AuthPlugin`, `BasicAuthPlugin`, `BasicAuthStaticOptions`, `BasicAuthVerifyOptions`, `BearerOptions`, `BodyLimitOptions`, `ByteRange`, `ByteRangeResult`, `CacheControlOptions`, `CacheOptions`, `CachedResponse`, `Composable`, `CompressionOptions`, `ConditionalResponseOptions`, `ContentPreference`, `CorsOptions`, `CsrfOptions`, `DurableCommandOptions`, `ETagOptions`, `HealthcheckOptions`, `IdempotencyClaim`, `IdempotencyOptions`, `IdempotencyRecord`, `IdempotencyStore`, `IpMatcher`, `IpRestrictionOptions`, `JwkKey`, `JwksOptions`, `JwtAlgorithm`, `JwtClaims`, `JwtHeader`, `JwtKeyResolver`, `JwtOptions`, `JwtPlugin`, `JwtVerificationKey`, `LanguageMatch`, `LanguageOptions`, `LoggerOptions`, `LoopDelayHistogram`, `LoopDelayMonitor`, `MemoryIdempotencyStore`, `MemoryIdempotencyStoreOptions`, `MemoryResponseCache`, `MemoryResponseCacheOptions`, `MemoryStore`, `MemoryStoreOptions`, `MethodOverrideOptions`, `MultipartBody`, `MultipartHeadersInit`, `MultipartPart`, `MultipartResponseOptions`, `OpenApiInfo`, `OpenApiOptions`, `OpenApiServer`, `OpenApiTag`, `OpenApiUiOptions`, `PoweredByOptions`, `PrettyJsonOptions`, `ProblemDetailsOptions`, `RangeResponseOptions`, `RateLimitOptions`, `RateLimitResult`, `RateLimitStore`, `RateLimitTier`, `RateLimitTiersOptions`, `RequestIdOptions`, `RequestLogFields`, `ResponseCacheStore`, `RouteLike`, `SecurityHeadersOptions`, `SecurityRequirement`, `ShedReason`, `TimingControls`, `TimingMetric`, `TimingOptions`, `TrailingSlashOptions`, `VerifiedJwt`, `VerifyJwtOptions`, `VerifyJwtResult`, `apiKey`, `appendTrailingSlash`, `basicAuth`, `bearer`, `bodyLimit`, `buildOpenApiDocument`, `cache`, `cacheControl`, `combine`, `compression`, `conditionalResponse`, `cors`, `createAdmissionController`, `createCsrfToken`, `createEventLoopLagSampler`, `csrf`, `durableCommand`, `etag`, `healthcheck`, `idempotency`, `ipRestriction`, `jwk`, `jwks`, `jwt`, `language`, `logger`, `methodOverride`, `multipartResponse`, `namedCombine`, `negotiateContentType`, `openapi`, `parseAcceptHeader`, `parseByteRange`, `pickLanguage`, `poweredBy`, `prettyJson`, `problemDetails`, `rangeResponse`, `rateLimit`, `rateLimitTiers`, `requestId`, `responseCache`, `securityHeaders`, `timing`, `trimTrailingSlash`, `tryVerifyJwt`, `verifyCsrfToken`, `verifyJwt`
|
|
15216
16617
|
|
|
15217
16618
|
## @nifrajs/mock
|
|
15218
16619
|
|
|
15219
|
-
`MockServer`, `MockServerOptions`, `MockableApp`, `MockableRoute`, `UnsupportedMockSchemaError`, `autoReflectJsonSchema`, `createMockServer`, `generateMockValue`
|
|
16620
|
+
`MockServer`, `MockServerOptions`, `MockableApp`, `MockableRoute`, `UnsupportedMockSchemaError`, `autoReflectJsonSchema`, `createMockServer`, `explainAutoReflectJsonSchema`, `generateMockValue`
|
|
15220
16621
|
|
|
15221
16622
|
## @nifrajs/node
|
|
15222
16623
|
|
|
@@ -15244,7 +16645,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15244
16645
|
|
|
15245
16646
|
## @nifrajs/schema
|
|
15246
16647
|
|
|
15247
|
-
`DeferredValue`, `ImportOpenAPIOptions`, `ImportedApiInventory`, `ImportedRoute`, `ImportedRouteQuery`, `NifraSchema`, `OpenAPIDocument`, `OpenAPIImportError`, `OpenAPIInfo`, `OpenApiInventoryDrift`, `Page`, `decodeCursor`, `diffOpenApiInventory`, `encodeCursor`, `fromTypeBox`, `importOpenAPI`, `paginate`, `registerFormat`, `t`, `toOpenAPI`, `toOpenAPIFromEvidence`
|
|
16648
|
+
`DeferredValue`, `ImportOpenAPIOptions`, `ImportedApiInventory`, `ImportedRoute`, `ImportedRouteQuery`, `JsonValue`, `NifraSchema`, `OpenAPIDocument`, `OpenAPIImportError`, `OpenAPIInfo`, `OpenAPIVersion`, `OpenApiInventoryDrift`, `Page`, `decodeCursor`, `diffOpenApiInventory`, `encodeCursor`, `fromTypeBox`, `importOpenAPI`, `paginate`, `registerFormat`, `t`, `toOpenAPI`, `toOpenAPIFromEvidence`
|
|
15248
16649
|
|
|
15249
16650
|
## @nifrajs/storage
|
|
15250
16651
|
|
|
@@ -15252,7 +16653,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15252
16653
|
|
|
15253
16654
|
## @nifrajs/testing
|
|
15254
16655
|
|
|
15255
|
-
`AdapterCertificationError`, `AdapterCertificationProfile`, `AdapterCertificationReport`, `AdversarialContractError`, `AdversarialContractOptions`, `AdversarialContractReport`, `AdversarialContractResult`, `AgentEvalCase`, `AgentEvalComponentEvidence`, `AgentEvalCompositionOptions`, `AgentEvalCompositionReport`, `AgentEvalRegressionError`, `AgentEvalReport`, `AgentEvalSuite`, `AgentEvalSuiteSpec`, `AgentFailureMatrixCase`, `AgentFailureMatrixError`, `AgentFailureMatrixKind`, `AgentFailureMatrixReport`, `AppLike`, `BaselineComparison`, `BaselineOptions`, `CaptureIncidentOptions`, `CapturedRequest`, `CapturedRequestInput`, `CaseComparison`, `CaseResult`, `CertifiableCacheEntry`, `CertifiableCacheStore`, `CertifiableDeploymentAdapter`, `CertifiableDescriptorAdapter`, `CertifiableDomainEvent`, `CertifiableEventDeliveryAdapter`, `CertifiableEventRecord`, `CertifiableJobStore`, `CertifiableModelGateway`, `CertifiableRuntimeAdapter`, `CertifiableRuntimeServer`, `CertifiableStorageAdapter`, `CertificationCapabilityEvidence`, `CertificationCheck`, `CertificationCheckEvidence`, `CertificationTarget`, `ComparisonCode`, `ContractAuthContext`, `ContractCaseContext`, `ContractCaseKind`, `ContractCoverageGap`, `ContractCoverageGapCode`, `ContractExpectedResponse`, `ContractLabHandler`, `ContractLabRuntimeAdapter`, `ContractLabServer`, `ContractLabWitness`, `ContractReplay`, `ContractRuntime`, `ContractTarget`, `ContractTestApp`, `ContractWitness`, `CookieJar`, `CreateTrajectoryTranscriptOptions`, `DataAdapterConformanceError`, `DataAdapterConformanceReport`, `DataAdapterConformanceTarget`, `DataConformanceCheck`, `DataConformanceFailure`, `DataConformancePage`, `DataConformanceScope`, `DataConformanceTransaction`, `EffectLedger`, `FailureDirective`, `FailureEvidence`, `FailureInjectedError`, `FailureKind`, `FailureLab`, `FailureLabOptions`, `FailureReplay`, `FailureScenario`, `FailureScenarioReport`, `FaultProfile`, `FaultProfileFault`, `FaultProfileFaultKind`, `FaultProfileReport`, `FaultProfileScenario`, `FaultProfileScenarioReport`, `GenerateRegressionTestOptions`, `IdempotencyDivergence`, `IdempotencyProof`, `IncidentCapsule`, `IncidentReplayError`, `IncidentReplayResult`, `PREDICTION_LAB_SEED`, `PredictionLabContext`, `PredictionLabOptions`, `PredictionLabReplay`, `PredictionLabReport`, `PredictionLabResult`, `PredictionLabTarget`, `ReplayIncidentOptions`, `ReplayTrajectoryOptions`, `RubricShape`, `RubricSpec`, `RubricVerdict`, `RunFaultProfileOptions`, `ScoreTolerance`, `TestSession`, `TestSessionOptions`, `TrajectoryInvariantId`, `TrajectoryInvariantOptions`, `TrajectoryInvariantResult`, `TrajectoryReplayResult`, `TrajectoryRun`, `TrajectoryTranscript`, `assertAdapterCertification`, `assertAdversarialContract`, `assertAgentEval`, `assertAgentEvalBaseline`, `assertAgentFailureMatrix`, `assertDataAdapterConformance`, `assertIncidentReplays`, `assertPredictionLab`, `assertTrajectoryInvariants`, `cacheStoreCertificationProfile`, `captureIncident`, `certifyAdapter`, `checkTrajectoryInvariants`, `compareAgentEvalBaseline`, `contractLabWitnesses`, `cookieJar`, `createFailureLab`, `createReferenceContractLabHandler`, `createTrajectoryTranscript`, `defineAgentEvalSuite`, `defineCertificationProfile`, `defineFaultProfile`, `deploymentAdapterCertificationProfile`, `deploymentCertificationProfile`, `eventDeliveryCertificationProfile`, `gatewayCertificationProfile`, `generateRegressionTest`, `httpToolAdapter`, `inProcessToolAdapter`, `jobStoreCertificationProfile`, `mcpToolAdapter`, `modelGatewayCertificationProfile`, `parseRubricVerdict`, `predictionLabCaseIds`, `proveIdempotency`, `recordTrajectory`, `redactForEmission`, `referenceFaultProfile`, `registryCertificationProfile`, `replayIncident`, `replayTrajectory`, `runAdversarialContract`, `runAgentEvalComposition`, `runAgentFailureMatrix`, `runContractLab`, `runContractLabOverHttp`, `runContractLabThroughAdapter`, `runDataAdapterConformance`, `runFailureScenario`, `runFaultProfile`, `runPredictionLab`, `runTrajectory`, `runtimeAdapterCertificationProfile`, `shapeOf`, `storageAdapterCertificationProfile`, `testSession`, `testToolAdapter`, `toPredictionLabContext`, `trajectoryRegressionId`, `verifyAdapterCertification`
|
|
16656
|
+
`AdapterCertificationError`, `AdapterCertificationProfile`, `AdapterCertificationReport`, `AdversarialContractError`, `AdversarialContractOptions`, `AdversarialContractReport`, `AdversarialContractResult`, `AgentEvalCase`, `AgentEvalComponentEvidence`, `AgentEvalCompositionOptions`, `AgentEvalCompositionReport`, `AgentEvalRegressionError`, `AgentEvalReport`, `AgentEvalSuite`, `AgentEvalSuiteSpec`, `AgentFailureMatrixCase`, `AgentFailureMatrixError`, `AgentFailureMatrixKind`, `AgentFailureMatrixOptions`, `AgentFailureMatrixReport`, `AppLike`, `BaselineComparison`, `BaselineOptions`, `CaptureIncidentOptions`, `CapturedRequest`, `CapturedRequestInput`, `CaseComparison`, `CaseResult`, `CertifiableCacheEntry`, `CertifiableCacheStore`, `CertifiableDeploymentAdapter`, `CertifiableDescriptorAdapter`, `CertifiableDomainEvent`, `CertifiableEventDeliveryAdapter`, `CertifiableEventRecord`, `CertifiableJobStore`, `CertifiableModelGateway`, `CertifiableRuntimeAdapter`, `CertifiableRuntimeServer`, `CertifiableStorageAdapter`, `CertificationCapabilityEvidence`, `CertificationCheck`, `CertificationCheckEvidence`, `CertificationTarget`, `ComparisonCode`, `ContractAuthContext`, `ContractCaseContext`, `ContractCaseKind`, `ContractCoverageGap`, `ContractCoverageGapCode`, `ContractExpectedResponse`, `ContractLabHandler`, `ContractLabRuntimeAdapter`, `ContractLabServer`, `ContractLabWitness`, `ContractReplay`, `ContractRuntime`, `ContractTarget`, `ContractTestApp`, `ContractWitness`, `CookieJar`, `CreateTrajectoryTranscriptOptions`, `DataAdapterConformanceError`, `DataAdapterConformanceReport`, `DataAdapterConformanceTarget`, `DataConformanceCheck`, `DataConformanceFailure`, `DataConformancePage`, `DataConformanceScope`, `DataConformanceTransaction`, `EffectLedger`, `FailureDirective`, `FailureEvidence`, `FailureInjectedError`, `FailureKind`, `FailureLab`, `FailureLabOptions`, `FailureReplay`, `FailureScenario`, `FailureScenarioReport`, `FaultProfile`, `FaultProfileFault`, `FaultProfileFaultKind`, `FaultProfileReport`, `FaultProfileScenario`, `FaultProfileScenarioReport`, `GenerateRegressionTestOptions`, `IdempotencyDivergence`, `IdempotencyProof`, `IncidentCapsule`, `IncidentReplayError`, `IncidentReplayResult`, `PREDICTION_LAB_SEED`, `PredictionLabContext`, `PredictionLabOptions`, `PredictionLabReplay`, `PredictionLabReport`, `PredictionLabResult`, `PredictionLabTarget`, `ReplayIncidentOptions`, `ReplayTrajectoryOptions`, `RubricShape`, `RubricSpec`, `RubricVerdict`, `RunFaultProfileOptions`, `ScoreTolerance`, `TestSession`, `TestSessionOptions`, `TrajectoryInvariantId`, `TrajectoryInvariantOptions`, `TrajectoryInvariantResult`, `TrajectoryReplayResult`, `TrajectoryRun`, `TrajectoryTranscript`, `assertAdapterCertification`, `assertAdversarialContract`, `assertAgentEval`, `assertAgentEvalBaseline`, `assertAgentFailureMatrix`, `assertDataAdapterConformance`, `assertIncidentReplays`, `assertPredictionLab`, `assertTrajectoryInvariants`, `cacheStoreCertificationProfile`, `captureIncident`, `certifyAdapter`, `checkTrajectoryInvariants`, `compareAgentEvalBaseline`, `contractLabWitnesses`, `cookieJar`, `createFailureLab`, `createReferenceContractLabHandler`, `createTrajectoryTranscript`, `defineAgentEvalSuite`, `defineCertificationProfile`, `defineFaultProfile`, `deploymentAdapterCertificationProfile`, `deploymentCertificationProfile`, `eventDeliveryCertificationProfile`, `gatewayCertificationProfile`, `generateRegressionTest`, `httpToolAdapter`, `inProcessToolAdapter`, `jobStoreCertificationProfile`, `mcpToolAdapter`, `modelGatewayCertificationProfile`, `parseRubricVerdict`, `predictionLabCaseIds`, `proveIdempotency`, `recordTrajectory`, `redactForEmission`, `referenceFaultProfile`, `registryCertificationProfile`, `replayIncident`, `replayTrajectory`, `runAdversarialContract`, `runAgentEvalComposition`, `runAgentFailureMatrix`, `runContractLab`, `runContractLabOverHttp`, `runContractLabThroughAdapter`, `runDataAdapterConformance`, `runFailureScenario`, `runFaultProfile`, `runPredictionLab`, `runTrajectory`, `runtimeAdapterCertificationProfile`, `shapeOf`, `storageAdapterCertificationProfile`, `testSession`, `testToolAdapter`, `toPredictionLabContext`, `trajectoryRegressionId`, `verifyAdapterCertification`
|
|
15256
16657
|
|
|
15257
16658
|
## @nifrajs/ts-plugin
|
|
15258
16659
|
|
|
@@ -15264,7 +16665,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15264
16665
|
|
|
15265
16666
|
## @nifrajs/web
|
|
15266
16667
|
|
|
15267
|
-
`ACTION_GLOBAL`, `Action`, `BOUNDARY_GLOBAL`, `BackendOnly`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `Boundary`, `BoundaryDescriptor`, `BoundaryError`, `BoundaryMode`, `BoundaryRegistration`, `BoundaryRequestCtx`, `BoundaryState`, `BoundaryStates`, `BoundaryStatus`, `BrowserNavigate`, `CacheStore`, `CachedResponse`, `CdnPurgeOutcome`, `CdnPurgeTarget`, `ClientAction`, `ClientActionArgs`, `ClientActionResult`, `ClientLoader`, `ClientLoaderArgs`, `ClientRequestBody`, `ClientRouteHooks`, `ClientRouter`, `ClientRouterOptions`, `CreateCspPolicyOptions`, `CreateNonceResolverOptions`, `CreateWebAppOptions`, `CspHeaderContext`, `CspPolicy`, `CssLoadingMode`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_CSS_LOADING`, `DEFAULT_DEV_PORT`, `DRAFT_COOKIE`, `Deferred`, `DehydratedState`, `DraftCookieControls`, `DynamicBoundary`, `DynamicBoundaryBatch`, `EnableDraftOptions`, `FetchRouteData`, `Fetcher`, `FetcherState`, `FontDisplay`, `FontFace`, `FontPreloadInput`, `FontSource`, `GenerateClientEntryOptions`, `GenerateRouteSearchTypesOptions`, `GenerateServerManifestOptions`, `GetStaticPaths`, `HANDOVER_ID`, `HtmlSanitizer`, `IDLE_BLOCKER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `ISRQuery`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `InterceptBoundary`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `LoaderResponseControls`, `LoadingEntry`, `Manifest`, `MatchChain`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `MemoryStaticBoundaryCache`, `Meta`, `MetaArgs`, `MetaDescriptor`, `MetaInput`, `MiddlewareOutcome`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `NonceContext`, `NonceGenerator`, `NonceHeader`, `NonceHeaderContext`, `NonceResolver`, `NotFoundEntry`, `NotFoundScope`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `PendingBoundary`, `PrefetchMode`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `REDIRECT_HEADER`, `RETAIN_HEADER`, `REVALIDATE_HEADER`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RedirectOptions`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderPageInput`, `RenderPageOptions`, `RenderProps`, `RenderStreamOptions`, `RenderedPage`, `RevalidateEndpointOptions`, `RevalidateResult`, `RevalidateTags`, `RevalidateTagsInput`, `RouteEntry`, `RouteMatch`, `RouteMiddleware`, `RouteModule`, `RoutePattern`, `RouteSearch`, `RouterState`, `SERVER_FN_MODULE`, `STATUS_HEADER`, `SanitizedHtml`, `ScriptDescriptor`, `SearchOf`, `ServePublicDirOptions`, `ShouldRevalidate`, `ShouldRevalidateArgs`, `SsrModuleLoader`, `StaticBoundary`, `StaticBoundaryCache`, `StaticBoundaryImportEdge`, `StaticBoundaryRoot`, `StaticCtx`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `StatusPageOptions`, `Submission`, `SubmitOptions`, `TrustedHtml`, `UIMatch`, `UnsafeScriptDescriptor`, `assertCssLoadingCompatible`, `assertRenderAdapterConformance`, `assertStaticBoundaryImports`, `boundaryDescriptors`, `boundaryModeKey`, `buildManifest`, `canonical`, `createClientRouter`, `createCspPolicy`, `createMatcher`, `createMutation`, `createNonceResolver`, `createQueryClient`, `createWebApp`, `defer`, `disableDraft`, `enableDraft`, `enumerateStaticRoutes`, `filePathToPattern`, `filePathToPatterns`, `fontFace`, `fontPreload`, `generateClientEntry`, `generateRouteSearchTypes`, `generateServerManifest`, `getBrowserNavigate`, `gone`, `hashQueryKey`, `isCacheablePage`, `isDraftEnabled`, `jsonLd`, `mergeHeads`, `nifraScriptHashes`, `normalizeCssLoading`, `notFound`, `openGraph`, `previewEndpoint`, `redirect`, `registerBlocker`, `renderPage`, `renderPageResult`, `resolveDynamicBoundaries`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `resolveStaticBoundaries`, `revalidate`, `revalidateEndpoint`, `sanitizeHtml`, `sanitizedHtml`, `searchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `startDynamicBoundaries`, `statusPage`, `trustHtml`, `unsafeInlineScript`, `webProjectEvidence`, `withISR`
|
|
16668
|
+
`ACTION_GLOBAL`, `Action`, `BOUNDARY_GLOBAL`, `BackendOnly`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `Boundary`, `BoundaryDescriptor`, `BoundaryError`, `BoundaryMode`, `BoundaryRegistration`, `BoundaryRequestCtx`, `BoundaryState`, `BoundaryStates`, `BoundaryStatus`, `BrowserNavigate`, `CacheStore`, `CachedResponse`, `CdnPurgeOutcome`, `CdnPurgeTarget`, `ClientAction`, `ClientActionArgs`, `ClientActionResult`, `ClientLoader`, `ClientLoaderArgs`, `ClientRequestBody`, `ClientRouteHooks`, `ClientRouter`, `ClientRouterOptions`, `CreateCspPolicyOptions`, `CreateNonceResolverOptions`, `CreateWebAppOptions`, `CspHeaderContext`, `CspPolicy`, `CssLoadingMode`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_CSS_LOADING`, `DEFAULT_DEV_PORT`, `DRAFT_COOKIE`, `Deferred`, `DehydratedState`, `DraftCookieControls`, `DynamicBoundary`, `DynamicBoundaryBatch`, `EnableDraftOptions`, `FetchRouteData`, `Fetcher`, `FetcherState`, `FontDisplay`, `FontFace`, `FontPreloadInput`, `FontSource`, `GenerateClientEntryOptions`, `GenerateRouteSearchTypesOptions`, `GenerateServerManifestOptions`, `GetStaticPaths`, `HANDOVER_ID`, `HtmlSanitizer`, `IDLE_BLOCKER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `ISRQuery`, `ISRStoreOp`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `InterceptBoundary`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `LoaderResponseControls`, `LoadingEntry`, `Manifest`, `MatchChain`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `MemoryStaticBoundaryCache`, `Meta`, `MetaArgs`, `MetaDescriptor`, `MetaInput`, `MiddlewareOutcome`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `NonceContext`, `NonceGenerator`, `NonceHeader`, `NonceHeaderContext`, `NonceResolver`, `NotFoundEntry`, `NotFoundScope`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `PendingBoundary`, `PrefetchMode`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `REDIRECT_HEADER`, `RETAIN_HEADER`, `REVALIDATE_HEADER`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RedirectOptions`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderPageInput`, `RenderPageOptions`, `RenderProps`, `RenderStreamOptions`, `RenderedPage`, `RevalidateEndpointOptions`, `RevalidateResult`, `RevalidateTags`, `RevalidateTagsInput`, `RouteEntry`, `RouteMatch`, `RouteMiddleware`, `RouteModule`, `RoutePattern`, `RouteSearch`, `RouterState`, `SERVER_FN_MODULE`, `STATUS_HEADER`, `SanitizedHtml`, `ScriptDescriptor`, `SearchOf`, `ServePublicDirOptions`, `ShouldRevalidate`, `ShouldRevalidateArgs`, `SsrModuleLoader`, `StaticBoundary`, `StaticBoundaryCache`, `StaticBoundaryImportEdge`, `StaticBoundaryRoot`, `StaticCtx`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `StatusPageOptions`, `Submission`, `SubmitOptions`, `TrustedHtml`, `UIMatch`, `UnsafeScriptDescriptor`, `assertCssLoadingCompatible`, `assertRenderAdapterConformance`, `assertStaticBoundaryImports`, `boundaryDescriptors`, `boundaryModeKey`, `buildManifest`, `canonical`, `createClientRouter`, `createCspPolicy`, `createMatcher`, `createMutation`, `createNonceResolver`, `createQueryClient`, `createWebApp`, `defer`, `disableDraft`, `enableDraft`, `enumerateStaticRoutes`, `filePathToPattern`, `filePathToPatterns`, `fontFace`, `fontPreload`, `generateClientEntry`, `generateRouteSearchTypes`, `generateServerManifest`, `getBrowserNavigate`, `gone`, `hashQueryKey`, `isCacheablePage`, `isDraftEnabled`, `jsonLd`, `mergeHeads`, `nifraScriptHashes`, `normalizeCssLoading`, `notFound`, `openGraph`, `previewEndpoint`, `redirect`, `registerBlocker`, `renderPage`, `renderPageResult`, `resolveChainMeta`, `resolveDynamicBoundaries`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `resolveStaticBoundaries`, `revalidate`, `revalidateEndpoint`, `sanitizeHtml`, `sanitizedHtml`, `searchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `startDynamicBoundaries`, `statusPage`, `trustHtml`, `unsafeInlineScript`, `webProjectEvidence`, `withISR`
|
|
15268
16669
|
|
|
15269
16670
|
## @nifrajs/web-preact
|
|
15270
16671
|
|
|
@@ -15292,7 +16693,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
15292
16693
|
|
|
15293
16694
|
## @nifrajs/webmcp
|
|
15294
16695
|
|
|
15295
|
-
`AgentCapability`, `AgentCapabilityReference`, `AgentSurfaceAuthoritativeState`, `AgentSurfaceConformanceCase`, `AgentSurfaceConformanceOptions`, `AgentSurfaceConformanceResult`, `AgentSurfaceDescriptor`, `AgentSurfacePrediction`, `AgentSurfacePredictionContext`, `AgentSurfacePredictor`, `AgentSurfaceReconcileContext`, `AgentSurfaceReconciler`, `AgentSurfaceScope`, `AgentSurfaceSnapshot`, `DefineAgentCapabilityOptions`, `ExecutePredictedOptions`, `PredictedExecutionResult`, `PredictionCommitOptions`, `PredictionPatchOperation`, `PredictionStore`, `PredictionStoreEvent`, `PredictionStoreResult`, `PredictionStoreSnapshot`, `ReconciliationMode`, `ToolApproval`, `ToolCallOptions`, `ToolCallResult`, `ToolContract`, `ToolContractOptions`, `ToolError`, `ToolEvidence`, `WebMcpExecutionOptions`, `WebMcpModelContext`, `WebMcpReceipt`, `WebMcpRegistrationFailure`, `WebMcpRegistrationReport`, `WebMcpTarget`, `WebMcpToolDefinition`, `createPredictionStore`, `defineAgentCapability`, `executeAgentCapability`, `executePredicted`, `registerWebMcpTools`, `runAgentSurfaceConformance`, `toWebMcpTool`
|
|
16696
|
+
`AgentCapability`, `AgentCapabilityReference`, `AgentSurfaceAuthoritativeState`, `AgentSurfaceConformanceCase`, `AgentSurfaceConformanceOptions`, `AgentSurfaceConformanceResult`, `AgentSurfaceDescriptor`, `AgentSurfacePrediction`, `AgentSurfacePredictionContext`, `AgentSurfacePredictor`, `AgentSurfaceReconcileContext`, `AgentSurfaceReconciler`, `AgentSurfaceScope`, `AgentSurfaceSnapshot`, `DefineAgentCapabilityOptions`, `ExecutePredictedOptions`, `PredictedExecutionResult`, `PredictionCommitOptions`, `PredictionPatchOperation`, `PredictionStore`, `PredictionStoreEvent`, `PredictionStoreResult`, `PredictionStoreSnapshot`, `ReconciliationMode`, `ToolApproval`, `ToolCallOptions`, `ToolCallResult`, `ToolContract`, `ToolContractOptions`, `ToolError`, `ToolEvidence`, `WebMcpExecuteContext`, `WebMcpExecutionOptions`, `WebMcpModelContext`, `WebMcpReceipt`, `WebMcpRegistrationFailure`, `WebMcpRegistrationReport`, `WebMcpTarget`, `WebMcpToolDefinition`, `createPredictionStore`, `defineAgentCapability`, `executeAgentCapability`, `executePredicted`, `registerWebMcpTools`, `runAgentSurfaceConformance`, `toWebMcpTool`
|
|
15296
16697
|
|
|
15297
16698
|
## @nifrajs/workers
|
|
15298
16699
|
|