@nifrajs/cli 1.0.0 → 1.2.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/dist/cli.js CHANGED
@@ -68,7 +68,7 @@ routes/ from the current directory. Run from your project root.
68
68
  Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with \`--port <n>\` (alias \`-p\`) or the
69
69
  \`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).`;
70
70
  // Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
71
- const CLI_VERSION = "1.0.0";
71
+ const CLI_VERSION = "1.2.0";
72
72
  // A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
73
73
  const asAdapter = (v) => v;
74
74
  const asBunPlugins = (v) => v;
package/dist/mcp-http.js CHANGED
@@ -19,7 +19,7 @@ import { loadTypesCorpus } from "./types-search.js";
19
19
  // tool factory + the transport core from one entry: `import { respondMcpHttp, docsTools } from "@nifrajs/cli/mcp"`.
20
20
  export { docsTools } from "./mcp-docs-tools.js";
21
21
  // Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
22
- const VERSION = "1.0.0";
22
+ const VERSION = "1.2.0";
23
23
  const SERVER_INFO = { name: "nifra-docs", version: VERSION };
24
24
  const DOCS_HEALTH = "nifra docs MCP — POST JSON-RPC 2.0 here (methods: initialize, tools/list, tools/call). Tools: nifra_docs, nifra_example.";
25
25
  /** The two project-independent tools, reading the package's bundled corpus from disk (CLI use). */
@@ -8,7 +8,7 @@ nifra is a Bun-native, contract-first, framework-agnostic full-stack TypeScript
8
8
 
9
9
  - **ESM-only.** Bun is the first-class runtime (`app.listen(port)` → `Bun.serve`); every other runtime uses `app.fetch`. No CommonJS.
10
10
  - **The client never throws.** Every `@nifrajs/client` call returns `{ ok, status, data, error }` — branch on it, don't try/catch.
11
- - **Validate at the boundary.** Per-route `body`/`query`/`params`/`headers`/`response` is any Standard Schema (zod/valibot/arktype) or `@nifrajs/schema`'s `t`; invalid input → structured `400` before the handler runs.
11
+ - **Validate at the boundary.** Per-route `body`/`query`/`params`/`headers`/`response` is any Standard Schema (zod/valibot/arktype) or `@nifrajs/schema`'s `t`; invalid input → structured `422` before the handler runs.
12
12
  - **Secure by default.** Body-size cap, `requestTimeoutMs` + `c.signal`, graceful shutdown, redacting logger, same-origin `redirect()`, constant-time secret comparison, fail-closed middleware.
13
13
  - **Money** in integer minor units; **time** parsed to absolute UTC at the boundary.
14
14
  - Throwing a `Response` anywhere in the lifecycle is control flow (returned as-is), not an error.
@@ -4411,6 +4411,61 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
4411
4411
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
4412
4412
  done-gate, or `nifra mcp` to give the agent live project tools.
4413
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
+
4414
4469
  ## @nifrajs/cli
4415
4470
 
4416
4471
  The `nifra` CLI — zero-config dev/build/start for a nifra app, plus the agent surfaces (`nifra context`,
@@ -4611,7 +4666,7 @@ export type App = typeof app // hand this to @nifrajs/client for end-to-end type
4611
4666
  surface — handlers lift over unchanged.
4612
4667
  - **Validation at the boundary.** Per-route `body`/`query` is any
4613
4668
  [Standard Schema](https://standardschema.dev) (zod/valibot/arktype, or `@nifrajs/schema`'s
4614
- `t`); invalid input is rejected with a structured `400` before the handler runs.
4669
+ `t`); invalid input is rejected with a structured `422` before the handler runs.
4615
4670
  - **Lifecycle middleware.** `derive`/`decorate` extend the typed context;
4616
4671
  `onRequest`/`beforeHandle`/`afterHandle`/`onResponse`/`onError` run around handlers;
4617
4672
  `use(middleware)` applies a bundle.
@@ -4942,6 +4997,89 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
4942
4997
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
4943
4998
  done-gate, or `nifra mcp` to give the agent live project tools.
4944
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
+
4945
5083
  ## @nifrajs/middleware
4946
5084
 
4947
5085
  Composable, dependency-light middleware for [nifra](../../README.md) — CORS, security headers, body
@@ -5258,6 +5396,85 @@ one cheap read instead of the whole corpus. For the wider framework: the repo's
5258
5396
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
5259
5397
  done-gate, or `nifra mcp` to give the agent live project tools.
5260
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
+
5261
5478
  ## @nifrajs/uploads
5262
5479
 
5263
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.
@@ -5590,6 +5807,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5590
5807
 
5591
5808
  `betterAuth`, `BetterAuthLike`, `BetterAuthOptions`, `getSession`, `requireSession`, `RequireSessionOptions`, `SessionOf`
5592
5809
 
5810
+ ## @nifrajs/cache
5811
+
5812
+ `Cache`, `CacheOptions`, `CacheStore`, `createCache`, `MemoryCache`, `MemoryCacheOptions`, `SetOptions`, `StoredEntry`, `WrapOptions`
5813
+
5593
5814
  ## @nifrajs/client
5594
5815
 
5595
5816
  `ActionArgs`, `ActionData`, `ApiError`, `ApiProxy`, `client`, `ClientOptions`, `FetchFn`, `inProcessClient`, `Jsonify`, `LoaderArgs`, `LoaderData`, `RegistryOf`, `Result`, `testClient`, `Treaty`, `TreatyFromRegistry`
@@ -5626,6 +5847,10 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5626
5847
 
5627
5848
  `batch`, `BindableElement`, `BindableRoot`, `bindScope`, `computed`, `effect`, `island`, `IslandContext`, `IslandHost`, `IslandScope`, `IslandSetup`, `islandState`, `mountIslands`, `signal`, `Signal`
5628
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
+
5629
5854
  ## @nifrajs/mcp
5630
5855
 
5631
5856
  `bridgeScript`, `createMcpServer`, `CreateMcpServerOptions`, `defineMcpTool`, `DefineMcpToolOptions`, `defineMcpWidget`, `DefineMcpWidgetOptions`, `McpAppBridge`, `McpHttpOptions`, `McpServer`, `McpToolHandlerResult`, `McpUiIntent`, `McpWidget`, `respondMcpHttp`, `uiResourceMeta`, `widgetDocument`
@@ -5648,7 +5873,15 @@ done-gate, or `nifra mcp` to give the agent live project tools.
5648
5873
 
5649
5874
  ## @nifrajs/schema
5650
5875
 
5651
- `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`
5652
5885
 
5653
5886
  ## @nifrajs/uploads
5654
5887
 
package/docs/types.json CHANGED
@@ -569,7 +569,7 @@
569
569
  "name": "CacheOptions",
570
570
  "kind": "interface",
571
571
  "package": "@nifrajs/middleware",
