@nifrajs/cli 2.11.0 → 2.12.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 +6 -0
- package/dist/assure-hydration.d.ts +14 -0
- package/dist/assure-hydration.d.ts.map +1 -0
- package/dist/assure-hydration.js +483 -0
- package/dist/assure-hydration.js.map +1 -0
- package/dist/assure.d.ts +35 -0
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +222 -1
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +13 -0
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +64 -1
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check.d.ts +125 -10
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +706 -145
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +23 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +194 -10
- package/dist/cli.js.map +1 -1
- package/dist/contract-proof.d.ts +49 -0
- package/dist/contract-proof.d.ts.map +1 -0
- package/dist/contract-proof.js +128 -0
- package/dist/contract-proof.js.map +1 -0
- package/dist/contracts.d.ts +22 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +134 -0
- package/dist/contracts.js.map +1 -0
- package/dist/diagnostics.d.ts +23 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +9 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/diff-tool.d.ts +2 -0
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +1 -1
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts +8 -3
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +36 -15
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +28 -2
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +173 -7
- package/dist/doctor.js.map +1 -1
- package/dist/examples.d.ts +1 -2
- package/dist/examples.d.ts.map +1 -1
- package/dist/examples.js +37 -16
- package/dist/examples.js.map +1 -1
- package/dist/fix-recipes.d.ts +12 -0
- package/dist/fix-recipes.d.ts.map +1 -0
- package/dist/fix-recipes.js +106 -0
- package/dist/fix-recipes.js.map +1 -0
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +60 -4
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/source-facts.d.ts +15 -0
- package/dist/internal/source-facts.d.ts.map +1 -0
- package/dist/internal/source-facts.js +82 -0
- package/dist/internal/source-facts.js.map +1 -0
- package/dist/internal/typescript-import.d.ts +9 -0
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +38 -0
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -2
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +53 -4
- package/dist/introspect.js.map +1 -1
- package/dist/load.d.ts +6 -0
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +14 -9
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +3 -0
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-root.d.ts +83 -0
- package/dist/mcp-root.d.ts.map +1 -0
- package/dist/mcp-root.js +221 -0
- package/dist/mcp-root.js.map +1 -0
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +18 -4
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp.d.ts +27 -4
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +358 -25
- package/dist/mcp.js.map +1 -1
- package/dist/project-path.d.ts +3 -0
- package/dist/project-path.d.ts.map +1 -0
- package/dist/project-path.js +36 -0
- package/dist/project-path.js.map +1 -0
- package/dist/replay.d.ts +12 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +36 -0
- package/dist/replay.js.map +1 -0
- package/dist/rules/codes.d.ts +42 -0
- package/dist/rules/codes.d.ts.map +1 -0
- package/dist/rules/codes.js +52 -0
- package/dist/rules/codes.js.map +1 -0
- package/dist/rules/comment-markers.d.ts +10 -0
- package/dist/rules/comment-markers.d.ts.map +1 -0
- package/dist/rules/comment-markers.js +24 -0
- package/dist/rules/comment-markers.js.map +1 -0
- package/dist/rules/index.d.ts +31 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +133 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/legacy.d.ts +7 -0
- package/dist/rules/legacy.d.ts.map +1 -0
- package/dist/rules/legacy.js +85 -0
- package/dist/rules/legacy.js.map +1 -0
- package/dist/rules/routes.d.ts +12 -0
- package/dist/rules/routes.d.ts.map +1 -0
- package/dist/rules/routes.js +142 -0
- package/dist/rules/routes.js.map +1 -0
- package/dist/rules/security.d.ts +10 -0
- package/dist/rules/security.d.ts.map +1 -0
- package/dist/rules/security.js +471 -0
- package/dist/rules/security.js.map +1 -0
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +5 -1
- package/dist/search-terms.js.map +1 -1
- package/dist/types-search.d.ts +1 -2
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +33 -16
- package/dist/types-search.js.map +1 -1
- package/dist/verification.d.ts +9 -0
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +17 -1
- package/dist/verification.js.map +1 -1
- package/dist/work-graph.d.ts +123 -0
- package/dist/work-graph.d.ts.map +1 -0
- package/dist/work-graph.js +465 -0
- package/dist/work-graph.js.map +1 -0
- package/docs/examples.json +32 -2
- package/docs/llms-full.txt +906 -15
- package/docs/types.json +1682 -130
- package/package.json +34 -9
- package/src/assure-hydration.ts +606 -0
- package/src/assure.ts +316 -2
- package/src/capabilities-tool.ts +80 -1
- package/src/check.ts +873 -168
- package/src/cli.ts +198 -11
- package/src/contract-proof.ts +194 -0
- package/src/contracts.ts +157 -0
- package/src/diagnostics.ts +32 -0
- package/src/diff-tool.ts +1 -1
- package/src/docs-search.ts +45 -15
- package/src/doctor.ts +259 -8
- package/src/examples.ts +43 -15
- package/src/fix-recipes.ts +126 -0
- package/src/init-agents.ts +61 -4
- package/src/internal/source-facts.ts +133 -0
- package/src/internal/typescript-import.ts +29 -0
- package/src/introspect.ts +75 -6
- package/src/load.ts +6 -0
- package/src/manifest-tool.ts +16 -10
- package/src/mcp-docs-tools.ts +3 -0
- package/src/mcp-http.ts +1 -1
- package/src/mcp-root.ts +271 -0
- package/src/mcp-run.ts +19 -4
- package/src/mcp.ts +438 -32
- package/src/project-path.ts +36 -0
- package/src/replay.ts +44 -0
- package/src/rules/codes.ts +57 -0
- package/src/rules/comment-markers.ts +26 -0
- package/src/rules/index.ts +175 -0
- package/src/rules/legacy.ts +93 -0
- package/src/rules/routes.ts +157 -0
- package/src/rules/security.ts +505 -0
- package/src/search-terms.ts +9 -4
- package/src/types-search.ts +39 -15
- package/src/verification.ts +18 -1
- package/src/work-graph.ts +613 -0
package/docs/llms-full.txt
CHANGED
|
@@ -596,6 +596,23 @@ await api.search.get({ query: { page: "3" } }) // query string
|
|
|
596
596
|
await api.users({ id: "1" }).posts({ postId: "2" }).get() // nested params
|
|
597
597
|
```
|
|
598
598
|
|
|
599
|
+
**Reserved proxy keys.** The client proxy resolves a fixed set of property names
|
|
600
|
+
_before_ path segments: the seven HTTP verbs
|
|
601
|
+
(`get`/`post`/`put`/`patch`/`delete`/
|
|
602
|
+
`head`/`options`, any casing) call the route, and
|
|
603
|
+
`subscribe`, `ws`, `index`, and `then` (exact
|
|
604
|
+
match) are the SSE, WebSocket, root-path, and thenable-guard keys. A route whose path
|
|
605
|
+
contains a static segment spelling one of these - `.post("/api/delete", …)` -
|
|
606
|
+
cannot be reached by **dot access**: `api.delete.post` resolves the
|
|
607
|
+
`delete` verb, not the segment. The typed spelling is a **call on the parent
|
|
608
|
+
node** - `api.api("delete").post()` sends `POST /api/delete` - the
|
|
609
|
+
same call params use, accepting exactly the colliding segment names. The client type rejects
|
|
610
|
+
the dot access at compile time with that guidance, and `nifra check` reports the
|
|
611
|
+
collision (`NF-C018`, advisory). Prefer a verb-free segment
|
|
612
|
+
(e.g. `/api/remove`) when you control the path; mark a route served only to
|
|
613
|
+
non-typed-client consumers with `// nifra-expect reserved-segment` above its
|
|
614
|
+
registration.
|
|
615
|
+
|
|
599
616
|
### Results never throw
|
|
600
617
|
|
|
601
618
|
Every call resolves to a discriminated `Result`: branch on `ok` (or
|
|
@@ -1743,6 +1760,12 @@ does custom, typed verification.
|
|
|
1743
1760
|
`cacheControl()` sets `Cache-Control` on matching responses without
|
|
1744
1761
|
clobbering one a handler already set.
|
|
1745
1762
|
|
|
1763
|
+
**Security note (BREACH):** any HTTP compression makes the response size depend on its
|
|
1764
|
+
content, so a response that mixes a secret with attacker-reflected request input can leak
|
|
1765
|
+
the secret through the compressed length. Nifra's CSRF tokens are HMAC-signed per session
|
|
1766
|
+
and safe to compress, but if a route reflects request input alongside a per-request secret,
|
|
1767
|
+
exclude it via the `compressible` option or route scoping.
|
|
1768
|
+
|
|
1746
1769
|
```ts
|
|
1747
1770
|
import { server } from "@nifrajs/core/server"
|
|
1748
1771
|
import { compression, cacheControl } from "@nifrajs/middleware"
|
|
@@ -1894,12 +1917,13 @@ createWebApp({
|
|
|
1894
1917
|
|
|
1895
1918
|
## Nifra - security & hardening
|
|
1896
1919
|
|
|
1897
|
-
> Bounded request bodies, magic-byte file-upload validation, constant-time webhook verification, and idempotency-key replay - the hardening primitives every production app needs, built in.
|
|
1920
|
+
> Bounded request bodies, JSON prototype-poisoning defense, magic-byte file-upload validation, constant-time webhook verification, security response headers, and idempotency-key replay - the hardening primitives every production app needs, built in.
|
|
1898
1921
|
|
|
1899
1922
|
The pieces every production endpoint needs - a body-size cap for raw routes, real file-type
|
|
1900
1923
|
validation, constant-time webhook verification, and idempotent retries - ship as first-party
|
|
1901
1924
|
primitives. All are **edge-safe** (WebCrypto, no `node:crypto`) and run unchanged
|
|
1902
|
-
on Bun, Node, Deno, and Workers.
|
|
1925
|
+
on Bun, Node, Deno, and Workers. For how these defaults stack up against Hono, Fastify,
|
|
1926
|
+
Express, and Elysia, see [security posture, compared](/docs/security-comparison).
|
|
1903
1927
|
|
|
1904
1928
|
### Responses that cannot leak more than they declare
|
|
1905
1929
|
|
|
@@ -1992,6 +2016,65 @@ bad JSON a `400` - thrown as control-flow `Response`s the lifecycle catches,
|
|
|
1992
2016
|
so the cap can't be silently skipped. Pass a larger `maxBytes` for an upload route, a
|
|
1993
2017
|
smaller one to tighten an endpoint.
|
|
1994
2018
|
|
|
2019
|
+
The cap is enforced on the bytes actually **delivered**, never on the number the caller wrote in
|
|
2020
|
+
`Content-Length`. That distinction is the whole defense: an adapter that rebuilds a
|
|
2021
|
+
request from an event envelope, or any code assembling a `Request` by hand, can carry a
|
|
2022
|
+
header claiming 5 bytes over a payload of five megabytes. A runtime HTTP parser cannot lie that way,
|
|
2023
|
+
because it framed the body at the declared length itself, so nifra's ingress adapters mark their
|
|
2024
|
+
requests as runtime-framed and skip the recount entirely:
|
|
2025
|
+
`listen()` on Bun, `toFetchHandler(app)` on Workers and edge,
|
|
2026
|
+
`@nifrajs/deno`, and `@nifrajs/node` (which reads exact byte counts in the
|
|
2027
|
+
first place). Nothing to configure on any of them.
|
|
2028
|
+
|
|
2029
|
+
Only a request arriving through a path nifra ships no adapter for pays the extra pass over the body.
|
|
2030
|
+
If you serve an edge runtime by exporting the app directly rather than through
|
|
2031
|
+
`toFetchHandler`, `server({ trustBodyFraming: true })` asserts that every
|
|
2032
|
+
`app.fetch` request came from the platform's own parser. It is an assertion about
|
|
2033
|
+
deployment topology, so do not set it on an app whose `fetch` is also called with
|
|
2034
|
+
requests built from untrusted input. It trusts the _frame_, never the cap: an over-cap declared
|
|
2035
|
+
length is still a `413`, and a body with no `Content-Length` still goes
|
|
2036
|
+
through the streaming guard.
|
|
2037
|
+
|
|
2038
|
+
### Prototype-poisoning defense
|
|
2039
|
+
|
|
2040
|
+
Every JSON body nifra parses - the schema-validated route and `c.boundedJson`
|
|
2041
|
+
alike - is screened for the prototype-pollution shape: an own `__proto__` key, or
|
|
2042
|
+
a `constructor` whose value carries a `prototype`. `JSON.parse`
|
|
2043
|
+
creates these as ordinary data properties, and the damage lands later, when innocent code does
|
|
2044
|
+
`{ ...body }`, `Object.assign(target, body)`, or a deep merge and
|
|
2045
|
+
walks that key onto a real prototype. The screen is on by default.
|
|
2046
|
+
|
|
2047
|
+
```ts
|
|
2048
|
+
import { server } from "@nifrajs/core/server"
|
|
2049
|
+
|
|
2050
|
+
// Default is "reject". A JSON body with an own `__proto__` key - or a `constructor`
|
|
2051
|
+
// carrying a `prototype` - is the exact shape that turns a later innocent { ...body }
|
|
2052
|
+
// merge or Object.assign into prototype pollution. It answers the SAME flat 400 as
|
|
2053
|
+
// malformed JSON, so an attacker learns nothing from the response.
|
|
2054
|
+
const app = server({ protoPoisoning: "reject" }) // "strip" | "ignore" also available
|
|
2055
|
+
|
|
2056
|
+
app.post("/profile", async (c) => {
|
|
2057
|
+
const body = await c.boundedJson<{ name: string }>() // and the schema-route path
|
|
2058
|
+
// Reached only for a clean payload. Under "strip" the offending keys are deleted and
|
|
2059
|
+
// the handler sees the cleaned object; under "ignore" the body passes through as-is.
|
|
2060
|
+
return { name: body.name }
|
|
2061
|
+
})
|
|
2062
|
+
```
|
|
2063
|
+
|
|
2064
|
+
`protoPoisoning` is a server option with three settings.
|
|
2065
|
+
` "reject"` (the default) answers the same flat `400` as
|
|
2066
|
+
malformed JSON - indistinguishable on the wire, so a probe learns nothing.
|
|
2067
|
+
`"strip"` deletes the offending keys and hands the handler the cleaned
|
|
2068
|
+
value, siblings intact. `"ignore"` parses as-is, for a route you are sure
|
|
2069
|
+
never merges body input into another object. A string _value_ of `"__proto__"`
|
|
2070
|
+
is legal data and never triggers - only an own key of that name does.
|
|
2071
|
+
|
|
2072
|
+
The check is sound against escape smuggling: a `__proto__`-spelled key
|
|
2073
|
+
parses to the same own property, so it is caught the same way. And it is cheap on the common
|
|
2074
|
+
path - a clean body pays a substring pre-scan only; the deep walk runs solely when the raw text
|
|
2075
|
+
actually contains a suspect token, so an honest payload is never charged for the tree it does
|
|
2076
|
+
not have.
|
|
2077
|
+
|
|
1995
2078
|
### File uploads - `@nifrajs/uploads`
|
|
1996
2079
|
|
|
1997
2080
|
A dependency-free package for the upload-hardening basics. `validateUpload` enforces a
|
|
@@ -2171,6 +2254,37 @@ trustworthy client IP can be derived, and never trusts `X-Forwarded-For` unless
|
|
|
2171
2254
|
bodies before routing (fails closed with `411` on a length-less body). The read-time
|
|
2172
2255
|
guard above (`c.boundedBody` / schema cap) remains the source of truth.
|
|
2173
2256
|
|
|
2257
|
+
### Security response headers - `securityHeaders`
|
|
2258
|
+
|
|
2259
|
+
`securityHeaders()` sets a safe-by-default response header set on every response,
|
|
2260
|
+
errors and 404s included. Three are always on - `X-Content-Type-Options: nosniff`,
|
|
2261
|
+
`X-Frame-Options: DENY`, and `Referrer-Policy: no-referrer`. The
|
|
2262
|
+
cross-origin isolation headers (`COOP`/`COEP`/`CORP`),
|
|
2263
|
+
`Permissions-Policy`, `Content-Security-Policy`, and `HSTS` are
|
|
2264
|
+
opt-in, because each one can break a working app (embedding, popups, HTTP) and so is a
|
|
2265
|
+
deliberate choice, never a silent default.
|
|
2266
|
+
|
|
2267
|
+
```ts
|
|
2268
|
+
import { server } from "@nifrajs/core/server"
|
|
2269
|
+
import { securityHeaders } from "@nifrajs/middleware"
|
|
2270
|
+
|
|
2271
|
+
// Always on (covering errors and 404s too): X-Content-Type-Options: nosniff,
|
|
2272
|
+
// X-Frame-Options: DENY, Referrer-Policy: no-referrer. The rest are opt-in - each is a
|
|
2273
|
+
// deliberate cross-origin decision, so nifra never turns them on behind your back:
|
|
2274
|
+
const app = server().use(securityHeaders({
|
|
2275
|
+
crossOriginOpenerPolicy: "same-origin", // isolate the browsing-context group
|
|
2276
|
+
crossOriginResourcePolicy: "same-origin", // block cross-origin embedding of your responses
|
|
2277
|
+
crossOriginEmbedderPolicy: "require-corp", // enable crossOriginIsolated (SharedArrayBuffer, …)
|
|
2278
|
+
permissionsPolicy: "camera=(), geolocation=()",
|
|
2279
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
2280
|
+
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true }, // opt in once HTTPS-only
|
|
2281
|
+
}))
|
|
2282
|
+
```
|
|
2283
|
+
|
|
2284
|
+
Every value is fixed at construction, so the headers are declared statically rather than
|
|
2285
|
+
written by a response hook - an app whose response middleware is only this keeps the fused
|
|
2286
|
+
native response lanes. A route that sets one of these names itself keeps its own value.
|
|
2287
|
+
|
|
2174
2288
|
### Route assurance - prove every route is guarded
|
|
2175
2289
|
|
|
2176
2290
|
Installing security middleware is not the same as proving it covers every route. Nifra's
|
|
@@ -2313,8 +2427,11 @@ const sagas = createSagaEngine({
|
|
|
2313
2427
|
### Already built in
|
|
2314
2428
|
|
|
2315
2429
|
These add to Nifra's standing defaults: strict-by-default schema validation (unknown fields
|
|
2316
|
-
rejected), SSR serialization that escapes every inline-script value,
|
|
2317
|
-
|
|
2430
|
+
rejected), SSR serialization that escapes every inline-script value,
|
|
2431
|
+
`__Host-`/`__Secure-` cookie-prefix enforcement (a cookie whose
|
|
2432
|
+
attributes violate its prefix contract fails at serialization rather than being silently
|
|
2433
|
+
dropped by the browser), signed-cookie sessions + CSRF + route guards
|
|
2434
|
+
([@nifrajs/auth](/docs/auth)), bearer/apiKey auth + a shared-store rate
|
|
2318
2435
|
limiter ([@nifrajs/middleware](/docs/plugins)), and a hardened image-resize endpoint
|
|
2319
2436
|
([@nifrajs/image/server](/docs/images)).
|
|
2320
2437
|
|
|
@@ -3004,10 +3121,13 @@ you bring JSON catalogs.
|
|
|
3004
3121
|
|
|
3005
3122
|
### Negotiate the locale
|
|
3006
3123
|
|
|
3007
|
-
`negotiateLocale` picks the best supported locale from a
|
|
3008
|
-
then
|
|
3009
|
-
|
|
3010
|
-
|
|
3124
|
+
`negotiateLocale` picks the best supported locale from a `?lang=` query
|
|
3125
|
+
parameter (an explicit ask), then a cookie (a remembered choice), then
|
|
3126
|
+
` Accept-Language` (quality-ranked, with `fr-CA`→`fr`
|
|
3127
|
+
base-subtag fallback), else your default. The answer is always drawn from your
|
|
3128
|
+
` locales` allow-list - request input is matched, never echoed - so a hostile
|
|
3129
|
+
` ?lang=` can't reach the response. Resolve it in a loader and return just that
|
|
3130
|
+
locale's messages.
|
|
3011
3131
|
|
|
3012
3132
|
```ts
|
|
3013
3133
|
// In a loader: resolve the locale + return only that catalog's messages.
|
|
@@ -3015,12 +3135,34 @@ import { negotiateLocale } from "@nifrajs/i18n"
|
|
|
3015
3135
|
import { catalogs, locales } from "../catalogs"
|
|
3016
3136
|
|
|
3017
3137
|
export async function loader({ request }: { request: Request }) {
|
|
3018
|
-
const
|
|
3019
|
-
|
|
3020
|
-
return { locale, messages: catalogs[locale] } // cookie → Accept-Language → default
|
|
3138
|
+
const locale = negotiateLocale(request, { locales, defaultLocale: "en", queryParam: "lang", cookie: "lang" })
|
|
3139
|
+
return { locale, messages: catalogs[locale] } // ?lang= → cookie → Accept-Language → default
|
|
3021
3140
|
}
|
|
3022
3141
|
```
|
|
3023
3142
|
|
|
3143
|
+
On the server, `localeDetector()` (from `@nifrajs/i18n/detector`, needs
|
|
3144
|
+
` @nifrajs/core`) wraps the same negotiation as a plugin: handlers read
|
|
3145
|
+
` c.locale`/`c.localeSource`, responses carry
|
|
3146
|
+
` Content-Language`. With `persist: true` it writes the locale cookie
|
|
3147
|
+
** only** when an explicit `?lang=` choice differs from the cookie - a
|
|
3148
|
+
header-derived guess is never pinned, and plain requests never grow a
|
|
3149
|
+
`Set-Cookie`, so responses stay cacheable. (`@nifrajs/middleware`'s
|
|
3150
|
+
` language()` is the header-only sibling; use one or the other.)
|
|
3151
|
+
|
|
3152
|
+
```ts
|
|
3153
|
+
// Or as a server plugin: c.locale on every handler, Content-Language on every response.
|
|
3154
|
+
import { server } from "@nifrajs/core"
|
|
3155
|
+
import { localeDetector } from "@nifrajs/i18n/detector"
|
|
3156
|
+
|
|
3157
|
+
const app = server().use(localeDetector({
|
|
3158
|
+
locales: ["en", "fr", "de"],
|
|
3159
|
+
defaultLocale: "en",
|
|
3160
|
+
queryParam: "lang",
|
|
3161
|
+
cookie: "locale",
|
|
3162
|
+
persist: true, // pin an explicit ?lang= choice into the cookie
|
|
3163
|
+
})).get("/", (c) => c.json({ locale: c.locale, via: c.localeSource }))
|
|
3164
|
+
```
|
|
3165
|
+
|
|
3024
3166
|
### Format messages
|
|
3025
3167
|
|
|
3026
3168
|
`createFormatter(locale, messages)` → `{ t, n, d }`. `t`
|
|
@@ -3108,6 +3250,14 @@ export default { fetch: (req: Request, env: Env, ctx: ExecutionContext) =>
|
|
|
3108
3250
|
app.fetch(req, { env, waitUntil: ctx.waitUntil.bind(ctx) }) }
|
|
3109
3251
|
```
|
|
3110
3252
|
|
|
3253
|
+
Export through `toFetchHandler` rather than handing the app out directly. Besides
|
|
3254
|
+
`env` and `ctx.waitUntil`, it tells nifra the request came from the
|
|
3255
|
+
platform's own HTTP parser, which lets the
|
|
3256
|
+
[bounded-body cap](/docs/security) trust the declared
|
|
3257
|
+
`Content-Length` as a real transport frame instead of re-counting every JSON body.
|
|
3258
|
+
A bare `export default app` has no such signal and pays the extra pass unless you
|
|
3259
|
+
set `server({ trustBodyFraming: true })`.
|
|
3260
|
+
|
|
3111
3261
|
### Background work
|
|
3112
3262
|
|
|
3113
3263
|
`c.waitUntil(promise)` keeps work alive after the response is sent - cache writes,
|
|
@@ -3511,6 +3661,30 @@ content-typed, with an immutable cache - before the app runs, leaving the SSR fa
|
|
|
3511
3661
|
untouched: `serve(app, {'{ port, static: { dir: new URL("./assets/", import.meta.url) } }'})`.
|
|
3512
3662
|
On Cloudflare/Vercel the platform serves assets, so you don't need it there.
|
|
3513
3663
|
|
|
3664
|
+
### AWS Lambda
|
|
3665
|
+
|
|
3666
|
+
`@nifrajs/aws-lambda` - API Gateway HTTP APIs (payload v2) and Lambda Function
|
|
3667
|
+
URLs. `handle(app)` is the buffered handler; `streamHandle(app)`
|
|
3668
|
+
streams responses on Function URLs with `InvokeMode: RESPONSE_STREAM`. Build the
|
|
3669
|
+
app at module scope (once per container). The adapter decodes the body and checks its real
|
|
3670
|
+
size before a `Request` exists, assembles headers + the v2 `cookies`
|
|
3671
|
+
array in one place, decides response base64 by what the bytes actually are, and feeds
|
|
3672
|
+
`sourceIp` into `c.clientIp`. `waitUntil` work settles
|
|
3673
|
+
before the container freezes. REST APIs (payload v1) and ALB are not supported.
|
|
3674
|
+
|
|
3675
|
+
```ts
|
|
3676
|
+
// handler.ts - one app per container, built at module scope
|
|
3677
|
+
import { handle, streamHandle, type LambdaEnv } from "@nifrajs/aws-lambda"
|
|
3678
|
+
import { server } from "@nifrajs/core"
|
|
3679
|
+
|
|
3680
|
+
const app = server<LambdaEnv>()
|
|
3681
|
+
.get("/", (c) => c.json({ ip: c.clientIp, requestId: c.env.context?.awsRequestId }))
|
|
3682
|
+
|
|
3683
|
+
export const handler = handle(app) // API Gateway HTTP API (v2) + Function URLs
|
|
3684
|
+
// or, on a Function URL with InvokeMode: RESPONSE_STREAM:
|
|
3685
|
+
// export const handler = streamHandle(app)
|
|
3686
|
+
```
|
|
3687
|
+
|
|
3514
3688
|
### Cloudflare Workers / Pages (the edge)
|
|
3515
3689
|
|
|
3516
3690
|
`buildServer` bundles with edge conditions (`workerd`,
|
|
@@ -5557,7 +5731,9 @@ const app = server().merge(createBillingRouter({ charge: async (plan) => ({ id:
|
|
|
5557
5731
|
// 4. Build the client from the EXPORTED contract value - decoupled, no `typeof app` import needed.
|
|
5558
5732
|
const api = client(billingContract, "https://api.example.com")
|
|
5559
5733
|
const { data } = await api.billing.plan.get() // GET /billing/plan
|
|
5560
|
-
|
|
5734
|
+
// "subscribe" is a reserved client word (the SSE subscriber), so this segment is reached by
|
|
5735
|
+
// CALLING the parent node with the segment name - same request, same typed body.
|
|
5736
|
+
await api.billing("subscribe").post({ plan: "pro" }) // POST /billing/subscribe, body typed
|
|
5561
5737
|
```
|
|
5562
5738
|
|
|
5563
5739
|
Why this survives `.d.ts` emit where the fluent chain doesn't: the contract is a
|
|
@@ -6455,6 +6631,488 @@ backend you can also ship on its own. Start with [Getting started](/docs).
|
|
|
6455
6631
|
Moving a backend (Express, Hono, Fastify, Elysia) instead? See
|
|
6456
6632
|
[Migrating a backend](/docs/migrate-backend).
|
|
6457
6633
|
|
|
6634
|
+
## Nifra - Migrate route by route
|
|
6635
|
+
|
|
6636
|
+
> Keep an existing Hono, Elysia, Express, or Workers app running while moving routes into typed Nifra handlers one at a time.
|
|
6637
|
+
|
|
6638
|
+
Keep the old app live while you move its surface into Nifra. Mount the legacy fetch handler at
|
|
6639
|
+
a prefix, add typed Nifra routes as they are ready, then delete the mount when the prefix is
|
|
6640
|
+
empty.
|
|
6641
|
+
|
|
6642
|
+
[!WARNING] Mounted handlers are an escape hatch. Their request and response shapes are outside
|
|
6643
|
+
Nifra's typed route contract, response schemas, and response-contract enforcement. Keep the
|
|
6644
|
+
mount small and temporary, and use typed routes for every migrated endpoint.
|
|
6645
|
+
|
|
6646
|
+
### 1. Mount the existing app
|
|
6647
|
+
|
|
6648
|
+
`mountFetch` accepts any handler with the Web fetch shape. Use a path ending in
|
|
6649
|
+
`/*` for a subtree. By default the legacy app sees the original URL. Set
|
|
6650
|
+
`stripPrefix: true` when the legacy app declares paths relative to its mount point.
|
|
6651
|
+
The platform object is forwarded as the second argument when the runtime supplies one.
|
|
6652
|
+
|
|
6653
|
+
```ts
|
|
6654
|
+
import { Hono } from "hono"
|
|
6655
|
+
import { server } from "@nifrajs/core/server"
|
|
6656
|
+
import { t } from "@nifrajs/schema"
|
|
6657
|
+
|
|
6658
|
+
const legacy = new Hono()
|
|
6659
|
+
legacy.get("/users", (c) => c.json({ source: "hono", users: [] }))
|
|
6660
|
+
legacy.post("/users", async (c) => c.json(await c.req.json()))
|
|
6661
|
+
|
|
6662
|
+
export const app = server()
|
|
6663
|
+
// The wildcard is a literal mount prefix, not a typed Nifra route.
|
|
6664
|
+
.mountFetch("/legacy/*", legacy.fetch, { stripPrefix: true })
|
|
6665
|
+
// Typed routes take precedence, so move one route out of Hono at a time.
|
|
6666
|
+
.get("/legacy/users/:id", (c) => ({ id: c.params.id, source: "nifra" }))
|
|
6667
|
+
.post(
|
|
6668
|
+
"/users",
|
|
6669
|
+
{ body: t.object({ name: t.string() }) },
|
|
6670
|
+
(c) => ({ id: crypto.randomUUID(), name: c.body.name }),
|
|
6671
|
+
)
|
|
6672
|
+
```
|
|
6673
|
+
|
|
6674
|
+
### 2. Move the routes
|
|
6675
|
+
|
|
6676
|
+
- Choose one legacy endpoint and add it as a typed Nifra route.
|
|
6677
|
+
|
|
6678
|
+
- Keep the mount in place for the remaining endpoints.
|
|
6679
|
+
|
|
6680
|
+
- Add a body, query, or params schema so the trust boundary is explicit.
|
|
6681
|
+
|
|
6682
|
+
- Run `nifra check` and the route tests, then repeat.
|
|
6683
|
+
|
|
6684
|
+
Typed routes win when both surfaces match. That lets a new Nifra route replace its legacy
|
|
6685
|
+
counterpart without changing the mount prefix or adding a second server.
|
|
6686
|
+
|
|
6687
|
+
### 3. Remove the escape hatch
|
|
6688
|
+
|
|
6689
|
+
When the legacy prefix has no remaining routes, remove `mountFetch` and delete the
|
|
6690
|
+
old app. The remaining routes now have typed params, validated inputs, reflected contracts,
|
|
6691
|
+
and the end-to-end typed client.
|
|
6692
|
+
|
|
6693
|
+
### Other legacy handlers
|
|
6694
|
+
|
|
6695
|
+
The same seam works with Elysia, an Express app exposed through an adapter, or a raw Workers
|
|
6696
|
+
handler. Pass its fetch-compatible function directly. For a Workers-style handler, the
|
|
6697
|
+
platform argument carries bindings and `waitUntil`; mounted code remains responsible
|
|
6698
|
+
for its own framework-specific request and response conventions.
|
|
6699
|
+
|
|
6700
|
+
## Nifra security posture vs Hono, Fastify, Express, Elysia
|
|
6701
|
+
|
|
6702
|
+
> A dimension-by-dimension security comparison of Nifra against the current releases of Hono, Fastify, Express, and Elysia - which hardening each framework ships by default, which is opt-in, and which is left to third-party packages.
|
|
6703
|
+
|
|
6704
|
+
Which hardening each framework actually ships. A dimension-by-dimension read of Nifra’s
|
|
6705
|
+
security defaults against the **current releases** of Hono, Fastify, Express, and Elysia -
|
|
6706
|
+
what is on by default, what is opt-in, and what is left to a third-party package. For how each
|
|
6707
|
+
Nifra primitive works, see [Security & hardening](/docs/security).
|
|
6708
|
+
|
|
6709
|
+
### The honest verdict
|
|
6710
|
+
|
|
6711
|
+
Nifra is **at or above parity on every dimension where a comparison is meaningful**, and
|
|
6712
|
+
ahead of all four on several:
|
|
6713
|
+
|
|
6714
|
+
-
|
|
6715
|
+
**Ahead of all four:** CORS misconfiguration resistance, redirect safety, WebSocket
|
|
6716
|
+
origin policy, rate-limiter safe-by-construction design, route-level security evidence
|
|
6717
|
+
(`nifra assure`), response over-exposure enforcement, upload content
|
|
6718
|
+
verification, env-independent error responses, client-IP trust model.
|
|
6719
|
+
|
|
6720
|
+
-
|
|
6721
|
+
**Par with the best-in-class:** body limits (Fastify class), JSON prototype-poisoning
|
|
6722
|
+
rejection (par Fastify, default-on), schema validation (par Fastify/Elysia), CSRF (par
|
|
6723
|
+
Fastify), security headers (par Hono, first-party), cookie prefixes (par Hono), sessions,
|
|
6724
|
+
JWT hardening, static serving, supply chain (par Hono’s zero-dependency core).
|
|
6725
|
+
|
|
6726
|
+
**One caveat no capability table removes.** Hono, Fastify, and Express have orders of
|
|
6727
|
+
magnitude more production exposure and more external researchers looking at them. Nifra’s
|
|
6728
|
+
protections are implementation-verified and unit-tested, but have not had that volume of
|
|
6729
|
+
adversarial attention or a third-party audit. Ongoing scrutiny is the part only time buys.
|
|
6730
|
+
|
|
6731
|
+
### How this was measured
|
|
6732
|
+
|
|
6733
|
+
Nifra side: an implementation-level read of every security-relevant module across
|
|
6734
|
+
`core`, `middleware`, `auth`, `node`,
|
|
6735
|
+
`web`, `uploads`, and `cli` - defaults and failure modes, not
|
|
6736
|
+
marketing claims. Competitor side: current official documentation for defaults, verified as of
|
|
6737
|
+
August 2026 against Hono 4.12.x, Fastify 5.x, Express 5.1 / 4.21.x, and Elysia 1.4.x. The
|
|
6738
|
+
comparison unit is what a developer gets **by default** and what the framework makes
|
|
6739
|
+
**impossible to get wrong**, not what a maximally careful expert could configure - because
|
|
6740
|
+
most real-world security incidents are misconfiguration-by-default.
|
|
6741
|
+
|
|
6742
|
+
### Summary matrix
|
|
6743
|
+
|
|
6744
|
+
Legend: **B** built-in and on by default, **b** built-in opt-in, **O** official
|
|
6745
|
+
add-on package, **3** third-party / community, **-** absent. The verdict column is Nifra
|
|
6746
|
+
relative to the field.
|
|
6747
|
+
|
|
6748
|
+
Dimension
|
|
6749
|
+
Nifra
|
|
6750
|
+
Hono
|
|
6751
|
+
Fastify
|
|
6752
|
+
Express
|
|
6753
|
+
Elysia
|
|
6754
|
+
Verdict
|
|
6755
|
+
|
|
6756
|
+
Default body-size cap
|
|
6757
|
+
B 1 MB
|
|
6758
|
+
- (opt-in mw)
|
|
6759
|
+
B 1 MiB
|
|
6760
|
+
- (needs body-parser)
|
|
6761
|
+
- (runtime cap only)
|
|
6762
|
+
par Fastify; ahead of the rest
|
|
6763
|
+
|
|
6764
|
+
Bounded streaming reads
|
|
6765
|
+
B
|
|
6766
|
+
b
|
|
6767
|
+
B
|
|
6768
|
+
O
|
|
6769
|
+
-
|
|
6770
|
+
par-plus
|
|
6771
|
+
|
|
6772
|
+
Body cap on delivered bytes (not the claimed length)
|
|
6773
|
+
B
|
|
6774
|
+
- (keys on Content-Length)
|
|
6775
|
+
b
|
|
6776
|
+
-
|
|
6777
|
+
-
|
|
6778
|
+
ahead
|
|
6779
|
+
|
|
6780
|
+
Proto-pollution-inert parsers (query / cookie / form / params)
|
|
6781
|
+
B null-proto
|
|
6782
|
+
B
|
|
6783
|
+
B
|
|
6784
|
+
B (5.x simple parser)
|
|
6785
|
+
B
|
|
6786
|
+
par
|
|
6787
|
+
|
|
6788
|
+
JSON proto-key rejection
|
|
6789
|
+
B (reject default)
|
|
6790
|
+
-
|
|
6791
|
+
B (proto + constructor)
|
|
6792
|
+
-
|
|
6793
|
+
b (schema-strip)
|
|
6794
|
+
par Fastify
|
|
6795
|
+
|
|
6796
|
+
Route ReDoS surface
|
|
6797
|
+
B none (strict grammar)
|
|
6798
|
+
B
|
|
6799
|
+
B (find-my-way)
|
|
6800
|
+
B (path-to-regexp)
|
|
6801
|
+
B
|
|
6802
|
+
par
|
|
6803
|
+
|
|
6804
|
+
Input schema validation
|
|
6805
|
+
B per-route Standard Schema
|
|
6806
|
+
b (validator mw)
|
|
6807
|
+
B ajv
|
|
6808
|
+
-
|
|
6809
|
+
B TypeBox
|
|
6810
|
+
par with best
|
|
6811
|
+
|
|
6812
|
+
Response over-exposure prevention
|
|
6813
|
+
B contract warn/enforce
|
|
6814
|
+
-
|
|
6815
|
+
b (serializer)
|
|
6816
|
+
-
|
|
6817
|
+
B normalize
|
|
6818
|
+
ahead (with Elysia)
|
|
6819
|
+
|
|
6820
|
+
Error responses leak-free by construction
|
|
6821
|
+
B env-independent flat 500
|
|
6822
|
+
B
|
|
6823
|
+
B (mostly)
|
|
6824
|
+
b (dev leaks stacks)
|
|
6825
|
+
B (mostly)
|
|
6826
|
+
ahead
|
|
6827
|
+
|
|
6828
|
+
CORS safe-by-construction
|
|
6829
|
+
B fail-loud ctor
|
|
6830
|
+
b (reflect-with-creds configurable)
|
|
6831
|
+
O
|
|
6832
|
+
O
|
|
6833
|
+
O
|
|
6834
|
+
ahead
|
|
6835
|
+
|
|
6836
|
+
CSRF
|
|
6837
|
+
B two layers (origin + signed double-submit)
|
|
6838
|
+
b (origin heuristic)
|
|
6839
|
+
O token-based
|
|
6840
|
+
3 (csurf deprecated)
|
|
6841
|
+
-
|
|
6842
|
+
ahead; par Fastify
|
|
6843
|
+
|
|
6844
|
+
Security headers (first-party)
|
|
6845
|
+
b (nosniff/XFO/RP on; COOP/COEP/CORP/PP/CSP/HSTS opt-in)
|
|
6846
|
+
b (broader default set once added)
|
|
6847
|
+
O helmet
|
|
6848
|
+
3 helmet
|
|
6849
|
+
3
|
|
6850
|
+
par Hono; ahead of the helmet-dependent ones
|
|
6851
|
+
|
|
6852
|
+
Cookie signing + safe serialization
|
|
6853
|
+
B
|
|
6854
|
+
b
|
|
6855
|
+
O
|
|
6856
|
+
O
|
|
6857
|
+
B
|
|
6858
|
+
par-plus
|
|
6859
|
+
|
|
6860
|
+
`__Host-`/`__Secure-` prefix enforcement
|
|
6861
|
+
|
|
6862
|
+
B (throws on violation)
|
|
6863
|
+
B (throws on violation)
|
|
6864
|
+
O
|
|
6865
|
+
O
|
|
6866
|
+
-
|
|
6867
|
+
par Hono
|
|
6868
|
+
|
|
6869
|
+
Sessions (HttpOnly, SameSite, fixation, fail-closed)
|
|
6870
|
+
B
|
|
6871
|
+
-
|
|
6872
|
+
O
|
|
6873
|
+
3 (express-session)
|
|
6874
|
+
3
|
|
6875
|
+
ahead
|
|
6876
|
+
|
|
6877
|
+
JWT hardening (alg allowlist, none-reject, exp required)
|
|
6878
|
+
B
|
|
6879
|
+
b (no allowlist, exp optional)
|
|
6880
|
+
O (fast-jwt)
|
|
6881
|
+
3
|
|
6882
|
+
O (jose)
|
|
6883
|
+
par-plus
|
|
6884
|
+
|
|
6885
|
+
Rate limiting
|
|
6886
|
+
B forced-decision ctor
|
|
6887
|
+
3
|
|
6888
|
+
O
|
|
6889
|
+
3
|
|
6890
|
+
3
|
|
6891
|
+
ahead
|
|
6892
|
+
|
|
6893
|
+
Client IP / proxy trust
|
|
6894
|
+
B explicit, fail-closed
|
|
6895
|
+
b (per-runtime helpers)
|
|
6896
|
+
B (trustProxy)
|
|
6897
|
+
B (manual trust proxy)
|
|
6898
|
+
-
|
|
6899
|
+
ahead
|
|
6900
|
+
|
|
6901
|
+
Reverse-proxy hop-by-hop hygiene
|
|
6902
|
+
B (strips both directions)
|
|
6903
|
+
b (proxy helper)
|
|
6904
|
+
O (http-proxy)
|
|
6905
|
+
3
|
|
6906
|
+
-
|
|
6907
|
+
ahead
|
|
6908
|
+
|
|
6909
|
+
Load shedding / overload
|
|
6910
|
+
B admission (in-flight + loop lag)
|
|
6911
|
+
-
|
|
6912
|
+
O under-pressure
|
|
6913
|
+
-
|
|
6914
|
+
-
|
|
6915
|
+
ahead; par Fastify
|
|
6916
|
+
|
|
6917
|
+
Static serving traversal defense
|
|
6918
|
+
B 7-layer + dotfile deny
|
|
6919
|
+
3
|
|
6920
|
+
O (send-based)
|
|
6921
|
+
O (send-based)
|
|
6922
|
+
O
|
|
6923
|
+
ahead
|
|
6924
|
+
|
|
6925
|
+
Redirect safety (open-redirect + CRLF)
|
|
6926
|
+
B same-origin default
|
|
6927
|
+
-
|
|
6928
|
+
-
|
|
6929
|
+
-
|
|
6930
|
+
-
|
|
6931
|
+
ahead (unique)
|
|
6932
|
+
|
|
6933
|
+
WebSocket origin check
|
|
6934
|
+
B same-origin default
|
|
6935
|
+
-
|
|
6936
|
+
-
|
|
6937
|
+
-
|
|
6938
|
+
-
|
|
6939
|
+
ahead (unique)
|
|
6940
|
+
|
|
6941
|
+
Upload content verification (magic bytes, EXIF strip)
|
|
6942
|
+
B package
|
|
6943
|
+
-
|
|
6944
|
+
O limits only
|
|
6945
|
+
3 (multer)
|
|
6946
|
+
b (t.File type/size)
|
|
6947
|
+
ahead
|
|
6948
|
+
|
|
6949
|
+
Constant-time webhook verification
|
|
6950
|
+
B (multi-key rotation)
|
|
6951
|
+
-
|
|
6952
|
+
-
|
|
6953
|
+
-
|
|
6954
|
+
-
|
|
6955
|
+
ahead (unique)
|
|
6956
|
+
|
|
6957
|
+
Idempotency-key replay
|
|
6958
|
+
B (atomic-claim store)
|
|
6959
|
+
-
|
|
6960
|
+
-
|
|
6961
|
+
-
|
|
6962
|
+
-
|
|
6963
|
+
ahead (unique)
|
|
6964
|
+
|
|
6965
|
+
Supply chain (runtime deps in core)
|
|
6966
|
+
0
|
|
6967
|
+
0
|
|
6968
|
+
~dozen
|
|
6969
|
+
many
|
|
6970
|
+
few
|
|
6971
|
+
par with Hono
|
|
6972
|
+
|
|
6973
|
+
Machine-checkable route security evidence
|
|
6974
|
+
|
|
6975
|
+
B (`nifra assure`)
|
|
6976
|
+
|
|
6977
|
+
-
|
|
6978
|
+
-
|
|
6979
|
+
-
|
|
6980
|
+
-
|
|
6981
|
+
unique
|
|
6982
|
+
|
|
6983
|
+
Security lint (fail-open, non-constant-time, PII logs)
|
|
6984
|
+
B (NF-S001..007)
|
|
6985
|
+
-
|
|
6986
|
+
-
|
|
6987
|
+
-
|
|
6988
|
+
-
|
|
6989
|
+
unique
|
|
6990
|
+
|
|
6991
|
+
### Dimension detail
|
|
6992
|
+
|
|
6993
|
+
#### Body limits and bounded reads
|
|
6994
|
+
|
|
6995
|
+
Nifra applies a 1 MB default cap to the schema lane and to
|
|
6996
|
+
`c.boundedBody`/`c.boundedJson`: an over-cap `Content-Length`
|
|
6997
|
+
is rejected before buffering (413), a malformed one is a 400, and a chunked or length-less
|
|
6998
|
+
body is drained through a true streaming byte counter that cancels at the cap. The cap is
|
|
6999
|
+
charged on the bytes actually **delivered**, never on the number a caller wrote in the
|
|
7000
|
+
header - so an adapter that rebuilds a request from an event envelope cannot smuggle a large
|
|
7001
|
+
payload behind a small declared length. Fastify enforces its 1 MiB default on real bytes and
|
|
7002
|
+
is the gold standard among the four; Nifra matches it. Hono’s `bodyLimit` is
|
|
7003
|
+
an opt-in middleware that keys on `Content-Length`; Express relies on
|
|
7004
|
+
body-parser’s 100 KB default only if you mount it; Elysia inherits the runtime cap
|
|
7005
|
+
(Bun’s 128 MB).
|
|
7006
|
+
|
|
7007
|
+
#### Prototype pollution
|
|
7008
|
+
|
|
7009
|
+
Every hand-rolled parser (query, cookies, form, route params) produces null-prototype objects,
|
|
7010
|
+
so `__proto__`/`constructor` keys are inert there by construction. The
|
|
7011
|
+
JSON body lane **rejects** the poisoning shape by default - an own `__proto__`
|
|
7012
|
+
key, or a `constructor` carrying a `prototype` - answering the same flat
|
|
7013
|
+
400 as malformed JSON, with `"strip"` and `"ignore"`
|
|
7014
|
+
as alternatives. That matches Fastify, whose `onProtoPoisoning` and
|
|
7015
|
+
`onConstructorPoisoning` both default to `'error'`. Hono and
|
|
7016
|
+
Express do not reject proto keys in the JSON body; Elysia strips undeclared keys only on a
|
|
7017
|
+
validated route via its schema normalization.
|
|
7018
|
+
|
|
7019
|
+
#### Validation and response contracts
|
|
7020
|
+
|
|
7021
|
+
Nifra does per-route Standard Schema validation for body/query/params, and - the
|
|
7022
|
+
differentiating half - **response** schemas as an enforced upper bound:
|
|
7023
|
+
`"warn"` logs undeclared fields, `"enforce"` strips
|
|
7024
|
+
them. That kills the `passwordHash`-in-the-response class at the framework layer.
|
|
7025
|
+
Fastify offers the same protection through a declared response schema (opt-in); Elysia’s
|
|
7026
|
+
exactMirror normalization strips undeclared response fields and is its strongest default.
|
|
7027
|
+
Hono’s validator is opt-in with no response-side story, and Express ships nothing.
|
|
7028
|
+
|
|
7029
|
+
#### CORS, CSRF, and error handling
|
|
7030
|
+
|
|
7031
|
+
Nifra’s `cors()` throws at construction when `credentials: true`
|
|
7032
|
+
meets `origin: "*"`, never reflects an origin by default with credentials,
|
|
7033
|
+
and lands headers on error responses too - it is **the only one of the five where the
|
|
7034
|
+
dangerous configuration is a construction-time error**. The other four leave
|
|
7035
|
+
reflect-with-credentials configurable. CSRF is two independent layers (Origin/Referer check
|
|
7036
|
+
plus a signed double-submit token, timing-safe); Hono’s CSRF is an Origin /
|
|
7037
|
+
Sec-Fetch-Site heuristic, Fastify’s official token package is comparable, Express’s
|
|
7038
|
+
csurf is deprecated, and Elysia ships none in-tree. Uncaught errors become a flat 500
|
|
7039
|
+
unconditionally - not env-gated - so no configuration leaks a stack to the wire; Express’s
|
|
7040
|
+
dev handler printing stacks when `NODE_ENV` is unset is the perennial
|
|
7041
|
+
counter-example.
|
|
7042
|
+
|
|
7043
|
+
#### Headers, cookies, sessions, JWT
|
|
7044
|
+
|
|
7045
|
+
Both Nifra and Hono ship a first-party security-headers middleware you add with
|
|
7046
|
+
`use()`. Nifra’s turns on nosniff, `X-Frame-Options: DENY`, and
|
|
7047
|
+
`Referrer-Policy: no-referrer`, and keeps the headers that can break a working app
|
|
7048
|
+
(COOP/COEP/CORP, Permissions-Policy, CSP, HSTS) opt-in by design; Hono’s covers a broader
|
|
7049
|
+
list on by default once added, HSTS included. Cookie serialization enforces RFC 6265 name
|
|
7050
|
+
tokens, caps at 4096 bytes, and enforces `__Host-`/`__Secure-` prefix
|
|
7051
|
+
preconditions at serialization time, throwing on a violation - the same behavior Hono’s
|
|
7052
|
+
cookie helper has, and stricter than a silent browser drop. Sessions are always HMAC-signed,
|
|
7053
|
+
HttpOnly, SameSite=Lax, fail-closed, with `regenerate()` for fixation - no
|
|
7054
|
+
competitor ships sessions in-tree. JWT verification demands an algorithm allowlist, rejects
|
|
7055
|
+
`alg: none`, blocks algorithm confusion at the key level, and requires
|
|
7056
|
+
`exp` by default; Hono’s JWT middleware takes a single required algorithm with
|
|
7057
|
+
no enforced allowlist and treats `exp` as optional, so Nifra is stricter there.
|
|
7058
|
+
|
|
7059
|
+
#### Static serving, redirects, uploads, proxying
|
|
7060
|
+
|
|
7061
|
+
Static serving runs seven layers - decode with 400 on malformed input, NUL rejection,
|
|
7062
|
+
segment-precise `..` rejection, backslash rejection, containment via
|
|
7063
|
+
`resolve`+`relative`, a `realpath` symlink re-check, and a
|
|
7064
|
+
default **dotfile deny** (a served `.env`/`.git` answers 404).
|
|
7065
|
+
`redirect()` requires a same-origin path by default and rejects CR/LF - Nifra is the
|
|
7066
|
+
only one of the five with a safe-by-default redirect. Uploads verify the real type by magic
|
|
7067
|
+
bytes and strip EXIF/GPS by re-encode, where the others do size limits at best.
|
|
7068
|
+
`@nifrajs/proxy` strips the RFC 9110 hop-by-hop set - plus every
|
|
7069
|
+
`Connection`-nominated header - in **both** directions by default.
|
|
7070
|
+
|
|
7071
|
+
#### Rate limiting, IP trust, load shedding
|
|
7072
|
+
|
|
7073
|
+
The rate limiter’s constructor refuses to run until you choose a key source, so the
|
|
7074
|
+
“trust proxy = spoofable keys” footgun is a construction-time error;
|
|
7075
|
+
`X-Forwarded-For` is ignored at the default and counted from the right only when you
|
|
7076
|
+
declare trusted proxies. `c.clientIp` is the socket peer unless you pass an explicit
|
|
7077
|
+
trust config, and short chains fail closed. Admission sheds on live capacity evidence
|
|
7078
|
+
(in-flight count plus event-loop lag). Fastify’s under-pressure is the only peer for load
|
|
7079
|
+
shedding; nothing else in the field forces the key decision or fails closed on IP trust.
|
|
7080
|
+
|
|
7081
|
+
### Beyond parity - what none of the four ship
|
|
7082
|
+
|
|
7083
|
+
-
|
|
7084
|
+
**Route assurance evidence.** Middleware publishes machine-checkable per-route claims
|
|
7085
|
+
(`AUTHENTICATED`, `BODY_BOUNDED`, `CSRF`,
|
|
7086
|
+
`RATE_LIMITED`, `IP_RESTRICTED`, `SECURITY_HEADERS`,
|
|
7087
|
+
`IDEMPOTENCY_KEY`), and `nifra assure` fails CI when a policy is unmet.
|
|
7088
|
+
Evidence is honest: an escape hatch suppresses its own claim.
|
|
7089
|
+
|
|
7090
|
+
-
|
|
7091
|
+
**Security lint rules** - fail-open gate detection, non-constant-time secret comparison,
|
|
7092
|
+
sensitive values in log calls, and more, running in `nifra check`.
|
|
7093
|
+
|
|
7094
|
+
-
|
|
7095
|
+
**Fail-loud constructors as a design norm** - CORS credentials+wildcard, rate-limit
|
|
7096
|
+
missing key decision, CSRF short secrets, in-memory stores in production - all
|
|
7097
|
+
construction-time errors, not runtime surprises.
|
|
7098
|
+
|
|
7099
|
+
-
|
|
7100
|
+
**Response-contract enforce mode, admission load-shedding, idempotency keys, magic-byte +
|
|
7101
|
+
EXIF-strip uploads, and constant-time webhook verification** - each exists somewhere as a
|
|
7102
|
+
third-party add-on for the others; Nifra ships them first-party with assurance integration.
|
|
7103
|
+
|
|
7104
|
+
### Where each alternative is still the right call
|
|
7105
|
+
|
|
7106
|
+
This is a security read, not a blanket “pick Nifra”. Hono’s run-anywhere
|
|
7107
|
+
minimalism and its zero-dependency core are genuinely excellent; Fastify’s maturity,
|
|
7108
|
+
ecosystem, and years of adversarial attention are real advantages a young framework cannot
|
|
7109
|
+
claim; Express’s ubiquity means every hire already knows it. Nifra’s argument is
|
|
7110
|
+
narrower and honest: it makes the dangerous configurations _unrepresentable_, ships the
|
|
7111
|
+
hardening primitives first-party, and lets `nifra assure` prove in CI that every
|
|
7112
|
+
route is guarded. For the full capability and throughput comparison, see
|
|
7113
|
+
[vs other frameworks](/docs/comparison) and the
|
|
7114
|
+
[per-framework pages](/compare).
|
|
7115
|
+
|
|
6458
7116
|
## Nifra - Contract-derived adversarial testing
|
|
6459
7117
|
|
|
6460
7118
|
> Turn route schemas into hostile input, response-conformance, replay, shrinking, and runtime-matrix tests with @nifrajs/testing.
|
|
@@ -6837,6 +7495,33 @@ level that regressed comes back with the reason it broke.
|
|
|
6837
7495
|
|
|
6838
7496
|
# Package reference (READMEs)
|
|
6839
7497
|
|
|
7498
|
+
## @nifrajs/agent
|
|
7499
|
+
|
|
7500
|
+
Provider-neutral, typed, resumable agent turns over Nifra tool contracts.
|
|
7501
|
+
|
|
7502
|
+
The package owns one bounded turn at a time. Model providers, state, approvals, budgets, idempotency,
|
|
7503
|
+
and telemetry are ports. The package never imports a model vendor and its state adapter stores only
|
|
7504
|
+
token-only turn evidence.
|
|
7505
|
+
|
|
7506
|
+
```ts
|
|
7507
|
+
import { createAgentState, runAgent } from "@nifrajs/agent"
|
|
7508
|
+
|
|
7509
|
+
const result = await runAgent(definition, { value: input }, ports, {
|
|
7510
|
+
state: createAgentState("support-1"),
|
|
7511
|
+
maxTurns: 8,
|
|
7512
|
+
})
|
|
7513
|
+
```
|
|
7514
|
+
|
|
7515
|
+
The public in-memory adapters are for local development and tests. Durable state, provider
|
|
7516
|
+
credentials, and operated policy remain adapter concerns.
|
|
7517
|
+
|
|
7518
|
+
Execution policies can be required by a tool contract. `createLocalProcessAdapter` applies cwd and
|
|
7519
|
+
environment filtering, timeouts, and cancellation to child processes. The local adapter is NOT a
|
|
7520
|
+
security boundary. Without OS-level sandboxing it contains crashes and accidents, not hostile code.
|
|
7521
|
+
|
|
7522
|
+
For the compact agent contract, see [`LLM.md`](./LLM.md). The full machine-readable corpus is
|
|
7523
|
+
[`../../llms-full.txt`](../../llms-full.txt).
|
|
7524
|
+
|
|
6840
7525
|
## @nifrajs/agent-telemetry
|
|
6841
7526
|
|
|
6842
7527
|
Child-span telemetry for AI tool calls made through Nifra tool and MCP endpoints.
|
|
@@ -6893,6 +7578,59 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
|
6893
7578
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
6894
7579
|
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
6895
7580
|
|
|
7581
|
+
## @nifrajs/aws-lambda
|
|
7582
|
+
|
|
7583
|
+
AWS Lambda adapter for nifra - API Gateway HTTP API (payload v2) and Lambda Function URLs, buffered or streaming, with post-decode body limits and single-site header merging. Dependency-free.
|
|
7584
|
+
|
|
7585
|
+
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
|
|
7586
|
+
|
|
7587
|
+
## Install
|
|
7588
|
+
|
|
7589
|
+
```sh
|
|
7590
|
+
bun add @nifrajs/aws-lambda
|
|
7591
|
+
```
|
|
7592
|
+
|
|
7593
|
+
## Use
|
|
7594
|
+
|
|
7595
|
+
```ts
|
|
7596
|
+
// handler.ts - one app per container, built at module scope
|
|
7597
|
+
import { handle, streamHandle, type LambdaEnv } from "@nifrajs/aws-lambda"
|
|
7598
|
+
import { server } from "@nifrajs/core"
|
|
7599
|
+
|
|
7600
|
+
const app = server<LambdaEnv>()
|
|
7601
|
+
.get("/", (c) => c.json({ ip: c.clientIp, requestId: c.env.context?.awsRequestId }))
|
|
7602
|
+
|
|
7603
|
+
export const handler = handle(app) // API Gateway HTTP API (v2) + Function URLs
|
|
7604
|
+
// or, on a Function URL with InvokeMode: RESPONSE_STREAM:
|
|
7605
|
+
// export const handler = streamHandle(app)
|
|
7606
|
+
```
|
|
7607
|
+
|
|
7608
|
+
How it holds the line:
|
|
7609
|
+
|
|
7610
|
+
- **Post-decode body limits.** The body is decoded (base64 or UTF-8) and its real `byteLength` checked against `maxBodyBytes` (default 1,000,000 - match your app's setting if you changed it) *before* a `Request` is constructed. The event's own length claims are never trusted; over the limit answers a flat `413`.
|
|
7611
|
+
- **One header merge site.** Request headers come from the v2 `headers` map plus the separate `cookies` array in exactly one function; the array is canonical, so a `cookie` key smuggled into `headers` never reaches the app. Response `Set-Cookie` values travel in the result's `cookies` array, one entry each - never comma-joined.
|
|
7612
|
+
- **Base64 honesty.** Response `isBase64Encoded` is decided by a strict UTF-8 decode of the actual bytes, never by content-type guessing. Binary round-trips exactly; text ships as text.
|
|
7613
|
+
- **Client IP.** `event.requestContext.http.sourceIp` feeds core's client-IP seam; `X-Forwarded-For` trust stays where it belongs, in the app's `clientIp` server option.
|
|
7614
|
+
- **Flat errors.** Uncaught failures collapse to the same `{"ok":false,"error":"internal_error"}` 500 as every other nifra runtime - the event is never echoed.
|
|
7615
|
+
- **Freeze-safe `waitUntil`.** Background work settles before the handler returns, so Lambda's container freeze cannot strand it.
|
|
7616
|
+
|
|
7617
|
+
The event and invocation context ride on `c.env.event` / `c.env.context` (type your app `server<LambdaEnv>()`). REST APIs (payload v1) and ALB events are out of scope; API Gateway does not support response streaming - `streamHandle` is for Function URLs.
|
|
7618
|
+
|
|
7619
|
+
## Docs
|
|
7620
|
+
|
|
7621
|
+
- Reference: <https://nifra.dev/docs>
|
|
7622
|
+
- AI-readable: <https://nifra.dev/llms.txt>
|
|
7623
|
+
|
|
7624
|
+
MIT
|
|
7625
|
+
|
|
7626
|
+
## For AI agents
|
|
7627
|
+
|
|
7628
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
7629
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
7630
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
7631
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
7632
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
7633
|
+
|
|
6896
7634
|
## @nifrajs/better-auth
|
|
6897
7635
|
|
|
6898
7636
|
Mount [better-auth](https://better-auth.com) into a [nifra](https://github.com/nifra) app: one
|
|
@@ -7202,6 +7940,12 @@ Claude Code:
|
|
|
7202
7940
|
claude mcp add nifra -- nifra mcp
|
|
7203
7941
|
```
|
|
7204
7942
|
|
|
7943
|
+
## Structured verification
|
|
7944
|
+
|
|
7945
|
+
`nifra check --json` returns stable diagnostic codes and machine-actionable evidence. `nifra assure --json --strict` returns one versioned bundle with explicit pass, fail, and skip gate results plus a `green` or `red` verdict. Use `nifra fix --code NF-...` for a registered fix recipe, and `nifra contracts snapshot` to opt into route contract drift detection.
|
|
7946
|
+
|
|
7947
|
+
Assurance configs may append application-supplied rule packs. Pack codes must not use the reserved `NF-` prefix. Rules are pure source scans and run after the built-in rules.
|
|
7948
|
+
|
|
7205
7949
|
Run the client from (or point it at) your project root. The protocol is hand-rolled
|
|
7206
7950
|
(newline-delimited JSON-RPC 2.0) - no SDK dependency.
|
|
7207
7951
|
|
|
@@ -7645,6 +8389,27 @@ Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one
|
|
|
7645
8389
|
bun add @nifrajs/i18n
|
|
7646
8390
|
```
|
|
7647
8391
|
|
|
8392
|
+
## Locale detection
|
|
8393
|
+
|
|
8394
|
+
`negotiateLocale()` / `resolveLocale()` are pure: query parameter → cookie → `Accept-Language` →
|
|
8395
|
+
default, always answering from your `locales` allow-list (request input is matched, never echoed).
|
|
8396
|
+
The server plugin lives at `@nifrajs/i18n/detector` and needs `@nifrajs/core`:
|
|
8397
|
+
|
|
8398
|
+
```ts
|
|
8399
|
+
import { localeDetector } from "@nifrajs/i18n/detector"
|
|
8400
|
+
|
|
8401
|
+
app.use(
|
|
8402
|
+
localeDetector({
|
|
8403
|
+
locales: ["en", "fr", "de"],
|
|
8404
|
+
defaultLocale: "en",
|
|
8405
|
+
queryParam: "lang",
|
|
8406
|
+
cookie: "locale",
|
|
8407
|
+
persist: true, // pin an explicit ?lang= choice into the cookie
|
|
8408
|
+
}),
|
|
8409
|
+
)
|
|
8410
|
+
// handlers see c.locale / c.localeSource; responses carry Content-Language
|
|
8411
|
+
```
|
|
8412
|
+
|
|
7648
8413
|
## Docs
|
|
7649
8414
|
|
|
7650
8415
|
- Reference: <https://nifra.dev/docs>
|
|
@@ -8124,6 +8889,17 @@ await serve(app, {
|
|
|
8124
8889
|
})
|
|
8125
8890
|
```
|
|
8126
8891
|
|
|
8892
|
+
By default, `request.url` reflects the inbound `Host` header. Do not use that value for
|
|
8893
|
+
security decisions unless the deployment validates it. Set `allowedHosts` to reject unknown
|
|
8894
|
+
host authorities, or set `canonicalHost` behind a trusted proxy to build URLs from a fixed
|
|
8895
|
+
authority. If neither option is set, treat the host in `request.url` as client-controlled.
|
|
8896
|
+
|
|
8897
|
+
## Static files
|
|
8898
|
+
|
|
8899
|
+
The optional `static` root is served before `app.fetch` for GET and HEAD requests. It is a
|
|
8900
|
+
public asset directory and must contain nothing private: no secrets, source maps, build
|
|
8901
|
+
metadata, or user uploads.
|
|
8902
|
+
|
|
8127
8903
|
## Graceful shutdown on signals
|
|
8128
8904
|
|
|
8129
8905
|
Opt in to SIGTERM/SIGINT handling so a `docker stop` / Ctrl-C drains in-flight requests
|
|
@@ -8278,6 +9054,45 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
|
8278
9054
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
8279
9055
|
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
8280
9056
|
|
|
9057
|
+
## @nifrajs/proxy
|
|
9058
|
+
|
|
9059
|
+
Reverse proxy for nifra - stream requests to one fixed upstream origin with hop-by-hop hygiene, SSRF-proof by construction. Dependency-free.
|
|
9060
|
+
|
|
9061
|
+
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
|
|
9062
|
+
|
|
9063
|
+
## Install
|
|
9064
|
+
|
|
9065
|
+
```sh
|
|
9066
|
+
bun add @nifrajs/proxy
|
|
9067
|
+
```
|
|
9068
|
+
|
|
9069
|
+
## Use
|
|
9070
|
+
|
|
9071
|
+
```ts
|
|
9072
|
+
import { server } from "@nifrajs/core"
|
|
9073
|
+
import { createProxy } from "@nifrajs/proxy"
|
|
9074
|
+
|
|
9075
|
+
const upstream = createProxy({ upstream: "http://127.0.0.1:8081" })
|
|
9076
|
+
const app = server().mountFetch("/api", upstream, { stripPrefix: true })
|
|
9077
|
+
```
|
|
9078
|
+
|
|
9079
|
+
The upstream is a **bare origin** fixed at construction - the forwarded URL is built by mutating a clone of that origin, never by resolving request-derived strings, so no request input can change which host is dialed. Hop-by-hop and `Connection`-nominated headers are stripped in both directions, forwarding metadata (`X-Forwarded-*`) is dropped unless `forwardClientIp: true`, upstream redirects are never followed, and TLS verification has no off switch. Unreachable upstreams answer a flat `502`; the deadline (`timeoutMs`, default 30s) answers `504`.
|
|
9080
|
+
|
|
9081
|
+
## Docs
|
|
9082
|
+
|
|
9083
|
+
- Reference: <https://nifra.dev/docs>
|
|
9084
|
+
- AI-readable: <https://nifra.dev/llms.txt>
|
|
9085
|
+
|
|
9086
|
+
MIT
|
|
9087
|
+
|
|
9088
|
+
## For AI agents
|
|
9089
|
+
|
|
9090
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
9091
|
+
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
9092
|
+
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
9093
|
+
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
9094
|
+
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
9095
|
+
|
|
8281
9096
|
## @nifrajs/runner
|
|
8282
9097
|
|
|
8283
9098
|
Run requests through a nifra app and capture **structured results** - the shared engine behind the
|
|
@@ -8391,6 +9206,69 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
|
8391
9206
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
|
8392
9207
|
done-gate, or `nifra mcp` to give the agent live project tools.
|
|
8393
9208
|
|
|
9209
|
+
## @nifrajs/skills
|
|
9210
|
+
|
|
9211
|
+
Portable agent skills for [Nifra](../../README.md) - one `SKILL.md` bundle, published to every agent
|
|
9212
|
+
surface that reads a `skills/` directory.
|
|
9213
|
+
|
|
9214
|
+
A skill is loaded on demand: the agent sees only each skill's name and description until it decides
|
|
9215
|
+
one applies, then reads the body. That keeps Nifra's conventions out of the context window until they
|
|
9216
|
+
matter.
|
|
9217
|
+
|
|
9218
|
+
```
|
|
9219
|
+
skills/
|
|
9220
|
+
nifra/SKILL.md entry point - MCP-first rule, the core loop, the non-negotiables
|
|
9221
|
+
nifra-api/SKILL.md typed routes, schemas, middleware, WebSockets, the never-throwing client
|
|
9222
|
+
nifra-web/SKILL.md file routing, loaders/actions, SSR, islands, the client/server boundary
|
|
9223
|
+
nifra-verify/SKILL.md check, assure, capabilities, manifest diff, the verification ladder
|
|
9224
|
+
```
|
|
9225
|
+
|
|
9226
|
+
## Install
|
|
9227
|
+
|
|
9228
|
+
**Pi**
|
|
9229
|
+
|
|
9230
|
+
```sh
|
|
9231
|
+
pi install npm:@nifrajs/skills
|
|
9232
|
+
```
|
|
9233
|
+
|
|
9234
|
+
**Claude Code**
|
|
9235
|
+
|
|
9236
|
+
```sh
|
|
9237
|
+
/plugin marketplace add nifrajs/nifra
|
|
9238
|
+
/plugin install nifra@nifra
|
|
9239
|
+
```
|
|
9240
|
+
|
|
9241
|
+
**Anything that reads a skills directory** (Cursor, `.agents/skills`, a project `.pi/skills`)
|
|
9242
|
+
|
|
9243
|
+
```sh
|
|
9244
|
+
bun add -d @nifrajs/skills
|
|
9245
|
+
cp -R node_modules/@nifrajs/skills/skills/* .agents/skills/
|
|
9246
|
+
```
|
|
9247
|
+
|
|
9248
|
+
## These skills are deliberately thin
|
|
9249
|
+
|
|
9250
|
+
They teach shape and rules, not signatures. Signatures come from the MCP server, which is typechecked
|
|
9251
|
+
against the version installed in the project being edited:
|
|
9252
|
+
|
|
9253
|
+
```sh
|
|
9254
|
+
bunx @nifrajs/cli init-agents # register the MCP server in an existing app
|
|
9255
|
+
```
|
|
9256
|
+
|
|
9257
|
+
Not in a Nifra project? The docs tools are hosted at `https://mcp.nifra.dev`.
|
|
9258
|
+
|
|
9259
|
+
That split is the anti-drift design: a skill that restated the API would rot on the next release,
|
|
9260
|
+
while a skill that says "ask `nifra_types`" stays correct. Keep it that way when editing these files -
|
|
9261
|
+
if you find yourself pasting a signature in, reach for a tool reference instead.
|
|
9262
|
+
|
|
9263
|
+
## For AI agents
|
|
9264
|
+
|
|
9265
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card, one cheap read instead of the whole
|
|
9266
|
+
corpus. For the wider framework: the repo's [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick
|
|
9267
|
+
reference, and [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run
|
|
9268
|
+
`nifra check` as the done-gate, or `nifra mcp` to give the agent live project tools.
|
|
9269
|
+
|
|
9270
|
+
MIT licensed.
|
|
9271
|
+
|
|
8394
9272
|
## @nifrajs/storage
|
|
8395
9273
|
|
|
8396
9274
|
Blob storage for nifra - one `StorageAdapter` interface, several adapters. The **persistence half of
|
|
@@ -8651,6 +9529,7 @@ that every call carries and captures cookies via a shared jar.
|
|
|
8651
9529
|
- `testSession<App>(app, { origin?, cookies? })` → `{ client: Treaty<App>, cookies: CookieJar }`.
|
|
8652
9530
|
- `cookieJar()` → `CookieJar` - `header()` · `applyTo(headers)` · `store(response)` · `set` · `get` · `clear` · `size`.
|
|
8653
9531
|
Honours removal (`Max-Age=0` / past `Expires`); other cookie attributes are ignored (in-process, same-origin).
|
|
9532
|
+
- `proveIdempotency({ run, runs? })` → a token-only proof that canonical effect ledgers replay identically.
|
|
8654
9533
|
|
|
8655
9534
|
For a **stateless** request (no cookies), use `testClient` from `@nifrajs/client` directly.
|
|
8656
9535
|
|
|
@@ -9024,6 +9903,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
9024
9903
|
|
|
9025
9904
|
# Complete export index (generated from each package's src/index.ts)
|
|
9026
9905
|
|
|
9906
|
+
## @nifrajs/agent
|
|
9907
|
+
|
|
9908
|
+
`AgentApprovalPort`, `AgentApprovalResult`, `AgentDefinition`, `AgentModelPort`, `AgentModelRequest`, `AgentModelResponse`, `AgentPendingContinuation`, `AgentPendingKind`, `AgentPorts`, `AgentRunResult`, `AgentStateStore`, `AgentStatus`, `AgentStepEvidence`, `AgentTelemetryPort`, `AgentToolContract`, `AgentToolDescriptor`, `AgentToolResult`, `AgentTranscript`, `AgentTurnBaseResult`, `AgentTurnError`, `AgentTurnInput`, `AgentTurnResult`, `AgentTurnState`, `createAgentState`, `createLocalProcessAdapter`, `ExecutionPolicy`, `ExecutionPolicyAdapter`, `LOCAL_PROCESS_LIMITATION`, `LocalProcessAdapter`, `LocalProcessAdapterOptions`, `LocalProcessPolicyError`, `LocalProcessRequest`, `LocalProcessResult`, `MemoryAgentStateStore`, `replayAgent`, `resumeAgent`, `runAgent`, `RunAgentOptions`, `turn`
|
|
9909
|
+
|
|
9027
9910
|
## @nifrajs/agent-telemetry
|
|
9028
9911
|
|
|
9029
9912
|
`agentTelemetry`, `AgentTelemetryOptions`, `consoleAgentExporter`
|
|
@@ -9032,6 +9915,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
9032
9915
|
|
|
9033
9916
|
`AuthorizationRequest`, `Authorizer`, `createSessions`, `csrf`, `CsrfOptions`, `GuardOptions`, `isAuthorized`, `KVNamespaceLike`, `KVSessionStore`, `MemorySessionStore`, `MemorySessionStoreOptions`, `requireAuthorization`, `requireSession`, `requireUser`, `Session`, `SessionContext`, `SessionCookieOptions`, `SessionManager`, `SessionOptions`, `SessionRecord`, `SessionStore`
|
|
9034
9917
|
|
|
9918
|
+
## @nifrajs/aws-lambda
|
|
9919
|
+
|
|
9920
|
+
`FetchHandler`, `handle`, `handler`, `LambdaContext`, `LambdaEnv`, `LambdaEvent`, `LambdaOptions`, `LambdaPlatform`, `LambdaResult`, `ResponseStream`, `streamHandle`
|
|
9921
|
+
|
|
9035
9922
|
## @nifrajs/better-auth
|
|
9036
9923
|
|
|
9037
9924
|
`authed`, `AuthedOptions`, `betterAuth`, `BetterAuthLike`, `BetterAuthOptions`, `getSession`, `Principal`, `PrincipalFor`, `requirePrincipal`, `requireSession`, `RequireSessionOptions`, `SessionOf`, `SessionUserOf`, `WithPrincipal`
|
|
@@ -9074,7 +9961,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
9074
9961
|
|
|
9075
9962
|
## @nifrajs/i18n
|
|
9076
9963
|
|
|
9077
|
-
`createFormatter`, `Formatter`, `Locale`, `Messages`, `negotiateLocale`, `NegotiateOptions`
|
|
9964
|
+
`createFormatter`, `Formatter`, `Locale`, `LocaleParts`, `LocaleSource`, `Messages`, `negotiateLocale`, `NegotiateOptions`, `ResolvedLocale`, `resolveLocale`
|
|
9078
9965
|
|
|
9079
9966
|
## @nifrajs/image
|
|
9080
9967
|
|
|
@@ -9116,6 +10003,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
9116
10003
|
|
|
9117
10004
|
`prompt`, `Prompt`, `PromptInputError`, `PromptMessage`, `PromptOutputError`, `PromptRequest`, `PromptResponseFormat`, `RunOptions`
|
|
9118
10005
|
|
|
10006
|
+
## @nifrajs/proxy
|
|
10007
|
+
|
|
10008
|
+
`createProxy`, `ProxyContext`, `ProxyHandler`, `ProxyOptions`
|
|
10009
|
+
|
|
9119
10010
|
## @nifrajs/runner
|
|
9120
10011
|
|
|
9121
10012
|
`AppLike`, `RequestSpec`, `runApp`, `RunOptions`, `runRequest`, `RunResult`
|
|
@@ -9130,7 +10021,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
9130
10021
|
|
|
9131
10022
|
## @nifrajs/testing
|
|
9132
10023
|
|
|
9133
|
-
`AdapterCertificationError`, `AdapterCertificationProfile`, `AdapterCertificationReport`, `AdversarialContractError`, `AdversarialContractOptions`, `AdversarialContractReport`, `AdversarialContractResult`, `AppLike`, `assertAdapterCertification`, `assertAdversarialContract`, `assertIncidentReplays`, `cacheStoreCertificationProfile`, `CapturedRequest`, `CapturedRequestInput`, `captureIncident`, `CaptureIncidentOptions`, `CertifiableCacheEntry`, `CertifiableCacheStore`, `CertifiableDomainEvent`, `CertifiableEventDeliveryAdapter`, `CertifiableEventRecord`, `CertifiableJobStore`, `CertifiableRuntimeAdapter`, `CertifiableRuntimeServer`, `CertifiableStorageAdapter`, `CertificationCapabilityEvidence`, `CertificationCheck`, `CertificationCheckEvidence`, `certifyAdapter`, `ContractCaseContext`, `ContractCaseKind`, `ContractCoverageGap`, `ContractCoverageGapCode`, `ContractReplay`, `ContractRuntime`, `ContractTarget`, `ContractTestApp`, `ContractWitness`, `cookieJar`, `CookieJar`, `createFailureLab`, `defineCertificationProfile`, `eventDeliveryCertificationProfile`, `FailureDirective`, `FailureEvidence`, `FailureInjectedError`, `FailureKind`, `FailureLab`, `FailureLabOptions`, `FailureReplay`, `FailureScenario`, `FailureScenarioReport`, `generateRegressionTest`, `GenerateRegressionTestOptions`, `IncidentCapsule`, `IncidentReplayError`, `IncidentReplayResult`, `jobStoreCertificationProfile`, `redactForEmission`, `replayIncident`, `ReplayIncidentOptions`, `runAdversarialContract`, `runFailureScenario`, `runtimeAdapterCertificationProfile`, `shapeOf`, `storageAdapterCertificationProfile`, `testSession`, `TestSession`, `TestSessionOptions`, `verifyAdapterCertification`
|
|
10024
|
+
`AdapterCertificationError`, `AdapterCertificationProfile`, `AdapterCertificationReport`, `AdversarialContractError`, `AdversarialContractOptions`, `AdversarialContractReport`, `AdversarialContractResult`, `AppLike`, `assertAdapterCertification`, `assertAdversarialContract`, `assertIncidentReplays`, `assertTrajectoryInvariants`, `cacheStoreCertificationProfile`, `CapturedRequest`, `CapturedRequestInput`, `captureIncident`, `CaptureIncidentOptions`, `CertifiableCacheEntry`, `CertifiableCacheStore`, `CertifiableDomainEvent`, `CertifiableEventDeliveryAdapter`, `CertifiableEventRecord`, `CertifiableJobStore`, `CertifiableRuntimeAdapter`, `CertifiableRuntimeServer`, `CertifiableStorageAdapter`, `CertificationCapabilityEvidence`, `CertificationCheck`, `CertificationCheckEvidence`, `certifyAdapter`, `checkTrajectoryInvariants`, `ContractCaseContext`, `ContractCaseKind`, `ContractCoverageGap`, `ContractCoverageGapCode`, `ContractReplay`, `ContractRuntime`, `ContractTarget`, `ContractTestApp`, `ContractWitness`, `cookieJar`, `CookieJar`, `createFailureLab`, `createTrajectoryTranscript`, `CreateTrajectoryTranscriptOptions`, `defineCertificationProfile`, `defineFaultProfile`, `EffectLedger`, `eventDeliveryCertificationProfile`, `FailureDirective`, `FailureEvidence`, `FailureInjectedError`, `FailureKind`, `FailureLab`, `FailureLabOptions`, `FailureReplay`, `FailureScenario`, `FailureScenarioReport`, `FaultProfile`, `FaultProfileFault`, `FaultProfileFaultKind`, `FaultProfileReport`, `FaultProfileScenario`, `FaultProfileScenarioReport`, `generateRegressionTest`, `GenerateRegressionTestOptions`, `httpToolAdapter`, `IdempotencyDivergence`, `IdempotencyProof`, `IncidentCapsule`, `IncidentReplayError`, `IncidentReplayResult`, `inProcessToolAdapter`, `jobStoreCertificationProfile`, `mcpToolAdapter`, `proveIdempotency`, `recordTrajectory`, `redactForEmission`, `referenceFaultProfile`, `replayIncident`, `ReplayIncidentOptions`, `replayTrajectory`, `ReplayTrajectoryOptions`, `runAdversarialContract`, `runFailureScenario`, `runFaultProfile`, `RunFaultProfileOptions`, `runtimeAdapterCertificationProfile`, `runTrajectory`, `shapeOf`, `storageAdapterCertificationProfile`, `testSession`, `TestSession`, `TestSessionOptions`, `testToolAdapter`, `TrajectoryInvariantId`, `TrajectoryInvariantOptions`, `TrajectoryInvariantResult`, `trajectoryRegressionId`, `TrajectoryReplayResult`, `TrajectoryRun`, `TrajectoryTranscript`, `verifyAdapterCertification`
|
|
9134
10025
|
|
|
9135
10026
|
## @nifrajs/ts-plugin
|
|
9136
10027
|
|
|
@@ -9142,7 +10033,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
|
|
|
9142
10033
|
|
|
9143
10034
|
## @nifrajs/web
|
|
9144
10035
|
|
|
9145
|
-
`Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `BrowserNavigate`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createMutation`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `DehydratedState`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `gate`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateRouteSearchTypes`, `GenerateRouteSearchTypesOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `getBrowserNavigate`, `GetStaticPaths`, `gone`, `hashQueryKey`, `IDLE_BLOCKER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaInput`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `notFound`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `previewEndpoint`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `registerBlocker`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderedPage`, `renderPage`, `RenderPageInput`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `RETAIN_HEADER`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `RouteSearch`, `routeStyles`, `ScriptDescriptor`, `searchOf`, `SearchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `ServePublicDirOptions`, `SERVER_FN_MODULE`, `SERVER_ONLY_MODULE`, `ServerOnly`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `SsrModuleLoader`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `STATUS_HEADER`, `statusPage`, `StatusPageOptions`, `styles`, `Submission`, `SubmitOptions`, `unsafeInlineScript`, `UnsafeScriptDescriptor`, `withISR`
|
|
10036
|
+
`Action`, `ACTION_GLOBAL`, `apiKey`, `assertRenderAdapterConformance`, `Blocker`, `BlockerController`, `BlockerFunction`, `BlockerLocation`, `BlockerState`, `BrowserNavigate`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createMutation`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `DehydratedState`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `gate`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateRouteSearchTypes`, `GenerateRouteSearchTypesOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `getBrowserNavigate`, `GetStaticPaths`, `gone`, `hashQueryKey`, `IDLE_BLOCKER`, `InertScriptType`, `InfiniteData`, `InfiniteQueryHandle`, `InfiniteQueryOptions`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_REVALIDATE_TAGS_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LAYOUT_DATA_GLOBAL`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaDescriptor`, `MetaInput`, `MountRouterOptions`, `MutationCallbacks`, `MutationHandle`, `MutationState`, `MutationStatus`, `NAV_FROM_HEADER`, `NavigateFunction`, `NavigateOptions`, `NavigateSearchOf`, `NavigateTargetInput`, `notFound`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `previewEndpoint`, `PreviewEndpointOptions`, `PublicDirCache`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryOptions`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `registerBlocker`, `RenderAdapter`, `RenderAdapterConformanceError`, `RenderAdapterConformanceFixture`, `RenderAssemblyCache`, `RenderedPage`, `renderPage`, `RenderPageInput`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `resolveNavigate`, `resolvePublicPath`, `RETAIN_HEADER`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROOT_ATTRIBUTE`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `RouteSearch`, `routeStyles`, `ScriptDescriptor`, `searchOf`, `SearchOf`, `searchOfChain`, `serializeData`, `serializeSearch`, `servePublicDir`, `ServePublicDirOptions`, `SERVER_FN_MODULE`, `SERVER_ONLY_MODULE`, `ServerOnly`, `setBlockerController`, `setBrowserNavigate`, `setSsrModuleLoader`, `ssrModuleLoader`, `SsrModuleLoader`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `STATUS_HEADER`, `statusPage`, `StatusPageOptions`, `styles`, `Submission`, `SubmitOptions`, `unsafeInlineScript`, `UnsafeScriptDescriptor`, `withISR`
|
|
9146
10037
|
|
|
9147
10038
|
## @nifrajs/web-preact
|
|
9148
10039
|
|