@nifrajs/cli 1.13.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +17 -2
  2. package/dist/check.d.ts +17 -6
  3. package/dist/check.d.ts.map +1 -1
  4. package/dist/check.js +256 -15
  5. package/dist/check.js.map +1 -1
  6. package/dist/cli.d.ts +2 -3
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +58 -71
  9. package/dist/cli.js.map +1 -1
  10. package/dist/doctor.d.ts.map +1 -1
  11. package/dist/doctor.js +108 -48
  12. package/dist/doctor.js.map +1 -1
  13. package/dist/mcp-http.js +1 -1
  14. package/dist/mcp-http.js.map +1 -1
  15. package/dist/mcp.d.ts +2 -0
  16. package/dist/mcp.d.ts.map +1 -1
  17. package/dist/mcp.js +42 -0
  18. package/dist/mcp.js.map +1 -1
  19. package/dist/recipes/2.0.0.d.ts +9 -0
  20. package/dist/recipes/2.0.0.d.ts.map +1 -0
  21. package/dist/recipes/2.0.0.js +25 -0
  22. package/dist/recipes/2.0.0.js.map +1 -0
  23. package/dist/recipes/index.d.ts +7 -0
  24. package/dist/recipes/index.d.ts.map +1 -1
  25. package/dist/recipes/index.js +2 -0
  26. package/dist/recipes/index.js.map +1 -1
  27. package/dist/sync-manifest.d.ts +33 -0
  28. package/dist/sync-manifest.d.ts.map +1 -0
  29. package/dist/sync-manifest.js +97 -0
  30. package/dist/sync-manifest.js.map +1 -0
  31. package/dist/upgrade.d.ts +23 -2
  32. package/dist/upgrade.d.ts.map +1 -1
  33. package/dist/upgrade.js +144 -13
  34. package/dist/upgrade.js.map +1 -1
  35. package/docs/examples.json +69 -21
  36. package/docs/llms-full.txt +1065 -104
  37. package/docs/types.json +880 -175
  38. package/package.json +9 -9
  39. package/src/check.ts +251 -18
  40. package/src/cli.ts +57 -73
  41. package/src/doctor.ts +133 -49
  42. package/src/mcp-http.ts +1 -1
  43. package/src/mcp.ts +52 -0
  44. package/src/recipes/2.0.0.ts +26 -0
  45. package/src/recipes/index.ts +10 -0
  46. package/src/sync-manifest.ts +111 -0
  47. package/src/upgrade.ts +163 -11
@@ -15,7 +15,7 @@
15
15
  "name": "BACKEND",
16
16
  "topic": "Nifra — Backends & API (dev and prod)",
17
17
  "slug": "backends",
18
- "code": "// backend.ts — a normal @nifrajs/core server. Routes live at the full /api/... path.\nimport { server } from \"@nifrajs/core\"\nimport { t } from \"@nifrajs/schema\"\n\nexport const backend = server()\n .post(\"/api/sync\", { body: t.object({ cursor: t.string() }) }, async (c) => {\n // validated body; runs the same whether called in-process (a loader) or over HTTP (a client fetch)\n return { applied: 12, nextCursor: c.body.cursor }\n })\n .get(\"/api/me\", (c) => ({ id: c.cookies.session ?? null }))"
18
+ "code": "// backend.ts — a normal @nifrajs/core server. Routes live at the full /api/... path.\nimport { server } from \"@nifrajs/core/server\"\nimport { t } from \"@nifrajs/schema\"\n\nexport const backend = server()\n .post(\"/api/sync\", { body: t.object({ cursor: t.string() }) }, async (c) => {\n // validated body; runs the same whether called in-process (a loader) or over HTTP (a client fetch)\n return { applied: 12, nextCursor: c.body.cursor }\n })\n .get(\"/api/me\", (c) => ({ id: c.cookies.session ?? null }))"
19
19
  },
20
20
  {
21
21
  "name": "CLIENT",
@@ -23,6 +23,12 @@
23
23
  "slug": "backends",
24
24
  "code": "// A browser island / client component hits the mounted HTTP routes with the typed client.\nimport { client } from \"@nifrajs/client\"\nimport type { backend } from \"./backend\"\n\nconst api = client<typeof backend>(\"\") // same-origin: /api/sync is served by createWebApp's mount\nexport async function runSync(cursor: string) {\n const { data, error } = await api.api.sync.post({ cursor })\n return error ? { applied: 0 } : data\n}"
25
25
  },