572
- "signature": "export interface CacheOptions {\n readonly store: ResponseCacheStore;\n /** Time to live in milliseconds. */\n readonly ttlMs: number;\n /** Methods cached. Default `[\"GET\", \"HEAD\"]`. */\n readonly methods?: readonly string[];\n /** Status predicate. Default: `200` only. */\n readonly status?: (status: number) => boolean;\n /** Header names included in the cache key and emitted in `Vary`. */\n readonly vary?: readonly string[];\n /** Custom cache key. Receives the normalized `vary` headers through the request itself. */\n readonly key?: (request: Request) => string;\n /** Maximum response bytes to store. Default `1_000_000`. Larger responses pass through. */\n readonly maxBytes?: number;\n /** Respect request `Cache-Control: no-cache/no-store`. Default `true`. */\n readonly respectRequestCacheControl?: boolean;\n /** Respect response `Cache-Control: private/no-store`. Default `true`. */\n readonly respectResponseCacheControl?: boolean;\n /** Cache responses with `Set-Cookie`. Default `false`. */\n readonly cacheSetCookie?: boolean;\n /** Response header for cache status. Default `\"x-nifra-cache\"`; set `false` to disable. */\n readonly cacheStatusHeader?: string | false;\n}"
572
+ "signature": "export interface CacheOptions {\n readonly store: ResponseCacheStore;\n /** Time to live in milliseconds. */\n readonly ttlMs: number;\n /** Methods cached. Default `[\"GET\", \"HEAD\"]`. */\n readonly methods?: readonly string[];\n /** Status predicate. Default: `200` only. */\n readonly status?: (status: number) => boolean;\n /** Header names included in the cache key and emitted in `Vary`. */\n readonly vary?: readonly string[];\n /** Custom cache key. Receives the normalized `vary` headers through the request itself. */\n readonly key?: (request: Request) => string;\n /** Maximum response bytes to store. Default `1_000_000`. Larger responses pass through. */\n readonly maxBytes?: number;\n /** Respect request `Cache-Control: no-cache/no-store`. Default `true`. */\n readonly respectRequestCacheControl?: boolean;\n /** Respect response `Cache-Control: private/no-store`. Default `true`. */\n readonly respectResponseCacheControl?: boolean;\n /** Cache responses with `Set-Cookie`. Default `false`. */\n readonly cacheSetCookie?: boolean;\n /** Cache responses to requests bearing `Authorization` or `Cookie` even when the response is not\n * explicitly `Cache-Control: public` (or `s-maxage`). Default `false`: a shared cache must not store a\n * personalized response (RFC 9111 §3.5), or it would be replayed to other users. Leave this off unless\n * the cached route is genuinely the same for every caller. */\n readonly cacheAuthenticated?: boolean;\n /** Response header for cache status. Default `\"x-nifra-cache\"`; set `false` to disable. */\n readonly cacheStatusHeader?: string | false;\n}"
573
573
  },
574
574
  {
575
575
  "name": "CacheStore",
@@ -1414,8 +1414,8 @@
1414
1414
  "name": "docsTools",
1415
1415
  "kind": "function",
1416
1416
  "package": "@nifrajs/cli",
1417
- "signature": "export declare function docsTools(loadDocs: () => Promise<string | undefined>, loadExamples: () => Promise<Example[] | undefined>): McpTool[];",
1418
- "doc": "/** Build `nifra_docs` + `nifra_example` over injected corpus loaders. */"
1417
+ "signature": "export declare function docsTools(loadDocs: () => Promise<string | undefined>, loadExamples: () => Promise<Example[] | undefined>, loadTypes: () => Promise<TypeEntry[] | undefined>): McpTool[];",
1418
+ "doc": "/** Build `nifra_docs` + `nifra_example` + `nifra_types` over injected corpus loaders. */"
1419
1419
  },
1420
1420
  {
1421
1421
  "name": "DoctorAppliedFix",
@@ -2168,8 +2168,7 @@
2168
2168
  "name": "idempotency",
2169
2169
  "kind": "function",
2170
2170
  "package": "@nifrajs/middleware",
2171
- "signature": "export declare function idempotency(options: IdempotencyOptions): Middleware;",
2172
- "doc": "/**\n * Idempotency-key middleware. Apply with `app.use(idempotency({ store }))`.\n *\n * **`Set-Cookie` is intentionally not cached or replayed** — a cookie set on the first request is\n * session-specific, so replaying it to a different caller (key collision or abuse) would leak/fixate a\n * session. Cache the body + status + the rest of the headers; let auth cookies re-issue per request.\n *\n * Caching buffers the response body, so apply this to JSON/API routes, not streaming SSR responses.\n */"
2171
+ "signature": "export declare function idempotency(options: IdempotencyOptions): Middleware;"
2173
2172
  },
2174
2173
  {
2175
2174
  "name": "IdempotencyClaim",
@@ -2181,7 +2180,7 @@
2181
2180
  "name": "IdempotencyOptions",
2182
2181
  "kind": "interface",
2183
2182
  "package": "@nifrajs/middleware",
2184
- "signature": "export interface IdempotencyOptions {\n /** Where claims + cached responses live. `MemoryIdempotencyStore` for dev; a shared store in production. */\n readonly store: IdempotencyStore;\n /** Header carrying the key. Default `\"idempotency-key\"`. */\n readonly header?: string;\n /** Methods the guard applies to. Default `[\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]` (unsafe methods). */\n readonly methods?: readonly string[];\n /** How long a completed response is replayable, in ms. Default 24h. */\n readonly ttlMs?: number;\n /** How long the in-flight lock survives a crashed handler, in ms. Default 60s. */\n readonly lockTtlMs?: number;\n /** Max response bytes to cache. A larger response is returned but **not** stored. Default 1 MiB. */\n readonly maxBytes?: number;\n /** Whether a response should be cached for replay. Default: status `< 500` (don't replay transient 5xx). */\n readonly shouldCache?: (response: Response) => boolean;\n}"
2183
+ "signature": "export interface IdempotencyOptions {\n /** Where claims + cached responses live. `MemoryIdempotencyStore` for dev; a shared store in production. */\n readonly store: IdempotencyStore;\n /** Header carrying the key. Default `\"idempotency-key\"`. */\n readonly header?: string;\n /** Methods the guard applies to. Default `[\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]` (unsafe methods). */\n readonly methods?: readonly string[];\n /** How long a completed response is replayable, in ms. Default 24h. */\n readonly ttlMs?: number;\n /** How long the in-flight lock survives a crashed handler, in ms. Default 60s. */\n readonly lockTtlMs?: number;\n /** Max response bytes to cache. A larger response is returned but **not** stored. Default 1 MiB. */\n readonly maxBytes?: number;\n /** Whether a response should be cached for replay. Default: status `< 500` (don't replay transient 5xx). */\n readonly shouldCache?: (response: Response) => boolean;\n /**\n * Derive the store key from the request. Default: the `header` value **scoped by method + path**, so the\n * same key on a different endpoint can't collide. Return a **principal-scoped** key (e.g.\n * `` `${userId}:${req.headers.get(\"idempotency-key\")}` ``) so one user can't replay another's stored\n * response on a shared key. Return `null`/`\"\"` to skip dedupe for this request.\n */\n readonly key?: (req: Request, header: string) => string | null;\n}"
2185
2184
  },
2186
2185
  {
2187
2186
  "name": "IdempotencyRecord",
@@ -2873,6 +2872,13 @@
2873
2872
  "signature": "export declare function loadReactDomServer(resolve?: ResolveSync | undefined): Promise<ReactDomServer>;",
2874
2873
  "doc": "/**\n * Load `react-dom/server`, preferring the app-root-resolved copy under the Bun runtime. Exported for unit\n * tests: `resolve` defaults to the ambient `Bun.resolveSync` (undefined on non-Bun hosts), and a test can\n * inject a stub that succeeds (re-root branch) or throws (fallback branch) to cover both deterministically\n * without depending on the machine's node_modules layout.\n */"
2875
2874
  },
2875
+ {
2876
+ "name": "loadTypesCorpus",
2877
+ "kind": "function",
2878
+ "package": "@nifrajs/cli",
2879
+ "signature": "export declare function loadTypesCorpus(): Promise<TypeEntry[] | undefined>;",
2880
+ "doc": "/** Load the bundled type corpus (`docs/types.json`), resolved relative to this module so it works from\n * `src/` (dev) and `dist/` (published) alike. */"
2881
+ },
2876
2882
  {
2877
2883
  "name": "Locale",
2878
2884
  "kind": "type",
@@ -2906,6 +2912,13 @@
2906
2912
  "package": "@nifrajs/middleware",
2907
2913
  "signature": "export interface LoggerOptions {\n /** Sink for each line. Default: `console.log(JSON.stringify(fields))`. Route to your own logger here. */\n readonly log?: (fields: RequestLogFields) => void;\n}"
2908
2914
  },
2915
+ {
2916
+ "name": "lookupType",
2917
+ "kind": "function",
2918
+ "package": "@nifrajs/cli",
2919
+ "signature": "export declare function lookupType(types: readonly TypeEntry[], name: string): TypeEntry[];",
2920
+ "doc": "/** Exact (case-insensitive) name lookup — returns every declaration with that name (a name can exist in\n * more than one package). */"
2921
+ },
2909
2922
  {
2910
2923
  "name": "Manifest",
2911
2924
  "kind": "interface",
@@ -4262,6 +4275,13 @@
4262
4275
  "signature": "export declare function renderSizeReport(report: SizeReport): string;",
4263
4276
  "doc": "/**\n * Render a {@link SizeReport} as a terse aligned table (biggest first) with a totals row — the text\n * `nifra build --report` prints. Pure (string in, string out) so the formatting is unit-testable.\n */"
4264
4277
  },
