@nifrajs/cli 1.0.0-beta.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * `nifra_types` — the EXACT TypeScript of every exported `@nifrajs/*` symbol. The corpus is `types.json`
3
+ * (written into this package by `bun run gen:llms`): each entry's `signature` is the literal declaration
4
+ * parsed from the package's BUILT `.d.ts` with the TS compiler — a clean signature, never prose, never
5
+ * truncated. So an agent that needs the precise shape of a type (`RateLimitStore`, `RouteSchema`, a
6
+ * function signature) gets the authoritative source here and never has to read a `.d.ts` file.
7
+ *
8
+ * Lookup by `name` is exact (case-insensitive); `query` keyword-scores over the name (heavy) + the
9
+ * signature/doc body; no args → a per-package index of the available type names.
10
+ */
11
+ import { countBodyHits, queryTermGroups, tokenize, tokenSetHas } from "./search-terms.js";
12
+ /** Exact (case-insensitive) name lookup — returns every declaration with that name (a name can exist in
13
+ * more than one package). */
14
+ export function lookupType(types, name) {
15
+ const wanted = name.trim().toLowerCase();
16
+ return types.filter((t) => t.name.toLowerCase() === wanted);
17
+ }
18
+ /** Keyword-score types: a term in the name is worth far more than one in the signature/doc body. */
19
+ export function searchTypes(types, query, limit) {
20
+ const terms = queryTermGroups(query);
21
+ if (terms.length === 0)
22
+ return [];
23
+ const scored = [];
24
+ for (const t of types) {
25
+ const nameTokens = new Set(tokenize(t.name));
26
+ const body = `${t.signature} ${t.doc ?? ""}`.toLowerCase();
27
+ let score = 0;
28
+ for (const term of terms) {
29
+ if (tokenSetHas(nameTokens, term))
30
+ score += 8;
31
+ score += countBodyHits(body, term, 4);
32
+ }
33
+ if (score > 0)
34
+ scored.push({ ...t, score });
35
+ }
36
+ return scored.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, limit);
37
+ }
38
+ function renderEntry(t) {
39
+ const head = `## ${t.name} — ${t.kind}, \`${t.package}\``;
40
+ const doc = t.doc ? `${t.doc}\n` : "";
41
+ return `${head}\n\n\`\`\`ts\n${doc}${t.signature}\n\`\`\``;
42
+ }
43
+ /**
44
+ * Render the tool result. `name` → the exact declaration(s); else `query` → the top matches; else a
45
+ * per-package index of type names. The signatures are authoritative (generated from the built `.d.ts`),
46
+ * so this is the complete answer — there is no need to read a `.d.ts` file.
47
+ */
48
+ export function renderTypesResult(types, name, query, limit) {
49
+ if (name !== undefined && name.trim() !== "") {
50
+ const hits = lookupType(types, name);
51
+ if (hits.length === 0) {
52
+ return `No exported type named ${JSON.stringify(name)} in @nifrajs/*. Call nifra_types with a \`query\` (e.g. "rate limit") or no args for the per-package index of names.`;
53
+ }
54
+ return hits.map(renderEntry).join("\n\n---\n\n");
55
+ }
56
+ if (query !== undefined && query.trim() !== "") {
57
+ const matches = searchTypes(types, query, limit);
58
+ if (matches.length === 0) {
59
+ return `No types matched ${JSON.stringify(query)}. Try a broader term, pass an exact \`name\`, or call with no args for the index.`;
60
+ }
61
+ return matches.map(renderEntry).join("\n\n---\n\n");
62
+ }
63
+ const byPkg = new Map();
64
+ for (const t of types) {
65
+ const list = byPkg.get(t.package) ?? [];
66
+ list.push(t.name);
67
+ byPkg.set(t.package, list);
68
+ }
69
+ const index = [...byPkg.entries()]
70
+ .sort(([a], [b]) => a.localeCompare(b))
71
+ .map(([pkg, names]) => `- **${pkg}**: ${names.sort().join(", ")}`)
72
+ .join("\n");
73
+ return `# nifra types — index (${types.length} exported symbols; pass \`name\` for the exact declaration, or \`query\` to search)\n\nSignatures are generated from the built \`.d.ts\` — authoritative + complete. Use this instead of reading any \`.d.ts\` file.\n\n${index}`;
74
+ }
75
+ /** Load the bundled type corpus (`docs/types.json`), resolved relative to this module so it works from
76
+ * `src/` (dev) and `dist/` (published) alike. */
77
+ export async function loadTypesCorpus() {
78
+ const url = new URL("../docs/types.json", import.meta.url);
79
+ try {
80
+ const text = await Bun.file(url).text();
81
+ if (text.length === 0)
82
+ return undefined;
83
+ const parsed = JSON.parse(text);
84
+ return Array.isArray(parsed) && parsed.length > 0 ? parsed : undefined;
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ }
90
+ //# sourceMappingURL=types-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-search.js","sourceRoot":"","sources":["../src/types-search.ts"],"names":[],"mappings":"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"}
@@ -159,7 +159,7 @@
159
159
  "name": "IDENTITY",
160
160
  "topic": "Nifra — Plugins & middleware",
161
161
  "slug": "plugins",
162
- "code": "import { defineIdentityPlugin, server } from \"@nifrajs/core\"\n\n// A plugin that registers routes/hooks but adds NO context type is a type-IDENTITY plugin.\n// defineIdentityPlugin keeps app.use()'s return type EXACTLY the caller's server, so routes added\n// after .use() keep their types — and the typed client derived from them stays intact.\nexport const audit = defineIdentityPlugin(\"audit\", (app) =>\n app.onResponse((res) => res),\n)\n\n// /a AND /b stay fully typed across the .use():\nconst api = server().get(\"/a\", () => ({ a: 1 })).use(audit).get(\"/b\", () => ({ b: 2 }))"
162
+ "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 }))"
163
163
  },