26
+ {
27
+ "name": "COMPOSE",
28
+ "topic": "Nifra — Backends & API (dev and prod)",
29
+ "slug": "backends",
30
+ "code": "// backend.ts - compose domains with .merge(). Each group owns its FULL absolute paths.\nimport { server } from \"@nifrajs/core/server\"\n\nconst listings = server().get(\"/api/listings\", (c) => {\n // c.req.url is the ORIGINAL request URL - .merge() and the /api mount never strip the prefix.\n const path = new URL(c.req.url).pathname // GET /api/listings -> \"/api/listings\"\n return { path }\n})\n\nconst agents = server().get(\"/api/agents\", (c) => ({ ok: new URL(c.req.url).pathname }))\n\n// merge() folds both groups into ONE flat path space at their defined paths; a path+method clash\n// throws a RouteConfigError right here at merge time (fail-closed), never a silent shadow at runtime.\nexport const backend = server().merge(listings).merge(agents)"
31
+ },
26
32
  {
27
33
  "name": "LOADER",
28
34
  "topic": "Nifra — Backends & API (dev and prod)",
@@ -39,7 +45,25 @@
39
45
  "name": "SERVER",
40
46
  "topic": "Nifra — Request budgets",
41
47
  "slug": "budgets",
42
- "code": "import { server } from \"@nifrajs/core\"\n\nconst app = server({\n requestTimeoutMs: 2_000,\n acceptInboundDeadlines: true,\n maxInboundDeadlineMs: 5_000,\n}).get(\"/report\", async (c) => {\n // c.signal aborts at the SAME effective deadline.\n // remaining() is monotonic even if the wall clock jumps.\n return { remainingMs: c.budget.remaining() }\n})"
48
+ "code": "import { server } from \"@nifrajs/core/server\"\n\nconst app = server({\n requestTimeoutMs: 2_000,\n acceptInboundDeadlines: true,\n maxInboundDeadlineMs: 5_000,\n}).get(\"/report\", async (c) => {\n // c.signal aborts at the SAME effective deadline.\n // remaining() is monotonic even if the wall clock jumps.\n return { remainingMs: c.budget.remaining() }\n})"
49
+ },
50
+ {
51
+ "name": "CONTINUE",
52
+ "topic": "Nifra - Execution causality",
53
+ "slug": "causality",
54
+ "code": "import { continueCausality, startCausality } from \"@nifrajs/core/causality\"\n\nconst step = startCausality(\"request\", crypto.randomUUID(), {\n executionId: crypto.randomUUID(),\n})\n\n// The command this request caused. The edge relation is a bounded token.\nconst command = continueCausality(step.context, \"command\", crypto.randomUUID(), {\n relation: \"caused\",\n})\n\n// The event that command emitted, inside the same commit.\nconst event = continueCausality(command.context, \"event\", crypto.randomUUID(), {\n relation: \"emitted\",\n})"
55
+ },
56
+ {
57
+ "name": "HEADERS",
58
+ "topic": "Nifra - Execution causality",
59
+ "slug": "causality",
60
+ "code": "import {\n causalityHeaders,\n readCausalityHeaders,\n startCausality,\n} from \"@nifrajs/core/causality\"\n\nconst step = startCausality(\"request\", crypto.randomUUID(), {\n executionId: crypto.randomUUID(),\n})\n\n// Outbound: bounded headers only.\nawait fetch(\"https://billing.internal/charge\", {\n headers: causalityHeaders(step.context),\n})\n\n// Inbound: never throws on hostile input.\nconst request = new Request(\"https://api.example/charge\")\nconst parsed = readCausalityHeaders(request.headers)\nif (!parsed.success) {\n // \"missing\" | \"incomplete\" | \"invalid\" | \"unknown-field\" - start a fresh graph.\n}"
61
+ },
62
+ {
63
+ "name": "START",
64
+ "topic": "Nifra - Execution causality",
65
+ "slug": "causality",
66
+ "code": "import { createMemoryCausalityStore, startCausality } from \"@nifrajs/core/causality\"\n\nconst recorder = createMemoryCausalityStore()\nconst requestId = crypto.randomUUID()\n\n// One root node at the ingress boundary. executionId is the graph's identity:\n// generate it once, then propagate it.\nconst step = startCausality(\"request\", requestId, { executionId: crypto.randomUUID() })\n\n// step.context propagates; step.record is what a durable adapter appends.\nawait recorder.record(step.record)"
43
67
  },
44
68
  {
45
69
  "name": "DUAL_API",
@@ -59,6 +83,12 @@
59
83
  "slug": "contract",
60
84
  "code": "// process.env in the CLIENT bundle is compiled at build time:\n// process.env → ({}) (a bare read won't crash hydration)\n// process.env.NODE_ENV → \"production\" (the build mode — frameworks' prod/dev branch)\n// process.env.SECRET_KEY → undefined (unprefixed → never exposed; no secret leak)\n// process.env.PUBLIC_API_URL → \"https://api.example.com\" (PUBLIC_-prefixed → baked in by value)\n//\n// So to ship a value to the browser, give it a PUBLIC_ prefix in the BUILD environment:\n// PUBLIC_API_URL=https://api.example.com bun run build\n// then read process.env.PUBLIC_API_URL in app code — it becomes a string literal after the build.\nimport { buildClient } from \"@nifrajs/web/build\"\n\n// Override the prefix (or disable auto-exposure) via buildClient:\nawait buildClient({\n routesDir: \"./routes\",\n outDir: \"./dist\",\n clientModule: \"@nifrajs/web-react/client\",\n publicEnvPrefix: \"NIFRA_PUBLIC_\", // default \"PUBLIC_\"; \"\" disables auto-exposure entirely\n})"
61
85
  },
