@nifrajs/cli 1.13.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/dist/check.d.ts +17 -6
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +256 -15
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +2 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +58 -71
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +108 -48
- package/dist/doctor.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +42 -0
- package/dist/mcp.js.map +1 -1
- package/dist/recipes/2.0.0.d.ts +9 -0
- package/dist/recipes/2.0.0.d.ts.map +1 -0
- package/dist/recipes/2.0.0.js +25 -0
- package/dist/recipes/2.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/sync-manifest.d.ts +33 -0
- package/dist/sync-manifest.d.ts.map +1 -0
- package/dist/sync-manifest.js +97 -0
- package/dist/sync-manifest.js.map +1 -0
- package/dist/upgrade.d.ts +23 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +144 -13
- package/dist/upgrade.js.map +1 -1
- package/docs/examples.json +69 -21
- package/docs/llms-full.txt +1186 -142
- package/docs/types.json +1914 -213
- package/package.json +9 -9
- package/src/check.ts +251 -18
- package/src/cli.ts +57 -73
- package/src/doctor.ts +133 -49
- package/src/mcp-http.ts +1 -1
- package/src/mcp.ts +52 -0
- package/src/recipes/2.0.0.ts +26 -0
- package/src/recipes/index.ts +10 -0
- package/src/sync-manifest.ts +111 -0
- package/src/upgrade.ts +163 -11
package/docs/llms-full.txt
CHANGED
|
@@ -39,7 +39,7 @@ production backend (routing, validation, middleware, auth, WebSockets). Run it o
|
|
|
39
39
|
`.listen()`:
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
|
-
import { server } from "@nifrajs/core"
|
|
42
|
+
import { server } from "@nifrajs/core/server"
|
|
43
43
|
|
|
44
44
|
server()
|
|
45
45
|
.get("/", () => ({ hello: "world" }))
|
|
@@ -362,7 +362,7 @@ and `c.query` are the _validated_ types, and a bad request gets a structured
|
|
|
362
362
|
|
|
363
363
|
```ts
|
|
364
364
|
// doc-check: skip — uses the third-party `zod` schema lib (any Standard Schema works); install it to run this.
|
|
365
|
-
import { server } from "@nifrajs/core"
|
|
365
|
+
import { server } from "@nifrajs/core/server"
|
|
366
366
|
import { z } from "zod" // any Standard Schema works: zod, valibot, arktype…
|
|
367
367
|
|
|
368
368
|
export const app = server()
|
|
@@ -427,7 +427,7 @@ builder produces.
|
|
|
427
427
|
|
|
428
428
|
```ts
|
|
429
429
|
// doc-check: skip — uses the third-party `zod` schema lib + an illustrative `users` repo; install zod to run this.
|
|
430
|
-
import { defineContract, implement } from "@nifrajs/core"
|
|
430
|
+
import { defineContract, implement } from "@nifrajs/core/contract"
|
|
431
431
|
import { z } from "zod"
|
|
432
432
|
|
|
433
433
|
// 1. Declare the contract — methods, paths, and input schemas, no handlers.
|
|
@@ -945,7 +945,8 @@ resolves, you call `stream.close()`, or the client disconnects (
|
|
|
945
945
|
Node, Deno, and the edge — no `new Function`, no per-runtime API.
|
|
946
946
|
|
|
947
947
|
```ts
|
|
948
|
-
import { server
|
|
948
|
+
import { server } from "@nifrajs/core/server"
|
|
949
|
+
import { sse } from "@nifrajs/core/sse"
|
|
949
950
|
|
|
950
951
|
const app = server()
|
|
951
952
|
// Your pub-sub of choice — subscribe returns an unsubscribe function.
|
|
@@ -1171,7 +1172,7 @@ plugins both depend on it), it wires its hooks once.
|
|
|
1171
1172
|
|
|
1172
1173
|
```ts
|
|
1173
1174
|
// doc-check: skip — fragment: the outer `app`, `verify`, and `db` are your application's.
|
|
1174
|
-
import { definePlugin } from "@nifrajs/core"
|
|
1175
|
+
import { definePlugin } from "@nifrajs/core/server"
|
|
1175
1176
|
|
|
1176
1177
|
// A plugin is just (app) => app — call use/derive/decorate or register routes.
|
|
1177
1178
|
// definePlugin adds a name so applying it twice (even transitively) is a no-op.
|
|
@@ -1206,7 +1207,7 @@ client — to `any`, with **no type error and no runtime error**.
|
|
|
1206
1207
|
`server().use(betterAuth(auth)).get(...)` keeps every route typed.
|
|
1207
1208
|
|
|
1208
1209
|
```ts
|
|
1209
|
-
import { defineRouterPlugin, server } from "@nifrajs/core"
|
|
1210
|
+
import { defineRouterPlugin, server } from "@nifrajs/core/server"
|
|
1210
1211
|
|
|
1211
1212
|
// A plugin that mounts routes/hooks but adds NO context type: defineRouterPlugin (the clearer name for
|
|
1212
1213
|
// defineIdentityPlugin) keeps app.use()'s return type EXACTLY the caller's server, so routes added after
|
|
@@ -1228,7 +1229,7 @@ short-circuit), `beforeHandle`/`afterHandle` (around the handler),
|
|
|
1228
1229
|
error, 404). Hardening middleware uses the same hook model:
|
|
1229
1230
|
|
|
1230
1231
|
```ts
|
|
1231
|
-
import { server } from "@nifrajs/core"
|
|
1232
|
+
import { server } from "@nifrajs/core/server"
|
|
1232
1233
|
import { cors, rateLimit, securityHeaders, MemoryStore } from "@nifrajs/middleware"
|
|
1233
1234
|
|
|
1234
1235
|
// Hardening middleware is a hook bundle (context-agnostic) — same app.use():
|
|
@@ -1244,7 +1245,7 @@ const app = server()
|
|
|
1244
1245
|
`@nifrajs/middleware` seeds a few `definePlugin` plugins to build on:
|
|
1245
1246
|
|
|
1246
1247
|
```ts
|
|
1247
|
-
import { server } from "@nifrajs/core"
|
|
1248
|
+
import { server } from "@nifrajs/core/server"
|
|
1248
1249
|
import { requestId, logger, etag } from "@nifrajs/middleware"
|
|
1249
1250
|
|
|
1250
1251
|
const app = server()
|
|
@@ -1309,7 +1310,7 @@ does custom, typed verification.
|
|
|
1309
1310
|
clobbering one a handler already set.
|
|
1310
1311
|
|
|
1311
1312
|
```ts
|
|
1312
|
-
import { server } from "@nifrajs/core"
|
|
1313
|
+
import { server } from "@nifrajs/core/server"
|
|
1313
1314
|
import { compression, cacheControl } from "@nifrajs/middleware"
|
|
1314
1315
|
|
|
1315
1316
|
const app = server()
|
|
@@ -1356,7 +1357,7 @@ connection. It short-circuits before the handler; a concurrent retry gets a `409
|
|
|
1356
1357
|
guidance (shared store + DB constraint), and the `Set-Cookie` rule.
|
|
1357
1358
|
|
|
1358
1359
|
```ts
|
|
1359
|
-
import { server } from "@nifrajs/core"
|
|
1360
|
+
import { server } from "@nifrajs/core/server"
|
|
1360
1361
|
import { idempotency, MemoryIdempotencyStore } from "@nifrajs/middleware"
|
|
1361
1362
|
|
|
1362
1363
|
// A retried POST with the same Idempotency-Key replays the first response instead of
|
|
@@ -1376,7 +1377,7 @@ Read the typed claims off the returned plugin — `auth.requireClaims(c.req)` (t
|
|
|
1376
1377
|
compares static credentials in **constant time** (or takes a `verify` callback).
|
|
1377
1378
|
|
|
1378
1379
|
```ts
|
|
1379
|
-
import { server } from "@nifrajs/core"
|
|
1380
|
+
import { server } from "@nifrajs/core/server"
|
|
1380
1381
|
import { jwt, jwks, basicAuth } from "@nifrajs/middleware"
|
|
1381
1382
|
|
|
1382
1383
|
// JWT (WebCrypto): an explicit algorithm allowlist is REQUIRED; alg:none and RSA/HMAC confusion are rejected.
|
|
@@ -1400,7 +1401,7 @@ user's response to another. `MemoryResponseCache` is per-instance and refuses
|
|
|
1400
1401
|
pretty-prints JSON responses (capped, with an optional query toggle).
|
|
1401
1402
|
|
|
1402
1403
|
```ts
|
|
1403
|
-
import { server } from "@nifrajs/core"
|
|
1404
|
+
import { server } from "@nifrajs/core/server"
|
|
1404
1405
|
import { cache, MemoryResponseCache, prettyJson } from "@nifrajs/middleware"
|
|
1405
1406
|
|
|
1406
1407
|
// Full response cache: pluggable store, Vary-aware keys, byte cap. Bypasses Set-Cookie and respects
|
|
@@ -1422,7 +1423,7 @@ no open redirect); `language` negotiates `Accept-Language` into
|
|
|
1422
1423
|
is opt-in; `combine` bundles several middleware into one.
|
|
1423
1424
|
|
|
1424
1425
|
```ts
|
|
1425
|
-
import { server } from "@nifrajs/core"
|
|
1426
|
+
import { server } from "@nifrajs/core/server"
|
|
1426
1427
|
import { methodOverride, trimTrailingSlash, language, timing, poweredBy, combine } from "@nifrajs/middleware"
|
|
1427
1428
|
|
|
1428
1429
|
const app = server()
|
|
@@ -1475,7 +1476,7 @@ bypasses that read path. `c.boundedBody(maxBytes?)` and
|
|
|
1475
1476
|
`c.boundedJson<T>(maxBytes?)` extend the **same** cap to those routes.
|
|
1476
1477
|
|
|
1477
1478
|
```ts
|
|
1478
|
-
import { server } from "@nifrajs/core"
|
|
1479
|
+
import { server } from "@nifrajs/core/server"
|
|
1479
1480
|
|
|
1480
1481
|
const app = server()
|
|
1481
1482
|
|
|
@@ -1566,7 +1567,7 @@ the verified text for you to parse with your own schema.
|
|
|
1566
1567
|
|
|
1567
1568
|
```ts
|
|
1568
1569
|
// doc-check: skip — fragment: `app`, `env`, `StripeEvent`, and the rotation keys are your application's.
|
|
1569
|
-
import { verifyWebhook } from "@nifrajs/core"
|
|
1570
|
+
import { verifyWebhook } from "@nifrajs/core/webhook"
|
|
1570
1571
|
|
|
1571
1572
|
app.post("/webhooks/stripe", async (c) => {
|
|
1572
1573
|
// Reads the raw body BOUNDED (DoS guard), verifies the HMAC CONSTANT-TIME, and only
|
|
@@ -1646,7 +1647,7 @@ Transient `5xx` aren't cached, so a failed call stays retryable.
|
|
|
1646
1647
|
Every one is constant-time where it compares secrets and **fails closed** by default.
|
|
1647
1648
|
|
|
1648
1649
|
```ts
|
|
1649
|
-
import { server } from "@nifrajs/core"
|
|
1650
|
+
import { server } from "@nifrajs/core/server"
|
|
1650
1651
|
import { jwt, csrf, ipRestriction, bodyLimit } from "@nifrajs/middleware"
|
|
1651
1652
|
|
|
1652
1653
|
const app = server()
|
|
@@ -1730,12 +1731,23 @@ treated as a substitute for static provenance.
|
|
|
1730
1731
|
|
|
1731
1732
|
```ts
|
|
1732
1733
|
// doc-check: skip — combines route and assurance-config excerpts.
|
|
1733
|
-
// route: exact effect declaration +
|
|
1734
|
-
import {
|
|
1734
|
+
// route: exact effect declaration + correlated execution at the owned adapter seam
|
|
1735
|
+
import { executeCapability } from "@nifrajs/core/capabilities"
|
|
1736
|
+
|
|
1737
|
+
app.aroundCapability(async (effect, next) => {
|
|
1738
|
+
// Ask an entitlement service or short-lived approval gate using token-only metadata.
|
|
1739
|
+
// effect.signal aborts on request cancellation or the interceptor timeout.
|
|
1740
|
+
if (!policyAllows(effect.capability, effect.target)) return // deny fail-closed
|
|
1741
|
+
await next()
|
|
1742
|
+
}, { timeoutMs: 5_000 })
|
|
1735
1743
|
|
|
1736
1744
|
app.post("/orders", { capabilities: ["db.write"] }, async (c) => {
|
|
1737
|
-
|
|
1738
|
-
|
|
1745
|
+
return executeCapability(
|
|
1746
|
+
c,
|
|
1747
|
+
"db.write",
|
|
1748
|
+
{ target: "repo:orders" },
|
|
1749
|
+
({ signal }) => orders.write(c.body, { signal }),
|
|
1750
|
+
)
|
|
1739
1751
|
})
|
|
1740
1752
|
|
|
1741
1753
|
// in nifra.assurance.ts, alongside policy:
|
|
@@ -1765,6 +1777,51 @@ Domain writes on `GET`/`HEAD` are hard violations. Each write definition may
|
|
|
1765
1777
|
require request idempotency or durable command/provider-key evidence. The lockfile contains only
|
|
1766
1778
|
method, path, and capability tokens—no payloads or tenant data—and CI never rewrites it.
|
|
1767
1779
|
|
|
1780
|
+
### Durable approval, compensation, and reconciliation
|
|
1781
|
+
|
|
1782
|
+
The durable execution subpath turns the capability boundary into a crash-visible workflow.
|
|
1783
|
+
Approval resumes are HMAC-signed, expire, bind to the tenant, principal, capability, target, and
|
|
1784
|
+
digest, and are consumed atomically once. The effect journal marks execution before the provider
|
|
1785
|
+
call, so a crash after an external commit remains `ambiguous` for reconciliation instead
|
|
1786
|
+
of being retried blindly. Typed sagas persist compensation arguments separately from the sealed
|
|
1787
|
+
token-only ledger and compensate committed steps in reverse order with retry/backoff state.
|
|
1788
|
+
|
|
1789
|
+
```ts
|
|
1790
|
+
// doc-check: skip — durable store implementations are deployment-specific.
|
|
1791
|
+
import {
|
|
1792
|
+
createApprovalCoordinator,
|
|
1793
|
+
createDurableEffectJournal,
|
|
1794
|
+
createSagaEngine,
|
|
1795
|
+
} from "@nifrajs/core/durable-execution"
|
|
1796
|
+
import { effectTracing } from "@nifrajs/otel/effects"
|
|
1797
|
+
|
|
1798
|
+
const effects = effectTracing({ exporter })
|
|
1799
|
+
app.use(effects)
|
|
1800
|
+
|
|
1801
|
+
const approval = createApprovalCoordinator({
|
|
1802
|
+
store: durableApprovalStore, // must declare durability: "durable"
|
|
1803
|
+
secret: approvalHmacKey, // 32+ random bytes, stored separately
|
|
1804
|
+
})
|
|
1805
|
+
const journal = createDurableEffectJournal({ store: durableEffectStore })
|
|
1806
|
+
|
|
1807
|
+
await executeCapability(c, "payments.charge", {
|
|
1808
|
+
target: "provider:stripe",
|
|
1809
|
+
digest,
|
|
1810
|
+
journal,
|
|
1811
|
+
approval: {
|
|
1812
|
+
gate: approval,
|
|
1813
|
+
tenantId: c.principal.tenantId,
|
|
1814
|
+
principalId: c.principal.userId,
|
|
1815
|
+
resumeToken,
|
|
1816
|
+
},
|
|
1817
|
+
}, ({ effectId, signal }) => payments.charge(input, { idempotencyKey: effectId, signal }))
|
|
1818
|
+
|
|
1819
|
+
const sagas = createSagaEngine({
|
|
1820
|
+
store: durableSagaStore,
|
|
1821
|
+
observer: effects.observer,
|
|
1822
|
+
})
|
|
1823
|
+
```
|
|
1824
|
+
|
|
1768
1825
|
### Already built in
|
|
1769
1826
|
|
|
1770
1827
|
These add to Nifra's standing defaults: strict-by-default schema validation (unknown fields
|
|
@@ -1782,7 +1839,7 @@ embeds a token), pass opt-in `valuePatterns` — `commonSecretPatterns` covers
|
|
|
1782
1839
|
bearer tokens, JWTs, emails, and a few well-known key formats, or supply your own:
|
|
1783
1840
|
|
|
1784
1841
|
```ts
|
|
1785
|
-
import { server, jsonLogger, commonSecretPatterns } from "@nifrajs/core"
|
|
1842
|
+
import { server, jsonLogger, commonSecretPatterns } from "@nifrajs/core/server"
|
|
1786
1843
|
|
|
1787
1844
|
// Key-name redaction is always on; valuePatterns adds opt-in value + message scanning.
|
|
1788
1845
|
const app = server({
|
|
@@ -2342,6 +2399,13 @@ Standard Schema
|
|
|
2342
2399
|
, you can also bring Zod, Valibot, or ArkType (all eval-free) — only `t` also
|
|
2343
2400
|
emits OpenAPI from the same definition.
|
|
2344
2401
|
|
|
2402
|
+
Size is the other edge axis. `t` is TypeBox-backed - it carries the whole JSON
|
|
2403
|
+
Schema type system - so a validated app on `t` is heavier than one on a lean
|
|
2404
|
+
validator. Validation is per route, so reach for `t` where you want the contract
|
|
2405
|
+
(OpenAPI/MCP for free) and Valibot where the Workers cold-start bundle is what matters: a
|
|
2406
|
+
Valibot-validated Nifra app measures **~16 KB gzipped**. Both are first-class, and you can
|
|
2407
|
+
mix them in one app.
|
|
2408
|
+
|
|
2345
2409
|
### Scheduled (cron)
|
|
2346
2410
|
|
|
2347
2411
|
Pass `scheduled` to `toFetchHandler` to also export a Workers cron
|
|
@@ -2350,7 +2414,7 @@ typed `env` and `waitUntil` as your request handlers.
|
|
|
2350
2414
|
|
|
2351
2415
|
```ts
|
|
2352
2416
|
// doc-check: skip — `env.KV` is typed by your app's `Env` (the ./app import), supplied by the reader.
|
|
2353
|
-
import { toFetchHandler } from "@nifrajs/core"
|
|
2417
|
+
import { toFetchHandler } from "@nifrajs/core/server"
|
|
2354
2418
|
import { app } from "./app"
|
|
2355
2419
|
|
|
2356
2420
|
// Export fetch + a cron handler. Wire the schedule in wrangler.toml: [triggers] crons = ["0 * * * *"]
|
|
@@ -2369,15 +2433,18 @@ export default toFetchHandler(app, {
|
|
|
2369
2433
|
through a generic — and runs on every runtime nifra serves: Bun, Deno, Node, and Cloudflare
|
|
2370
2434
|
Workers.
|
|
2371
2435
|
|
|
2372
|
-
The WebSocket runtime ships as
|
|
2373
|
-
it
|
|
2374
|
-
`
|
|
2436
|
+
The WebSocket runtime ships as an opt-in plugin, so apps that never use it don’t bundle it.
|
|
2437
|
+
Enable it with `.use(websocket())` from `@nifrajs/core/ws`;
|
|
2438
|
+
`app.ws()` without it fails loud at registration with
|
|
2439
|
+
`WS_RUNTIME_MISSING`. It installs on that server instance only — the same
|
|
2440
|
+
`.use()` opt-in as `mcp()` and `streaming()`.
|
|
2375
2441
|
|
|
2376
2442
|
```ts
|
|
2377
|
-
import { server } from "@nifrajs/core"
|
|
2378
|
-
import "@nifrajs/core/ws" //
|
|
2443
|
+
import { server } from "@nifrajs/core/server"
|
|
2444
|
+
import { websocket } from "@nifrajs/core/ws" // .use(websocket()) enables app.ws()
|
|
2379
2445
|
|
|
2380
2446
|
const app = server()
|
|
2447
|
+
.use(websocket())
|
|
2381
2448
|
.get("/", () => ({ ok: true }))
|
|
2382
2449
|
.ws("/echo", {
|
|
2383
2450
|
open: (ws) => ws.send("welcome"),
|
|
@@ -2665,7 +2732,7 @@ SSR verified on real workerd — **this very site runs there**.
|
|
|
2665
2732
|
|
|
2666
2733
|
```ts
|
|
2667
2734
|
// _worker.ts — the edge SSR entry
|
|
2668
|
-
import { toFetchHandler } from "@nifrajs/core"
|
|
2735
|
+
import { toFetchHandler } from "@nifrajs/core/server"
|
|
2669
2736
|
import { createWebApp } from "@nifrajs/web"
|
|
2670
2737
|
import { reactAdapter } from "@nifrajs/web-react"
|
|
2671
2738
|
import { clientEntry, manifest } from "./server-manifest" // generated by buildServer
|
|
@@ -2682,7 +2749,7 @@ Deploy: `wrangler pages deploy dist` (Pages) or `wrangler deploy`
|
|
|
2682
2749
|
|
|
2683
2750
|
## Nifra — CLI
|
|
2684
2751
|
|
|
2685
|
-
> The zero-config Nifra CLI (`nifra dev`, `nifra build`, `nifra start`): true HMR
|
|
2752
|
+
> The zero-config Nifra CLI (`nifra dev`, `nifra build`, `nifra start`): true HMR and complete target-specific deploy builds.
|
|
2686
2753
|
|
|
2687
2754
|
`Nifra` is zero-config: it reads `routes/`, `framework.ts`, and
|
|
2688
2755
|
(optionally) `backend.ts` from your project and wires the right
|
|
@@ -2691,57 +2758,62 @@ Deploy: `wrangler pages deploy dist` (Pages) or `wrangler deploy`
|
|
|
2691
2758
|
|
|
2692
2759
|
```ts
|
|
2693
2760
|
nifra dev # true-HMR dev server (Vite middleware + nifra SSR) — http://localhost:4321
|
|
2694
|
-
nifra build #
|
|
2695
|
-
nifra start #
|
|
2761
|
+
nifra build # full Bun deploy → dist/server.js + content-hashed dist/assets/ (default target: bun)
|
|
2762
|
+
nifra start # run dist/server.js on Bun
|
|
2763
|
+
nifra build --target cf-pages # also: node | deno | vercel | static; add --report for chunk sizes
|
|
2696
2764
|
|
|
2697
2765
|
# dev + start share the default port 4321. Override per run: --port <n> (alias -p) or the PORT env var.
|
|
2698
|
-
# flags: --port <n> (dev/start) · --out <dir> (build/start) · --poll (dev
|
|
2766
|
+
# flags: --port <n> (dev/start) · --out <dir> (build/start) · --target <t> (build) · --poll (dev)
|
|
2699
2767
|
```
|
|
2700
2768
|
|
|
2701
2769
|
### The conventions
|
|
2702
2770
|
|
|
2703
|
-
|
|
2771
|
+
Four conventions at the project root. `nifra dev` runs the Vite-backed
|
|
2704
2772
|
[HMR dev server](/docs/dev); `nifra build` runs the Bun-native production
|
|
2705
|
-
build (content-hashed
|
|
2706
|
-
|
|
2707
|
-
chunks preloaded
|
|
2773
|
+
build (content-hashed client assets plus a target-specific server); `nifra start`
|
|
2774
|
+
runs the default Bun output. The generated server serves assets and SSR with matched-route
|
|
2775
|
+
chunks preloaded and route CSS linked in each `<head>`.
|
|
2708
2776
|
|
|
2709
2777
|
```ts
|
|
2710
2778
|
my-app/
|
|
2711
2779
|
routes/ # file-based routes (index.tsx, _layout.tsx, [id].tsx, …)
|
|
2712
|
-
framework.ts #
|
|
2780
|
+
framework.ts # deploy-safe render adapter
|
|
2781
|
+
nifra.config.ts # CLI-only client module + dev/build plugins
|
|
2713
2782
|
backend.ts # export const backend = server()... (optional — the typed contract)
|
|
2714
2783
|
```
|
|
2715
2784
|
|
|
2716
2785
|
### framework.ts — naming the framework once
|
|
2717
2786
|
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2787
|
+
Keep the render `adapter` in deploy-safe `framework.ts`. Put
|
|
2788
|
+
`clientModule`, Vite plugins, compiler plugins, conditions, and defines in CLI-only
|
|
2789
|
+
`nifra.config.ts`. This prevents build tooling and native dependencies from entering
|
|
2790
|
+
generated server bundles.
|
|
2721
2791
|
|
|
2722
2792
|
```ts
|
|
2723
|
-
//
|
|
2724
|
-
// framework.ts — the one place an app names its framework. The CLI reads these:
|
|
2725
|
-
import react from "@vitejs/plugin-react"
|
|
2793
|
+
// framework.ts — deploy-safe; generated server entries import this file.
|
|
2726
2794
|
import { reactAdapter } from "@nifrajs/web-react"
|
|
2727
2795
|
|
|
2728
2796
|
export const adapter = reactAdapter
|
|
2797
|
+
|
|
2798
|
+
// nifra.config.ts — CLI-only build/dev tooling; never imported by a deployed server.
|
|
2799
|
+
// doc-check: skip — needs the third-party @vitejs/plugin-react; install it to run this.
|
|
2800
|
+
import react from "@vitejs/plugin-react"
|
|
2801
|
+
export { adapter } from "./framework"
|
|
2729
2802
|
export const clientModule = "@nifrajs/web-react/client"
|
|
2730
2803
|
export const vitePlugins = [react()] // dev HMR (Fast Refresh)
|
|
2731
2804
|
// Vue/Svelte/Solid also export:
|
|
2732
2805
|
// clientPlugins = [vueBunPlugin("dom")] // compile routes for the client build
|
|
2733
|
-
// serverPlugins = [vueBunPlugin("ssr")] // compile routes
|
|
2806
|
+
// serverPlugins = [vueBunPlugin("ssr")] // compile routes into the target's server bundle
|
|
2734
2807
|
// conditions = ["solid"] // Solid: resolve solid-js to its source
|
|
2735
2808
|
// define = { __VUE_OPTIONS_API__: "true", ... } // Vue feature flags
|
|
2736
2809
|
```
|
|
2737
2810
|
|
|
2738
2811
|
### Scope
|
|
2739
2812
|
|
|
2740
|
-
`nifra build`
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
worker those targets need. A `nifra build --target` for edge is a future addition.
|
|
2813
|
+
`nifra build` defaults to a self-hosted Bun server. Use `--target node`,
|
|
2814
|
+
`deno`, `cf-pages`, `vercel`, or `static` for another
|
|
2815
|
+
complete deploy shape (see [Deployment](/docs/deployment)). Run non-Bun outputs with
|
|
2816
|
+
the command printed by the build.
|
|
2745
2817
|
|
|
2746
2818
|
## Nifra — Dev & HMR
|
|
2747
2819
|
|
|
@@ -3170,6 +3242,110 @@ params. The error reads out the fix; add a schema to the route:
|
|
|
3170
3242
|
`.get("/thing", { query: z.object({ page: z.string() }) }, h)`. Then
|
|
3171
3243
|
`c.query` is the validated type and the client accepts a typed `query`.
|
|
3172
3244
|
|
|
3245
|
+
###
|
|
3246
|
+
`TS2589` - "Type instantiation is excessively deep and possibly infinite" (one
|
|
3247
|
+
`server()` chain grew past ~95 routes)
|
|
3248
|
+
|
|
3249
|
+
The fluent builder's whole value - end-to-end inference, so `c.params.id` is typed
|
|
3250
|
+
straight from `:id` and the client is derived from `typeof app` - carries
|
|
3251
|
+
an **O(N) type-instantiation cost**. Each `.get(path, handler)` /
|
|
3252
|
+
`.post(...)` does two things at once: it computes the handler's context type from the
|
|
3253
|
+
path, _and_ it returns a server whose registry is one alias level deeper than the last.
|
|
3254
|
+
Neither strains the compiler alone; the **product** - recomputing the handler
|
|
3255
|
+
context while re-threading an ever-larger registry at every step - exhausts TypeScript's
|
|
3256
|
+
per-expression instantiation budget. A single chain hits `TS2589` at
|
|
3257
|
+
**~95-100 routes**.
|
|
3258
|
+
|
|
3259
|
+
[!NOTE]
|
|
3260
|
+
|
|
3261
|
+
This is a **healthy, growing app's wall, not abuse**. It is inherent to any
|
|
3262
|
+
builder that infers handler context _and_ accumulates a typed route registry (Elysia,
|
|
3263
|
+
tRPC, and Hono's typed clients cap the same way), so it is not fixable by reshaping the
|
|
3264
|
+
internal `AddRoute`. The fix is to use a shape that does not form the product.
|
|
3265
|
+
|
|
3266
|
+
**Fix 1 - split into domain groups and `.merge()` them.** Each group is
|
|
3267
|
+
its own short `server()` chain, so no single chain approaches the ceiling; a
|
|
3268
|
+
`.merge()` is one `R & R2` intersection with no per-call context work,
|
|
3269
|
+
so composing groups stays cheap. A 90-route single chain is inside the ceiling; 120 routes as
|
|
3270
|
+
four merged 30-route groups typecheck with full per-route fidelity.
|
|
3271
|
+
|
|
3272
|
+
```ts
|
|
3273
|
+
import { server } from "@nifrajs/core"
|
|
3274
|
+
|
|
3275
|
+
// Each domain is its OWN short server() chain, kept well under the ~95-route ceiling.
|
|
3276
|
+
const users = server()
|
|
3277
|
+
.get("/users/:id", (c) => ({ id: c.params.id }))
|
|
3278
|
+
.post("/users", () => ({ created: true }))
|
|
3279
|
+
|
|
3280
|
+
const orders = server()
|
|
3281
|
+
.get("/orders/:id", (c) => ({ id: c.params.id }))
|
|
3282
|
+
.post("/orders", () => ({ placed: true }))
|
|
3283
|
+
|
|
3284
|
+
// merge() adds ONE R & R2 intersection per group - no per-call context recompute - so the whole
|
|
3285
|
+
// app stays inside tsc's budget no matter how many groups (or routes) you compose.
|
|
3286
|
+
export const app = server()
|
|
3287
|
+
.get("/health", () => ({ ok: true }))
|
|
3288
|
+
.merge(users)
|
|
3289
|
+
.merge(orders)
|
|
3290
|
+
```
|
|
3291
|
+
|
|
3292
|
+
**Fix 2 - go contract-first, and stay flat at any route count.**
|
|
3293
|
+
`defineContract(...)` declares the entire registry as **one object type
|
|
3294
|
+
upfront**, and `implement(contract, handlers)` binds handlers to it. Nothing
|
|
3295
|
+
grows a registry per call, so there is **no ceiling at all** - this is the path for
|
|
3296
|
+
an API that will keep adding routes for years. Handlers stay checked against the contract exactly
|
|
3297
|
+
as inline routes are. See [Contract-first](/docs/contract).
|
|
3298
|
+
|
|
3299
|
+
```ts
|
|
3300
|
+
import { defineContract, implement } from "@nifrajs/core/contract"
|
|
3301
|
+
|
|
3302
|
+
// One object type, declared upfront - NOT an N-deep stack of `Server<AddRoute<…>>` aliases.
|
|
3303
|
+
const contract = defineContract({
|
|
3304
|
+
getUser: { method: "GET", path: "/users/:id" },
|
|
3305
|
+
listUsers: { method: "GET", path: "/users" },
|
|
3306
|
+
createUser: { method: "POST", path: "/users" },
|
|
3307
|
+
// ...hundreds more operations stay flat: the registry is one type, not a growing chain.
|
|
3308
|
+
})
|
|
3309
|
+
|
|
3310
|
+
export const app = implement(contract, {
|
|
3311
|
+
getUser: (c) => ({ id: c.params.id }),
|
|
3312
|
+
listUsers: () => ({ users: [] as string[] }),
|
|
3313
|
+
createUser: () => ({ created: true }),
|
|
3314
|
+
})
|
|
3315
|
+
```
|
|
3316
|
+
|
|
3317
|
+
####
|
|
3318
|
+
`TS2345` from an unrelated `.merge()` - budget exhausted "at a distance"
|
|
3319
|
+
|
|
3320
|
+
Because the budget is per-expression and global to a compilation, a type-heavy construct
|
|
3321
|
+
_elsewhere_ in the program can push an otherwise-fine `.merge()` chain over the
|
|
3322
|
+
edge. It surfaces not as `TS2589` but as a `TS2345` assignability error
|
|
3323
|
+
naming an **uninstantiated** `Server` - the shape
|
|
3324
|
+
the server type collapses to when the compiler gives up mid-inference. The usual trigger is a
|
|
3325
|
+
**generic higher-order function that wraps the builder** (a
|
|
3326
|
+
`withX(app)` that threads the `Server` type through its own type
|
|
3327
|
+
parameters); merely having that file in the program can be enough. Treat these as real
|
|
3328
|
+
constraints of an inference-first framework:
|
|
3329
|
+
|
|
3330
|
+
-
|
|
3331
|
+
Keep **service-layer types flat and explicitly annotated** - do not let
|
|
3332
|
+
inference-heavy generics thread the `Server` / registry type through your own code.
|
|
3333
|
+
|
|
3334
|
+
-
|
|
3335
|
+
**Avoid generic HOF wrappers around the builder.** Wrap with a
|
|
3336
|
+
`defineRouterPlugin` (identity plugin) or compose with `.merge()` instead
|
|
3337
|
+
of a `withX(app)` that re-infers the whole server type.
|
|
3338
|
+
|
|
3339
|
+
-
|
|
3340
|
+
Put an explicit `Promise` return annotation on async callbacks that thread
|
|
3341
|
+
framework types, so the compiler stops re-deriving the awaited type at each use.
|
|
3342
|
+
|
|
3343
|
+
[!TIP]
|
|
3344
|
+
|
|
3345
|
+
Both fixes preserve full type fidelity - the client derived from `typeof app` is
|
|
3346
|
+
exactly as precise after a `.merge()` or an `implement()` as it is for an
|
|
3347
|
+
inline route. Splitting or going contract-first costs you nothing at the call site.
|
|
3348
|
+
|
|
3173
3349
|
### Still stuck?
|
|
3174
3350
|
|
|
3175
3351
|
Run `nifra check --json` as the done-gate — it surfaces the import-chain leaks,
|
|
@@ -3195,7 +3371,7 @@ _over HTTP_. Both run the identical lifecycle — validation, middleware, contra
|
|
|
3195
3371
|
|
|
3196
3372
|
```ts
|
|
3197
3373
|
// backend.ts — a normal @nifrajs/core server. Routes live at the full /api/... path.
|
|
3198
|
-
import { server } from "@nifrajs/core"
|
|
3374
|
+
import { server } from "@nifrajs/core/server"
|
|
3199
3375
|
import { t } from "@nifrajs/schema"
|
|
3200
3376
|
|
|
3201
3377
|
export const backend = server()
|
|
@@ -3269,6 +3445,65 @@ Vite-backed dev server) routes every request through that same app's `fetch`. So
|
|
|
3269
3445
|
the `/api/*` routes are served identically in development and production — there is
|
|
3270
3446
|
nothing extra to wire for the dev loop.
|
|
3271
3447
|
|
|
3448
|
+
### Mounting, composition & path semantics
|
|
3449
|
+
|
|
3450
|
+
A useful thing to know up front, because it is the opposite of some frameworks: in Nifra
|
|
3451
|
+
**composition and mounting are prefix-less, and paths stay absolute**. There is no
|
|
3452
|
+
per-mount base path, and nothing rewrites the request URL. Two mechanisms are involved and both
|
|
3453
|
+
leave the path untouched:
|
|
3454
|
+
|
|
3455
|
+
-
|
|
3456
|
+
**
|
|
3457
|
+
`.merge()` (and contract-first `implement()`)
|
|
3458
|
+
**
|
|
3459
|
+
- the composition escape hatch for large apps. `merge()` folds another server's
|
|
3460
|
+
routes into this one _at the exact paths they were defined_, into a single shared path
|
|
3461
|
+
space. It takes no prefix argument; a path+method collision throws a
|
|
3462
|
+
`RouteConfigError` at merge time (fail-closed), so groups never silently shadow
|
|
3463
|
+
each other. Each domain therefore owns its full absolute paths (`/api/listings`,
|
|
3464
|
+
`/api/agents`), exactly as it would standalone.
|
|
3465
|
+
|
|
3466
|
+
-
|
|
3467
|
+
**the `/api/*` auto-mount** - a path _guard_, not a rewrite.
|
|
3468
|
+
`createWebApp` hands the _same_ `Request` (its URL and body
|
|
3469
|
+
intact) to the backend when the pathname is exactly `apiPrefix` or under
|
|
3470
|
+
`apiPrefix + "/"`. The backend's own router then matches on
|
|
3471
|
+
`new URL(c.req.url).pathname` - the full, original path.
|
|
3472
|
+
|
|
3473
|
+
The consequence for a handler: **
|
|
3474
|
+
`c.req.url` always carries the full, original path
|
|
3475
|
+
**
|
|
3476
|
+
- the same URL the caller sent. The `/api` prefix is _not_ stripped before a
|
|
3477
|
+
merged or mounted route sees it, and there is no `pathOf`-style helper that strips
|
|
3478
|
+
it. Whether a route was defined inline, merged in from a group, or reached through the HTTP
|
|
3479
|
+
mount, the pathname it observes is the request's own.
|
|
3480
|
+
|
|
3481
|
+
```ts
|
|
3482
|
+
// backend.ts - compose domains with .merge(). Each group owns its FULL absolute paths.
|
|
3483
|
+
import { server } from "@nifrajs/core/server"
|
|
3484
|
+
|
|
3485
|
+
const listings = server().get("/api/listings", (c) => {
|
|
3486
|
+
// c.req.url is the ORIGINAL request URL - .merge() and the /api mount never strip the prefix.
|
|
3487
|
+
const path = new URL(c.req.url).pathname // GET /api/listings -> "/api/listings"
|
|
3488
|
+
return { path }
|
|
3489
|
+
})
|
|
3490
|
+
|
|
3491
|
+
const agents = server().get("/api/agents", (c) => ({ ok: new URL(c.req.url).pathname }))
|
|
3492
|
+
|
|
3493
|
+
// merge() folds both groups into ONE flat path space at their defined paths; a path+method clash
|
|
3494
|
+
// throws a RouteConfigError right here at merge time (fail-closed), never a silent shadow at runtime.
|
|
3495
|
+
export const backend = server().merge(listings).merge(agents)
|
|
3496
|
+
```
|
|
3497
|
+
|
|
3498
|
+
**The one gotcha:** because nothing strips the prefix, a backend that will be
|
|
3499
|
+
mounted (or merged) under `/api` must **declare its routes at the full path**
|
|
3500
|
+
- `server().post("/api/sync", …)`, _not_ `.post("/sync", …)` in the
|
|
3501
|
+
hope that `/api` gets peeled off. Define `/sync` and a browser
|
|
3502
|
+
`POST /api/sync` returns the backend's 404: the router looks up
|
|
3503
|
+
`/api/sync` and finds only `/sync`. `apiPrefix` chooses
|
|
3504
|
+
_which_ requests are handed to the backend; it does not rewrite them, so the route paths
|
|
3505
|
+
and the caller's paths are one and the same.
|
|
3506
|
+
|
|
3272
3507
|
### Calling /api/* from the browser
|
|
3273
3508
|
|
|
3274
3509
|
With the routes mounted, a client island or component hits them with the same typed
|
|
@@ -3318,7 +3553,7 @@ to local policy, and exposes monotonic remaining time beside the existing cancel
|
|
|
3318
3553
|
### Clamp once at admission
|
|
3319
3554
|
|
|
3320
3555
|
```ts
|
|
3321
|
-
import { server } from "@nifrajs/core"
|
|
3556
|
+
import { server } from "@nifrajs/core/server"
|
|
3322
3557
|
|
|
3323
3558
|
const app = server({
|
|
3324
3559
|
requestTimeoutMs: 2_000,
|
|
@@ -3357,6 +3592,177 @@ Configure a finite `requestTimeoutMs` before handing `c.budget` to a
|
|
|
3357
3592
|
production adapter. The no-timeout compatibility path is deliberately unbounded and is not
|
|
3358
3593
|
written to the wire.
|
|
3359
3594
|
|
|
3595
|
+
## Nifra - Execution causality
|
|
3596
|
+
|
|
3597
|
+
> Carry one bounded, payload-free lineage across the durable seams a trace cannot span: commands, events, workflows, projections, and repairs.
|
|
3598
|
+
|
|
3599
|
+
A trace describes one observation tree. Causality survives the seams a trace cannot: a command
|
|
3600
|
+
commits an event, a workflow resumes tomorrow, a projection consumes that event, and
|
|
3601
|
+
reconciliation repairs the drift. Same execution, four identities that used to stop at the
|
|
3602
|
+
boundary.
|
|
3603
|
+
|
|
3604
|
+
### Tokens only, by construction
|
|
3605
|
+
|
|
3606
|
+
A causality record has **no payload field**. There is nowhere to put a request
|
|
3607
|
+
body, a tenant identifier, or a URL, and unknown fields are rejected at every nesting level
|
|
3608
|
+
rather than carried along. Identities are bounded tokens, edge relations are bounded tokens,
|
|
3609
|
+
and the lineage stays safe to store and read during an incident.
|
|
3610
|
+
|
|
3611
|
+
### Start at the boundary
|
|
3612
|
+
|
|
3613
|
+
```ts
|
|
3614
|
+
import { createMemoryCausalityStore, startCausality } from "@nifrajs/core/causality"
|
|
3615
|
+
|
|
3616
|
+
const recorder = createMemoryCausalityStore()
|
|
3617
|
+
const requestId = crypto.randomUUID()
|
|
3618
|
+
|
|
3619
|
+
// One root node at the ingress boundary. executionId is the graph's identity:
|
|
3620
|
+
// generate it once, then propagate it.
|
|
3621
|
+
const step = startCausality("request", requestId, { executionId: crypto.randomUUID() })
|
|
3622
|
+
|
|
3623
|
+
// step.context propagates; step.record is what a durable adapter appends.
|
|
3624
|
+
await recorder.record(step.record)
|
|
3625
|
+
```
|
|
3626
|
+
|
|
3627
|
+
### Continue across seams
|
|
3628
|
+
|
|
3629
|
+
```ts
|
|
3630
|
+
import { continueCausality, startCausality } from "@nifrajs/core/causality"
|
|
3631
|
+
|
|
3632
|
+
const step = startCausality("request", crypto.randomUUID(), {
|
|
3633
|
+
executionId: crypto.randomUUID(),
|
|
3634
|
+
})
|
|
3635
|
+
|
|
3636
|
+
// The command this request caused. The edge relation is a bounded token.
|
|
3637
|
+
const command = continueCausality(step.context, "command", crypto.randomUUID(), {
|
|
3638
|
+
relation: "caused",
|
|
3639
|
+
})
|
|
3640
|
+
|
|
3641
|
+
// The event that command emitted, inside the same commit.
|
|
3642
|
+
const event = continueCausality(command.context, "event", crypto.randomUUID(), {
|
|
3643
|
+
relation: "emitted",
|
|
3644
|
+
})
|
|
3645
|
+
```
|
|
3646
|
+
|
|
3647
|
+
`joinCausality` merges several parents into one node, and refuses to join contexts
|
|
3648
|
+
from different executions: a graph that silently spanned two requests would be worse than no
|
|
3649
|
+
graph.
|
|
3650
|
+
|
|
3651
|
+
### Propagation is a trust boundary
|
|
3652
|
+
|
|
3653
|
+
```ts
|
|
3654
|
+
import {
|
|
3655
|
+
causalityHeaders,
|
|
3656
|
+
readCausalityHeaders,
|
|
3657
|
+
startCausality,
|
|
3658
|
+
} from "@nifrajs/core/causality"
|
|
3659
|
+
|
|
3660
|
+
const step = startCausality("request", crypto.randomUUID(), {
|
|
3661
|
+
executionId: crypto.randomUUID(),
|
|
3662
|
+
})
|
|
3663
|
+
|
|
3664
|
+
// Outbound: bounded headers only.
|
|
3665
|
+
await fetch("https://billing.internal/charge", {
|
|
3666
|
+
headers: causalityHeaders(step.context),
|
|
3667
|
+
})
|
|
3668
|
+
|
|
3669
|
+
// Inbound: never throws on hostile input.
|
|
3670
|
+
const request = new Request("https://api.example/charge")
|
|
3671
|
+
const parsed = readCausalityHeaders(request.headers)
|
|
3672
|
+
if (!parsed.success) {
|
|
3673
|
+
// "missing" | "incomplete" | "invalid" | "unknown-field" - start a fresh graph.
|
|
3674
|
+
}
|
|
3675
|
+
```
|
|
3676
|
+
|
|
3677
|
+
Inbound headers are **untrusted by default**. A caller does not get to attach
|
|
3678
|
+
their request to your graph unless you say so, so a service-to-service seam opts in explicitly
|
|
3679
|
+
and anything that fails the check starts a fresh execution instead of being believed.
|
|
3680
|
+
|
|
3681
|
+
### Storage is yours
|
|
3682
|
+
|
|
3683
|
+
`createMemoryCausalityStore()` is for development and tests. It is bounded, and it
|
|
3684
|
+
refuses to construct under `NODE_ENV=production`: in-memory evidence disappears on
|
|
3685
|
+
restart, which is exactly when an incident timeline matters. Production supplies a durable
|
|
3686
|
+
adapter behind the same `CausalityRecorder` interface, ideally appending in the same
|
|
3687
|
+
transaction as the effect it describes.
|
|
3688
|
+
|
|
3689
|
+
### Traces still apply
|
|
3690
|
+
|
|
3691
|
+
The two models compose. `@nifrajs/otel` turns the nearest observed ancestor into a
|
|
3692
|
+
span link, so an event consumed tomorrow links back to the request that emitted it rather than
|
|
3693
|
+
inventing a trace identity that never existed.
|
|
3694
|
+
|
|
3695
|
+
## Nifra - Adapter certification
|
|
3696
|
+
|
|
3697
|
+
> Prove a storage, cache, job, runtime, or event adapter against its interface and emit a hash-verifiable capability matrix.
|
|
3698
|
+
|
|
3699
|
+
An interface is a promise. Certification is how an adapter proves it kept it, in a form someone
|
|
3700
|
+
else can check.
|
|
3701
|
+
|
|
3702
|
+
### Profiles
|
|
3703
|
+
|
|
3704
|
+
A profile is a named set of checks written against an interface, not an implementation:
|
|
3705
|
+
`cacheStoreCertificationProfile`, `jobStoreCertificationProfile`,
|
|
3706
|
+
`storageAdapterCertificationProfile`, `runtimeAdapterCertificationProfile`
|
|
3707
|
+
, and `eventDeliveryCertificationProfile`. Domain-specific seams define their own
|
|
3708
|
+
with `defineCertificationProfile`, which validates the profile at module load, so a
|
|
3709
|
+
check naming an undeclared capability fails before any adapter runs.
|
|
3710
|
+
|
|
3711
|
+
### Certify an adapter
|
|
3712
|
+
|
|
3713
|
+
```ts
|
|
3714
|
+
// doc-check: skip - createRedisCache stands in for the adapter under test
|
|
3715
|
+
import { test } from "bun:test"
|
|
3716
|
+
import {
|
|
3717
|
+
assertAdapterCertification,
|
|
3718
|
+
cacheStoreCertificationProfile,
|
|
3719
|
+
certifyAdapter,
|
|
3720
|
+
} from "@nifrajs/testing/certification"
|
|
3721
|
+
|
|
3722
|
+
test("the Redis cache adapter satisfies the cache contract", async () => {
|
|
3723
|
+
const report = await certifyAdapter({
|
|
3724
|
+
profile: cacheStoreCertificationProfile(),
|
|
3725
|
+
adapterId: "redis-cache",
|
|
3726
|
+
// A FRESH adapter per check, so one failure cannot contaminate the next.
|
|
3727
|
+
createAdapter: () => createRedisCache(url),
|
|
3728
|
+
cleanup: (adapter) => adapter.clear(),
|
|
3729
|
+
})
|
|
3730
|
+
|
|
3731
|
+
assertAdapterCertification(report) // throws, naming every failed check
|
|
3732
|
+
})
|
|
3733
|
+
```
|
|
3734
|
+
|
|
3735
|
+
Profiles are structural and dependency-free. A third-party adapter can certify itself in its own
|
|
3736
|
+
test suite without adopting anything else, which is the point: trust becomes portable rather
|
|
3737
|
+
than something we vouch for.
|
|
3738
|
+
|
|
3739
|
+
### The report is the evidence
|
|
3740
|
+
|
|
3741
|
+
```ts
|
|
3742
|
+
{
|
|
3743
|
+
"schemaVersion": 1,
|
|
3744
|
+
"ok": true,
|
|
3745
|
+
"profile": { "id": "cache-store", "version": 1 },
|
|
3746
|
+
"adapterId": "redis-cache",
|
|
3747
|
+
"capabilities": [
|
|
3748
|
+
{ "capability": "read-write", "status": "passed", "checks": ["set-get", "delete"] },
|
|
3749
|
+
{ "capability": "tag-invalidation", "status": "passed", "checks": ["invalidate-tag"] }
|
|
3750
|
+
],
|
|
3751
|
+
"evidenceHash": "9f2c..."
|
|
3752
|
+
}
|
|
3753
|
+
```
|
|
3754
|
+
|
|
3755
|
+
The capability matrix is the useful artifact. It says what an adapter supports, per capability,
|
|
3756
|
+
with the checks that back each one. `verifyAdapterCertification` recomputes
|
|
3757
|
+
`evidenceHash`, so a stored report can be checked rather than believed.
|
|
3758
|
+
|
|
3759
|
+
### Failures carry a class, not a message
|
|
3760
|
+
|
|
3761
|
+
A failed check records the error _class_ only. Provider messages routinely carry
|
|
3762
|
+
connection strings, tokens, and payload fragments, and evidence is meant to be shared, so it
|
|
3763
|
+
never becomes a place credentials leak. Cleanup errors cannot turn a failed functional check
|
|
3764
|
+
into a pass.
|
|
3765
|
+
|
|
3360
3766
|
## Nifra — Framework contract
|
|
3361
3767
|
|
|
3362
3768
|
> The one-page contract: LoaderContext shape, loader/action/meta/head signatures, the client↔server boundary, and the build-output contract — every signature verified against the source.
|
|
@@ -3636,13 +4042,13 @@ or `env`. Keep server-only imports out of a route file's top level.
|
|
|
3636
4042
|
|
|
3637
4043
|
### The build-output contract
|
|
3638
4044
|
|
|
3639
|
-
|
|
3640
|
-
`manifest.json` you hand to `createWebApp` (`entry`,
|
|
4045
|
+
The programmatic `buildClient()` API emits a content-hashed directory plus a
|
|
4046
|
+
`manifest.json` you can hand to `createWebApp` (`entry`,
|
|
3641
4047
|
`routes` for per-route preload, `css` + `routeStyles` for
|
|
3642
4048
|
`<link>` injection).
|
|
3643
4049
|
|
|
3644
4050
|
```ts
|
|
3645
|
-
//
|
|
4051
|
+
// buildClient() writes a browser bundle + manifest.json for custom build pipelines:
|
|
3646
4052
|
{
|
|
3647
4053
|
"entry": "/assets/_nifra-entry-HASH.js", // the bootstrap module script
|
|
3648
4054
|
"assets": ["/assets/…"], // every emitted chunk URL
|
|
@@ -3727,6 +4133,73 @@ const paths = prerendered.map((p) => p.path)
|
|
|
3727
4133
|
await Bun.write("./dist/_routes.json", JSON.stringify(cloudflarePagesRoutes({ prerendered: paths })))
|
|
3728
4134
|
```
|
|
3729
4135
|
|
|
4136
|
+
### Publish a reusable router (library code vs app code)
|
|
4137
|
+
|
|
4138
|
+
The fluent `server().get(...).post(...)` builder is **app code**: each
|
|
4139
|
+
call both infers the handler's context from the path _and_ returns a
|
|
4140
|
+
`Server` whose type has one more route intersected in (`AddRoute` /
|
|
4141
|
+
`RouteInfoFor`). That accreting registry type lives happily inside your own program,
|
|
4142
|
+
but it does **not** cleanly survive TypeScript's `.d.ts` declaration
|
|
4143
|
+
emit - for a router you **publish** as a package, the compiler has to serialize
|
|
4144
|
+
the whole inferred chain into your `.d.ts` and chokes (a `TS2883`-class
|
|
4145
|
+
error: the inferred type is too large to name), which forces ugly casts on the boundary. So a
|
|
4146
|
+
distributable router is written **contract-first** instead.
|
|
4147
|
+
|
|
4148
|
+
The recipe: declare the API as a plain `defineContract({…})` object and
|
|
4149
|
+
**export it** (it is the consumer's typed-client source), then ship a factory that
|
|
4150
|
+
returns `implement(contract, handlers)`. The consumer mounts the router with one
|
|
4151
|
+
`.merge(...)` and derives its client straight from the exported contract value -
|
|
4152
|
+
`client(contract, url)`, no `typeof app` import crossing the package
|
|
4153
|
+
boundary. Pass a preamble as the third arg -
|
|
4154
|
+
`implement(contract, handlers, server().use(auth))` - to bake a middleware /
|
|
4155
|
+
`derive` chain into the router's routes (captured at registration).
|
|
4156
|
+
|
|
4157
|
+
```ts
|
|
4158
|
+
// A PUBLISHABLE router - contract-first, so its types survive `.d.ts` emit for consumers.
|
|
4159
|
+
import { client } from "@nifrajs/client"
|
|
4160
|
+
import { defineContract, implement } from "@nifrajs/core/contract"
|
|
4161
|
+
import { server } from "@nifrajs/core/server"
|
|
4162
|
+
import { t } from "@nifrajs/schema"
|
|
4163
|
+
|
|
4164
|
+
// --- in the package (say @your-org/billing) ---
|
|
4165
|
+
// 1. The CONTRACT is ONE plain declared object - methods, paths, schemas, no handlers.
|
|
4166
|
+
// Export it: it is the single type the package publishes, and a consumer's typed client is
|
|
4167
|
+
// built from THIS value (not from `typeof app`), so no internal server type crosses the seam.
|
|
4168
|
+
export const billingContract = defineContract({
|
|
4169
|
+
getPlan: { method: "GET", path: "/billing/plan" },
|
|
4170
|
+
subscribe: { method: "POST", path: "/billing/subscribe", body: t.object({ plan: t.string() }) },
|
|
4171
|
+
})
|
|
4172
|
+
|
|
4173
|
+
// 2. A FACTORY closes over the host's dependencies and returns a real, mountable router.
|
|
4174
|
+
// `implement` binds handlers to the contract; each handler's context is typed from the contract
|
|
4175
|
+
// alone (`c.body` is { plan: string } below) - no per-route `AddRoute` inference accreting.
|
|
4176
|
+
export interface BillingDeps {
|
|
4177
|
+
charge(plan: string): Promise<{ id: string }>
|
|
4178
|
+
}
|
|
4179
|
+
export function createBillingRouter(deps: BillingDeps) {
|
|
4180
|
+
return implement(billingContract, {
|
|
4181
|
+
getPlan: () => ({ plan: "pro" }),
|
|
4182
|
+
subscribe: (c) => deps.charge(c.body.plan),
|
|
4183
|
+
})
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
// --- in the consuming app ---
|
|
4187
|
+
// 3. Mount the router with ONE `.merge()` - no per-route `.get()/.post()` chain to re-infer.
|
|
4188
|
+
const app = server().merge(createBillingRouter({ charge: async (plan) => ({ id: plan }) }))
|
|
4189
|
+
|
|
4190
|
+
// 4. Build the client from the EXPORTED contract value - decoupled, no `typeof app` import needed.
|
|
4191
|
+
const api = client(billingContract, "https://api.example.com")
|
|
4192
|
+
const { data } = await api.billing.plan.get() // GET /billing/plan
|
|
4193
|
+
await api.billing.subscribe.post({ plan: "pro" }) // POST /billing/subscribe, body typed
|
|
4194
|
+
```
|
|
4195
|
+
|
|
4196
|
+
Why this survives `.d.ts` emit where the fluent chain doesn't: the contract is a
|
|
4197
|
+
**plain declared object type** (`ContractShape`), fixed up front - there
|
|
4198
|
+
is no grow-the-registry-per-call inference to serialize, so its declaration is small and
|
|
4199
|
+
stable. The consumer still gets full end-to-end types both ways -
|
|
4200
|
+
`.merge()` threads the router's routes into the host app's registry, and
|
|
4201
|
+
`client(contract, url)` types every call from the same exported object.
|
|
4202
|
+
|
|
3730
4203
|
See [SSG & ISR](/docs/rendering) for prerendering, [Dev & HMR](/docs/dev) for the
|
|
3731
4204
|
two dev loops and the `--port` flag, and [Loaders & actions](/docs/data) for the
|
|
3732
4205
|
data layer in depth.
|
|
@@ -3767,7 +4240,7 @@ server, and every handler reads it as a typed `c.db`. This is how the scaffold w
|
|
|
3767
4240
|
it — and it keeps the client out of each handler's imports.
|
|
3768
4241
|
|
|
3769
4242
|
```ts
|
|
3770
|
-
import { server } from "@nifrajs/core"
|
|
4243
|
+
import { server } from "@nifrajs/core/server"
|
|
3771
4244
|
import { desc } from "drizzle-orm"
|
|
3772
4245
|
import { db, notes } from "./db" // your Drizzle client + schema (what the scaffold generates)
|
|
3773
4246
|
|
|
@@ -3786,7 +4259,7 @@ Node, or `libSQL`/Turso anywhere. Open the connection once and query from your
|
|
|
3786
4259
|
routes:
|
|
3787
4260
|
|
|
3788
4261
|
```ts
|
|
3789
|
-
import { server } from "@nifrajs/core"
|
|
4262
|
+
import { server } from "@nifrajs/core/server"
|
|
3790
4263
|
import { Database } from "bun:sqlite" // Node: better-sqlite3 · edge: Turso/libSQL
|
|
3791
4264
|
|
|
3792
4265
|
// Open the connection ONCE at module scope (the driver pools internally).
|
|
@@ -3920,7 +4393,7 @@ directly:
|
|
|
3920
4393
|
|
|
3921
4394
|
```ts
|
|
3922
4395
|
// doc-check: skip — illustrates a third-party driver (postgres + drizzle); install those to run it.
|
|
3923
|
-
import { server } from "@nifrajs/core"
|
|
4396
|
+
import { server } from "@nifrajs/core/server"
|
|
3924
4397
|
import { drizzle } from "drizzle-orm/postgres-js" // or node-postgres / neon-http
|
|
3925
4398
|
import postgres from "postgres"
|
|
3926
4399
|
import { eq } from "drizzle-orm"
|
|
@@ -3994,6 +4467,98 @@ Complete runnable references — a typed CRUD API you can diff your own against:
|
|
|
3994
4467
|
`examples/db-sqlite` (the same API on raw `bun:sqlite`, parameterized
|
|
3995
4468
|
queries, boundary validation).
|
|
3996
4469
|
|
|
4470
|
+
## Nifra - Failure laboratory
|
|
4471
|
+
|
|
4472
|
+
> Replayable durable-failure schedules on virtual time: crash after commit, duplicate delivery, reordering, budget expiry, and checkpoint contention.
|
|
4473
|
+
|
|
4474
|
+
Durable systems fail on a schedule you never chose: the crash lands between the commit and the
|
|
4475
|
+
publish, the queue delivers twice, the events arrive backwards. The laboratory lets you choose
|
|
4476
|
+
that schedule, then replay it exactly.
|
|
4477
|
+
|
|
4478
|
+
### Seven failures worth testing
|
|
4479
|
+
|
|
4480
|
+
-
|
|
4481
|
+
`crash` - stop dead at a named durability seam.
|
|
4482
|
+
|
|
4483
|
+
-
|
|
4484
|
+
`duplicate-delivery` - deliver the same batch N times.
|
|
4485
|
+
|
|
4486
|
+
-
|
|
4487
|
+
`reorder-events` - rotate a batch out of order.
|
|
4488
|
+
|
|
4489
|
+
-
|
|
4490
|
+
`delay` - advance virtual time, with no real sleeping.
|
|
4491
|
+
|
|
4492
|
+
-
|
|
4493
|
+
`expire-budget` - report zero remaining at a budget seam.
|
|
4494
|
+
|
|
4495
|
+
-
|
|
4496
|
+
`lose-provider-reply` - run the call, then lose only its reply.
|
|
4497
|
+
|
|
4498
|
+
-
|
|
4499
|
+
`contend-checkpoint` - make a checkpoint conflict.
|
|
4500
|
+
|
|
4501
|
+
### Place the seams, schedule the failure
|
|
4502
|
+
|
|
4503
|
+
```ts
|
|
4504
|
+
import { createFailureLab } from "@nifrajs/testing"
|
|
4505
|
+
|
|
4506
|
+
const lab = createFailureLab({
|
|
4507
|
+
seed: 77,
|
|
4508
|
+
schedule: [
|
|
4509
|
+
{ kind: "duplicate-delivery", point: "relay.batch", copies: 3 },
|
|
4510
|
+
{ kind: "reorder-events", point: "relay.batch" },
|
|
4511
|
+
],
|
|
4512
|
+
})
|
|
4513
|
+
|
|
4514
|
+
// The adapter under test just names its seams. Same seed, same schedule,
|
|
4515
|
+
// same result - every run.
|
|
4516
|
+
const delivered = lab.deliveries("relay.batch", ["evt-a", "evt-b", "evt-c"])
|
|
4517
|
+
```
|
|
4518
|
+
|
|
4519
|
+
The controller is an off-hot-path port. Your adapter names its seams; the schedule decides what
|
|
4520
|
+
happens at them. Time is virtual, so a 24-hour delay costs nothing and nothing is flaky.
|
|
4521
|
+
|
|
4522
|
+
### Assert the invariant, not the output
|
|
4523
|
+
|
|
4524
|
+
```ts
|
|
4525
|
+
// doc-check: skip - commit/publish/outboxStillPending stand in for the system under test
|
|
4526
|
+
import { runFailureScenario } from "@nifrajs/testing"
|
|
4527
|
+
|
|
4528
|
+
const report = await runFailureScenario(
|
|
4529
|
+
{
|
|
4530
|
+
name: "crash-after-commit",
|
|
4531
|
+
execute: async (lab) => {
|
|
4532
|
+
await commit()
|
|
4533
|
+
lab.checkpoint("outbox.after-commit") // crashes here, per the schedule
|
|
4534
|
+
await publish()
|
|
4535
|
+
return "published"
|
|
4536
|
+
},
|
|
4537
|
+
// The scenario passes only if the invariant survives the failure.
|
|
4538
|
+
verify: async ({ error }) => error !== undefined && (await outboxStillPending()),
|
|
4539
|
+
},
|
|
4540
|
+
{ seed: 1, schedule: [{ kind: "crash", point: "outbox.after-commit" }] },
|
|
4541
|
+
)
|
|
4542
|
+
|
|
4543
|
+
report.ok // did the invariant hold?
|
|
4544
|
+
report.replay // { seed, schedule } - reproduces this exact run
|
|
4545
|
+
```
|
|
4546
|
+
|
|
4547
|
+
A scenario passes only when `verify` returns true after the failure. Losing the
|
|
4548
|
+
provider reply is not a bug; losing the money is. That distinction is the whole point.
|
|
4549
|
+
|
|
4550
|
+
### Evidence is tokens only
|
|
4551
|
+
|
|
4552
|
+
Injection evidence records the kind, the point, the occurrence, and the virtual time. Never
|
|
4553
|
+
payloads, provider results, exception messages, or stacks. A failing report carries the error
|
|
4554
|
+
_class_ and the exact `replay` inputs, so it reproduces from the report alone
|
|
4555
|
+
without leaking what flowed through the system.
|
|
4556
|
+
|
|
4557
|
+
### Where it fits
|
|
4558
|
+
|
|
4559
|
+
The same engine backs `nifra levels` L4. Contract invariants fuzz the HTTP surface;
|
|
4560
|
+
the laboratory covers the durable paths underneath, where the interesting failures live.
|
|
4561
|
+
|
|
3997
4562
|
## Nifra — Fonts
|
|
3998
4563
|
|
|
3999
4564
|
> Self-host web fonts with zero layout shift: a build-time Google Fonts downloader (the next/font equivalent) plus CLS-safe @font-face + preload primitives.
|
|
@@ -4096,62 +4661,196 @@ stylesheet can't redirect the download elsewhere — an SSRF gate), and caps eac
|
|
|
4096
4661
|
Filenames are derived only from validated tokens plus a content hash, so there's no path
|
|
4097
4662
|
traversal.
|
|
4098
4663
|
|
|
4099
|
-
## Nifra
|
|
4664
|
+
## Nifra 2.0 migration guide — upgrade from 1.x
|
|
4100
4665
|
|
|
4101
|
-
>
|
|
4666
|
+
> Upgrade a Nifra 1.x application to 2.0: automated package and import moves, opt-in runtime plugins, client and web changes, external auth mounts, and release verification.
|
|
4102
4667
|
|
|
4103
|
-
Nifra
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
edge, and an SSR frontend whenever you want one.
|
|
4668
|
+
Nifra 2.0 removes the compatibility layer retained through 1.x and makes optional runtime
|
|
4669
|
+
systems explicit, instance-scoped plugins. The upgrade command handles deterministic package
|
|
4670
|
+
and import edits; this guide covers the structural changes it deliberately cannot guess.
|
|
4107
4671
|
|
|
4108
|
-
###
|
|
4672
|
+
### 1. Run the executable upgrade
|
|
4109
4673
|
|
|
4110
|
-
|
|
4111
|
-
Nifra
|
|
4674
|
+
```ts
|
|
4112
4675
|
|
|
4113
|
-
|
|
4676
|
+
```
|
|
4114
4677
|
|
|
4115
|
-
|
|
4678
|
+
The command updates existing `@nifrajs/*`, `nifra`, and
|
|
4679
|
+
`create-nifra` dependency ranges to `2.0.0` while preserving
|
|
4680
|
+
caret/tilde/exact style. It also replaces the removed `@nifrajs/budget` dependency
|
|
4681
|
+
with `@nifrajs/core` and moves its imports to
|
|
4682
|
+
`@nifrajs/core/budget`. Dry-run is the default; `--write` applies and
|
|
4683
|
+
verifies with `nifra check`.
|
|
4116
4684
|
|
|
4117
|
-
|
|
4685
|
+
### 2. Install optional server systems explicitly
|
|
4118
4686
|
|
|
4119
|
-
|
|
4120
|
-
|
|
4687
|
+
Idempotency, the effect ledger, MCP declarations, SSE, WebSockets, and Node-direct resolution
|
|
4688
|
+
are no longer enabled by server options, side-effect imports, or process-global state. Install
|
|
4689
|
+
only the systems an app uses with `.use()`; registration fails loudly if a required
|
|
4690
|
+
plugin is missing.
|
|
4121
4691
|
|
|
4122
|
-
|
|
4692
|
+
```ts
|
|
4123
4693
|
|
|
4124
|
-
|
|
4694
|
+
```
|
|
4125
4695
|
|
|
4126
|
-
`
|
|
4127
|
-
|
|
4696
|
+
The `@nifrajs/node` adapter installs `nodeDirect()` automatically.
|
|
4697
|
+
Only applications calling `app.resolveNode()` directly need to install it.
|
|
4128
4698
|
|
|
4129
|
-
|
|
4699
|
+
### 3. Use the lean package entry points
|
|
4130
4700
|
|
|
4131
|
-
`
|
|
4132
|
-
|
|
4701
|
+
The `@nifrajs/core` and `nifra` roots now expose the lean HTTP server
|
|
4702
|
+
surface. Prefer `@nifrajs/core/server` for `server()`, and import
|
|
4703
|
+
contracts, assurance, capabilities, budgets, manifests, reflection, SSE, WebSockets, and
|
|
4704
|
+
other optional systems from their documented subpaths.
|
|
4133
4705
|
|
|
4134
|
-
|
|
4706
|
+
```ts
|
|
4135
4707
|
|
|
4136
|
-
|
|
4708
|
+
```
|
|
4137
4709
|
|
|
4138
|
-
|
|
4710
|
+
Contract-generated adversarial testing lives in `@nifrajs/testing`; the deprecated
|
|
4711
|
+
core invariant runner is removed.
|
|
4139
4712
|
|
|
4140
|
-
|
|
4141
|
-
`toFetchHandler` (edge)
|
|
4713
|
+
### 4. Update full-stack backend mounts
|
|
4142
4714
|
|
|
4143
|
-
|
|
4144
|
-
|
|
4715
|
+
`createWebApp` now auto-mounts a backend only through the platform-aware,
|
|
4716
|
+
symbol-keyed `BackendMount` interface. The old duck-typed
|
|
4717
|
+
`api.fetch(url, init)` convention is removed. Normal applications should pass the
|
|
4718
|
+
result of `inProcessClient(backend)` or `testClient(backend)`; both
|
|
4719
|
+
already implement the interface and forward `env` and `waitUntil`.
|
|
4145
4720
|
|
|
4146
|
-
|
|
4721
|
+
```ts
|
|
4147
4722
|
|
|
4148
|
-
|
|
4149
|
-
|
|
4723
|
+
```
|
|
4724
|
+
|
|
4725
|
+
### 5. Declare external library mounts
|
|
4726
|
+
|
|
4727
|
+
Better Auth and similar libraries can own routes such as `/auth/**` outside the
|
|
4728
|
+
Nifra typed contract. A relative fetch to that library is intentional, but otherwise resembles
|
|
4729
|
+
a hand-rolled own-API call. Declare only the exact mounted prefix in
|
|
4730
|
+
`nifra.check.json` so `nifra check` does not keep CI red:
|
|
4150
4731
|
|
|
4151
4732
|
```ts
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4733
|
+
|
|
4734
|
+
```
|
|
4735
|
+
|
|
4736
|
+
Prefixes are segment-anchored: `/auth` covers `/auth` and its children,
|
|
4737
|
+
never `/authors`. Traversal paths are not suppressed. The normalized allowlist is
|
|
4738
|
+
echoed in human, JSON, and MCP check results, so every bypass remains auditable. This option
|
|
4739
|
+
affects only the typed-client lint; it does not mount, authorize, or trust the library.
|
|
4740
|
+
|
|
4741
|
+
### 6. Narrow typed-client failures by status
|
|
4742
|
+
|
|
4743
|
+
Routes with declared error schemas now return a failure union discriminated by HTTP status.
|
|
4744
|
+
After checking `!result.ok`, also narrow `result.status` before reading
|
|
4745
|
+
typed failure data. Undeclared statuses and transport status `0` remain unknown.
|
|
4746
|
+
|
|
4747
|
+
```ts
|
|
4748
|
+
|
|
4749
|
+
```
|
|
4750
|
+
|
|
4751
|
+
### 7. Apply the remaining web and protocol changes
|
|
4752
|
+
|
|
4753
|
+
-
|
|
4754
|
+
Redirects accept an options object as their second argument.
|
|
4755
|
+
|
|
4756
|
+
```ts
|
|
4757
|
+
|
|
4758
|
+
```
|
|
4759
|
+
|
|
4760
|
+
-
|
|
4761
|
+
Replace the removed prerender wrapper with `enumerateStaticRoutes()`.
|
|
4762
|
+
|
|
4763
|
+
- Fragment navigation resolves element IDs only.
|
|
4764
|
+
|
|
4765
|
+
-
|
|
4766
|
+
MCP Apps metadata uses only `_meta.ui.resourceUri`; remove the deprecated flat
|
|
4767
|
+
`ui/resourceUri` key.
|
|
4768
|
+
|
|
4769
|
+
-
|
|
4770
|
+
Telemetry integrations use `ObservationAdapter`; the
|
|
4771
|
+
`AgentSpan`, `AgentSpanExporter`, and `SpanExporter` aliases
|
|
4772
|
+
are removed.
|
|
4773
|
+
|
|
4774
|
+
- Invalid HTTP method overrides always fail closed with 400.
|
|
4775
|
+
|
|
4776
|
+
-
|
|
4777
|
+
`nifra build` always emits a complete deploy directory and defaults to Bun;
|
|
4778
|
+
`nifra start` runs the generated `server.js`.
|
|
4779
|
+
|
|
4780
|
+
### 8. Run the release gates
|
|
4781
|
+
|
|
4782
|
+
-
|
|
4783
|
+
Run `nifra check --json` and fix every error.
|
|
4784
|
+
|
|
4785
|
+
-
|
|
4786
|
+
If the project has `nifra.assurance.ts`, run `nifra assure --json`.
|
|
4787
|
+
|
|
4788
|
+
-
|
|
4789
|
+
If capabilities are configured, review a new snapshot and run
|
|
4790
|
+
`nifra capabilities check --json`.
|
|
4791
|
+
|
|
4792
|
+
- Run the application test suite and a production build.
|
|
4793
|
+
|
|
4794
|
+
-
|
|
4795
|
+
Exercise authentication, redirects, backend mounting, SSE/WebSocket routes, and each deploy
|
|
4796
|
+
adapter the application uses.
|
|
4797
|
+
|
|
4798
|
+
## Nifra — Migrating from Express, Hono, Fastify & Elysia
|
|
4799
|
+
|
|
4800
|
+
> Move a backend to Nifra: routing, middleware, validation, and body parsing map directly from Express, Hono, Fastify, and Elysia — and you gain a typed client, multi-runtime deploy, and optional SSR.
|
|
4801
|
+
|
|
4802
|
+
Nifra's `server()` is a chainable, Web-standard router — the move from Express, Hono,
|
|
4803
|
+
Fastify, or Elysia is mostly mechanical renames. What you gain: an end-to-end-typed client with
|
|
4804
|
+
zero codegen, schema validation built into the route, the same app on Bun / Node / Deno / the
|
|
4805
|
+
edge, and an SSR frontend whenever you want one.
|
|
4806
|
+
|
|
4807
|
+
### How the concepts map
|
|
4808
|
+
|
|
4809
|
+
Express / Hono / Fastify
|
|
4810
|
+
Nifra
|
|
4811
|
+
|
|
4812
|
+
`app.get(path, handler)`
|
|
4813
|
+
|
|
4814
|
+
`server().get(path, handler)` — chainable, fully type-inferred
|
|
4815
|
+
|
|
4816
|
+
`req` / `res` · Hono `Context`
|
|
4817
|
+
|
|
4818
|
+
`c` — `c.req`, `c.params`, `c.query`; **return** a value (JSON) or a
|
|
4819
|
+
`Response`
|
|
4820
|
+
|
|
4821
|
+
`app.use(mw)` · Hono middleware
|
|
4822
|
+
|
|
4823
|
+
`.use()` + the `@nifrajs/middleware` pack (CORS, auth, rate limit, …)
|
|
4824
|
+
|
|
4825
|
+
`express.json()` / body parsing
|
|
4826
|
+
built in — declare a schema and the parsed body is typed
|
|
4827
|
+
|
|
4828
|
+
Fastify JSON schema · `zod` middleware · Elysia `t`
|
|
4829
|
+
|
|
4830
|
+
`{ body, query, params }` validated by `t` (TypeBox) or any
|
|
4831
|
+
[Standard Schema](https://standardschema.dev)
|
|
4832
|
+
|
|
4833
|
+
`res.json(x)` / `reply.send(x)` / `c.json(x)`
|
|
4834
|
+
|
|
4835
|
+
`return x`
|
|
4836
|
+
|
|
4837
|
+
`app.listen(3000)`
|
|
4838
|
+
|
|
4839
|
+
`app.listen()` (Bun) · `@nifrajs/node` · `@nifrajs/deno` ·
|
|
4840
|
+
`toFetchHandler` (edge)
|
|
4841
|
+
|
|
4842
|
+
OpenAPI plugins / swagger
|
|
4843
|
+
generated from the same `t` schemas — no second source of truth
|
|
4844
|
+
|
|
4845
|
+
### Express → Nifra
|
|
4846
|
+
|
|
4847
|
+
The handler returns its result instead of calling `res.json`, and validation moves
|
|
4848
|
+
onto the route. The client then infers all of this for free.
|
|
4849
|
+
|
|
4850
|
+
```ts
|
|
4851
|
+
// Express
|
|
4852
|
+
app.get("/users/:id", (req, res) => res.json({ id: req.params.id }))
|
|
4853
|
+
app.post("/users", express.json(), (req, res) => res.status(201).json(create(req.body)))
|
|
4155
4854
|
|
|
4156
4855
|
// nifra — return the value (or a Response); c is Web-standard; the body is parsed + validated
|
|
4157
4856
|
server()
|
|
@@ -4438,7 +5137,7 @@ boundary _before_ your handler runs — invalid input is rejected with a 422, so
|
|
|
4438
5137
|
handler only ever sees well-formed data.
|
|
4439
5138
|
|
|
4440
5139
|
```ts
|
|
4441
|
-
import { server } from "@nifrajs/core"
|
|
5140
|
+
import { server } from "@nifrajs/core/server"
|
|
4442
5141
|
import { GetUser } from "./schema"
|
|
4443
5142
|
|
|
4444
5143
|
export const app = server().get("/users/:id", GetUser, (c) => {
|
|
@@ -4481,7 +5180,7 @@ routes and their schemas — generated lazily on first request, never hand-writt
|
|
|
4481
5180
|
`ui: true` to also serve a Scalar reference page.
|
|
4482
5181
|
|
|
4483
5182
|
```ts
|
|
4484
|
-
import { server } from "@nifrajs/core"
|
|
5183
|
+
import { server } from "@nifrajs/core/server"
|
|
4485
5184
|
import { openapi } from "@nifrajs/middleware"
|
|
4486
5185
|
import { GetUser } from "./schema" // the contract defined above
|
|
4487
5186
|
|
|
@@ -4523,10 +5222,132 @@ coupling — server code never ships to the client.
|
|
|
4523
5222
|
The generated OpenAPI document is a structural subset of 3.1 derived from your schemas;
|
|
4524
5223
|
it reflects exactly what the routes declare, not hand-authored prose.
|
|
4525
5224
|
|
|
5225
|
+
## Nifra - Verification ladder
|
|
5226
|
+
|
|
5227
|
+
> Five cumulative levels, from a typed contract to contract-derived invariant tests, that say what a Nifra project actually proves.
|
|
5228
|
+
|
|
5229
|
+
"It compiles" and "it is secure" are different claims. The ladder makes the difference
|
|
5230
|
+
legible: five levels, each earned, so a project can state what it proves instead of what it
|
|
5231
|
+
hopes.
|
|
5232
|
+
|
|
5233
|
+
### The five levels
|
|
5234
|
+
|
|
5235
|
+
Levels are **cumulative**. A level only counts when every level below it holds,
|
|
5236
|
+
so `achieved` is the highest rung with unbroken support underneath, and it is
|
|
5237
|
+
`-1` when even L0 fails.
|
|
5238
|
+
|
|
5239
|
+
-
|
|
5240
|
+
**L0 typed contract** - `nifra check` passes: the frontend and
|
|
5241
|
+
backend cannot have silently diverged.
|
|
5242
|
+
|
|
5243
|
+
-
|
|
5244
|
+
**L1 route assurance** - every route is classified, and the enforcement a rule
|
|
5245
|
+
requires is actually installed.
|
|
5246
|
+
|
|
5247
|
+
-
|
|
5248
|
+
**L2 capability lockfile** - declared effects match a reviewed lockfile, so a
|
|
5249
|
+
route that starts writing to the database shows up in review.
|
|
5250
|
+
|
|
5251
|
+
-
|
|
5252
|
+
**L3 route manifest** - the committed trust artifact still matches the routes,
|
|
5253
|
+
evidence, effects, and response classification it was emitted from.
|
|
5254
|
+
|
|
5255
|
+
-
|
|
5256
|
+
**L4 invariant-tested** - contract-derived invariants ran against an isolated
|
|
5257
|
+
executor with nothing skipped.
|
|
5258
|
+
|
|
5259
|
+
### Run it
|
|
5260
|
+
|
|
5261
|
+
```ts
|
|
5262
|
+
# What does this project prove right now?
|
|
5263
|
+
nifra levels
|
|
5264
|
+
|
|
5265
|
+
# Fail CI below a level the project already reached.
|
|
5266
|
+
nifra levels --min 2
|
|
5267
|
+
|
|
5268
|
+
# The same report an agent reads over MCP (nifra_levels).
|
|
5269
|
+
nifra levels --json
|
|
5270
|
+
```
|
|
5271
|
+
|
|
5272
|
+
```ts
|
|
5273
|
+
L0 typed contract ok
|
|
5274
|
+
L1 route assurance ok
|
|
5275
|
+
L2 capability lockfile ok
|
|
5276
|
+
L3 route manifest FAIL manifest drifted from routes: run `nifra manifest emit`
|
|
5277
|
+
L4 invariant-tested FAIL no isolated invariant executor configured
|
|
5278
|
+
|
|
5279
|
+
achieved: 2
|
|
5280
|
+
```
|
|
5281
|
+
|
|
5282
|
+
Every level that does not hold explains itself, so the output is a work list rather than a
|
|
5283
|
+
verdict. `--min <n>` is the CI floor: pin it to the level you have reached and
|
|
5284
|
+
the build fails when a change quietly costs you a rung.
|
|
5285
|
+
|
|
5286
|
+
### Wiring the levels
|
|
5287
|
+
|
|
5288
|
+
L0 needs nothing. L1 through L4 hang off `nifra.assurance.ts`, and each one you
|
|
5289
|
+
configure is a level you can start claiming.
|
|
5290
|
+
|
|
5291
|
+
```ts
|
|
5292
|
+
// nifra.assurance.ts
|
|
5293
|
+
// doc-check: skip - policy rules and the isolated executor are app-specific
|
|
5294
|
+
import { defineAssuranceConfig } from "@nifrajs/core/assurance"
|
|
5295
|
+
import { backend } from "./backend"
|
|
5296
|
+
|
|
5297
|
+
export default defineAssuranceConfig({
|
|
5298
|
+
source: backend,
|
|
5299
|
+
policy: { rules: [/* ... */] },
|
|
5300
|
+
capabilities: { lockfile: "capabilities.lock.json", policy: { /* ... */ } },
|
|
5301
|
+
manifest: { path: "nifra.manifest.json" },
|
|
5302
|
+
invariants: {
|
|
5303
|
+
// L4 fuzzes the contract, so it needs an app you are willing to throw hostile
|
|
5304
|
+
// input at. Point this at an isolated instance, never a live one.
|
|
5305
|
+
executor: (request) => backend.fetch(request),
|
|
5306
|
+
},
|
|
5307
|
+
})
|
|
5308
|
+
```
|
|
5309
|
+
|
|
5310
|
+
L4 requires an **explicit** executor. Invariant runs send deliberately hostile
|
|
5311
|
+
input, so nifra will not guess which app to point them at; a level that fuzzed your live
|
|
5312
|
+
server by default would be a footgun, and a skipped route is reported rather than passed over
|
|
5313
|
+
in silence.
|
|
5314
|
+
|
|
5315
|
+
### For coding agents
|
|
5316
|
+
|
|
5317
|
+
The `nifra_levels` MCP tool returns the same
|
|
5318
|
+
`{ achieved, levels[] }` report. An agent can read what its change proved, and a
|
|
5319
|
+
level that regressed comes back with the reason it broke.
|
|
5320
|
+
|
|
4526
5321
|
---
|
|
4527
5322
|
|
|
4528
5323
|
# Package reference (READMEs)
|
|
4529
5324
|
|
|
5325
|
+
## @nifrajs/agent-telemetry
|
|
5326
|
+
|
|
5327
|
+
Child-span telemetry for AI tool calls made through Nifra tool and MCP endpoints.
|
|
5328
|
+
|
|
5329
|
+
```sh
|
|
5330
|
+
bun add @nifrajs/agent-telemetry @nifrajs/otel
|
|
5331
|
+
```
|
|
5332
|
+
|
|
5333
|
+
```ts
|
|
5334
|
+
import { agentTelemetry, consoleAgentExporter } from "@nifrajs/agent-telemetry"
|
|
5335
|
+
|
|
5336
|
+
app.use(agentTelemetry({ exporter: consoleAgentExporter() }))
|
|
5337
|
+
```
|
|
5338
|
+
|
|
5339
|
+
Requests under `/_nifra/tool/*` and requests to `/mcp` receive tool-name, input-size, output-size,
|
|
5340
|
+
duration, and status observations. When `@nifrajs/otel` owns the request observation, tool spans are
|
|
5341
|
+
attached as children; otherwise the plugin creates a standalone observation. Other routes pass through.
|
|
5342
|
+
|
|
5343
|
+
## For AI agents
|
|
5344
|
+
|
|
5345
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
5346
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
5347
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
5348
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
5349
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
5350
|
+
|
|
4530
5351
|
## @nifrajs/auth
|
|
4531
5352
|
|
|
4532
5353
|
Session primitives for nifra — signed-cookie + server-store sessions, CSRF, and route guards. Bring your own identity (Better Auth / Lucia / OAuth); nifra owns the session.
|
|
@@ -4586,7 +5407,7 @@ export const auth = createBetterAuth({
|
|
|
4586
5407
|
|
|
4587
5408
|
```ts
|
|
4588
5409
|
// server.ts
|
|
4589
|
-
import { server } from "@nifrajs/core"
|
|
5410
|
+
import { server } from "@nifrajs/core/server"
|
|
4590
5411
|
import { betterAuth } from "@nifrajs/better-auth"
|
|
4591
5412
|
import { auth } from "./auth"
|
|
4592
5413
|
|
|
@@ -4655,38 +5476,6 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
|
4655
5476
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
4656
5477
|
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
4657
5478
|
|
|
4658
|
-
## @nifrajs/budget
|
|
4659
|
-
|
|
4660
|
-
Compatibility import for the request-deadline mechanics owned by `@nifrajs/core/budget`. Existing
|
|
4661
|
-
Nifra, Hono, and Fetch consumers can keep this import unchanged; new core consumers may use the
|
|
4662
|
-
subpath directly. Both paths resolve to the same implementation and classes.
|
|
4663
|
-
|
|
4664
|
-
```ts
|
|
4665
|
-
import {
|
|
4666
|
-
assertBudgetRemaining,
|
|
4667
|
-
createRequestBudget,
|
|
4668
|
-
withDeadlineHeader,
|
|
4669
|
-
} from "@nifrajs/budget"
|
|
4670
|
-
|
|
4671
|
-
const budget = createRequestBudget({
|
|
4672
|
-
deadline: Date.now() + 2_000,
|
|
4673
|
-
signal: request.signal,
|
|
4674
|
-
})
|
|
4675
|
-
|
|
4676
|
-
assertBudgetRemaining(budget, 100)
|
|
4677
|
-
await fetch(url, {
|
|
4678
|
-
signal: budget.signal,
|
|
4679
|
-
headers: withDeadlineHeader(undefined, budget.child(50)),
|
|
4680
|
-
})
|
|
4681
|
-
```
|
|
4682
|
-
|
|
4683
|
-
The wire value is an absolute Unix epoch deadline. `remaining()` samples wall time once, then uses a
|
|
4684
|
-
monotonic clock so NTP changes cannot extend or shorten admitted work. A budget does not choose retry,
|
|
4685
|
-
provider, tenant, or money policy; it only answers how much time remains.
|
|
4686
|
-
|
|
4687
|
-
Nifra route handlers receive the same primitive as `c.budget`. `c.signal` remains the cancellation
|
|
4688
|
-
signal and aborts at the effective server deadline.
|
|
4689
|
-
|
|
4690
5479
|
## @nifrajs/cache
|
|
4691
5480
|
|
|
4692
5481
|
Typed KV cache for nifra — **TTL**, **stale-while-revalidate**, **tag invalidation**, and **single-flight
|
|
@@ -4742,6 +5531,14 @@ the Cache API) via a `CacheStore` for anything that should survive across reques
|
|
|
4742
5531
|
- `cache.wrap(key, loader, { ttlMs?, swrMs?, tags? })` · `get` · `has` · `set` · `delete` · `invalidateTag` · `clear`.
|
|
4743
5532
|
- `MemoryCache({ maxEntries?, now? })` — the default store; implement `CacheStore` for your own.
|
|
4744
5533
|
|
|
5534
|
+
## For AI agents
|
|
5535
|
+
|
|
5536
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
5537
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
5538
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
5539
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
5540
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
5541
|
+
|
|
4745
5542
|
## @nifrajs/cli
|
|
4746
5543
|
|
|
4747
5544
|
The `nifra` CLI — zero-config dev/build/start for a nifra app, plus the agent surfaces (`nifra context`,
|
|
@@ -4750,8 +5547,8 @@ The `nifra` CLI — zero-config dev/build/start for a nifra app, plus the agent
|
|
|
4750
5547
|
|
|
4751
5548
|
```
|
|
4752
5549
|
nifra dev [--port <n>] [--poll] True-HMR dev server (Vite + nifra SSR).
|
|
4753
|
-
nifra build [--out <dir>]
|
|
4754
|
-
nifra start [--port <n>] [--out <dir>]
|
|
5550
|
+
nifra build [--out <dir>] [--target <t>] Complete deploy directory; target defaults to Bun.
|
|
5551
|
+
nifra start [--port <n>] [--out <dir>] Run the generated Bun server at <out>/server.js.
|
|
4755
5552
|
nifra context Print this project's API + page routes + conventions.
|
|
4756
5553
|
nifra mcp MCP server (stdio) exposing this project to a coding agent.
|
|
4757
5554
|
nifra check [--json] Gate: typecheck + lints; --json includes fixes/suggestions.
|
|
@@ -4816,6 +5613,17 @@ For hand-rolled own-API `fetch()` calls, `nifra check` stays conservative: simpl
|
|
|
4816
5613
|
that match a statically visible Nifra route get an exact typed-client rewrite diff; dynamic URLs, custom
|
|
4817
5614
|
headers, bodies, query strings, or ambiguous routes fall back to manual steps.
|
|
4818
5615
|
|
|
5616
|
+
When a library such as Better Auth intentionally owns a mounted prefix outside the Nifra contract,
|
|
5617
|
+
declare that prefix in `nifra.check.json` so its relative calls do not keep CI red:
|
|
5618
|
+
|
|
5619
|
+
```json
|
|
5620
|
+
{ "externalMounts": ["/auth"] }
|
|
5621
|
+
```
|
|
5622
|
+
|
|
5623
|
+
The match is segment-anchored (`/auth` never suppresses `/authors`) and traversal paths are rejected.
|
|
5624
|
+
The normalized prefixes are echoed in human, JSON, and MCP results so the lint bypass stays auditable.
|
|
5625
|
+
This suppresses only the typed-client diagnostic; it does not mount or authorize the external handler.
|
|
5626
|
+
|
|
4819
5627
|
## For AI coding agents
|
|
4820
5628
|
|
|
4821
5629
|
**`nifra context`** prints this project's actual surface — API routes (from `backend.routes()`), page
|
|
@@ -4842,6 +5650,10 @@ an agent can act on the project, not just read about it. Core tools:
|
|
|
4842
5650
|
- **`nifra_check`** — run the drift gate and get the structured `{ ok, typecheck, diagnostics[] }`
|
|
4843
5651
|
result with suggestions, so the agent verifies its own work and fixes each diagnostic before finishing.
|
|
4844
5652
|
- **`nifra_assure`** — evaluate `nifra.assurance.ts` and return the complete per-route evidence report.
|
|
5653
|
+
- **`nifra_levels`** - compute the cumulative verification ladder (L0 typed contract, L1 route
|
|
5654
|
+
assurance, L2 capability lockfile, L3 trust manifest, L4 contract invariants) and return
|
|
5655
|
+
`{ achieved, levels[] }`, so an agent can see what its change proves and read why a level it lost
|
|
5656
|
+
no longer holds.
|
|
4845
5657
|
- **`nifra_doctor`** — catch imported packages missing from `package.json`; with `autoFix:true`, write
|
|
4846
5658
|
safe dependency entries when a version can be inferred locally and return exact `bun add` commands
|
|
4847
5659
|
for anything that needs a package-manager decision.
|
|
@@ -4959,7 +5771,7 @@ bun add @nifrajs/core
|
|
|
4959
5771
|
```
|
|
4960
5772
|
|
|
4961
5773
|
```ts
|
|
4962
|
-
import { server } from "@nifrajs/core"
|
|
5774
|
+
import { server } from "@nifrajs/core/server"
|
|
4963
5775
|
|
|
4964
5776
|
const app = server()
|
|
4965
5777
|
.get("/users/:id", (c) => ({ id: c.params.id }))
|
|
@@ -4969,6 +5781,15 @@ const app = server()
|
|
|
4969
5781
|
export type App = typeof app // hand this to @nifrajs/client for end-to-end types
|
|
4970
5782
|
```
|
|
4971
5783
|
|
|
5784
|
+
`@nifrajs/core` and `@nifrajs/core/server` expose the same lean common runtime. Optional systems are
|
|
5785
|
+
available only from their explicit subpaths, so an ordinary HTTP server never evaluates them:
|
|
5786
|
+
|
|
5787
|
+
```ts
|
|
5788
|
+
import { defineContract, implement } from "@nifrajs/core/contract"
|
|
5789
|
+
import { startCausality } from "@nifrajs/core/causality"
|
|
5790
|
+
import { defineAssurancePolicy } from "@nifrajs/core/assurance"
|
|
5791
|
+
```
|
|
5792
|
+
|
|
4972
5793
|
- **Inline or contract-first.** Write routes inline (types inferred from the
|
|
4973
5794
|
builder), or `defineContract(...)` + `implement(...)` for a decoupled, versionable
|
|
4974
5795
|
surface — handlers lift over unchanged.
|
|
@@ -4989,6 +5810,33 @@ export type App = typeof app // hand this to @nifrajs/client for end-to-end type
|
|
|
4989
5810
|
IP-restriction, and security-header modules publish reflection-safe enforcement evidence.
|
|
4990
5811
|
An ordered `AssurancePolicy` classifies every route and fails closed on missing or forbidden
|
|
4991
5812
|
evidence without adding work to the request path.
|
|
5813
|
+
- **Owned effect execution.** `executeCapability()` correlates intent and terminal evidence with an
|
|
5814
|
+
opaque `effectId`, records outcomes automatically, and forwards request cancellation. Add
|
|
5815
|
+
order-scoped `aroundCapability()` policies for async approval/admission; they receive token-only
|
|
5816
|
+
metadata, have bounded timeouts, and must call `next()` exactly once before the effect can run.
|
|
5817
|
+
- **Durable workflows (opt in).** `@nifrajs/core/durable-execution` provides tenant/principal-bound,
|
|
5818
|
+
signed single-use approval resumes; a durable effect journal + reconciliation scanner; and a typed
|
|
5819
|
+
saga state machine with reverse compensation, retry/backoff, and ambiguous-crash detection. Production
|
|
5820
|
+
constructors reject stores that do not declare `durability: "durable"`. Operational scans use bounded
|
|
5821
|
+
cursor pages through `reconcileEffectsPage()` / `reconcileSagasPage()`. Provider-confirmed manual
|
|
5822
|
+
review uses effect-ID-bound `resolveAmbiguity()`, followed by `resume()` or `compensate()`.
|
|
5823
|
+
- **Production durable adapters.** `@nifrajs/core/durable-adapters` supplies
|
|
5824
|
+
`PostgresDurableExecutionAdapter`, `SQLiteDurableExecutionAdapter`, and
|
|
5825
|
+
`DurableObjectExecutionAdapter`. Each exposes compatible `effects`, `approvals`, `sagas`, and
|
|
5826
|
+
`leases` stores. Run `runDurableExecutionAdapterConformance()` against the deployment backend.
|
|
5827
|
+
- **Bounded reconciliation workers.** `@nifrajs/core/reconciliation-worker` runs effect or saga
|
|
5828
|
+
scans under an atomic lease with durable cursor checkpoints, a finite page budget, bounded handler
|
|
5829
|
+
concurrency, filters, cancellation, and token-only metrics. A worker invocation always terminates.
|
|
5830
|
+
- **Rich wire values (opt in).** `@nifrajs/core/wire` round-trips dates, bigints, maps, sets, binary,
|
|
5831
|
+
shared references, and cycles through JSON transports. Decoding validates every reachable shape,
|
|
5832
|
+
preserves owned `__proto__` keys without prototype mutation, and enforces configurable node, depth,
|
|
5833
|
+
collection-entry, and decoded-byte limits.
|
|
5834
|
+
- **Versioned transport codecs (opt in).** Add `.use(transportCodecs(registry))` from
|
|
5835
|
+
`@nifrajs/core/transport-plugin` and configure the typed
|
|
5836
|
+
client's `transport` option with the same registry. `@nifrajs/core/transport-codec` negotiates
|
|
5837
|
+
bounded HTTP representations and supplies the same frame/loader adapters for WebSockets and
|
|
5838
|
+
deferred data. Import `richWireCodec()` from `@nifrajs/core/transport-codec-rich`; the separate
|
|
5839
|
+
subpath keeps rich-wire code out of plain JSON bundles.
|
|
4992
5840
|
|
|
4993
5841
|
```ts
|
|
4994
5842
|
import { defineAssurancePolicy, evaluateRouteAssurance, NIFRA_ASSURANCE } from "@nifrajs/core/assurance"
|
|
@@ -5094,7 +5942,7 @@ deno add npm:@nifrajs/deno # or import npm:@nifrajs/deno directly
|
|
|
5094
5942
|
```
|
|
5095
5943
|
|
|
5096
5944
|
```ts
|
|
5097
|
-
import { server } from "@nifrajs/core"
|
|
5945
|
+
import { server } from "@nifrajs/core/server"
|
|
5098
5946
|
import { serve } from "@nifrajs/deno"
|
|
5099
5947
|
|
|
5100
5948
|
const app = server().get("/users/:id", (c) => ({ id: c.params.id }))
|
|
@@ -5129,6 +5977,32 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
|
5129
5977
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
5130
5978
|
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
5131
5979
|
|
|
5980
|
+
## @nifrajs/devtools
|
|
5981
|
+
|
|
5982
|
+
Interactive request-trace DevTools for `nifra dev`, with a secured SSE stream and browser overlay.
|
|
5983
|
+
|
|
5984
|
+
```sh
|
|
5985
|
+
bun add -d @nifrajs/devtools
|
|
5986
|
+
```
|
|
5987
|
+
|
|
5988
|
+
```ts
|
|
5989
|
+
import { devtools } from "@nifrajs/devtools"
|
|
5990
|
+
|
|
5991
|
+
app.use(devtools())
|
|
5992
|
+
```
|
|
5993
|
+
|
|
5994
|
+
The plugin is enabled by default only when `NODE_ENV=development`. It records a bounded event buffer
|
|
5995
|
+
and serves the overlay stream at `/_nifra/devtools`; remote hosts and cross-origin access stay denied
|
|
5996
|
+
unless explicitly allowed. Leave it unregistered in production for zero request-path overhead.
|
|
5997
|
+
|
|
5998
|
+
## For AI agents
|
|
5999
|
+
|
|
6000
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6001
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6002
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6003
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6004
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6005
|
+
|
|
5132
6006
|
## @nifrajs/env
|
|
5133
6007
|
|
|
5134
6008
|
Typed, validated environment variables. Define a schema once at startup; get a **frozen, typed**
|
|
@@ -5215,6 +6089,14 @@ lineage metadata.
|
|
|
5215
6089
|
|
|
5216
6090
|
See the [nifra docs](https://github.com/nifrajs/nifra#readme).
|
|
5217
6091
|
|
|
6092
|
+
## For AI agents
|
|
6093
|
+
|
|
6094
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6095
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6096
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6097
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6098
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6099
|
+
|
|
5218
6100
|
## @nifrajs/i18n
|
|
5219
6101
|
|
|
5220
6102
|
Framework-agnostic i18n for nifra — locale negotiation + a tiny ICU message formatter on the platform Intl. Dependency-free.
|
|
@@ -5422,6 +6304,78 @@ export default {
|
|
|
5422
6304
|
- `queue.counts()` → `{ pending, active, dead }`. `queue.store` — the underlying store.
|
|
5423
6305
|
- Stores: `MemoryJobStore`. Backoff: `exponentialBackoff`, `fixedBackoff`, `noBackoff`.
|
|
5424
6306
|
|
|
6307
|
+
## For AI agents
|
|
6308
|
+
|
|
6309
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6310
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6311
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6312
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6313
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6314
|
+
|
|
6315
|
+
## @nifrajs/mcp
|
|
6316
|
+
|
|
6317
|
+
Build transport-agnostic MCP servers and interactive MCP Apps for Nifra applications.
|
|
6318
|
+
|
|
6319
|
+
```sh
|
|
6320
|
+
bun add @nifrajs/mcp
|
|
6321
|
+
```
|
|
6322
|
+
|
|
6323
|
+
```ts
|
|
6324
|
+
import { createMcpServer, defineMcpTool } from "@nifrajs/mcp"
|
|
6325
|
+
|
|
6326
|
+
const tools = [
|
|
6327
|
+
defineMcpTool({
|
|
6328
|
+
name: "health",
|
|
6329
|
+
description: "Report service health",
|
|
6330
|
+
handler: () => ({ text: "ok" }),
|
|
6331
|
+
}),
|
|
6332
|
+
]
|
|
6333
|
+
|
|
6334
|
+
const mcp = createMcpServer({ name: "orders", version: "1.0.0", tools })
|
|
6335
|
+
```
|
|
6336
|
+
|
|
6337
|
+
Mount `mcp.fetch` at `POST /mcp`. The package also exposes the JSON-RPC protocol and Streamable HTTP
|
|
6338
|
+
layers directly, plus `defineMcpWidget` and the React adapter for tool results that render UI in MCP
|
|
6339
|
+
hosts.
|
|
6340
|
+
|
|
6341
|
+
## For AI agents
|
|
6342
|
+
|
|
6343
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6344
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6345
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6346
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6347
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6348
|
+
|
|
6349
|
+
## @nifrajs/mcp-db
|
|
6350
|
+
|
|
6351
|
+
Expose an allowlisted SQLite schema as a fail-closed MCP server, with opt-in read-only queries.
|
|
6352
|
+
|
|
6353
|
+
```sh
|
|
6354
|
+
bun add @nifrajs/mcp-db
|
|
6355
|
+
```
|
|
6356
|
+
|
|
6357
|
+
```ts
|
|
6358
|
+
import { Database } from "bun:sqlite"
|
|
6359
|
+
import { serveDatabaseAsMcp } from "@nifrajs/mcp-db"
|
|
6360
|
+
|
|
6361
|
+
const mcp = serveDatabaseAsMcp(new Database("app.db"), {
|
|
6362
|
+
tables: ["habits", "entries"],
|
|
6363
|
+
})
|
|
6364
|
+
```
|
|
6365
|
+
|
|
6366
|
+
Only `list_tables` and `describe_table` are exposed by default, restricted to the explicit table
|
|
6367
|
+
allowlist. `run_query` requires an authorization hook and is guarded by SQLite query-only mode,
|
|
6368
|
+
single-statement/read-only checks, query-plan verification, and bounded output. D1 is not supported
|
|
6369
|
+
because it cannot provide the same engine-level read-only guarantee.
|
|
6370
|
+
|
|
6371
|
+
## For AI agents
|
|
6372
|
+
|
|
6373
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6374
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6375
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6376
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6377
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6378
|
+
|
|
5425
6379
|
## @nifrajs/middleware
|
|
5426
6380
|
|
|
5427
6381
|
Composable, dependency-light middleware for [nifra](../../README.md) — CORS, security headers, body
|
|
@@ -5433,7 +6387,7 @@ bun add @nifrajs/middleware
|
|
|
5433
6387
|
```
|
|
5434
6388
|
|
|
5435
6389
|
```ts
|
|
5436
|
-
import { server } from "@nifrajs/core"
|
|
6390
|
+
import { server } from "@nifrajs/core/server"
|
|
5437
6391
|
import {
|
|
5438
6392
|
bodyLimit,
|
|
5439
6393
|
cors,
|
|
@@ -5510,6 +6464,34 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
|
5510
6464
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
5511
6465
|
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
5512
6466
|
|
|
6467
|
+
## @nifrajs/mock
|
|
6468
|
+
|
|
6469
|
+
Generate deterministic mock responses from a Nifra application's reflected response contracts.
|
|
6470
|
+
|
|
6471
|
+
```sh
|
|
6472
|
+
bun add -d @nifrajs/mock
|
|
6473
|
+
```
|
|
6474
|
+
|
|
6475
|
+
```ts
|
|
6476
|
+
import { createMockServer } from "@nifrajs/mock"
|
|
6477
|
+
import { app } from "./app"
|
|
6478
|
+
|
|
6479
|
+
const mock = createMockServer(app, { seed: 42 })
|
|
6480
|
+
const response = await mock.fetch(new Request("http://local.test/users/1"))
|
|
6481
|
+
```
|
|
6482
|
+
|
|
6483
|
+
The mock server matches reflected routes and synthesizes values from supported JSON Schema keywords.
|
|
6484
|
+
Unsupported or unsatisfiable schemas fail explicitly instead of returning known-invalid data. Use a
|
|
6485
|
+
fixed seed when responses must be replayable across local development and CI.
|
|
6486
|
+
|
|
6487
|
+
## For AI agents
|
|
6488
|
+
|
|
6489
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6490
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6491
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6492
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6493
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6494
|
+
|
|
5513
6495
|
## @nifrajs/node
|
|
5514
6496
|
|
|
5515
6497
|
Run a [nifra](../../README.md) app on Node's `http` server — nifra's ergonomics, deployed
|
|
@@ -5520,7 +6502,7 @@ bun add @nifrajs/node # or: npm add @nifrajs/node
|
|
|
5520
6502
|
```
|
|
5521
6503
|
|
|
5522
6504
|
```ts
|
|
5523
|
-
import { server } from "@nifrajs/core"
|
|
6505
|
+
import { server } from "@nifrajs/core/server"
|
|
5524
6506
|
import { serve } from "@nifrajs/node"
|
|
5525
6507
|
|
|
5526
6508
|
const app = server().get("/users/:id", (c) => ({ id: c.params.id }))
|
|
@@ -5644,8 +6626,7 @@ inventing trace identity.
|
|
|
5644
6626
|
|
|
5645
6627
|
## Adapters
|
|
5646
6628
|
|
|
5647
|
-
Implement `ObservationAdapter` to send spans wherever you collect them
|
|
5648
|
-
backwards-compatible type alias:
|
|
6629
|
+
Implement `ObservationAdapter` to send spans wherever you collect them:
|
|
5649
6630
|
|
|
5650
6631
|
```ts
|
|
5651
6632
|
interface ObservationAdapter {
|
|
@@ -5678,6 +6659,37 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
|
5678
6659
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
5679
6660
|
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
5680
6661
|
|
|
6662
|
+
## @nifrajs/prompt
|
|
6663
|
+
|
|
6664
|
+
Provider-neutral, type-safe prompts with Standard Schema input and validated structured output.
|
|
6665
|
+
|
|
6666
|
+
```sh
|
|
6667
|
+
bun add @nifrajs/prompt
|
|
6668
|
+
```
|
|
6669
|
+
|
|
6670
|
+
```ts
|
|
6671
|
+
import { prompt } from "@nifrajs/prompt"
|
|
6672
|
+
import { t } from "@nifrajs/schema"
|
|
6673
|
+
|
|
6674
|
+
const extract = prompt("Extract the contact from the text.")
|
|
6675
|
+
.input(t.object({ text: t.string() }))
|
|
6676
|
+
.output(t.object({ name: t.string(), email: t.string({ format: "email" }) }))
|
|
6677
|
+
|
|
6678
|
+
const contact = await extract.run({ text }, { complete: callYourProvider })
|
|
6679
|
+
```
|
|
6680
|
+
|
|
6681
|
+
The package imports no vendor SDK. Supply a `complete` adapter for your provider; inputs are validated
|
|
6682
|
+
before the call and model output is parsed and validated before it reaches application code. An optional
|
|
6683
|
+
`heal` hook can repair and retry invalid structured output.
|
|
6684
|
+
|
|
6685
|
+
## For AI agents
|
|
6686
|
+
|
|
6687
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6688
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6689
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6690
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6691
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6692
|
+
|
|
5681
6693
|
## @nifrajs/runner
|
|
5682
6694
|
|
|
5683
6695
|
Run requests through a nifra app and capture **structured results** — the shared engine behind the
|
|
@@ -5727,7 +6739,7 @@ bun add @nifrajs/schema
|
|
|
5727
6739
|
```
|
|
5728
6740
|
|
|
5729
6741
|
```ts
|
|
5730
|
-
import { server } from "@nifrajs/core"
|
|
6742
|
+
import { server } from "@nifrajs/core/server"
|
|
5731
6743
|
import { t, toOpenAPI } from "@nifrajs/schema"
|
|
5732
6744
|
|
|
5733
6745
|
const app = server().post("/users", { body: t.object({ name: t.string(), age: t.integer() }) }, (c) => ({
|
|
@@ -5747,6 +6759,22 @@ const openapi = toOpenAPI(app) // OpenAPI 3.1
|
|
|
5747
6759
|
`operationId`s); also works on a live app. Routes using a BYO Standard Schema are
|
|
5748
6760
|
emitted without a detailed schema (Standard Schema exposes no JSON Schema).
|
|
5749
6761
|
|
|
6762
|
+
### `t` vs bring-your-own (bundle size)
|
|
6763
|
+
|
|
6764
|
+
`t` is the batteries-included default because a TypeBox schema *is* a JSON Schema: it gives you
|
|
6765
|
+
OpenAPI + MCP `tools/list` for free and compiles to a fast validator. That completeness has a
|
|
6766
|
+
cost - TypeBox carries the whole JSON Schema type system, so `t` adds meaningful bytes to a bundle.
|
|
6767
|
+
|
|
6768
|
+
nifra validates through **Standard Schema**, so any Standard-Schema validator works on a route with
|
|
6769
|
+
no adapter - `{ body: v.object({ ... }) }` with valibot, for instance. A validated app built on a
|
|
6770
|
+
lean validator like valibot is a fraction of the size (measured ~16 KB gzipped), which matters for
|
|
6771
|
+
edge/Workers cold-starts. The trade is the one noted above: a bring-your-own validator exposes no
|
|
6772
|
+
JSON Schema, so those routes carry no OpenAPI/MCP detail.
|
|
6773
|
+
|
|
6774
|
+
Rule of thumb: reach for `t` when you want the contract (OpenAPI/MCP) for free; bring valibot (or any
|
|
6775
|
+
Standard-Schema validator) when bundle size on the edge is the priority. Both are first-class - the
|
|
6776
|
+
choice is per route, and you can mix them in one app.
|
|
6777
|
+
|
|
5750
6778
|
### `toOpenAPI` coverage
|
|
5751
6779
|
|
|
5752
6780
|
It emits `paths`, `parameters` (path + object query), `requestBody`, and `responses`, plus:
|
|
@@ -5835,6 +6863,14 @@ import { assertSafeKey, StorageKeyError } from "@nifrajs/storage"
|
|
|
5835
6863
|
- `assertStorageAdapterConformance({ createAdapter })` — dependency-free executable adapter contract.
|
|
5836
6864
|
- Optional interfaces: `PagedStorageAdapter` · `PresignableStorageAdapter` · `MovableStorageAdapter`.
|
|
5837
6865
|
|
|
6866
|
+
## For AI agents
|
|
6867
|
+
|
|
6868
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
6869
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
6870
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
6871
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6872
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6873
|
+
|
|
5838
6874
|
## @nifrajs/testing
|
|
5839
6875
|
|
|
5840
6876
|
Contract-derived adversarial testing and stateful in-process helpers for Nifra apps.
|
|
@@ -6004,6 +7040,14 @@ that every call carries and captures cookies via a shared jar.
|
|
|
6004
7040
|
|
|
6005
7041
|
For a **stateless** request (no cookies), use `testClient` from `@nifrajs/client` directly.
|
|
6006
7042
|
|
|
7043
|
+
## For AI agents
|
|
7044
|
+
|
|
7045
|
+
Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
|
|
7046
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
7047
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
7048
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
7049
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
7050
|
+
|
|
6007
7051
|
## @nifrajs/uploads
|
|
6008
7052
|
|
|
6009
7053
|
File-upload hardening for nifra — magic-byte MIME detection, size + type validation, short-TTL signed download URLs, and EXIF/metadata stripping. Dependency-free, edge-safe.
|
|
@@ -6330,7 +7374,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
6330
7374
|
|
|
6331
7375
|
## @nifrajs/agent-telemetry
|
|
6332
7376
|
|
|
6333
|
-
`
|
|
7377
|
+
`agentTelemetry`, `AgentTelemetryOptions`, `consoleAgentExporter`
|
|
6334
7378
|
|
|
6335
7379
|
## @nifrajs/auth
|
|
6336
7380
|
|
|
@@ -6338,7 +7382,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
6338
7382
|
|
|
6339
7383
|
## @nifrajs/better-auth
|
|
6340
7384
|
|
|
6341
|
-
`betterAuth`, `BetterAuthLike`, `BetterAuthOptions`, `getSession`, `requireSession`, `RequireSessionOptions`, `SessionOf`
|
|
7385
|
+
`authed`, `AuthedOptions`, `betterAuth`, `BetterAuthLike`, `BetterAuthOptions`, `getSession`, `Principal`, `PrincipalFor`, `requirePrincipal`, `requireSession`, `RequireSessionOptions`, `SessionOf`, `SessionUserOf`, `WithPrincipal`
|
|
6342
7386
|
|
|
6343
7387
|
## @nifrajs/cache
|
|
6344
7388
|
|
|
@@ -6346,7 +7390,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
6346
7390
|
|
|
6347
7391
|
## @nifrajs/client
|
|
6348
7392
|
|
|
6349
|
-
`ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `FetchFn`, `inProcessClient`, `InProcessClient`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `Result`, `SubscribeOptions`, `Subscription`, `testClient`, `Treaty`, `TreatyFromRegistry`
|
|
7393
|
+
`ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `ClientRetryOptions`, `FetchFn`, `inProcessClient`, `InProcessClient`, `InProcessClientOptions`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `ResponseContractViolation`, `Result`, `SubscribeOptions`, `Subscription`, `testClient`, `Treaty`, `TreatyFromRegistry`, `WsCallOptions`, `WsHandle`
|
|
6350
7394
|
|
|
6351
7395
|
## @nifrajs/content
|
|
6352
7396
|
|
|
@@ -6354,7 +7398,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
6354
7398
|
|
|
6355
7399
|
## @nifrajs/core
|
|
6356
7400
|
|
|
6357
|
-
`
|
|
7401
|
+
`Version`, `VERSION`
|
|
6358
7402
|
|
|
6359
7403
|
## @nifrajs/cron
|
|
6360
7404
|
|
|
@@ -6394,7 +7438,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
6394
7438
|
|
|
6395
7439
|
## @nifrajs/mcp
|
|
6396
7440
|
|
|
6397
|
-
`bridgeScript`, `createMcpServer`, `CreateMcpServerOptions`, `defineMcpTool`, `DefineMcpToolOptions`, `defineMcpWidget`, `DefineMcpWidgetOptions`, `McpAppBridge`, `McpHttpOptions`, `McpServer`, `McpToolHandlerResult`, `McpUiIntent`, `McpWidget`, `respondMcpHttp`, `uiResourceMeta`, `widgetDocument`
|
|
7441
|
+
`bridgeScript`, `createMcpServer`, `CreateMcpServerOptions`, `defineMcpTool`, `DefineMcpToolOptions`, `defineMcpWidget`, `DefineMcpWidgetOptions`, `InferOutput`, `McpAppBridge`, `McpHttpOptions`, `McpServer`, `McpToolHandlerResult`, `McpUiIntent`, `McpWidget`, `respondMcpHttp`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `uiResourceMeta`, `widgetDocument`
|
|
6398
7442
|
|
|
6399
7443
|
## @nifrajs/mcp-db
|
|
6400
7444
|
|
|
@@ -6414,7 +7458,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
6414
7458
|
|
|
6415
7459
|
## @nifrajs/otel
|
|
6416
7460
|
|
|
6417
|
-
`ActiveObservation`, `AttributeValue`, `causalitySpanLink`, `combineObservationAdapters`, `consoleSpanExporter`, `createObservationLifecycle`, `EndObservation`, `formatTraceparent`, `generateSpanId`, `generateTraceId`, `NifraSpan`, `ObservationAdapter`, `ObservationClock`, `ObservationContext`, `ObservationLifecycle`, `ObservationLifecycleOptions`, `ObservationLink`, `ObservationParent`, `
|
|
7461
|
+
`ActiveObservation`, `AttributeValue`, `causalitySpanLink`, `combineObservationAdapters`, `consoleSpanExporter`, `createObservationLifecycle`, `effectTracing`, `EffectTracingOptions`, `EffectTracingPlugin`, `EndObservation`, `formatTraceparent`, `generateSpanId`, `generateTraceId`, `NifraSpan`, `ObservationAdapter`, `ObservationClock`, `ObservationContext`, `ObservationLifecycle`, `ObservationLifecycleOptions`, `ObservationLink`, `ObservationParent`, `otlpExporter`, `OtlpExporter`, `OtlpExporterOptions`, `ParsedTraceparent`, `parseTraceparent`, `SpanStatus`, `StartObservation`, `TraceContext`, `traceHeaders`, `tracing`, `TracingOptions`
|
|
6418
7462
|
|
|
6419
7463
|
## @nifrajs/prompt
|
|
6420
7464
|
|
|
@@ -6442,7 +7486,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
6442
7486
|
|
|
6443
7487
|
## @nifrajs/web
|
|
6444
7488
|
|
|
6445
|
-
`Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `BrowserNavigate`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createMutation`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `DehydratedState`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `
|
|
7489
|
+
`Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `BrowserNavigate`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createMutation`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `DehydratedState`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `getBrowserNavigate`, `GetStaticPaths`, `hashQueryKey`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaInput`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NavigateOptions`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderedPage`, `renderPage`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `routeStyles`, `ScriptDescriptor`, `serializeData`, `ServerOnly`, `setBrowserNavigate`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `styles`, `Submission`, `SubmitOptions`, `withISR`
|
|
6446
7490
|
|
|
6447
7491
|
## @nifrajs/web-preact
|
|
6448
7492
|
|