4278
+ {
4279
+ "name": "renderTypesResult",
4280
+ "kind": "function",
4281
+ "package": "@nifrajs/cli",
4282
+ "signature": "export declare function renderTypesResult(types: readonly TypeEntry[], name: string | undefined, query: string | undefined, limit: number): string;",
4283
+ "doc": "/**\n * Render the tool result. `name` → the exact declaration(s); else `query` → the top matches; else a\n * per-package index of type names. The signatures are authoritative (generated from the built `.d.ts`),\n * so this is the complete answer — there is no need to read a `.d.ts` file.\n */"
4284
+ },
4265
4285
  {
4266
4286
  "name": "reproduciblePath",
4267
4287
  "kind": "function",
@@ -4948,6 +4968,12 @@
4948
4968
  "package": "@nifrajs/cli",
4949
4969
  "signature": "export interface ScoredSection extends DocSection {\n readonly score: number;\n}"
4950
4970
  },
4971
+ {
4972
+ "name": "ScoredType",
4973
+ "kind": "interface",
4974
+ "package": "@nifrajs/cli",
4975
+ "signature": "export interface ScoredType extends TypeEntry {\n readonly score: number;\n}"
4976
+ },
4951
4977
  {
4952
4978
  "name": "ScriptDescriptor",
4953
4979
  "kind": "interface",
@@ -4995,6 +5021,13 @@
4995
5021
  "signature": "export interface SearchTermGroup {\n readonly term: string;\n readonly variants: readonly string[];\n}",
4996
5022
  "doc": "/**\n * Tiny deterministic query expansion for the agent-facing docs/example search tools.\n *\n * This is intentionally not \"semantic search\": the corpora are small, curated, and bundled with the\n * package. A few framework-specific aliases plus safe prefix matching cover the common human/agent\n * wording drift (\"authentication\" vs \"auth\", \"ws\" vs \"websocket\") without adding dependencies,\n * model calls, or nondeterministic ranking.\n */"
4997
5023
  },