86
+ {
87
+ "name": "REUSABLE_ROUTER",
88
+ "topic": "Nifra — Framework contract",
89
+ "slug": "contract",
90
+ "code": "// A PUBLISHABLE router - contract-first, so its types survive `.d.ts` emit for consumers.\nimport { client } from \"@nifrajs/client\"\nimport { defineContract, implement } from \"@nifrajs/core/contract\"\nimport { server } from \"@nifrajs/core/server\"\nimport { t } from \"@nifrajs/schema\"\n\n// --- in the package (say @your-org/billing) ---\n// 1. The CONTRACT is ONE plain declared object - methods, paths, schemas, no handlers.\n// Export it: it is the single type the package publishes, and a consumer's typed client is\n// built from THIS value (not from `typeof app`), so no internal server type crosses the seam.\nexport const billingContract = defineContract({\n getPlan: { method: \"GET\", path: \"/billing/plan\" },\n subscribe: { method: \"POST\", path: \"/billing/subscribe\", body: t.object({ plan: t.string() }) },\n})\n\n// 2. A FACTORY closes over the host's dependencies and returns a real, mountable router.\n// `implement` binds handlers to the contract; each handler's context is typed from the contract\n// alone (`c.body` is { plan: string } below) - no per-route `AddRoute` inference accreting.\nexport interface BillingDeps {\n charge(plan: string): Promise<{ id: string }>\n}\nexport function createBillingRouter(deps: BillingDeps) {\n return implement(billingContract, {\n getPlan: () => ({ plan: \"pro\" }),\n subscribe: (c) => deps.charge(c.body.plan),\n })\n}\n\n// --- in the consuming app ---\n// 3. Mount the router with ONE `.merge()` - no per-route `.get()/.post()` chain to re-infer.\nconst app = server().merge(createBillingRouter({ charge: async (plan) => ({ id: plan }) }))\n\n// 4. Build the client from the EXPORTED contract value - decoupled, no `typeof app` import needed.\nconst api = client(billingContract, \"https://api.example.com\")\nconst { data } = await api.billing.plan.get() // GET /billing/plan\nawait api.billing.subscribe.post({ plan: \"pro\" }) // POST /billing/subscribe, body typed"
91
+ },
62
92
  {
63
93
  "name": "SEO_EXAMPLE",
64
94
  "topic": "Nifra — Framework contract",
@@ -81,19 +111,25 @@
81
111
  "name": "DECORATE",
82
112
  "topic": "Nifra — Databases",
83
113
  "slug": "database",
84
- "code": "import { server } from \"@nifrajs/core\"\nimport { desc } from \"drizzle-orm\"\nimport { db, notes } from \"./db\" // your Drizzle client + schema (what the scaffold generates)\n\n// decorate() hangs the client on the context ONCE — every handler then reads it as `c.db`, fully typed.\nexport const app = server()\n .decorate(\"db\", db)\n .get(\"/notes\", async (c) => c.db.select().from(notes).orderBy(desc(notes.createdAt)))\n\nexport type App = typeof app"
114
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { desc } from \"drizzle-orm\"\nimport { db, notes } from \"./db\" // your Drizzle client + schema (what the scaffold generates)\n\n// decorate() hangs the client on the context ONCE — every handler then reads it as `c.db`, fully typed.\nexport const app = server()\n .decorate(\"db\", db)\n .get(\"/notes\", async (c) => c.db.select().from(notes).orderBy(desc(notes.createdAt)))\n\nexport type App = typeof app"
85
115
  },
86
116
  {
87
117
  "name": "SQLITE",
88
118
  "topic": "Nifra — Databases",
89
119
  "slug": "database",
90
- "code": "import { server } from \"@nifrajs/core\"\nimport { Database } from \"bun:sqlite\" // Node: better-sqlite3 · edge: Turso/libSQL\n\n// Open the connection ONCE at module scope (the driver pools internally).\nconst db = new Database(\"app.db\")\nconst byId = db.query<{ id: number; name: string }, [number]>(\"SELECT * FROM users WHERE id = ?\")\n\nexport const app = server().get(\"/users/:id\", (c) =>\n // Parameterized (?) — never interpolate user input into SQL.\n byId.get(Number(c.params.id)) ?? new Response(\"Not found\", { status: 404 }),\n)"
120
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { Database } from \"bun:sqlite\" // Node: better-sqlite3 · edge: Turso/libSQL\n\n// Open the connection ONCE at module scope (the driver pools internally).\nconst db = new Database(\"app.db\")\nconst byId = db.query<{ id: number; name: string }, [number]>(\"SELECT * FROM users WHERE id = ?\")\n\nexport const app = server().get(\"/users/:id\", (c) =>\n // Parameterized (?) — never interpolate user input into SQL.\n byId.get(Number(c.params.id)) ?? new Response(\"Not found\", { status: 404 }),\n)"
91
121
  },
92
122
  {
93
123
  "name": "CF",
94
124
  "topic": "Nifra — Deployment",
95
125
  "slug": "deployment",
96
- "code": "// _worker.ts — the edge SSR entry\nimport { toFetchHandler } from \"@nifrajs/core\"\nimport { createWebApp } from \"@nifrajs/web\"\nimport { reactAdapter } from \"@nifrajs/web-react\"\nimport { clientEntry, manifest } from \"./server-manifest\" // generated by buildServer\n\nconst app = createWebApp({ adapter: reactAdapter, manifest, clientEntry })\nexport default toFetchHandler(app) // a Workers/Pages fetch handler\n\n// build.ts — buildClient (→ /assets) + buildServer (edge conditions → _worker.js)\nimport { buildClient, buildServer } from \"@nifrajs/web/build\""
126
+ "code": "// _worker.ts — the edge SSR entry\nimport { toFetchHandler } from \"@nifrajs/core/server\"\nimport { createWebApp } from \"@nifrajs/web\"\nimport { reactAdapter } from \"@nifrajs/web-react\"\nimport { clientEntry, manifest } from \"./server-manifest\" // generated by buildServer\n\nconst app = createWebApp({ adapter: reactAdapter, manifest, clientEntry })\nexport default toFetchHandler(app) // a Workers/Pages fetch handler\n\n// build.ts — buildClient (→ /assets) + buildServer (edge conditions → _worker.js)\nimport { buildClient, buildServer } from \"@nifrajs/web/build\""
127
+ },
128
+ {
129
+ "name": "LAB",
130
+ "topic": "Nifra - Failure laboratory",
131
+ "slug": "failure-lab",
132
+ "code": "import { createFailureLab } from \"@nifrajs/testing\"\n\nconst lab = createFailureLab({\n seed: 77,\n schedule: [\n { kind: \"duplicate-delivery\", point: \"relay.batch\", copies: 3 },\n { kind: \"reorder-events\", point: \"relay.batch\" },\n ],\n})\n\n// The adapter under test just names its seams. Same seed, same schedule,\n// same result - every run.\nconst delivered = lab.deliveries(\"relay.batch\", [\"evt-a\", \"evt-b\", \"evt-c\"])"
97
133
  },