164
164
  {
165
165
  "name": "JWT_BASIC",
@@ -404,6 +404,19 @@ return makes the typed client infer `data: never`, so you silently lose drift
404
404
  detection for that route (`nifra check` flags it). `c.set` keeps your
405
405
  plain-object return fully typed.
406
406
 
407
+ When you genuinely want a `Response` — an **error short-circuit** from a
408
+ `derive` / `beforeHandle` (auth, rate limits) — `c.json(body, status?)`
409
+ and `c.text(body, status?)` build one in a line:
410
+ `throw c.json({ error: "unauthorized" }, 401)` instead of
411
+ `new Response(JSON.stringify(…), { status: 401, headers: … })`. The second arg is a
412
+ status number or a full `ResponseInit`, and both work whether you `return` or
413
+ `throw` them. (In a route's happy path keep returning a plain object, as above, so the
414
+ typed client stays in sync.)
415
+
416
+ The request is on `c.req`, also available as `c.request` — the same name a
417
+ page loader/action receives (which in turn also accepts `ctx.req`), so one name works
418
+ in both places.
419
+
407
420
  ### Contract-first (defineContract + implement)
408
421
 
409
422
  For larger apps — or when the contract is shared across services — declare it with
@@ -1174,29 +1187,37 @@ app
1174
1187
  app.use((a) => a.decorate("db", db).derive((c) => ({ now: Date.now() })))
1175
1188
  ```
1176
1189
 
1177
- ### Route/hook plugins: keep types with defineIdentityPlugin
1190
+ ### Route/hook plugins: keep types with defineRouterPlugin
1178
1191
 
1179
1192
  A plugin that registers routes or hooks but adds **no context type** — e.g. mounting an auth
1180
- handler — should be built with `defineIdentityPlugin`, not `definePlugin`.
1181
- It threads the app's _exact_ type through `use`, so the route registry (and the
1182
- typed client derived from it) survives the plugin. A plain
1183
- `definePlugin((app) => app)` infers `app` as
1184
- `Server`, which collapses `use()`'s result — and your whole
1185
- typed client — to `any`. `@nifrajs/better-auth` is built this way, so
1193
+ router — should be built with `defineRouterPlugin` (the clearer-named alias of
1194
+ `defineIdentityPlugin`), not `definePlugin`. It threads the app's
1195
+ _exact_ type through `use`, so the route registry (and the typed client derived
1196
+ from it) survives the plugin. Mount routes as a **side effect** then `return app`
1197
+ (registering with `.get`/`.post` directly would change the type away from the
1198
+ identity, so those routes run but aren't in the typed registry — the trade that keeps everything
1199
+ else typed).
1200
+
1201
+ **Footgun:** reach for `definePlugin` here instead and a plain
1202
+ `definePlugin((app) => app.get(...))` infers `app` as
1203
+ `Server`, collapsing `use()`'s result — and your whole typed
1204
+ client — to `any`, with **no type error and no runtime error**.
1205
+ `@nifrajs/better-auth` is built with the identity form, so
1186
1206
  `server().use(betterAuth(auth)).get(...)` keeps every route typed.
1187
1207
 
1188
1208
  ```ts
1189
- import { defineIdentityPlugin, server } from "@nifrajs/core"
1209
+ import { defineRouterPlugin, server } from "@nifrajs/core"
1190
1210
 
1191
- // A plugin that registers routes/hooks but adds NO context type is a type-IDENTITY plugin.
1192
- // defineIdentityPlugin keeps app.use()'s return type EXACTLY the caller's server, so routes added
1193
- // after .use() keep their types — and the typed client derived from them stays intact.
1194
- export const audit = defineIdentityPlugin("audit", (app) =>
1195
- app.onResponse((res) => res),
1196
- )
1211
+ // A plugin that mounts routes/hooks but adds NO context type: defineRouterPlugin (the clearer name for
1212
+ // defineIdentityPlugin) keeps app.use()'s return type EXACTLY the caller's server, so routes added after
1213
+ // .use() stay typed — and the typed client derived from them stays intact.
1214
+ export const scim = defineRouterPlugin("scim", (app) => {
1215
+ app.get("/scim/v2/Users", () => ({ Resources: [] })) // mount as a SIDE EFFECT (a runtime-only route)
1216
+ return app // return the app unchanged → caller's registry preserved
1217
+ })
1197
1218
 
1198
1219
  // /a AND /b stay fully typed across the .use():
1199
- const api = server().get("/a", () => ({ a: 1 })).use(audit).get("/b", () => ({ b: 2 }))
1220
+ const api = server().get("/a", () => ({ a: 1 })).use(scim).get("/b", () => ({ b: 2 }))
1200
1221
  ```
1201
1222
 
1202
1223
  ### Lifecycle hooks
@@ -3030,6 +3051,42 @@ Vue, Solid, Svelte). Nifra dedupes the active adapter's runtime in build and dev
3030
3051
  [Dev & HMR](/docs/dev) and the `file:`-linked-package note in
3031
3052
  `AGENTS.md`.
3032
3053
 
3054
+ ###
3055
+ `client` resolves to `never` (or `data: never`)
3056
+
3057
+ The typed client is derived from your backend's type. Two things collapse it:
3058
+
3059
+ -
3060
+ **
3061
+ A route returns a raw `Response`.
3062
+ **
3063
+ That route's `data` infers `never` (Nifra can't see the shape). Return a
3064
+ plain object and shape the response with `c.set` — reach for `c.json` /
3065
+ `c.text` only for an error short-circuit (`throw` from a
3066
+ `derive` / `beforeHandle`), not a route's happy path. See
3067
+ [API & typed client](/docs/api).
3068
+
3069
+ -
3070
+ **A plugin widened the app's type.** A plugin that registers routes/hooks but whose return
3071
+ type isn't the concrete server (e.g. an untyped `app => app.onResponse(…)`)
3072
+ makes `.use()` return `Server` and the client loses your
3073
+ registry. Build it with `defineRouterPlugin(name, …)` (the clearer-named
3074
+ `defineIdentityPlugin`) so `.use()` returns your server unchanged and
3075
+ routes added after it stay typed. See
3076
+ [Plugins → keep types with defineRouterPlugin](/docs/plugins).
3077
+
3078
+ `nifra check` flags the raw-`Response` case; the plugin case surfaces as a
3079
+ `never` client at the call site.
3080
+
3081
+ ####
3082
+ Call site rejects `{ query: {…} }`
3083
+
3084
+ If `api.thing.get({ query: { … } })` errors, the route declares no
3085
+ `query` schema — its query types as `never`, so the client can't accept query
3086
+ params. The error reads out the fix; add a schema to the route:
3087
+ `.get("/thing", { query: z.object({ page: z.string() }) }, h)`. Then
3088
+ `c.query` is the validated type and the client accepts a typed `query`.
3089
+
3033
3090
  ### Still stuck?
3034
3091
 
3035
3092
  Run `nifra check --json` as the done-gate — it surfaces the import-chain leaks,
@@ -4354,6 +4411,61 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
4354
4411
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
4355
4412
  done-gate, or `nifra mcp` to give the agent live project tools.
4356
4413
 
4414
+ ## @nifrajs/cache
4415
+
4416
+ Typed KV cache for nifra — **TTL**, **stale-while-revalidate**, **tag invalidation**, and **single-flight
4417
+ stampede protection** on a **pluggable store** (in-memory by default; bring CF KV / Redis for a shared
4418
+ cache). **Dependency-free**; runs on Bun/Node/Deno/Workers.
4419
+
4420
+ ```ts
4421
+ import { createCache } from "@nifrajs/cache"
4422
+
4423
+ const cache = createCache({ defaultTtlMs: 30_000 })
4424
+
4425
+ // Cache-aside in a loader — one DB hit per key per TTL, even under a stampede:
4426
+ export async function loader({ params }) {
4427
+ const user = await cache.wrap(
4428
+ `user:${params.id}`,
4429
+ () => db.users.find(params.id),
4430
+ { ttlMs: 30_000, swrMs: 60_000, tags: [`user:${params.id}`] },
4431
+ )
4432
+ return { user }
4433
+ }
4434
+
4435
+ // On write, drop everything tagged for that user:
4436
+ await cache.invalidateTag(`user:${id}`)
4437
+ ```
4438
+
4439
+ ## Semantics
4440
+
4441
+ - **`wrap(key, loader, opts)`** — returns the cached value, or runs `loader`, stores, and returns it.
4442
+ - **Fresh** (`now < staleAt`): the cached value, no loader call.
4443
+ - **Stale-but-live** (`staleAt ≤ now < expiresAt`, i.e. within `swrMs`): the stale value is returned
4444
+ **immediately** while a **background** refresh runs (deduped). Latency stays flat; data self-heals.
4445
+ - **Miss/expired**: awaits `loader`. Concurrent misses for the same key share **one** call (no stampede).
4446
+ - A throwing `loader` is **not** cached (and in the background path goes to `onError`, never rejects the caller).
4447
+ - **`set` / `get` / `has` / `delete`**, **`invalidateTag(tag)`**, **`clear()`**.
4448
+ - TTL: `ttlMs` (→ stale) + optional `swrMs` (→ served-stale window) + optional `tags`.
4449
+
4450
+ ## Stores
4451
+
4452
+ The default `MemoryCache` is in-process with lazy expiry, a tag index, and an optional LRU cap
4453
+ (`new MemoryCache({ maxEntries: 10_000 })`). For a cache shared across instances, implement `CacheStore`
4454
+ (`get` / `set` / `delete` / `invalidateTag` / `clear`) over CF KV, Redis, etc.:
4455
+
4456
+ ```ts
4457
+ const cache = createCache({ store: new RedisCacheStore(redis) })
4458
+ ```
4459
+
4460
+ On **Cloudflare Workers** the in-memory cache is per-isolate and short-lived — back it with **CF KV** (or
4461
+ the Cache API) via a `CacheStore` for anything that should survive across requests/instances.
4462
+
4463
+ ## API
4464
+
4465
+ - `createCache(options?)` → `Cache` — `{ store?, defaultTtlMs?, now?, onError? }`.
4466
+ - `cache.wrap(key, loader, { ttlMs?, swrMs?, tags? })` · `get` · `has` · `set` · `delete` · `invalidateTag` · `clear`.
4467
+ - `MemoryCache({ maxEntries?, now? })` — the default store; implement `CacheStore` for your own.
4468
+
4357
4469
  ## @nifrajs/cli
4358
4470
 
4359
4471
  The `nifra` CLI — zero-config dev/build/start for a nifra app, plus the agent surfaces (`nifra context`,
@@ -4885,6 +4997,89 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
4885
4997
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
4886
4998
  done-gate, or `nifra mcp` to give the agent live project tools.
4887
4999
 
5000
+ ## @nifrajs/jobs
5001
+
5002
+ Typed background jobs for nifra — enqueue work off the request path, run it with **retries + exponential
5003
+ backoff + dead-lettering** on a **pluggable store**. The async companion to [`@nifrajs/cron`](../cron)
5004
+ (cron schedules; jobs does the deferred work — email, webhooks, image processing). **Dependency-free.**
5005
+
5006
+ ```ts
5007
+ import { createQueue } from "@nifrajs/jobs"
5008
+ import { t } from "@nifrajs/schema"
5009
+
5010
+ const q = createQueue()
5011
+
5012
+ const email = q.define("send-email", {
5013
+ input: t.object({ to: t.string(), subject: t.string() }), // validated at enqueue (the trust boundary)
5014
+ retries: { attempts: 5 }, // 5 tries, exponential backoff, then dead-letter
5015
+ async handler({ to, subject }, ctx) {
5016
+ await send(to, subject) // ctx = { id, name, attempt }
5017
+ },
5018
+ })
5019
+
5020
+ // In a route handler — enqueue and return immediately:
5021
+ await email.enqueue({ to: "a@b.com", subject: "Welcome" })
5022
+ await email.enqueue({ to: "b@b.com", subject: "Later" }, { delayMs: 60_000 }) // run in 1 min
5023
+
5024
+ // Start the worker on a long-running server (Bun/Node/Deno):
5025
+ const worker = q.start({ concurrency: 4 })
5026
+ // graceful shutdown: await worker.stop()
5027
+ ```
5028
+
5029
+ `enqueue` is typed against the handler's payload, and `input` (any [Standard Schema](https://standardschema.dev)
5030
+ validator — `@nifrajs/schema`'s `t`, Zod, Valibot, …) validates it before it's stored, so a bad payload
5031
+ fails at the call site, not three retries later.
5032
+
5033
+ ## Retries
5034
+
5035
+ Per-job: `retries: number` (attempts) or `{ attempts, backoff }`. A handler that throws is routed to
5036
+ `onError`, then retried with the backoff delay; after the last attempt it's **dead-lettered**, not lost.
5037
+
5038
+ ```ts
5039
+ import { exponentialBackoff, fixedBackoff } from "@nifrajs/jobs"
5040
+
5041
+ q.define("flaky", { retries: { attempts: 3, backoff: fixedBackoff(5_000) }, handler })
5042
+ createQueue({ backoff: exponentialBackoff({ baseMs: 500, maxMs: 60_000, jitter: 0.2 }) }) // queue default
5043
+ ```
5044
+
5045
+ ## Stores
5046
+
5047
+ The default `MemoryJobStore` is single-process — correct for dev and a single server, but not durable and
5048
+ not multi-worker. Implement the `JobStore` interface (`enqueue` / `lease` / `complete` / `retry` /
5049
+ `deadLetter` / `counts`) over Redis/Postgres for durability or horizontal workers:
5050
+
5051
+ ```ts
5052
+ const q = createQueue({ store: new RedisJobStore(redis) })
5053
+ ```
5054
+
5055
+ Leasing is at-least-once: a leased job is hidden for `leaseMs`; a worker that dies mid-job releases it
5056
+ back automatically. Make handlers **idempotent**.
5057
+
5058
+ ## Cloudflare Workers
5059
+
5060
+ Workers has no long-lived process, so don't call `start()`. Back the queue with a durable store and a
5061
+ [CF Queue](https://developers.cloudflare.com/queues/), enqueue via the producer binding, and drain from
5062
+ the consumer:
5063
+
5064
+ ```ts
5065
+ export default {
5066
+ async queue(_batch, env) {
5067
+ const q = createQueue({ store: new D1JobStore(env.DB) })
5068
+ await q.process() // one round; the platform schedules invocations
5069
+ },
5070
+ }
5071
+ ```
5072
+
5073
+ ## API
5074
+
5075
+ - `createQueue(options?)` → `Queue` — `{ store?, onError?, now?, defaultAttempts?, backoff? }`.
5076
+ - `queue.define(name, { handler, input?, retries? })` → typed `JobHandle` with `.enqueue(payload, { delayMs? | runAt? })`.
5077
+ - `queue.enqueue(name, payload, options?)` — enqueue by name.
5078
+ - `queue.start({ concurrency?, pollIntervalMs?, leaseMs? })` → `Worker` (`.stop()` drains gracefully).
5079
+ - `queue.process()` — run one poll round (for Workers / custom drivers). `queue.drain()` — process until empty.
5080
+ - `queue.counts()` → `{ pending, active, dead }`. `queue.store` — the underlying store.
5081
+ - Stores: `MemoryJobStore`. Backoff: `exponentialBackoff`, `fixedBackoff`, `noBackoff`.
5082
+
4888
5083
  ## @nifrajs/middleware
4889
5084
 
4890
5085
  Composable, dependency-light middleware for [nifra](../../README.md) — CORS, security headers, body
@@ -5201,6 +5396,85 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
5201
5396
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5202
5397
  done-gate, or `nifra mcp` to give the agent live project tools.
5203
5398
 
5399
+ ## @nifrajs/storage
5400
+
5401
+ Blob storage for nifra — one `StorageAdapter` interface, several adapters. The **persistence half of
5402
+ [`@nifrajs/uploads`](../uploads)**: `uploads` decides what's allowed (MIME, size, signed URLs, EXIF
5403
+ stripping); `storage` puts the bytes somewhere. **Dependency-free.**
5404
+
5405
+ ```ts
5406
+ import { FileStorage } from "@nifrajs/storage"
5407
+
5408
+ const storage = new FileStorage("./uploads")
5409
+ await storage.put("avatars/u1.png", bytes, { contentType: "image/png" })
5410
+
5411
+ const object = await storage.get("avatars/u1.png") // { body, size, contentType?, metadata? } | null
5412
+ await storage.delete("avatars/u1.png")
5413
+ const keys = await storage.list({ prefix: "avatars/" })
5414
+ ```
5415
+
5416
+ ## Adapters
5417
+
5418
+ | Adapter | Use | Notes |
5419
+ |---|---|---|
5420
+ | `MemoryStorage` | dev / tests | not durable, not shared across instances |
5421
+ | `FileStorage(root)` | a long-running server (Bun/Node/Deno) | bytes-only: `contentType` inferred from the extension, custom metadata not persisted |
5422
+ | `R2Storage(env.BUCKET)` | the edge (Cloudflare R2) | round-trips `contentType` + metadata; binding typed structurally (no `@cloudflare/workers-types`) |
5423
+
5424
+ Implement `StorageAdapter` (`put` / `get` / `delete` / `exists` / `list`) for **S3 / GCS / anything else** —
5425
+ the same five methods, and your routes don't change.
5426
+
5427
+ ## Key safety
5428
+
5429
+ Keys are POSIX-ish relative paths (`avatars/u1.png`). **Every** adapter rejects unsafe keys — absolute
5430
+ paths, `..` traversal, NUL bytes, backslashes — via `assertSafeKey`, so a `FileStorage` key can never
5431
+ escape its root and a key valid in one adapter is valid in all.
5432
+
5433
+ ```ts
5434
+ import { assertSafeKey, StorageKeyError } from "@nifrajs/storage"
5435
+ ```
5436
+
5437
+ ## API
5438
+
5439
+ - `new MemoryStorage()` · `new FileStorage(root)` · `new R2Storage(bucket)`.
5440
+ - `put(key, data, { contentType?, metadata? })` — `data` is `Uint8Array | ArrayBuffer | string`.
5441
+ - `get(key)` → `{ body, size, contentType?, metadata? } | null` · `delete(key)` · `exists(key)` · `list({ prefix?, limit? })`.
5442
+ - `assertSafeKey(key)` / `StorageKeyError` · `toBytes(data)`.
5443
+
5444
+ ## @nifrajs/testing
5445
+
5446
+ Testing helpers for nifra apps. [`@nifrajs/client`](../client)'s **`testClient`** is already the typed,
5447
+ no-network in-process request client (nifra's supertest / `inject`). This package adds what it doesn't: a
5448
+ **cookie jar** and a **cookie-persisting `testSession`**, so a login → authenticated-request flow tests as
5449
+ easily as a single request.
5450
+
5451
+ ```ts
5452
+ import { testSession } from "@nifrajs/testing"
5453
+ import { app } from "../src/app"
5454
+
5455
+ const { client, cookies } = testSession<typeof app>(app)
5456
+
5457
+ await client.auth.login.post({ email, password }) // Set-Cookie captured into the jar
5458
+ const me = await client.me.get() // Cookie sent automatically
5459
+ expect(me.ok && me.data.id).toBeDefined()
5460
+ expect(cookies.get("sid")).toBeDefined() // inspect the jar
5461
+
5462
+ await client.auth.logout.post() // a Max-Age=0 Set-Cookie clears it
5463
+ expect(cookies.get("sid")).toBeUndefined()
5464
+ ```
5465
+
5466
+ Same in-process client as `testClient` — the app's own `fetch`, no server/port/network, the full real
5467
+ lifecycle (validation, middleware, contracts, auth) and end-to-end types from `App`. The only addition is
5468
+ that every call carries and captures cookies via a shared jar.
5469
+
5470
+ ## API
5471
+
5472
+ - `testSession<App>(app, { origin?, cookies? })` → `{ client: Treaty<App>, cookies: CookieJar }`.
5473
+ - `cookieJar()` → `CookieJar` — `header()` · `applyTo(headers)` · `store(response)` · `set` · `get` · `clear` · `size`.
5474
+ Honours removal (`Max-Age=0` / past `Expires`); other cookie attributes are ignored (in-process, same-origin).
5475
+
5476
+ For a **stateless** request (no cookies), use `testClient` from `@nifrajs/client` directly.
5477
+
5204
5478
  ## @nifrajs/uploads
5205
5479
 
5206
5480
  File-upload hardening for nifra — magic-byte MIME detection, size + type validation, short-TTL signed download URLs, and EXIF/metadata stripping. Dependency-free, edge-safe.
@@ -5533,6 +5807,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5533
5807
 
5534
5808
  `betterAuth`, `BetterAuthLike`, `BetterAuthOptions`, `getSession`, `requireSession`, `RequireSessionOptions`, `SessionOf`
5535
5809
 
5810
+ ## @nifrajs/cache
5811
+
5812
+ `Cache`, `CacheOptions`, `CacheStore`, `createCache`, `MemoryCache`, `MemoryCacheOptions`, `SetOptions`, `StoredEntry`, `WrapOptions`
5813
+
5536
5814
  ## @nifrajs/client
5537
5815
 
5538
5816
  `ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `FetchFn`, `inProcessClient`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `Result`, `testClient`, `Treaty`, `TreatyFromRegistry`
@@ -5543,7 +5821,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5543
5821
 
5544
5822
  ## @nifrajs/core
5545
5823
 
5546
- `AnyServer`, `attachWebSocket`, `commonSecretPatterns`, `Context`, `ContextForOp`, `ContractShape`, `CookieOptions`, `defineContract`, `defineIdentityPlugin`, `definePlugin`, `DurableObjectNamespaceLike`, `ExecutionContext`, `FRAMEWORK_NAME`, `FrameworkError`, `FrameworkName`, `Handler`, `HandlersFor`, `IdentityPlugin`, `implement`, `InferInput`, `InferOutput`, `jsonLogger`, `LogFields`, `Logger`, `Method`, `METHODS`, `Middleware`, `NifraPlugin`, `NifraWebSocket`, `NodeServeOutcome`, `OnRequestResult`, `OperationDef`, `Params`, `parseCookies`, `Platform`, `Prettify`, `redactLogFields`, `RedactOptions`, `Registry`, `RegistryFor`, `RegistryFromImpl`, `ResponseControls`, `ResponseDef`, `robots`, `RobotsOptions`, `RobotsRule`, `RouteConfigError`, `RouteConfigErrorCode`, `RouteDescriptor`, `RouteInfo`, `Router`, `RouterMatch`, `RouteSchema`, `RunningServer`, `ScheduledController`, `ScheduledHandler`, `serializeCookie`, `server`, `Server`, `ServerOptions`, `SignatureEncoding`, `signValue`, `silentLogger`, `sitemap`, `SitemapChangeFreq`, `SitemapEntry`, `SitemapOptions`, `sse`, `SSEContext`, `SSEInit`, `SSEMessage`, `SSEStream`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `StandardTypes`, `StandardWebSocket`, `toFetchHandler`, `TopicRegistry`, `unsignValue`, `validateStandard`, `ValidationOutcome`, `verifyWebhook`, `VerifyWebhookOptions`, `Version`, `VERSION`, `WebhookFailureReason`, `WebhookProvider`, `WebhookResult`, `WebSocketContext`, `WebSocketData`, `WebSocketHandler`, `WebSocketUpgradeOutcome`
5824
+ `AnyServer`, `attachWebSocket`, `commonSecretPatterns`, `Context`, `ContextForOp`, `ContractShape`, `CookieOptions`, `defineContract`, `defineIdentityPlugin`, `definePlugin`, `defineRouterPlugin`, `DurableObjectNamespaceLike`, `ExecutionContext`, `FRAMEWORK_NAME`, `FrameworkError`, `FrameworkName`, `Handler`, `HandlersFor`, `IdentityPlugin`, `implement`, `InferInput`, `InferOutput`, `jsonLogger`, `LogFields`, `Logger`, `Method`, `METHODS`, `Middleware`, `NifraPlugin`, `NifraWebSocket`, `NodeServeOutcome`, `OnRequestResult`, `OperationDef`, `Params`, `parseCookies`, `Platform`, `Prettify`, `redactLogFields`, `RedactOptions`, `Registry`, `RegistryFor`, `RegistryFromImpl`, `ResponseControls`, `ResponseDef`, `robots`, `RobotsOptions`, `RobotsRule`, `RouteConfigError`, `RouteConfigErrorCode`, `RouteDescriptor`, `RouteInfo`, `Router`, `RouterMatch`, `RouteSchema`, `RunningServer`, `ScheduledController`, `ScheduledHandler`, `serializeCookie`, `server`, `Server`, `ServerOptions`, `SignatureEncoding`, `signValue`, `silentLogger`, `sitemap`, `SitemapChangeFreq`, `SitemapEntry`, `SitemapOptions`, `sse`, `SSEContext`, `SSEInit`, `SSEMessage`, `SSEStream`, `StandardIssue`, `StandardResult`, `StandardSchemaV1`, `StandardTypes`, `StandardWebSocket`, `toFetchHandler`, `TopicRegistry`, `unsignValue`, `validateStandard`, `ValidationOutcome`, `verifyWebhook`, `VerifyWebhookOptions`, `Version`, `VERSION`, `WebhookFailureReason`, `WebhookProvider`, `WebhookResult`, `WebSocketContext`, `WebSocketData`, `WebSocketHandler`, `WebSocketUpgradeOutcome`
5547
5825
 
5548
5826
  ## @nifrajs/cron
5549
5827
 
@@ -5569,6 +5847,14 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5569
5847
 
5570
5848
  `batch`, `BindableElement`, `BindableRoot`, `bindScope`, `computed`, `effect`, `island`, `IslandContext`, `IslandHost`, `IslandScope`, `IslandSetup`, `islandState`, `mountIslands`, `signal`, `Signal`
5571
5849
 
5850
+ ## @nifrajs/jobs
5851
+
5852
+ `Backoff`, `createQueue`, `EnqueueOptions`, `exponentialBackoff`, `ExponentialOptions`, `fixedBackoff`, `JobContext`, `JobCounts`, `JobDefinition`, `JobError`, `JobHandle`, `JobHandler`, `JobStore`, `JobValidationError`, `MemoryJobStore`, `noBackoff`, `Queue`, `QueueOptions`, `RetryPolicy`, `StandardResult`, `StandardSchemaV1`, `StoredJob`, `Worker`, `WorkerOptions`
5853
+
5854
+ ## @nifrajs/mcp
5855
+
5856
+ `bridgeScript`, `createMcpServer`, `CreateMcpServerOptions`, `defineMcpTool`, `DefineMcpToolOptions`, `defineMcpWidget`, `DefineMcpWidgetOptions`, `McpAppBridge`, `McpHttpOptions`, `McpServer`, `McpToolHandlerResult`, `McpUiIntent`, `McpWidget`, `respondMcpHttp`, `uiResourceMeta`, `widgetDocument`
5857
+
5572
5858
  ## @nifrajs/middleware
5573
5859
 
5574
5860
  `apiKey`, `ApiKeyStaticOptions`, `ApiKeyVerifyOptions`, `appendTrailingSlash`, `AuthPlugin`, `basicAuth`, `BasicAuthPlugin`, `BasicAuthStaticOptions`, `BasicAuthVerifyOptions`, `bearer`, `BearerOptions`, `bodyLimit`, `BodyLimitOptions`, `buildOpenApiDocument`, `cache`, `cacheControl`, `CacheControlOptions`, `CachedResponse`, `CacheOptions`, `combine`, `Composable`, `compression`, `CompressionOptions`, `cors`, `CorsOptions`, `createCsrfToken`, `csrf`, `CsrfOptions`, `etag`, `ETagOptions`, `healthcheck`, `HealthcheckOptions`, `idempotency`, `IdempotencyClaim`, `IdempotencyOptions`, `IdempotencyRecord`, `IdempotencyStore`, `IpMatcher`, `ipRestriction`, `IpRestrictionOptions`, `jwk`, `JwkKey`, `jwks`, `JwksOptions`, `jwt`, `JwtAlgorithm`, `JwtClaims`, `JwtHeader`, `JwtKeyResolver`, `JwtOptions`, `JwtPlugin`, `JwtVerificationKey`, `language`, `LanguageMatch`, `LanguageOptions`, `logger`, `LoggerOptions`, `MemoryIdempotencyStore`, `MemoryIdempotencyStoreOptions`, `MemoryResponseCache`, `MemoryResponseCacheOptions`, `MemoryStore`, `MemoryStoreOptions`, `methodOverride`, `MethodOverrideOptions`, `namedCombine`, `openapi`, `OpenApiInfo`, `OpenApiOptions`, `OpenApiServer`, `OpenApiTag`, `OpenApiUiOptions`, `pickLanguage`, `poweredBy`, `PoweredByOptions`, `prettyJson`, `PrettyJsonOptions`, `rateLimit`, `RateLimitOptions`, `RateLimitResult`, `RateLimitStore`, `requestId`, `RequestIdOptions`, `RequestLogFields`, `responseCache`, `ResponseCacheStore`, `RouteLike`, `securityHeaders`, `SecurityHeadersOptions`, `SecurityRequirement`, `timing`, `TimingControls`, `TimingMetric`, `TimingOptions`, `TrailingSlashOptions`, `trimTrailingSlash`, `tryVerifyJwt`, `VerifiedJwt`, `verifyCsrfToken`, `verifyJwt`, `VerifyJwtOptions`, `VerifyJwtResult`
@@ -5587,7 +5873,15 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5587
5873
 
5588
5874
  ## @nifrajs/schema
5589
5875
 
5590
- `fromTypeBox`, `NifraSchema`, `OpenAPIDocument`, `OpenAPIInfo`, `registerFormat`, `t`, `toOpenAPI`
5876
+ `decodeCursor`, `encodeCursor`, `fromTypeBox`, `NifraSchema`, `OpenAPIDocument`, `OpenAPIInfo`, `Page`, `paginate`, `registerFormat`, `t`, `toOpenAPI`
5877
+
5878
+ ## @nifrajs/storage
5879
+
5880
+ `assertSafeKey`, `FileStorage`, `ListOptions`, `MemoryStorage`, `PutOptions`, `R2BucketLike`, `R2ObjectLike`, `R2Storage`, `StorageAdapter`, `StorageData`, `StorageKeyError`, `StorageObject`, `toBytes`
5881
+
5882
+ ## @nifrajs/testing
5883
+
5884
+ `AppLike`, `cookieJar`, `CookieJar`, `testSession`, `TestSession`, `TestSessionOptions`
5591
5885
 
5592
5886
  ## @nifrajs/uploads
5593
5887
 
@@ -5595,7 +5889,7 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5595
5889
 
5596
5890
  ## @nifrajs/web
5597
5891
 
5598
- `Action`, `ACTION_GLOBAL`, `apiKey`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `enumeratePrerenderedPaths`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `GetStaticPaths`, `hashQueryKey`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaInput`, `MountRouterOptions`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `RenderAdapter`, `RenderedPage`, `renderPage`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `ScriptDescriptor`, `serializeData`, `ServerOnly`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `Submission`, `SubmitOptions`, `withISR`
5892
+ `Action`, `ACTION_GLOBAL`, `apiKey`, `buildManifest`, `CachedResponse`, `CacheStore`, `canonical`, `clientEntry`, `ClientRouter`, `ClientRouterOptions`, `createClientRouter`, `createMatcher`, `createQueryClient`, `createWebApp`, `CreateWebAppOptions`, `DATA_GLOBAL`, `DATA_HEADER`, `DEFAULT_DEV_PORT`, `defer`, `Deferred`, `disableDraft`, `DRAFT_COOKIE`, `DraftCookieControls`, `enableDraft`, `EnableDraftOptions`, `enumeratePrerenderedPaths`, `enumerateStaticRoutes`, `Fetcher`, `FetcherState`, `FetchRouteData`, `filePathToPattern`, `filePathToPatterns`, `FontDisplay`, `fontFace`, `FontFace`, `fontPreload`, `FontPreloadInput`, `FontSource`, `generateClientEntry`, `GenerateClientEntryOptions`, `generateServerManifest`, `GenerateServerManifestOptions`, `GetStaticPaths`, `hashQueryKey`, `isDraftEnabled`, `ISR_REVALIDATE_HEADER`, `ISR_STATUS_HEADER`, `ISRApp`, `ISROptions`, `ISRPlatform`, `jsonLd`, `KVCacheStore`, `KVCacheStoreOptions`, `KVNamespaceLike`, `LayoutEntry`, `LinkDescriptor`, `Loader`, `LoaderContext`, `manifest`, `Manifest`, `MemoryCacheStore`, `MemoryCacheStoreOptions`, `mergeHeads`, `meta`, `Meta`, `MetaArgs`, `MetaInput`, `MountRouterOptions`, `openGraph`, `OpenGraphInput`, `PRE_HYDRATION_GUARD`, `QueryClient`, `QueryClientOptions`, `QueryHandle`, `QueryState`, `QueryStatus`, `redirect`, `REDIRECT_HEADER`, `RedirectOptions`, `RenderAdapter`, `RenderedPage`, `renderPage`, `RenderPageOptions`, `renderPageResult`, `RenderProps`, `resolveMeta`, `revalidate`, `REVALIDATE_HEADER`, `revalidateEndpoint`, `RevalidateEndpointOptions`, `RevalidateResult`, `ROUTE_GLOBAL`, `RouteEntry`, `RouteMatch`, `RouteModule`, `RoutePattern`, `RouterState`, `routeStyles`, `ScriptDescriptor`, `serializeData`, `ServerOnly`, `StaticPath`, `StaticPaths`, `StaticRoutes`, `styles`, `Submission`, `SubmitOptions`, `withISR`
5599
5893
 
5600
5894
  ## @nifrajs/web-preact
5601
5895