5024
+ {
5025
+ "name": "searchTypes",
5026
+ "kind": "function",
5027
+ "package": "@nifrajs/cli",
5028
+ "signature": "export declare function searchTypes(types: readonly TypeEntry[], query: string, limit: number): ScoredType[];",
5029
+ "doc": "/** Keyword-score types: a term in the name is worth far more than one in the signature/doc body. */"
5030
+ },
4998
5031
  {
4999
5032
  "name": "secretBytes",
5000
5033
  "kind": "function",
@@ -5079,7 +5112,7 @@
5079
5112
  "name": "Server",
5080
5113
  "kind": "class",
5081
5114
  "package": "@nifrajs/core",
5082
- "signature": "export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {\n private readonly router;\n /** WebSocket routes, matched separately at upgrade time (a GET + `Upgrade: websocket`). */\n private readonly wsRouter;\n private wsRouteCount;\n /** In-process pub/sub backing `ws.subscribe(topic)` + `app.publish(topic, data)` (single-instance).\n * Created by the first `app.ws()` via the `@nifrajs/core/ws` runtime — `undefined` until then, so a\n * no-WebSocket app never constructs (or bundles) it. */\n private topics;\n private readonly routeList;\n private readonly maxBodyBytes;\n private readonly requestTimeoutMs;\n private readonly gracefulSignals;\n private readonly logger;\n private bunServer;\n private readonly derives;\n private readonly decorations;\n private readonly beforeHandleHooks;\n private readonly afterHandleHooks;\n private readonly onErrorHooks;\n private readonly aroundHooks;\n private readonly onRequestHooks;\n private readonly onResponseHooks;\n private readonly responseRequests;\n /** Names of plugins/middleware already applied via `use` — for idempotent dedupe. */\n private readonly appliedPlugins;\n constructor(options?: ServerOptions);\n /** Add a per-request, computed context extension for subsequent routes. */\n derive<D extends object>(fn: (context: Context & Ctx) => MaybePromise<D>): Server<R, Ctx & D>;\n /** Add a static context value for subsequent routes. */\n decorate<const K extends string, V>(key: K, value: V): Server<R, Ctx & Record<K, V>>;\n /**\n * Run before routing on the raw request. Return a `Response` to short-circuit, or a replacement\n * `Request` to continue routing with a rewritten method/URL/headers. Global.\n */\n onRequest(fn: (req: Request) => MaybePromise<OnRequestResult>): this;\n /** Run after validation, before the handler; a non-`undefined` return short-circuits. Order-scoped. */\n beforeHandle(fn: (context: Context & Ctx) => MaybePromise<unknown>): this;\n /**\n * Wrap the matched route lifecycle for subsequent routes. This is intentionally generic over the\n * route output, so wrappers like async context storage do not force Node's direct JSON path through\n * a Web `Response`. The first registered wrapper is outermost.\n */\n around(fn: <T>(context: Context & Ctx, next: () => MaybePromise<T>) => MaybePromise<T>): this;\n /** Transform the handler's result before it is serialized. Order-scoped. */\n afterHandle(fn: (result: unknown, context: Context & Ctx) => MaybePromise<unknown>): this;\n /** Handle a thrown error; a non-`undefined` return becomes the response (else the default 500). Order-scoped. */\n onError(fn: (error: unknown, context: Context & Ctx) => MaybePromise<unknown>): this;\n /** Transform every outgoing response — success, error, 404, 405, short-circuit. Global. */\n onResponse(fn: (response: Response, req: Request) => MaybePromise<Response>): this;\n /**\n * Apply a type-**identity** plugin ({@link IdentityPlugin}, from {@link defineIdentityPlugin}) — it\n * registers routes/hooks but doesn't change the types, so this returns `this` with the route registry\n * and context fully intact. This overload exists specifically so a *named* identity plugin (e.g.\n * `@nifrajs/better-auth`) threads the registry: its `& { pluginName }` intersection would otherwise\n * defeat the generic inference of the transforming overload below and collapse the result to `any`.\n */\n use(plugin: IdentityPlugin): this;\n /**\n * Apply a **plugin function** — `(app) => app`, typically built with {@link definePlugin}. It's\n * called with `this` and its result is returned, so an inline plugin's `derive`/`decorate` thread\n * the added context to handlers defined after `use` (the overload is generic over the concrete\n * `this`). A named plugin already applied is skipped (idempotent dedupe).\n */\n use<Out extends AnyServer>(plugin: (app: this) => Out): Out;\n /**\n * Apply a {@link Middleware} bundle — wire each hook it provides to its lifecycle point. Returns\n * `this` (no context-type merging); call it before the routes its `beforeHandle`/`afterHandle`\n * should cover (those are order-scoped; `onRequest`/`onResponse` are global). A named bundle already\n * applied is skipped (idempotent).\n */\n use(mw: Middleware): this;\n get<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"GET\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n get<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"GET\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n post<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"POST\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n post<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"POST\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n put<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"PUT\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n put<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"PUT\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n patch<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"PATCH\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n patch<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"PATCH\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n delete<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"DELETE\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n delete<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"DELETE\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n /**\n * Register a **WebSocket** route. The connection upgrades on a `GET` to `path` carrying\n * `Upgrade: websocket`; the optional `handler.upgrade(c)` runs in the request context first and may\n * reject (return a `Response`) or seed per-connection `ws.data`. WebSockets are served by the\n * adapter (`listen()`, `@nifrajs/node`, `@nifrajs/deno`, `toFetchHandler`) — not by bare `app.fetch`, which\n * has no socket (a WS path through `app.fetch` is a normal HTTP response).\n *\n * app.ws(\"/chat\", { open: (ws) => ws.send(\"hi\"), message: (ws, data) => ws.send(data) })\n */\n ws<Data = unknown, Schema extends StandardSchemaV1 | undefined = undefined>(path: string, handler: WebSocketHandler<Data, EnvOf<Ctx>, Schema>): this;\n /**\n * Broadcast `data` to every WebSocket connection subscribed to `topic` (via `ws.subscribe(topic)`).\n * In-process and **single-instance** (see {@link TopicRegistry}) — a multi-instance deploy must bridge\n * an external fan-out (Redis, a Durable Object) to this. A no-op when nobody is subscribed.\n */\n publish(topic: string, data: string | ArrayBufferView | ArrayBuffer): void;\n private route;\n /**\n * Low-level route registration shared by the inline builder and `implement()`.\n * Captures the server's current `derive`/`decorate` chain into the route — this\n * is the \"compiled\", order-scoped per-route chain.\n */\n register(method: Method, path: string, schema: RouteSchema | undefined, handler: (context: never) => unknown): void;\n /**\n * Enumerate the registered routes (method, path, input schemas), in registration\n * order. Powers `toOpenAPI` and other introspection; the router trie itself no\n * longer holds the original patterns.\n */\n routes(): ReadonlyArray<RouteDescriptor>;\n /**\n * Resolve a `Request` to a `Response` — the whole lifecycle, testable without a port. The\n * optional `platform` carries edge inputs (`env`, `waitUntil`); edge adapters pass it, and\n * Bun/Node/Deno omit it (then `c.env` is `undefined` and `c.waitUntil` runs fire-and-forget).\n */\n fetch(req: Request, platform?: Platform<EnvOf<Ctx>>): MaybePromise<Response>;\n private fetchSource;\n /**\n * Like {@link fetch}, but renders a plain-data result **without** building a Web `Response` — the\n * `@nifrajs/node` adapter serializes the returned primitives straight to the socket, skipping the undici\n * `Response` build + body drain (the bulk of the Node bridge cost, measured ≈4µs/req). A handler that\n * returns a `Response`, an error/short-circuit, or any registered `onResponse` hook falls back to the\n * full Web path (`{ kind: \"response\" }`), so behavior is identical — only the common JSON-data case is\n * faster. Same lifecycle as {@link fetch} (body cap, validation, hooks all run); only the final\n * render differs.\n */\n resolveNode(req: Request, platform?: Platform<EnvOf<Ctx>>): MaybePromise<NodeServeOutcome>;\n resolveNodeSource(source: RequestSource, platform?: Platform<EnvOf<Ctx>>): MaybePromise<NodeServeOutcome>;\n /**\n * Resolve a WebSocket upgrade — the seam every serving adapter uses. Returns `pass` (not a WS\n * upgrade for a registered route → handle as normal HTTP), `reject` (a WS route matched but\n * `upgrade()` rejected, or the path was malformed → return `response`), or `upgrade` (perform the\n * runtime upgrade, then dispatch the native socket's events to `handler`, seeding `ws.data` with\n * `data`). Runs the route's `upgrade(c)` guard in a real request context. Synchronous unless\n * `upgrade()` is async; a throw rejects with a flat 500 (no detail leaked).\n */\n resolveWebSocketUpgrade(req: Request, platform?: Platform<EnvOf<Ctx>>): MaybePromise<WebSocketUpgradeOutcome>;\n /** Bun `fetch` when WS routes exist: try a WS upgrade first, else run the normal HTTP lifecycle.\n * `undefined` ⇒ Bun owns the upgraded socket; a `Response` ⇒ a normal reply or a rejected upgrade.\n * (The socket dispatch itself lives in `ws-bun.ts`, loaded via `@nifrajs/core/ws`.) */\n private bunFetchWithWebSocket;\n /**\n * The shared lifecycle, generic over how the final value is rendered: `finalize` turns a handler's\n * result + `set` into the output `T` (`toResponse` → a Web `Response`; `toNodeOutcome` → node-direct\n * primitives), `wrapResponse` lifts an early/error `Response` into that same `T`, and `onTimeout`\n * produces the 503. The Web `fetch` and `resolveNode` are thin callers over this one routing +\n * context + lifecycle implementation — no duplication across the trust boundary.\n */\n private dispatch;\n /**\n * onRequest short-circuit path. Synchronous as long as every hook returns synchronously (the\n * common case — e.g. CORS returning `undefined` for a non-preflight request): an `async` version\n * here put EVERY request of any app with one onRequest hook onto the promise machinery, profiled\n * at ~13% of a realistic request. The first hook that returns a Promise hands the REMAINING\n * hooks to the async continuation; behavior is identical.\n */\n private runWithOnRequest;\n /** Async tail of {@link runWithOnRequest}: applies the first awaited hook's outcome, then runs\n * the remaining hooks (awaiting freely — we're already async here). */\n private continueOnRequest;\n private takeResponseRequest;\n /**\n * Route → build context → run. Synchronous through to the handler for a **bare** route\n * ({@link RouteEntry.bare}), so a sync handler produces its result with zero promise allocations;\n * routes with validation/hooks keep the full async {@link runLifecycle}, unchanged.\n */\n private routeAndRun;\n /** The narrowest bare route: a syntactic `() => ...` handler cannot observe the context argument, so\n * successful requests can skip allocating `RequestContext`. Errors still allocate one for logging. */\n private runContextlessBare;\n private contextlessBareError;\n /**\n * The synchronous fast path for a {@link RouteEntry.bare} route: apply static decorations, call the\n * handler, render the result — **no `await`** unless the handler itself returns a promise. It mirrors\n * the bare slice of {@link runLifecycle} (which a bare route would otherwise no-op through) and shares\n * {@link logRequestError}; a bare route has no `onError` hooks, so error handling is fully synchronous\n * (a thrown `Response` is control flow; anything else is a logged flat 500). This is where nifra skips\n * the per-request async-frame tax — the same win codegen routers get, but without `eval`.\n */\n private runBare;\n /** Bare-route error rendering — identical to {@link runLifecycle}'s catch minus the (absent) onError\n * loop: a thrown `Response` is returned as deliberate control flow; anything else is logged + 500. */\n /**\n * Build a route's fused Web lane (see {@link RouteEntry.fusedWeb}). Composition happens once at\n * registration; the returned closure is what every request to the route runs. Behavior is\n * byte-identical to the generic `runBare`/`runContextlessBare` + `toResponse` pair — same\n * decoration order, same error routing (thrown `Response` = control flow; anything else logs and\n * 500s), same respond semantics (the lifecycle parity suite pins it).\n */\n private buildFusedWeb;\n private bareError;\n private runWithAround;\n private runAround;\n private runBodyOnly;\n /** Validate + run the handler for the bodyOnly path — shared by the inline fast path and the\n * streaming fallback. A method (not per-request closures) so the hot path allocates nothing\n * beyond the one `.then` continuation. */\n private finishBodyOnly;\n private applyBodyValidation;\n private runQueryOnly;\n /** Validate-result → set `ctx.query` → run handler. A method (not a per-request closure), the\n * query analogue of {@link applyBodyValidation}. */\n private applyQueryValidation;\n /**\n * Thread the response through each global `onResponse` hook. Stays SYNCHRONOUS until a hook\n * actually returns a Promise — an `async` version forced a promise + microtask on EVERY response\n * of any app with an onResponse hook (cors/securityHeaders/etag/timing all use onResponse), the\n * same ~13%/req tax the onRequest walk was de-async'd to avoid. The first async hook hands the\n * rest to {@link continueOnResponse}.\n */\n private applyOnResponse;\n /** Async tail of {@link applyOnResponse}: runs the remaining hooks once one has gone async. */\n private continueOnResponse;\n /**\n * Bound the response time. On timeout we abort `ctx.signal` (so cancellation-aware\n * handlers can bail) and return 503; the in-flight work keeps running but its\n * result is discarded — JS can't forcibly cancel a promise.\n */\n private withTimeout;\n /**\n * The post-match lifecycle: validate → derive → beforeHandle → handler → afterHandle, with onError.\n * Generic over the render: a success value goes through `finalize(result, set)` (a Web `Response`, or\n * node-direct primitives); an early/error `Response` (validation, thrown, 500) through `wrapResponse`.\n */\n private runLifecycle;\n private handleLifecycleError;\n /** Log an unhandled request error to the (redacting) logger — shared by {@link runLifecycle} and the\n * bare fast path ({@link bareError}) so both record the same fields. Never throws; never leaks. */\n private logRequestError;\n private readAndValidateBody;\n /**\n * Read the body as text, capped at `maxBodyBytes`. Rejects (`null`) on a\n * `Content-Length` over the cap *before* buffering, and aborts mid-stream once the\n * running byte count exceeds it — so a lying or absent length can't force us to\n * buffer an oversized payload.\n *\n * Fast path: when a non-chunked request carries a `Content-Length` within the cap,\n * a native `req.json()` is already bounded — under HTTP/1.1 + HTTP/2 framing the\n * runtime delivers at most `Content-Length` bytes — so we skip the manual stream\n * loop and a separate text decode. It trusts the wire\n * *framing*, not the header value: nifra only ever receives framed Requests from the\n * runtime's HTTP server, never a hand-built one with a mismatched length. Chunked or\n * length-less bodies fall through to the streaming byte-cap guard below.\n */\n private readBoundedJson;\n /**\n * Start a `Bun.serve` instance bound to `port` (use `0` for an ephemeral port).\n *\n * `reusePort` sets `SO_REUSEPORT` so **multiple processes can bind the same port** and the kernel\n * load-balances connections across them — the standard way to use every core (Bun is\n * single-threaded per process). Spawn one process per core, each calling\n * `app.listen(PORT, { reusePort: true })`; see `examples/cluster.ts`. Every process must opt in,\n * and all of them must be the same app. Linux balances ~evenly; macOS accepts the flag but may\n * favor one socket (fine for dev, measure on Linux for production numbers).\n */\n listen(port: number, options?: {\n readonly reusePort?: boolean;\n }): RunningServer;\n /**\n * Gracefully stop: wait for in-flight requests to finish (up to `drainMs`), then\n * issue a single terminal stop — graceful if everything drained, forced if\n * stragglers remain. Safe to call when not listening.\n *\n * The Bun semantics: poll `pendingRequests` (awaiting\n * `stop()`'s promise drops in-flight requests), and decide graceful-vs-forced in\n * ONE call (Bun can't escalate an already-graceful `stop()` to a forced close).\n * New connections may be accepted during the drain window; in a real deploy the\n * load balancer has already stopped routing here, and `drainMs` bounds it.\n */\n stop({ drainMs }?: {\n drainMs?: number;\n }): Promise<void>;\n private installSignalHandlers;\n}",
5115
+ "signature": "export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {\n private readonly router;\n /** WebSocket routes, matched separately at upgrade time (a GET + `Upgrade: websocket`). */\n private readonly wsRouter;\n private wsRouteCount;\n /** In-process pub/sub backing `ws.subscribe(topic)` + `app.publish(topic, data)` (single-instance).\n * Created by the first `app.ws()` via the `@nifrajs/core/ws` runtime — `undefined` until then, so a\n * no-WebSocket app never constructs (or bundles) it. */\n private topics;\n private readonly routeList;\n private readonly maxBodyBytes;\n private readonly wsMaxPayloadBytes;\n private readonly requestTimeoutMs;\n private readonly gracefulSignals;\n private readonly logger;\n private bunServer;\n private readonly derives;\n private readonly decorations;\n private readonly beforeHandleHooks;\n private readonly afterHandleHooks;\n private readonly onErrorHooks;\n private readonly aroundHooks;\n private readonly onRequestHooks;\n private readonly onResponseHooks;\n private readonly responseRequests;\n /** Names of plugins/middleware already applied via `use` — for idempotent dedupe. */\n private readonly appliedPlugins;\n constructor(options?: ServerOptions);\n /** Add a per-request, computed context extension for subsequent routes. */\n derive<D extends object>(fn: (context: Context & Ctx) => MaybePromise<D>): Server<R, Ctx & D>;\n /** Add a static context value for subsequent routes. */\n decorate<const K extends string, V>(key: K, value: V): Server<R, Ctx & Record<K, V>>;\n /**\n * Run before routing on the raw request. Return a `Response` to short-circuit, or a replacement\n * `Request` to continue routing with a rewritten method/URL/headers. Global.\n */\n onRequest(fn: (req: Request) => MaybePromise<OnRequestResult>): this;\n /** Run after validation, before the handler; a non-`undefined` return short-circuits. Order-scoped. */\n beforeHandle(fn: (context: Context & Ctx) => MaybePromise<unknown>): this;\n /**\n * Wrap the matched route lifecycle for subsequent routes. This is intentionally generic over the\n * route output, so wrappers like async context storage do not force Node's direct JSON path through\n * a Web `Response`. The first registered wrapper is outermost.\n */\n around(fn: <T>(context: Context & Ctx, next: () => MaybePromise<T>) => MaybePromise<T>): this;\n /** Transform the handler's result before it is serialized. Order-scoped. */\n afterHandle(fn: (result: unknown, context: Context & Ctx) => MaybePromise<unknown>): this;\n /** Handle a thrown error; a non-`undefined` return becomes the response (else the default 500). Order-scoped. */\n onError(fn: (error: unknown, context: Context & Ctx) => MaybePromise<unknown>): this;\n /** Transform every outgoing response — success, error, 404, 405, short-circuit. Global. */\n onResponse(fn: (response: Response, req: Request) => MaybePromise<Response>): this;\n /**\n * Apply a type-**identity** plugin ({@link IdentityPlugin}, from {@link defineIdentityPlugin}) — it\n * registers routes/hooks but doesn't change the types, so this returns `this` with the route registry\n * and context fully intact. This overload exists specifically so a *named* identity plugin (e.g.\n * `@nifrajs/better-auth`) threads the registry: its `& { pluginName }` intersection would otherwise\n * defeat the generic inference of the transforming overload below and collapse the result to `any`.\n */\n use(plugin: IdentityPlugin): this;\n /**\n * Apply a **plugin function** — `(app) => app`, typically built with {@link definePlugin}. It's\n * called with `this` and its result is returned, so an inline plugin's `derive`/`decorate` thread\n * the added context to handlers defined after `use` (the overload is generic over the concrete\n * `this`). A named plugin already applied is skipped (idempotent dedupe).\n */\n use<Out extends AnyServer>(plugin: (app: this) => Out): Out;\n /**\n * Apply a {@link Middleware} bundle — wire each hook it provides to its lifecycle point. Returns\n * `this` (no context-type merging); call it before the routes its `beforeHandle`/`afterHandle`\n * should cover (those are order-scoped; `onRequest`/`onResponse` are global). A named bundle already\n * applied is skipped (idempotent).\n */\n use(mw: Middleware): this;\n get<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"GET\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n get<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"GET\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n post<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"POST\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n post<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"POST\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n put<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"PUT\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n put<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"PUT\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n patch<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"PATCH\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n patch<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"PATCH\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n delete<Path extends string, S extends RouteSchema, H extends Handler<Path, S, Ctx>>(path: Path, schema: S, handler: H): Server<AddRoute<R, \"DELETE\", Path, RouteInfoFor<Path, S, OutputOf<H>>>, Ctx>;\n delete<Path extends string, H extends Handler<Path, RouteSchema, Ctx>>(path: Path, handler: H): Server<AddRoute<R, \"DELETE\", Path, RouteInfoFor<Path, Record<never, never>, OutputOf<H>>>, Ctx>;\n /**\n * Register a **WebSocket** route. The connection upgrades on a `GET` to `path` carrying\n * `Upgrade: websocket`; the optional `handler.upgrade(c)` runs in the request context first and may\n * reject (return a `Response`) or seed per-connection `ws.data`. WebSockets are served by the\n * adapter (`listen()`, `@nifrajs/node`, `@nifrajs/deno`, `toFetchHandler`) — not by bare `app.fetch`, which\n * has no socket (a WS path through `app.fetch` is a normal HTTP response).\n *\n * app.ws(\"/chat\", { open: (ws) => ws.send(\"hi\"), message: (ws, data) => ws.send(data) })\n */\n ws<Data = unknown, Schema extends StandardSchemaV1 | undefined = undefined>(path: string, handler: WebSocketHandler<Data, EnvOf<Ctx>, Schema>): this;\n /**\n * Broadcast `data` to every WebSocket connection subscribed to `topic` (via `ws.subscribe(topic)`).\n * In-process and **single-instance** (see {@link TopicRegistry}) — a multi-instance deploy must bridge\n * an external fan-out (Redis, a Durable Object) to this. A no-op when nobody is subscribed.\n */\n publish(topic: string, data: string | ArrayBufferView | ArrayBuffer): void;\n private route;\n /**\n * Low-level route registration shared by the inline builder and `implement()`.\n * Captures the server's current `derive`/`decorate` chain into the route — this\n * is the \"compiled\", order-scoped per-route chain.\n */\n register(method: Method, path: string, schema: RouteSchema | undefined, handler: (context: never) => unknown): void;\n /**\n * Enumerate the registered routes (method, path, input schemas), in registration\n * order. Powers `toOpenAPI` and other introspection; the router trie itself no\n * longer holds the original patterns.\n */\n routes(): ReadonlyArray<RouteDescriptor>;\n /**\n * Resolve a `Request` to a `Response` — the whole lifecycle, testable without a port. The\n * optional `platform` carries edge inputs (`env`, `waitUntil`); edge adapters pass it, and\n * Bun/Node/Deno omit it (then `c.env` is `undefined` and `c.waitUntil` runs fire-and-forget).\n */\n fetch(req: Request, platform?: Platform<EnvOf<Ctx>>): MaybePromise<Response>;\n private fetchSource;\n /**\n * Like {@link fetch}, but renders a plain-data result **without** building a Web `Response` — the\n * `@nifrajs/node` adapter serializes the returned primitives straight to the socket, skipping the undici\n * `Response` build + body drain (the bulk of the Node bridge cost, measured ≈4µs/req). A handler that\n * returns a `Response`, an error/short-circuit, or any registered `onResponse` hook falls back to the\n * full Web path (`{ kind: \"response\" }`), so behavior is identical — only the common JSON-data case is\n * faster. Same lifecycle as {@link fetch} (body cap, validation, hooks all run); only the final\n * render differs.\n */\n resolveNode(req: Request, platform?: Platform<EnvOf<Ctx>>): MaybePromise<NodeServeOutcome>;\n resolveNodeSource(source: RequestSource, platform?: Platform<EnvOf<Ctx>>): MaybePromise<NodeServeOutcome>;\n /**\n * Resolve a WebSocket upgrade — the seam every serving adapter uses. Returns `pass` (not a WS\n * upgrade for a registered route → handle as normal HTTP), `reject` (a WS route matched but\n * `upgrade()` rejected, or the path was malformed → return `response`), or `upgrade` (perform the\n * runtime upgrade, then dispatch the native socket's events to `handler`, seeding `ws.data` with\n * `data`). Runs the route's `upgrade(c)` guard in a real request context. Synchronous unless\n * `upgrade()` is async; a throw rejects with a flat 500 (no detail leaked).\n */\n resolveWebSocketUpgrade(req: Request, platform?: Platform<EnvOf<Ctx>>): MaybePromise<WebSocketUpgradeOutcome>;\n /** Bun `fetch` when WS routes exist: try a WS upgrade first, else run the normal HTTP lifecycle.\n * `undefined` ⇒ Bun owns the upgraded socket; a `Response` ⇒ a normal reply or a rejected upgrade.\n * (The socket dispatch itself lives in `ws-bun.ts`, loaded via `@nifrajs/core/ws`.) */\n private bunFetchWithWebSocket;\n /**\n * The shared lifecycle, generic over how the final value is rendered: `finalize` turns a handler's\n * result + `set` into the output `T` (`toResponse` → a Web `Response`; `toNodeOutcome` → node-direct\n * primitives), `wrapResponse` lifts an early/error `Response` into that same `T`, and `onTimeout`\n * produces the 503. The Web `fetch` and `resolveNode` are thin callers over this one routing +\n * context + lifecycle implementation — no duplication across the trust boundary.\n */\n private dispatch;\n /**\n * onRequest short-circuit path. Synchronous as long as every hook returns synchronously (the\n * common case — e.g. CORS returning `undefined` for a non-preflight request): an `async` version\n * here put EVERY request of any app with one onRequest hook onto the promise machinery, profiled\n * at ~13% of a realistic request. The first hook that returns a Promise hands the REMAINING\n * hooks to the async continuation; behavior is identical.\n */\n private runWithOnRequest;\n /** Async tail of {@link runWithOnRequest}: applies the first awaited hook's outcome, then runs\n * the remaining hooks (awaiting freely — we're already async here). */\n private continueOnRequest;\n private takeResponseRequest;\n /**\n * Route → build context → run. Synchronous through to the handler for a **bare** route\n * ({@link RouteEntry.bare}), so a sync handler produces its result with zero promise allocations;\n * routes with validation/hooks keep the full async {@link runLifecycle}, unchanged.\n */\n private routeAndRun;\n /** The narrowest bare route: a syntactic `() => ...` handler cannot observe the context argument, so\n * successful requests can skip allocating `RequestContext`. Errors still allocate one for logging. */\n private runContextlessBare;\n private contextlessBareError;\n /**\n * The synchronous fast path for a {@link RouteEntry.bare} route: apply static decorations, call the\n * handler, render the result — **no `await`** unless the handler itself returns a promise. It mirrors\n * the bare slice of {@link runLifecycle} (which a bare route would otherwise no-op through) and shares\n * {@link logRequestError}; a bare route has no `onError` hooks, so error handling is fully synchronous\n * (a thrown `Response` is control flow; anything else is a logged flat 500). This is where nifra skips\n * the per-request async-frame tax — the same win codegen routers get, but without `eval`.\n */\n private runBare;\n /** Bare-route error rendering — identical to {@link runLifecycle}'s catch minus the (absent) onError\n * loop: a thrown `Response` is returned as deliberate control flow; anything else is logged + 500. */\n /**\n * Build a route's fused Web lane (see {@link RouteEntry.fusedWeb}). Composition happens once at\n * registration; the returned closure is what every request to the route runs. Behavior is\n * byte-identical to the generic `runBare`/`runContextlessBare` + `toResponse` pair — same\n * decoration order, same error routing (thrown `Response` = control flow; anything else logs and\n * 500s), same respond semantics (the lifecycle parity suite pins it).\n */\n private buildFusedWeb;\n private bareError;\n private runWithAround;\n private runAround;\n private runBodyOnly;\n /** Validate + run the handler for the bodyOnly path — shared by the inline fast path and the\n * streaming fallback. A method (not per-request closures) so the hot path allocates nothing\n * beyond the one `.then` continuation. */\n private finishBodyOnly;\n private applyBodyValidation;\n private runQueryOnly;\n /** Validate-result → set `ctx.query` → run handler. A method (not a per-request closure), the\n * query analogue of {@link applyBodyValidation}. */\n private applyQueryValidation;\n /**\n * Thread the response through each global `onResponse` hook. Stays SYNCHRONOUS until a hook\n * actually returns a Promise — an `async` version forced a promise + microtask on EVERY response\n * of any app with an onResponse hook (cors/securityHeaders/etag/timing all use onResponse), the\n * same ~13%/req tax the onRequest walk was de-async'd to avoid. The first async hook hands the\n * rest to {@link continueOnResponse}.\n */\n private applyOnResponse;\n /** Async tail of {@link applyOnResponse}: runs the remaining hooks once one has gone async. */\n private continueOnResponse;\n /**\n * Bound the response time. On timeout we abort `ctx.signal` (so cancellation-aware\n * handlers can bail) and return 503; the in-flight work keeps running but its\n * result is discarded — JS can't forcibly cancel a promise.\n */\n private withTimeout;\n /**\n * The post-match lifecycle: validate → derive → beforeHandle → handler → afterHandle, with onError.\n * Generic over the render: a success value goes through `finalize(result, set)` (a Web `Response`, or\n * node-direct primitives); an early/error `Response` (validation, thrown, 500) through `wrapResponse`.\n */\n private runLifecycle;\n private handleLifecycleError;\n /** Log an unhandled request error to the (redacting) logger — shared by {@link runLifecycle} and the\n * bare fast path ({@link bareError}) so both record the same fields. Never throws; never leaks. */\n private logRequestError;\n private readAndValidateBody;\n /**\n * Read the body as text, capped at `maxBodyBytes`. Rejects (`null`) on a\n * `Content-Length` over the cap *before* buffering, and aborts mid-stream once the\n * running byte count exceeds it — so a lying or absent length can't force us to\n * buffer an oversized payload.\n *\n * Fast path: when a non-chunked request carries a `Content-Length` within the cap,\n * a native `req.json()` is already bounded — under HTTP/1.1 + HTTP/2 framing the\n * runtime delivers at most `Content-Length` bytes — so we skip the manual stream\n * loop and a separate text decode. It trusts the wire\n * *framing*, not the header value: nifra only ever receives framed Requests from the\n * runtime's HTTP server, never a hand-built one with a mismatched length. Chunked or\n * length-less bodies fall through to the streaming byte-cap guard below.\n */\n private readBoundedJson;\n /**\n * Start a `Bun.serve` instance bound to `port` (use `0` for an ephemeral port).\n *\n * `reusePort` sets `SO_REUSEPORT` so **multiple processes can bind the same port** and the kernel\n * load-balances connections across them — the standard way to use every core (Bun is\n * single-threaded per process). Spawn one process per core, each calling\n * `app.listen(PORT, { reusePort: true })`; see `examples/cluster.ts`. Every process must opt in,\n * and all of them must be the same app. Linux balances ~evenly; macOS accepts the flag but may\n * favor one socket (fine for dev, measure on Linux for production numbers).\n */\n listen(port: number, options?: {\n readonly reusePort?: boolean;\n }): RunningServer;\n /**\n * Gracefully stop: wait for in-flight requests to finish (up to `drainMs`), then\n * issue a single terminal stop — graceful if everything drained, forced if\n * stragglers remain. Safe to call when not listening.\n *\n * The Bun semantics: poll `pendingRequests` (awaiting\n * `stop()`'s promise drops in-flight requests), and decide graceful-vs-forced in\n * ONE call (Bun can't escalate an already-graceful `stop()` to a forced close).\n * New connections may be accepted during the drain window; in a real deploy the\n * load balancer has already stopped routing here, and `drainMs` bounds it.\n */\n stop({ drainMs }?: {\n drainMs?: number;\n }): Promise<void>;\n private installSignalHandlers;\n}",
5083
5116
  "doc": "/**\n * The inline server. Routes are chainable and fully type-inferred. `derive`/\n * `decorate` extend the handler context (`Ctx`) for routes defined *after* them,\n * with full types; `Ctx` is server-only and never touches the client registry.\n *\n * app.decorate(\"db\", db).derive((c) => ({ user: auth(c) }))\n * .get(\"/me\", (c) => c.user) // c.user + c.db are typed\n */"
5084
5117
  },