98
134
  {
99
135
  "name": "AUTOMATED",
@@ -153,55 +189,55 @@
153
189
  "name": "HELLO",
154
190
  "topic": "Nifra — Getting started",
155
191
  "slug": "index",
156
- "code": "import { server } from \"@nifrajs/core\"\n\nserver()\n .get(\"/\", () => ({ hello: \"world\" }))\n .get(\"/users/:id\", (c) => ({ id: c.params.id }))\n .listen(3000)"
192
+ "code": "import { server } from \"@nifrajs/core/server\"\n\nserver()\n .get(\"/\", () => ({ hello: \"world\" }))\n .get(\"/users/:id\", (c) => ({ id: c.params.id }))\n .listen(3000)"
157
193
  },
158
194
  {
159
195
  "name": "CACHING",
160
196
  "topic": "Nifra — Plugins & middleware",
161
197
  "slug": "plugins",
162
- "code": "import { server } from \"@nifrajs/core\"\nimport { cache, MemoryResponseCache, prettyJson } from \"@nifrajs/middleware\"\n\n// Full response cache: pluggable store, Vary-aware keys, byte cap. Bypasses Set-Cookie and respects\n// Cache-Control (no-store/private). MemoryResponseCache is per-instance — refuses prod unless opted in.\nconst app = server()\napp.use(cache({ store: new MemoryResponseCache(), ttlMs: 30_000, vary: [\"accept-language\"] }))\napp.use(prettyJson()) // pretty-print JSON responses (size-capped; optional ?pretty query toggle)"
198
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { cache, MemoryResponseCache, prettyJson } from \"@nifrajs/middleware\"\n\n// Full response cache: pluggable store, Vary-aware keys, byte cap. Bypasses Set-Cookie and respects\n// Cache-Control (no-store/private). MemoryResponseCache is per-instance — refuses prod unless opted in.\nconst app = server()\napp.use(cache({ store: new MemoryResponseCache(), ttlMs: 30_000, vary: [\"accept-language\"] }))\napp.use(prettyJson()) // pretty-print JSON responses (size-capped; optional ?pretty query toggle)"
163
199
  },
164
200
  {
165
201
  "name": "IDENTITY",
166
202
  "topic": "Nifra — Plugins & middleware",
167
203
  "slug": "plugins",
168
- "code": "import { defineRouterPlugin, server } from \"@nifrajs/core\"\n\n// A plugin that mounts routes/hooks but adds NO context type: defineRouterPlugin (the clearer name for\n// defineIdentityPlugin) keeps app.use()'s return type EXACTLY the caller's server, so routes added after\n// .use() stay typed — and the typed client derived from them stays intact.\nexport const scim = defineRouterPlugin(\"scim\", (app) => {\n app.get(\"/scim/v2/Users\", () => ({ Resources: [] })) // mount as a SIDE EFFECT (a runtime-only route)\n return app // return the app unchanged → caller's registry preserved\n})\n\n// /a AND /b stay fully typed across the .use():\nconst api = server().get(\"/a\", () => ({ a: 1 })).use(scim).get(\"/b\", () => ({ b: 2 }))"
204
+ "code": "import { defineRouterPlugin, server } from \"@nifrajs/core/server\"\n\n// A plugin that mounts routes/hooks but adds NO context type: defineRouterPlugin (the clearer name for\n// defineIdentityPlugin) keeps app.use()'s return type EXACTLY the caller's server, so routes added after\n// .use() stay typed — and the typed client derived from them stays intact.\nexport const scim = defineRouterPlugin(\"scim\", (app) => {\n app.get(\"/scim/v2/Users\", () => ({ Resources: [] })) // mount as a SIDE EFFECT (a runtime-only route)\n return app // return the app unchanged → caller's registry preserved\n})\n\n// /a AND /b stay fully typed across the .use():\nconst api = server().get(\"/a\", () => ({ a: 1 })).use(scim).get(\"/b\", () => ({ b: 2 }))"
169
205
  },
170
206
  {
171
207
  "name": "JWT_BASIC",
172
208
  "topic": "Nifra — Plugins & middleware",
173
209
  "slug": "plugins",
174
- "code": "import { server } from \"@nifrajs/core\"\nimport { jwt, jwks, basicAuth } from \"@nifrajs/middleware\"\n\n// JWT (WebCrypto): an explicit algorithm allowlist is REQUIRED; alg:none and RSA/HMAC confusion are rejected.\nconst auth = jwt({ key: process.env.JWT_SECRET!, algorithms: [\"HS256\"], issuer: \"my-app\" })\nconst app = server()\n .use(auth) // 401s missing/invalid (optional:true lets them through)\n .get(\"/me\", (c) => auth.requireClaims(c.req)) // typed claims, or throws 401; auth.claims(req) is nullable\n// Asymmetric (rotating keys): key: jwks({ url: \"https://issuer/.well-known/jwks.json\" }) — https-only, cached.\n\n// HTTP Basic — static creds compared in CONSTANT TIME (SHA-256 + timing-safe), or a verify callback.\napp.use(basicAuth({ username: \"admin\", password: process.env.PASS!, realm: \"staging\" }))"
210
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { jwt, jwks, basicAuth } from \"@nifrajs/middleware\"\n\n// JWT (WebCrypto): an explicit algorithm allowlist is REQUIRED; alg:none and RSA/HMAC confusion are rejected.\nconst auth = jwt({ key: process.env.JWT_SECRET!, algorithms: [\"HS256\"], issuer: \"my-app\" })\nconst app = server()\n .use(auth) // 401s missing/invalid (optional:true lets them through)\n .get(\"/me\", (c) => auth.requireClaims(c.req)) // typed claims, or throws 401; auth.claims(req) is nullable\n// Asymmetric (rotating keys): key: jwks({ url: \"https://issuer/.well-known/jwks.json\" }) — https-only, cached.\n\n// HTTP Basic — static creds compared in CONSTANT TIME (SHA-256 + timing-safe), or a verify callback.\napp.use(basicAuth({ username: \"admin\", password: process.env.PASS!, realm: \"staging\" }))"
175
211
  },
176
212
  {
177
213
  "name": "MIDDLEWARE",
178
214
  "topic": "Nifra — Plugins & middleware",
179
215
  "slug": "plugins",
180
- "code": "import { server } from \"@nifrajs/core\"\nimport { cors, rateLimit, securityHeaders, MemoryStore } from \"@nifrajs/middleware\"\n\n// Hardening middleware is a hook bundle (context-agnostic) — same app.use():\nconst app = server()\n .use(securityHeaders())\n .use(cors({ origin: [\"https://app.example\"] }))\n // MemoryStore is dev/single-instance only — use a shared store (Redis, etc.) in production.\n .use(rateLimit({ store: new MemoryStore(), max: 100, windowMs: 60_000 }))"
216
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { cors, rateLimit, securityHeaders, MemoryStore } from \"@nifrajs/middleware\"\n\n// Hardening middleware is a hook bundle (context-agnostic) — same app.use():\nconst app = server()\n .use(securityHeaders())\n .use(cors({ origin: [\"https://app.example\"] }))\n // MemoryStore is dev/single-instance only — use a shared store (Redis, etc.) in production.\n .use(rateLimit({ store: new MemoryStore(), max: 100, windowMs: 60_000 }))"
181
217
  },
182
218
  {
183
219
  "name": "OFFICIAL",
184
220
  "topic": "Nifra — Plugins & middleware",
185
221
  "slug": "plugins",
186
- "code": "import { server } from \"@nifrajs/core\"\nimport { requestId, logger, etag } from \"@nifrajs/middleware\"\n\nconst app = server()\n .use(requestId()) // reuse/generate x-request-id → c.requestId (typed) + response header\n .use(logger()) // one structured line/request: { method, path, status, ms }\n .use(etag()) // content-hash ETag on GET 200s → 304 on matching If-None-Match"
222
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { requestId, logger, etag } from \"@nifrajs/middleware\"\n\nconst app = server()\n .use(requestId()) // reuse/generate x-request-id → c.requestId (typed) + response header\n .use(logger()) // one structured line/request: { method, path, status, ms }\n .use(etag()) // content-hash ETag on GET 200s → 304 on matching If-None-Match"
187
223
  },
188
224
  {
189
225
  "name": "PERF",
190
226
  "topic": "Nifra — Plugins & middleware",
191
227
  "slug": "plugins",
192
- "code": "import { server } from \"@nifrajs/core\"\nimport { compression, cacheControl } from \"@nifrajs/middleware\"\n\nconst app = server()\n .use(compression()) // gzip compressible responses (Accept-Encoding)\n .use(cacheControl(\"public, max-age=60\")) // Cache-Control on GET/HEAD 2xx (won't clobber)\n // …or a per-path policy — return undefined to leave a response untouched:\n .use(cacheControl((req) =>\n new URL(req.url).pathname.startsWith(\"/assets/\")\n ? \"public, max-age=31536000, immutable\"\n : undefined))"
228
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { compression, cacheControl } from \"@nifrajs/middleware\"\n\nconst app = server()\n .use(compression()) // gzip compressible responses (Accept-Encoding)\n .use(cacheControl(\"public, max-age=60\")) // Cache-Control on GET/HEAD 2xx (won't clobber)\n // …or a per-path policy — return undefined to leave a response untouched:\n .use(cacheControl((req) =>\n new URL(req.url).pathname.startsWith(\"/assets/\")\n ? \"public, max-age=31536000, immutable\"\n : undefined))"
193
229
  },
194
230
  {
195
231
  "name": "RELIABILITY",
196
232
  "topic": "Nifra — Plugins & middleware",
197
233
  "slug": "plugins",
198
- "code": "import { server } from \"@nifrajs/core\"\nimport { idempotency, MemoryIdempotencyStore } from \"@nifrajs/middleware\"\n\n// A retried POST with the same Idempotency-Key replays the first response instead of\n// re-running the side effect. Shared store in production (atomic claim); dev-only memory store.\nconst app = server()\napp.use(idempotency({ store: new MemoryIdempotencyStore() }))"
234
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { idempotency, MemoryIdempotencyStore } from \"@nifrajs/middleware\"\n\n// A retried POST with the same Idempotency-Key replays the first response instead of\n// re-running the side effect. Shared store in production (atomic claim); dev-only memory store.\nconst app = server()\napp.use(idempotency({ store: new MemoryIdempotencyStore() }))"
199
235
  },