5085
5118
  {
@@ -5127,7 +5160,7 @@
5127
5160
  "name": "ServerOptions",
5128
5161
  "kind": "interface",
5129
5162
  "package": "@nifrajs/core",
5130
- "signature": "export interface ServerOptions {\n /**\n * Max request body size (bytes), enforced **only when a route declares a body schema** — the cap\n * lives in the schema-validated read path. Default 1_000_000.\n *\n * A route WITHOUT a body schema (raw body, file upload, BYO-validation) that reads `c.req` directly\n * is not auto-bounded — use **`c.boundedBody(maxBytes?)`** / **`c.boundedJson(maxBytes?)`**, which\n * apply this same cap (override per route by passing `maxBytes` — larger for an upload endpoint,\n * smaller to tighten one).\n */\n readonly maxBodyBytes?: number;\n /** Per-request timeout (ms): a slower request gets a 503 and `ctx.signal` aborts. 0 disables (default). */\n readonly requestTimeoutMs?: number;\n /** When `listen()`ing, install SIGTERM/SIGINT handlers that gracefully `stop()`. Default false. */\n readonly gracefulSignals?: boolean;\n /** Structured logger for framework events (redacts secrets). Default: JSON to stderr. */\n readonly logger?: Logger;\n}"
5163
+ "signature": "export interface ServerOptions {\n /**\n * Max request body size (bytes), enforced **only when a route declares a body schema** — the cap\n * lives in the schema-validated read path. Default 1_000_000.\n *\n * A route WITHOUT a body schema (raw body, file upload, BYO-validation) that reads `c.req` directly\n * is not auto-bounded — use **`c.boundedBody(maxBytes?)`** / **`c.boundedJson(maxBytes?)`**, which\n * apply this same cap (override per route by passing `maxBytes` — larger for an upload endpoint,\n * smaller to tighten one).\n */\n readonly maxBodyBytes?: number;\n /** Max inbound WebSocket message size (bytes) when `listen()`ing on Bun — frames over this are rejected\n * by the runtime before reaching your handler (so a huge frame can't be JSON-parsed into memory).\n * Default: `maxBodyBytes` (1 MB). */\n readonly wsMaxPayloadBytes?: number;\n /** Per-request timeout (ms): a slower request gets a 503 and `ctx.signal` aborts. 0 disables (default). */\n readonly requestTimeoutMs?: number;\n /** When `listen()`ing, install SIGTERM/SIGINT handlers that gracefully `stop()`. Default false. */\n readonly gracefulSignals?: boolean;\n /** Structured logger for framework events (redacts secrets). Default: JSON to stderr. */\n readonly logger?: Logger;\n}"
5131
5164
  },