200
236
  {
201
237
  "name": "SHAPING",
202
238
  "topic": "Nifra — Plugins & middleware",
203
239
  "slug": "plugins",
204
- "code": "import { server } from \"@nifrajs/core\"\nimport { methodOverride, trimTrailingSlash, language, timing, poweredBy, combine } from \"@nifrajs/middleware\"\n\nconst app = server()\n .use(methodOverride()) // POST + X-HTTP-Method-Override → PUT/PATCH/DELETE (a real pre-routing request rewrite)\n .use(trimTrailingSlash()) // canonicalize URLs: 308 redirect (or rewrite), same-origin only, conservative methods\n .use(language({ supported: [\"en\", \"fr\"], defaultLanguage: \"en\" })) // Accept-Language → c.language + Content-Language\n .use(timing()) // Server-Timing header + typed c.timing marks/measures\n// poweredBy() is opt-in (nifra emits no X-Powered-By by default). combine(a, b, c) bundles several into one plugin."
240
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { methodOverride, trimTrailingSlash, language, timing, poweredBy, combine } from \"@nifrajs/middleware\"\n\nconst app = server()\n .use(methodOverride()) // POST + X-HTTP-Method-Override → PUT/PATCH/DELETE (a real pre-routing request rewrite)\n .use(trimTrailingSlash()) // canonicalize URLs: 308 redirect (or rewrite), same-origin only, conservative methods\n .use(language({ supported: [\"en\", \"fr\"], defaultLanguage: \"en\" })) // Accept-Language → c.language + Content-Language\n .use(timing()) // Server-Timing header + typed c.timing marks/measures\n// poweredBy() is opt-in (nifra emits no X-Powered-By by default). combine(a, b, c) bundles several into one plugin."
205
241
  },
206
242
  {
207
243
  "name": "BUILD",
@@ -225,25 +261,25 @@
225
261
  "name": "BOUNDED",
226
262
  "topic": "Nifra — security & hardening",
227
263
  "slug": "security",
228
- "code": "import { server } from \"@nifrajs/core\"\n\nconst app = server()\n\n// A schema route is ALREADY bounded — the validated read enforces `maxBodyBytes`.\n// But a raw-body / file / BYO-validation route reads the body directly, which\n// `maxBodyBytes` does not cover. `c.boundedBody` caps that read:\napp.post(\"/import\", async (c) => {\n const bytes = await c.boundedBody(5 * 1024 * 1024) // cap THIS route at 5 MiB\n // Over-cap throws a flat 413; a malformed Content-Length a 400 — as control-flow\n // Responses (caught by the lifecycle like `throw redirect()`), so a handler can't\n // accidentally ignore the cap. The over-cap length is rejected BEFORE buffering;\n // a chunked / length-less body is aborted mid-stream once it crosses the cap.\n return { received: bytes.byteLength } // a returned object is serialized as JSON 200\n})\n\napp.post(\"/rpc\", async (c) => {\n const body = await c.boundedJson<{ method: string }>() // default: the server's maxBodyBytes\n // …bad JSON → 400. Then validate `body` with your schema before trusting it.\n return { method: body.method }\n})"
264
+ "code": "import { server } from \"@nifrajs/core/server\"\n\nconst app = server()\n\n// A schema route is ALREADY bounded — the validated read enforces `maxBodyBytes`.\n// But a raw-body / file / BYO-validation route reads the body directly, which\n// `maxBodyBytes` does not cover. `c.boundedBody` caps that read:\napp.post(\"/import\", async (c) => {\n const bytes = await c.boundedBody(5 * 1024 * 1024) // cap THIS route at 5 MiB\n // Over-cap throws a flat 413; a malformed Content-Length a 400 — as control-flow\n // Responses (caught by the lifecycle like `throw redirect()`), so a handler can't\n // accidentally ignore the cap. The over-cap length is rejected BEFORE buffering;\n // a chunked / length-less body is aborted mid-stream once it crosses the cap.\n return { received: bytes.byteLength } // a returned object is serialized as JSON 200\n})\n\napp.post(\"/rpc\", async (c) => {\n const body = await c.boundedJson<{ method: string }>() // default: the server's maxBodyBytes\n // …bad JSON → 400. Then validate `body` with your schema before trusting it.\n return { method: body.method }\n})"
229
265
  },