5132
5165
  {
5133
5166
  "name": "ServeStaticOptions",
@@ -5854,6 +5887,13 @@
5854
5887
  "signature": "export declare function tsTypeOf(schema: unknown, depth?: number): string;",
5855
5888
  "doc": "/**\n * Render a JSON Schema as a compact TypeScript-like type (`{ id: string, name?: string }`) — the\n * shape agents already think in, at a fraction of the raw JSON Schema's tokens (~70-80% smaller).\n * Anything the renderer doesn't model falls back to the raw JSON for that subtree, so the output\n * is always faithful, just not always minimal.\n */"
5856
5889
  },
5890
+ {
5891
+ "name": "TypeEntry",
5892
+ "kind": "interface",
5893
+ "package": "@nifrajs/cli",
5894
+ "signature": "export interface TypeEntry {\n readonly name: string;\n readonly kind: \"interface\" | \"type\" | \"class\" | \"function\" | \"enum\" | \"const\";\n readonly package: string;\n /** The literal declaration text from the `.d.ts` — a clean signature, no implementation. */\n readonly signature: string;\n /** The declaration's JSDoc block, if any. */\n readonly doc?: string;\n}",
5895
+ "doc": "/**\n * `nifra_types` — the EXACT TypeScript of every exported `@nifrajs/*` symbol. The corpus is `types.json`\n * (written into this package by `bun run gen:llms`): each entry's `signature` is the literal declaration\n * parsed from the package's BUILT `.d.ts` with the TS compiler — a clean signature, never prose, never\n * truncated. So an agent that needs the precise shape of a type (`RateLimitStore`, `RouteSchema`, a\n * function signature) gets the authoritative source here and never has to read a `.d.ts` file.\n *\n * Lookup by `name` is exact (case-insensitive); `query` keyword-scores over the name (heavy) + the\n * signature/doc body; no args → a per-package index of the available type names.\n */"
5896
+ },
5857
5897
  {
5858
5898
  "name": "UI_EXTENSION_KEY",
5859
5899
  "kind": "const",
@@ -6260,6 +6300,13 @@
6260
6300
  "signature": "export declare function walkSource(cwd: string, visit: (rel: string, content: string) => void): Promise<void>;",
6261
6301
  "doc": "/** Walk the project's `.ts`/`.tsx` source (skipping deps/build/tests), calling `visit` per file.\n * Exported so `nifra doctor` ({@link ./doctor.ts}) scans the same source surface as `nifra check`. */"
6262
6302
  },
6303
+ {
6304
+ "name": "WarmWorker",
6305
+ "kind": "class",
6306
+ "package": "@nifrajs/cli",
6307
+ "signature": "export declare class WarmWorker {\n readonly fingerprint: string;\n private readonly label;\n private readonly proc;\n private readonly pending;\n private stdoutBuffer;\n private stderrBuffer;\n private nextId;\n private closed;\n constructor(child: \"mcp-run\" | \"mcp-render\", cwd: string, fingerprint: string, label: string);\n stop(): void;\n request(input: unknown, signal?: AbortSignal): Promise<string>;\n private readStdout;\n private readStderr;\n private handleLine;\n}",
6308
+ "doc": "/** A persistent `mcp-run`/`mcp-render` `--worker` subprocess: the backend/web app is loaded ONCE and\n * reused across newline-delimited `{ id, input }` requests, replying `{ id, output }`. The same machinery\n * powers both `nifra_run warm` and `nifra_render warm` — `child` selects which engine, `label` shapes the\n * cancellation message. The owning handler ({@link createWarmHandler}) fingerprints the source tree and\n * replaces the worker when a file changes, so warm reuse never serves a stale result. Exported for the\n * concurrency test that proves a single per-request cancel doesn't tear down the shared worker. */"
6309
+ },
6263
6310
  {
6264
6311
  "name": "wasmImageBackend",
6265
6312
  "kind": "function",
@@ -6311,7 +6358,7 @@
6311
6358
  "name": "WebSocketHandler",
6312
6359
  "kind": "interface",
6313
6360
  "package": "@nifrajs/core",
6314
- "signature": "export interface WebSocketHandler<Data = unknown, Env = unknown, Schema extends StandardSchemaV1 | undefined = undefined> {\n /**\n * Cross-site WebSocket hijacking (CSWSH) guard — checked BEFORE `upgrade()`. A browser does not\n * apply CORS to WebSocket handshakes and DOES send the page's cookies, so without an Origin check\n * any site can open an authenticated socket to your app. Set this to lock the route to known\n * origins: a string allow-list (exact `Origin` header match) or a predicate. A request whose\n * `Origin` doesn't match (or is absent, for the allow-list form) is rejected with `403` before any\n * per-connection work. Omit for no built-in check (same as today — then enforce it yourself in\n * `upgrade()`). Non-browser clients can spoof `Origin`, so this is a browser-CSWSH defense, not\n * authentication — pair it with auth in `upgrade()`.\n */\n allowedOrigins?: ReadonlyArray<string> | ((origin: string | null) => boolean);\n /**\n * Runs in the HTTP request context **before** the upgrade (and after {@link allowedOrigins}) — the\n * place to authenticate or rate-limit. Return the initial per-connection `data` (→ `ws.data`), or a\n * `Response` to **reject** the upgrade (the client never connects). Omit to accept with\n * `data: undefined`. A thrown error rejects with a flat 500.\n */\n upgrade?(c: WebSocketContext<Env>): MaybePromise<Data | Response>;\n /**\n * Contract-first messages: a Standard Schema (`t`, zod, valibot, …) validating each **inbound** frame.\n * Text frames are parsed as JSON first. When set, `message` receives the validated, typed value; a\n * non-JSON or schema-invalid frame is routed to `onInvalidMessage` (or dropped if that's omitted).\n */\n messageSchema?: Schema;\n open?(ws: NifraWebSocket<Data>): MaybePromise<void>;\n message?(ws: NifraWebSocket<Data>, data: WsMessageInput<Schema>): MaybePromise<void>;\n /** Inbound frame that failed JSON parse or `messageSchema` validation (only fires when a schema is\n * set). `raw` is the original frame; `issues` are the Standard Schema issues (one synthetic issue for\n * a JSON parse failure). Omit to silently drop invalid frames. */\n onInvalidMessage?(ws: NifraWebSocket<Data>, issues: ReadonlyArray<StandardIssue>, raw: WebSocketData): MaybePromise<void>;\n close?(ws: NifraWebSocket<Data>, code: number, reason: string): MaybePromise<void>;\n error?(ws: NifraWebSocket<Data>, error: unknown): MaybePromise<void>;\n}",
6361
+ "signature": "export interface WebSocketHandler<Data = unknown, Env = unknown, Schema extends StandardSchemaV1 | undefined = undefined> {\n /**\n * Cross-site WebSocket hijacking (CSWSH) guard — checked BEFORE `upgrade()`. A browser does not\n * apply CORS to WebSocket handshakes and DOES send the page's cookies, so without an Origin check\n * any site can open an authenticated socket to your app. Set this to lock the route to known\n * origins: a string allow-list (exact `Origin` header match) or a predicate. A request whose\n * `Origin` doesn't match (or is absent, for the allow-list form) is rejected with `403` before any\n * per-connection work.\n *\n * **When omitted, the default is same-origin:** a cross-origin BROWSER handshake (an `Origin` header\n * whose host differs from the request's) is rejected with `403`. Non-browser clients (no `Origin`) and\n * same-origin browsers pass. Set this to allow specific cross-origin clients, or `() => true` for a\n * genuinely public socket. Non-browser clients can spoof `Origin`, so this is a browser-CSWSH defense,\n * not authentication — pair it with auth in `upgrade()`.\n */\n allowedOrigins?: ReadonlyArray<string> | ((origin: string | null) => boolean);\n /**\n * Runs in the HTTP request context **before** the upgrade (and after {@link allowedOrigins}) — the\n * place to authenticate or rate-limit. Return the initial per-connection `data` (→ `ws.data`), or a\n * `Response` to **reject** the upgrade (the client never connects). Omit to accept with\n * `data: undefined`. A thrown error rejects with a flat 500.\n */\n upgrade?(c: WebSocketContext<Env>): MaybePromise<Data | Response>;\n /**\n * Contract-first messages: a Standard Schema (`t`, zod, valibot, …) validating each **inbound** frame.\n * Text frames are parsed as JSON first. When set, `message` receives the validated, typed value; a\n * non-JSON or schema-invalid frame is routed to `onInvalidMessage` (or dropped if that's omitted).\n */\n messageSchema?: Schema;\n open?(ws: NifraWebSocket<Data>): MaybePromise<void>;\n message?(ws: NifraWebSocket<Data>, data: WsMessageInput<Schema>): MaybePromise<void>;\n /** Inbound frame that failed JSON parse or `messageSchema` validation (only fires when a schema is\n * set). `raw` is the original frame; `issues` are the Standard Schema issues (one synthetic issue for\n * a JSON parse failure). Omit to silently drop invalid frames. */\n onInvalidMessage?(ws: NifraWebSocket<Data>, issues: ReadonlyArray<StandardIssue>, raw: WebSocketData): MaybePromise<void>;\n close?(ws: NifraWebSocket<Data>, code: number, reason: string): MaybePromise<void>;\n error?(ws: NifraWebSocket<Data>, error: unknown): MaybePromise<void>;\n}",
6315
6362
  "doc": "/** A WebSocket route's lifecycle. All callbacks optional; only `message` is needed for an echo. */"
6316
6363
  },