230
266
  {
231
267
  "name": "GATING",
232
268
  "topic": "Nifra — security & hardening",
233
269
  "slug": "security",
234
- "code": "import { server } from \"@nifrajs/core\"\nimport { jwt, csrf, ipRestriction, bodyLimit } from \"@nifrajs/middleware\"\n\nconst app = server()\n // JWT: the algorithm allowlist is REQUIRED; alg:none and RSA/HMAC confusion are rejected; exp enforced.\n .use(jwt({ key: process.env.JWT_SECRET!, algorithms: [\"HS256\"], issuer: \"my-app\" }))\n // Signed double-submit CSRF (HMAC) + Origin/Referer check on unsafe methods. Secret must be >= 32 bytes.\n .use(csrf({ secret: process.env.CSRF_SECRET! }))\n // Allow/deny by IPv4/IPv6 + CIDR. FAILS CLOSED with no trusted client IP; X-Forwarded-For is ignored\n // unless trustedProxies > 0 (set it to the number of proxies you actually run in front of the app).\n .use(ipRestriction({ allow: [\"10.0.0.0/8\", \"::1\"], trustedProxies: 1 }))\n // Reject oversized bodies at the EDGE by Content-Length, before routing — fails closed (411) on a\n // length-less body. (The schema / c.boundedBody cap is the read-time guard; this is the cheap pre-filter.)\n .use(bodyLimit({ maxBytes: 1_000_000 }))"
270
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { jwt, csrf, ipRestriction, bodyLimit } from \"@nifrajs/middleware\"\n\nconst app = server()\n // JWT: the algorithm allowlist is REQUIRED; alg:none and RSA/HMAC confusion are rejected; exp enforced.\n .use(jwt({ key: process.env.JWT_SECRET!, algorithms: [\"HS256\"], issuer: \"my-app\" }))\n // Signed double-submit CSRF (HMAC) + Origin/Referer check on unsafe methods. Secret must be >= 32 bytes.\n .use(csrf({ secret: process.env.CSRF_SECRET! }))\n // Allow/deny by IPv4/IPv6 + CIDR. FAILS CLOSED with no trusted client IP; X-Forwarded-For is ignored\n // unless trustedProxies > 0 (set it to the number of proxies you actually run in front of the app).\n .use(ipRestriction({ allow: [\"10.0.0.0/8\", \"::1\"], trustedProxies: 1 }))\n // Reject oversized bodies at the EDGE by Content-Length, before routing — fails closed (411) on a\n // length-less body. (The schema / c.boundedBody cap is the read-time guard; this is the cheap pre-filter.)\n .use(bodyLimit({ maxBytes: 1_000_000 }))"
235
271
  },
236
272
  {
237
273
  "name": "LOGGING",
238
274
  "topic": "Nifra — security & hardening",
239
275
  "slug": "security",
240
- "code": "import { server, jsonLogger, commonSecretPatterns } from \"@nifrajs/core\"\n\n// Key-name redaction is always on; valuePatterns adds opt-in value + message scanning.\nconst app = server({\n logger: jsonLogger(undefined, { valuePatterns: commonSecretPatterns }),\n})\n\n// logger.error(\"auth failed for user@example.com with Bearer abc.def\")\n// → { ...,\"message\":\"auth failed for [REDACTED] with [REDACTED]\" }\n// Add your own: { valuePatterns: [...commonSecretPatterns, /\\bord_[a-z0-9]+/g] }"
276
+ "code": "import { server, jsonLogger, commonSecretPatterns } from \"@nifrajs/core/server\"\n\n// Key-name redaction is always on; valuePatterns adds opt-in value + message scanning.\nconst app = server({\n logger: jsonLogger(undefined, { valuePatterns: commonSecretPatterns }),\n})\n\n// logger.error(\"auth failed for user@example.com with Bearer abc.def\")\n// → { ...,\"message\":\"auth failed for [REDACTED] with [REDACTED]\" }\n// Add your own: { valuePatterns: [...commonSecretPatterns, /\\bord_[a-z0-9]+/g] }"
241
277
  },
242
278
  {
243
279
  "name": "SSE",
244
280
  "topic": "Nifra — Streaming",
245
281
  "slug": "streaming",
246
- "code": "import { server, sse } from \"@nifrajs/core\"\n\nconst app = server()\n// Your pub-sub of choice — subscribe returns an unsubscribe function.\ndeclare const notifications: { subscribe(on: (n: { id: string }) => void): () => void }\n\n// A live feed — push events until the client disconnects.\napp.get(\"/notifications\", (c) =>\n sse(c, (stream) => {\n const off = notifications.subscribe((n) =>\n stream.send({ event: \"notification\", id: n.id, data: JSON.stringify(n) }),\n )\n // Keep the connection open until the client leaves, then tear down.\n return new Promise<void>((resolve) =>\n stream.signal.addEventListener(\"abort\", () => { off(); resolve() }, { once: true }),\n )\n }, { keepAlive: 15_000 }),\n)"
282
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { sse } from \"@nifrajs/core/sse\"\n\nconst app = server()\n// Your pub-sub of choice — subscribe returns an unsubscribe function.\ndeclare const notifications: { subscribe(on: (n: { id: string }) => void): () => void }\n\n// A live feed — push events until the client disconnects.\napp.get(\"/notifications\", (c) =>\n sse(c, (stream) => {\n const off = notifications.subscribe((n) =>\n stream.send({ event: \"notification\", id: n.id, data: JSON.stringify(n) }),\n )\n // Keep the connection open until the client leaves, then tear down.\n return new Promise<void>((resolve) =>\n stream.signal.addEventListener(\"abort\", () => { off(); resolve() }, { once: true }),\n )\n }, { keepAlive: 15_000 }),\n)"
247
283
  },
248
284
  {
249
285
  "name": "BASIC",
@@ -251,6 +287,18 @@
251
287
  "slug": "testing",
252
288
  "code": "import { assertAdversarialContract } from \"@nifrajs/testing\"\nimport { app } from \"../src/app\"\n\nconst { test } = await import(\"bun:test\")\n\ntest(\"the API contract withstands hostile inputs\", async () => {\n await assertAdversarialContract(app, { seed: 73 })\n})"
253
289
  },