6317
6364
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/cli",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "The nifra CLI — zero-config dev/build/start for a nifra app (`nifra dev`, `nifra build`, `nifra start`).",
5
5
  "keywords": [
6
6
  "bun",
@@ -41,12 +41,12 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@nifrajs/client": "1.0.0",
45
- "@nifrajs/mcp": "1.0.0",
46
- "@nifrajs/runner": "1.0.0",
47
- "@nifrajs/schema": "1.0.0",
48
- "@nifrajs/web": "1.0.0",
49
- "create-nifra": "1.0.0"
44
+ "@nifrajs/client": "1.2.0",
45
+ "@nifrajs/mcp": "1.2.0",
46
+ "@nifrajs/runner": "1.2.0",
47
+ "@nifrajs/schema": "1.2.0",
48
+ "@nifrajs/web": "1.2.0",
49
+ "create-nifra": "1.2.0"
50
50
  },
51
51
  "repository": {
52
52
  "type": "git",
package/src/cli.ts CHANGED
@@ -82,7 +82,7 @@ Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with
82
82
  \`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).`
83
83
 
84
84
  // Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
85
- const CLI_VERSION = "1.0.0"
85
+ const CLI_VERSION = "1.2.0"
86
86
 
87
87
  // A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
88
88
  const asAdapter = (v: unknown): RenderAdapter => v as RenderAdapter
package/src/mcp-http.ts CHANGED
@@ -26,7 +26,7 @@ export { docsTools } from "./mcp-docs-tools.ts"
26
26
  export type { TypeEntry } from "./types-search.ts"
27
27
 
28
28
  // Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
29
- const VERSION = "1.0.0"
29
+ const VERSION = "1.2.0"
30
30
  const SERVER_INFO = { name: "nifra-docs", version: VERSION }
31
31
  const DOCS_HEALTH =
32
32
  "nifra docs MCP — POST JSON-RPC 2.0 here (methods: initialize, tools/list, tools/call). Tools: nifra_docs, nifra_example."