290
+ {
291
+ "name": "CONTRACT_FIRST",
292
+ "topic": "Nifra — Troubleshooting",
293
+ "slug": "troubleshooting",
294
+ "code": "import { defineContract, implement } from \"@nifrajs/core/contract\"\n\n// One object type, declared upfront - NOT an N-deep stack of `Server<AddRoute<…>>` aliases.\nconst contract = defineContract({\n getUser: { method: \"GET\", path: \"/users/:id\" },\n listUsers: { method: \"GET\", path: \"/users\" },\n createUser: { method: \"POST\", path: \"/users\" },\n // ...hundreds more operations stay flat: the registry is one type, not a growing chain.\n})\n\nexport const app = implement(contract, {\n getUser: (c) => ({ id: c.params.id }),\n listUsers: () => ({ users: [] as string[] }),\n createUser: () => ({ created: true }),\n})"
295
+ },
296
+ {
297
+ "name": "MERGE_SPLIT",
298
+ "topic": "Nifra — Troubleshooting",
299
+ "slug": "troubleshooting",
300
+ "code": "import { server } from \"@nifrajs/core\"\n\n// Each domain is its OWN short server() chain, kept well under the ~95-route ceiling.\nconst users = server()\n .get(\"/users/:id\", (c) => ({ id: c.params.id }))\n .post(\"/users\", () => ({ created: true }))\n\nconst orders = server()\n .get(\"/orders/:id\", (c) => ({ id: c.params.id }))\n .post(\"/orders\", () => ({ placed: true }))\n\n// merge() adds ONE R & R2 intersection per group - no per-call context recompute - so the whole\n// app stays inside tsc's budget no matter how many groups (or routes) you compose.\nexport const app = server()\n .get(\"/health\", () => ({ ok: true }))\n .merge(users)\n .merge(orders)"
301
+ },
254
302
  {
255
303
  "name": "SERVER_ONLY_MARKER",
256
304
  "topic": "Nifra — Troubleshooting",
@@ -267,13 +315,13 @@
267
315
  "name": "OPENAPI",
268
316
  "topic": "Nifra — Types-first architecture",
269
317
  "slug": "types-first",
270
- "code": "import { server } from \"@nifrajs/core\"\nimport { openapi } from \"@nifrajs/middleware\"\nimport { GetUser } from \"./schema\" // the contract defined above\n\n// Generates an OpenAPI 3.1 document from your registered routes — lazily, on first request.\nexport const app = server()\n .use(openapi({ info: { title: \"My API\", version: \"1.0.0\" }, ui: true }))\n .get(\"/users/:id\", GetUser, (c) => ({ id: c.params.id, name: \"Ada\", role: \"admin\" as const }))\n\n// → GET /openapi.json (the spec, generated from your schemas)\n// → GET /reference (a Scalar API-reference page, because `ui: true`)"
318
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { openapi } from \"@nifrajs/middleware\"\nimport { GetUser } from \"./schema\" // the contract defined above\n\n// Generates an OpenAPI 3.1 document from your registered routes — lazily, on first request.\nexport const app = server()\n .use(openapi({ info: { title: \"My API\", version: \"1.0.0\" }, ui: true }))\n .get(\"/users/:id\", GetUser, (c) => ({ id: c.params.id, name: \"Ada\", role: \"admin\" as const }))\n\n// → GET /openapi.json (the spec, generated from your schemas)\n// → GET /reference (a Scalar API-reference page, because `ui: true`)"
271
319
  },
272
320
  {
273
321
  "name": "ROUTE",
274
322
  "topic": "Nifra — Types-first architecture",
275
323
  "slug": "types-first",
276
- "code": "import { server } from \"@nifrajs/core\"\nimport { GetUser } from \"./schema\"\n\nexport const app = server().get(\"/users/:id\", GetUser, (c) => {\n // c.params.id is typed `string` — parsed from the path and validated at the boundary.\n return { id: c.params.id, name: \"Ada\", role: \"admin\" as const }\n // ^ the return is checked against GetUser.response — a wrong shape is a tsc error.\n})"
324
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { GetUser } from \"./schema\"\n\nexport const app = server().get(\"/users/:id\", GetUser, (c) => {\n // c.params.id is typed `string` — parsed from the path and validated at the boundary.\n return { id: c.params.id, name: \"Ada\", role: \"admin\" as const }\n // ^ the return is checked against GetUser.response — a wrong shape is a tsc error.\n})"
277
325
  },
278
326
  {
279
327
  "name": "SCHEMA",
@@ -285,6 +333,6 @@
285
333
  "name": "BASIC",
286
334
  "topic": "Nifra — WebSockets",
287
335
  "slug": "websockets",
288
- "code": "import { server } from \"@nifrajs/core\"\nimport \"@nifrajs/core/ws\" // registers the WebSocket runtime app.ws() needs\n\nconst app = server()\n .get(\"/\", () => ({ ok: true }))\n .ws(\"/echo\", {\n open: (ws) => ws.send(\"welcome\"),\n message: (ws, data) => ws.send(data), // data: string | Uint8Array\n })\n\napp.listen(3000) // Bun"
336
+ "code": "import { server } from \"@nifrajs/core/server\"\nimport { websocket } from \"@nifrajs/core/ws\" // .use(websocket()) enables app.ws()\n\nconst app = server()\n .use(websocket())\n .get(\"/\", () => ({ ok: true }))\n .ws(\"/echo\", {\n open: (ws) => ws.send(\"welcome\"),\n message: (ws, data) => ws.send(data), // data: string | Uint8Array\n })\n\napp.listen(3000) // Bun"
289
337
  }
290
338
  ]