@nifrajs/core 1.12.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +10 -1
  2. package/dist/assurance.d.ts +11 -2
  3. package/dist/assurance.d.ts.map +1 -1
  4. package/dist/assurance.js +20 -0
  5. package/dist/assurance.js.map +1 -1
  6. package/dist/effect-ledger.d.ts +9 -0
  7. package/dist/effect-ledger.d.ts.map +1 -0
  8. package/dist/effect-ledger.js +8 -0
  9. package/dist/effect-ledger.js.map +1 -0
  10. package/dist/idempotency-plugin.d.ts +8 -0
  11. package/dist/idempotency-plugin.d.ts.map +1 -0
  12. package/dist/idempotency-plugin.js +8 -0
  13. package/dist/idempotency-plugin.js.map +1 -0
  14. package/dist/index.d.ts +6 -34
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -30
  17. package/dist/index.js.map +1 -1
  18. package/dist/mcp.d.ts +7 -0
  19. package/dist/mcp.d.ts.map +1 -0
  20. package/dist/mcp.js +56 -0
  21. package/dist/mcp.js.map +1 -0
  22. package/dist/node-direct.d.ts +5 -0
  23. package/dist/node-direct.d.ts.map +1 -0
  24. package/dist/node-direct.js +27 -0
  25. package/dist/node-direct.js.map +1 -0
  26. package/dist/reflection.d.ts +5 -0
  27. package/dist/reflection.d.ts.map +1 -1
  28. package/dist/reflection.js +2 -0
  29. package/dist/reflection.js.map +1 -1
  30. package/dist/router/pattern.d.ts +42 -0
  31. package/dist/router/pattern.d.ts.map +1 -0
  32. package/dist/router/pattern.js +127 -0
  33. package/dist/router/pattern.js.map +1 -0
  34. package/dist/router/router.d.ts +2 -1
  35. package/dist/router/router.d.ts.map +1 -1
  36. package/dist/router/router.js +11 -48
  37. package/dist/router/router.js.map +1 -1
  38. package/dist/server/client-ip.d.ts +35 -0
  39. package/dist/server/client-ip.d.ts.map +1 -0
  40. package/dist/server/client-ip.js +42 -0
  41. package/dist/server/client-ip.js.map +1 -0
  42. package/dist/server/context.d.ts +47 -2
  43. package/dist/server/context.d.ts.map +1 -1
  44. package/dist/server/contract.d.ts +37 -10
  45. package/dist/server/contract.d.ts.map +1 -1
  46. package/dist/server/contract.js +33 -8
  47. package/dist/server/contract.js.map +1 -1
  48. package/dist/server/http.d.ts +14 -0
  49. package/dist/server/http.d.ts.map +1 -0
  50. package/dist/server/http.js +45 -0
  51. package/dist/server/http.js.map +1 -0
  52. package/dist/server/idempotency-lane.d.ts +44 -0
  53. package/dist/server/idempotency-lane.d.ts.map +1 -0
  54. package/dist/server/idempotency-lane.js +181 -0
  55. package/dist/server/idempotency-lane.js.map +1 -0
  56. package/dist/server/install.d.ts +21 -0
  57. package/dist/server/install.d.ts.map +1 -0
  58. package/dist/server/install.js +21 -0
  59. package/dist/server/install.js.map +1 -0
  60. package/dist/server/ledger-lane.d.ts +53 -0
  61. package/dist/server/ledger-lane.d.ts.map +1 -0
  62. package/dist/server/ledger-lane.js +82 -0
  63. package/dist/server/ledger-lane.js.map +1 -0
  64. package/dist/server/mcp-hook.d.ts +28 -0
  65. package/dist/server/mcp-hook.d.ts.map +1 -0
  66. package/dist/server/mcp-hook.js +2 -0
  67. package/dist/server/mcp-hook.js.map +1 -0
  68. package/dist/server/node-outcome-hook.d.ts +9 -0
  69. package/dist/server/node-outcome-hook.d.ts.map +1 -0
  70. package/dist/server/node-outcome-hook.js +2 -0
  71. package/dist/server/node-outcome-hook.js.map +1 -0
  72. package/dist/server/node-outcome.d.ts +37 -0
  73. package/dist/server/node-outcome.d.ts.map +1 -0
  74. package/dist/server/node-outcome.js +75 -0
  75. package/dist/server/node-outcome.js.map +1 -0
  76. package/dist/server/plugin.d.ts +78 -0
  77. package/dist/server/plugin.d.ts.map +1 -0
  78. package/dist/server/plugin.js +57 -0
  79. package/dist/server/plugin.js.map +1 -0
  80. package/dist/server/query.d.ts +18 -0
  81. package/dist/server/query.d.ts.map +1 -0
  82. package/dist/server/query.js +102 -0
  83. package/dist/server/query.js.map +1 -0
  84. package/dist/server/registry.d.ts +31 -6
  85. package/dist/server/registry.d.ts.map +1 -1
  86. package/dist/server/request-context.d.ts +51 -0
  87. package/dist/server/request-context.d.ts.map +1 -0
  88. package/dist/server/request-context.js +184 -0
  89. package/dist/server/request-context.js.map +1 -0
  90. package/dist/server/respond.d.ts +17 -0
  91. package/dist/server/respond.d.ts.map +1 -0
  92. package/dist/server/respond.js +81 -0
  93. package/dist/server/respond.js.map +1 -0
  94. package/dist/server/runtime-core.d.ts +36 -0
  95. package/dist/server/runtime-core.d.ts.map +1 -0
  96. package/dist/server/runtime-core.js +51 -0
  97. package/dist/server/runtime-core.js.map +1 -0
  98. package/dist/server/server-types.d.ts +203 -0
  99. package/dist/server/server-types.d.ts.map +1 -0
  100. package/dist/server/server-types.js +2 -0
  101. package/dist/server/server-types.js.map +1 -0
  102. package/dist/server/server.d.ts +135 -358
  103. package/dist/server/server.d.ts.map +1 -1
  104. package/dist/server/server.js +476 -1065
  105. package/dist/server/server.js.map +1 -1
  106. package/dist/server/sse-hook.d.ts +10 -0
  107. package/dist/server/sse-hook.d.ts.map +1 -0
  108. package/dist/server/sse-hook.js +2 -0
  109. package/dist/server/sse-hook.js.map +1 -0
  110. package/dist/server/sse.d.ts +7 -23
  111. package/dist/server/sse.d.ts.map +1 -1
  112. package/dist/server/sse.js +18 -0
  113. package/dist/server/sse.js.map +1 -1
  114. package/dist/server/websocket.d.ts +17 -1
  115. package/dist/server/websocket.d.ts.map +1 -1
  116. package/dist/server/websocket.js.map +1 -1
  117. package/dist/server/ws-hook.d.ts +8 -11
  118. package/dist/server/ws-hook.d.ts.map +1 -1
  119. package/dist/server/ws-hook.js +7 -14
  120. package/dist/server/ws-hook.js.map +1 -1
  121. package/dist/server.d.ts +21 -0
  122. package/dist/server.d.ts.map +1 -0
  123. package/dist/server.js +16 -0
  124. package/dist/server.js.map +1 -0
  125. package/dist/ws.d.ts +16 -7
  126. package/dist/ws.d.ts.map +1 -1
  127. package/dist/ws.js +25 -10
  128. package/dist/ws.js.map +1 -1
  129. package/package.json +82 -6
  130. package/src/assurance.ts +36 -1
  131. package/src/effect-ledger.ts +9 -0
  132. package/src/idempotency-plugin.ts +7 -0
  133. package/src/index.ts +6 -132
  134. package/src/mcp.ts +65 -0
  135. package/src/node-direct.ts +37 -0
  136. package/src/reflection.ts +7 -0
  137. package/src/router/pattern.ts +176 -0
  138. package/src/router/router.ts +11 -69
  139. package/src/server/client-ip.ts +60 -0
  140. package/src/server/context.ts +50 -2
  141. package/src/server/contract.ts +68 -25
  142. package/src/server/http.ts +58 -0
  143. package/src/server/idempotency-lane.ts +287 -0
  144. package/src/server/install.ts +28 -0
  145. package/src/server/ledger-lane.ts +125 -0
  146. package/src/server/mcp-hook.ts +41 -0
  147. package/src/server/node-outcome-hook.ts +9 -0
  148. package/src/server/node-outcome.ts +112 -0
  149. package/src/server/plugin.ts +92 -0
  150. package/src/server/query.ts +119 -0
  151. package/src/server/registry.ts +36 -6
  152. package/src/server/request-context.ts +271 -0
  153. package/src/server/respond.ts +93 -0
  154. package/src/server/runtime-core.ts +74 -0
  155. package/src/server/server-types.ts +204 -0
  156. package/src/server/server.ts +863 -1657
  157. package/src/server/sse-hook.ts +14 -0
  158. package/src/server/sse.ts +26 -0
  159. package/src/server/websocket.ts +20 -0
  160. package/src/server/ws-hook.ts +8 -19
  161. package/src/server.ts +92 -0
  162. package/src/ws.ts +33 -10
  163. package/dist/invariants.d.ts +0 -70
  164. package/dist/invariants.d.ts.map +0 -1
  165. package/dist/invariants.js +0 -487
  166. package/dist/invariants.js.map +0 -1
  167. package/src/invariants.ts +0 -604
@@ -1,15 +1,24 @@
1
- import { type IdempotencyStore } from "../idempotency.js";
2
- import { type CapabilityUseEvent } from "../internal/capability-runtime.js";
3
- import { type AssuranceEvidence } from "../internal/route-assurance.js";
4
- import { type EffectLedgerOptions } from "../ledger.js";
1
+ import { type RequestBudget } from "../budget.js";
5
2
  import { type Method } from "../router/router.js";
6
3
  import type { InferOutput, StandardSchemaV1 } from "../schema/standard.js";
7
- import type { Context, Platform, RouteSchema } from "./context.js";
8
- import { type Logger } from "./logger.js";
9
- import type { AddRoute, EmptyRegistry, OutputOf, Registry, RouteInfoFor } from "./registry.js";
10
- import { type SSEInit, type TypedSSEStream } from "./sse.js";
4
+ import type { Context, Platform, ResponseControls, RouteSchema } from "./context.js";
5
+ import type { NodeServeOutcome } from "./node-outcome.js";
6
+ import type { NodeOutcomeRuntime } from "./node-outcome-hook.js";
7
+ import { type QueryValue, queryObjectOf, searchOf } from "./query.js";
8
+ import { CONTEXT_SEARCH, CONTEXT_SET, type HandlerResult } from "./runtime-core.js";
9
+ export type { NodeServeOutcome };
10
+ import type { IdempotencyRuntime } from "./idempotency-lane.js";
11
+ import { INSTALL_EFFECT_LEDGER, INSTALL_IDEMPOTENCY, INSTALL_MCP, INSTALL_NODE_DIRECT, INSTALL_SSE, INSTALL_WS } from "./install.js";
12
+ import type { EffectLedgerRuntime } from "./ledger-lane.js";
13
+ import type { McpRuntime } from "./mcp-hook.js";
14
+ import type { IdentityPlugin } from "./plugin.js";
15
+ import type { AddRoute, EmptyRegistry, OutputOf, Registry, RouteInfoFor, WsRouteInfoFor } from "./registry.js";
16
+ import type { AdmissionController, AdmissionDecision, McpPromptDescriptor, McpResourceDescriptor, Middleware, PromptArgument, PromptMessage, ResponseFinalization, RouteDescriptor, RunningServer, ServerOptions, ToolAnnotations } from "./server-types.js";
17
+ import type { SSEInit, TypedSSEStream } from "./sse.js";
18
+ import type { SseRuntime } from "./sse-hook.js";
11
19
  import type { WebSocketHandler, WebSocketUpgradeOutcome } from "./websocket.js";
12
- type MaybePromise<T> = T | Promise<T>;
20
+ import type { WsRuntime } from "./ws-hook.js";
21
+ export type MaybePromise<T> = T | Promise<T>;
13
22
  /**
14
23
  * Internal request view. A real Web `Request` already satisfies this shape, so Web/edge runtimes pass
15
24
  * their `Request` **directly** (zero wrapper allocation on the hot path — `request` is simply absent and
@@ -39,8 +48,27 @@ type EmptyContext = NonNullable<unknown>;
39
48
  type EnvOf<Ctx> = Ctx extends {
40
49
  readonly env: infer E;
41
50
  } ? E : unknown;
42
- /** A handler returns a `Response` (used as-is) or any value (serialized to JSON). */
43
- type HandlerResult = Response | unknown;
51
+ /** Internal, path-erased runtime context. The typed `Context<Path, S>` is a structural view of this. */
52
+ export interface RawContext {
53
+ readonly req: Request;
54
+ readonly request: Request;
55
+ readonly json: (body: unknown, init?: ResponseInit | number) => Response;
56
+ readonly text: (body: string, init?: ResponseInit | number) => Response;
57
+ params: Record<string, string>;
58
+ query: unknown;
59
+ readonly cookies: Readonly<Record<string, string>>;
60
+ body: unknown;
61
+ readonly set: ResponseControls;
62
+ readonly [CONTEXT_SET]: () => CtxSet | undefined;
63
+ readonly [CONTEXT_SEARCH]: string;
64
+ readonly signal: AbortSignal;
65
+ readonly budget: RequestBudget;
66
+ readonly env: unknown;
67
+ readonly clientIp: string | undefined;
68
+ readonly waitUntil: (promise: Promise<unknown>) => void;
69
+ readonly boundedBody: (maxBytes?: number) => Promise<Uint8Array>;
70
+ readonly boundedJson: <T = unknown>(maxBytes?: number) => Promise<T>;
71
+ }
44
72
  export type OnRequestResult = Response | Request | undefined;
45
73
  /** The handler's permitted return type. When the route declares a `response` schema, the return is
46
74
  * constrained to the contract's type (or a raw `Response`) — so the implementation can't drift from the
@@ -53,312 +81,18 @@ type ResponseOf<S extends RouteSchema> = S extends {
53
81
  * any accumulated middleware context `Ctx` (from `derive`/`decorate`).
54
82
  */
55
83
  export type Handler<Path extends string, S extends RouteSchema = RouteSchema, Ctx = EmptyContext> = (ctx: Context<Path, S> & Ctx) => MaybePromise<ResponseOf<S>>;
56
- /**
57
- * The outcome of a capacity-admission decision. `admitted` requests carry a `release` the server calls
58
- * exactly once when the response is finalized; a shed request carries a ready `429` Response.
59
- */
60
- export type AdmissionDecision = {
61
- readonly admitted: true;
62
- release(): void;
63
- } | {
64
- readonly admitted: false;
65
- readonly response: Response;
66
- };
67
- /**
68
- * A capacity-admission gate. Decides, per request, whether the instance has capacity to run it now —
69
- * bounding *concurrency*, which rate limits (frequency) and deadlines (duration) do not. Provide an
70
- * implementation (see `@nifrajs/middleware`'s `createAdmissionController`) as {@link ServerOptions.admission}.
71
- *
72
- * Enabling it trades the fused native-route lane for native matching + this gate (the gate cannot be a
73
- * per-request `onRequest` hook without disabling native routes entirely); when unset, the request path
74
- * is untouched.
75
- */
76
- export interface AdmissionController {
77
- admit(req: Request): AdmissionDecision | Promise<AdmissionDecision>;
78
- }
79
- export interface ServerOptions {
80
- /**
81
- * Max request body size (bytes), enforced **only when a route declares a body schema** — the cap
82
- * lives in the schema-validated read path. Default 1_000_000.
83
- *
84
- * A route WITHOUT a body schema (raw body, file upload, BYO-validation) that reads `c.req` directly
85
- * is not auto-bounded — use **`c.boundedBody(maxBytes?)`** / **`c.boundedJson(maxBytes?)`**, which
86
- * apply this same cap (override per route by passing `maxBytes` — larger for an upload endpoint,
87
- * smaller to tighten one).
88
- */
89
- readonly maxBodyBytes?: number;
90
- /** Max inbound WebSocket message size (bytes) when `listen()`ing on Bun — frames over this are rejected
91
- * by the runtime before reaching your handler (so a huge frame can't be JSON-parsed into memory).
92
- * Default: `maxBodyBytes` (1 MB). */
93
- readonly wsMaxPayloadBytes?: number;
94
- /** Per-request timeout (ms): a slower request gets a 503 and `ctx.signal` aborts. 0 disables (default). */
95
- readonly requestTimeoutMs?: number;
96
- /**
97
- * Admit the public `x-nifra-deadline` header and let it shorten local work. Disabled by default:
98
- * services that participate in trusted cross-service deadline propagation opt in explicitly,
99
- * while ordinary/public HTTP routes pay no header-admission tax and ignore hostile client values.
100
- */
101
- readonly acceptInboundDeadlines?: boolean;
102
- /**
103
- * Maximum time accepted from an inbound `x-nifra-deadline`, in milliseconds. An incoming absolute
104
- * deadline may shorten this cap (and `requestTimeoutMs`, when configured), never extend it.
105
- * Default 30_000; applies only when `acceptInboundDeadlines` is enabled and the header is present.
106
- */
107
- readonly maxInboundDeadlineMs?: number;
108
- /** When `listen()`ing, install SIGTERM/SIGINT handlers that gracefully `stop()`. Default false. */
109
- readonly gracefulSignals?: boolean;
110
- /**
111
- * Capacity-admission gate (see {@link AdmissionController}). Bounds concurrent in-flight work so a
112
- * healthy instance sheds load (`429` + `Retry-After`) instead of accepting more than it can finish.
113
- * Off by default. Enabling it disables the fused native-route fast path (requests route through the
114
- * shared matched lane where the gate runs); when unset, the request path pays nothing.
115
- */
116
- readonly admission?: AdmissionController;
117
- /**
118
- * Optional token-only observation hook called by `useCapability(c, id)`. It receives no request,
119
- * parameters, body, tenant, or values. Routes without capability declarations keep the old hot path.
120
- */
121
- readonly onCapabilityUse?: (event: CapabilityUseEvent) => void;
122
- /**
123
- * Enable the per-request effect ledger. Each route that declares `schema.capabilities` gets a
124
- * bounded, token-only ledger; `useCapability(c, id, …)` appends one entry per effect, and the sink
125
- * receives the sealed ledger when the response settles (only when it recorded entries). Token-only
126
- * by construction — capability ids, phases, counters, digests; never payloads, values, or the
127
- * concrete URL (the route *pattern* is recorded). Enabling it moves capability-declaring routes off
128
- * the fused fast path (they need a per-request context + a settle step); all other routes are
129
- * unaffected, and when unset the request path pays nothing.
130
- */
131
- readonly effectLedger?: EffectLedgerOptions;
132
- /**
133
- * Default store backing routes that declare `schema.idempotency` without their own `store`. Defaults
134
- * to a shared in-process {@link MemoryIdempotencyStore} (created lazily on first idempotent route).
135
- * Inject a durable store here to make request-scoped dedupe survive restarts app-wide.
136
- */
137
- readonly idempotencyStore?: IdempotencyStore;
138
- /** Structured logger for framework events (redacts secrets). Default: JSON to stderr. */
139
- readonly logger?: Logger;
140
- /**
141
- * App-wide fallback fired when a route **without its own `onValidationError`** fails body/query
142
- * validation. Same contract as the per-route hook (`(issues, ctx, kind) => Response | repaired-value |
143
- * undefined`): a route's own hook takes precedence, and a route can fall through to the plain `422` by
144
- * returning `undefined`. Use it for one app-wide error envelope (like tRPC's `errorFormatter` /
145
- * Fastify's `setErrorHandler`) instead of repeating a formatter per route.
146
- */
147
- readonly onValidationError?: RouteSchema["onValidationError"];
148
- }
149
- /**
150
- * MCP tool safety hints, surfaced in `tools/list`, that tell an agent how risky a `.tool()` call is — so it
151
- * can decide whether to auto-invoke or confirm first. All optional; an omitted hint means "unknown". Mirrors
152
- * the MCP spec's tool `annotations`.
153
- */
154
- export interface ToolAnnotations {
155
- /** Human-readable display title for the tool, distinct from the machine `name`. */
156
- readonly title?: string;
157
- /** The tool does not modify its environment (a pure read). */
158
- readonly readOnlyHint?: boolean;
159
- /** The tool may perform destructive updates — only meaningful when `readOnlyHint` is not `true`. */
160
- readonly destructiveHint?: boolean;
161
- /** Repeated calls with the same arguments have no additional effect beyond the first. */
162
- readonly idempotentHint?: boolean;
163
- /** The tool interacts with external entities (an "open world" beyond this server). */
164
- readonly openWorldHint?: boolean;
165
- }
166
- /**
167
- * A registered route's public descriptor — method, path, and input schemas. The
168
- * router trie discards the original patterns, so this flat list is what lets tools
169
- * (e.g. `toOpenAPI`) enumerate routes after registration.
170
- */
171
- export interface RouteDescriptor {
172
- readonly method: Method;
173
- readonly path: string;
174
- readonly schema: RouteSchema | undefined;
175
- /** Effective enforcement evidence, populated only during reflection/introspection. */
176
- readonly assurance?: readonly AssuranceEvidence[];
177
- /** Normalized declared effect tokens, populated only when the route declares any. */
178
- readonly capabilities?: readonly string[];
179
- readonly tool?: {
180
- readonly name: string;
181
- readonly description: string;
182
- readonly annotations?: ToolAnnotations;
183
- };
184
- }
185
- /** A message in an MCP prompt's rendered output (see {@link Server.prompt}). */
186
- export interface PromptMessage {
187
- readonly role: "user" | "assistant";
188
- readonly content: {
189
- readonly type: "text";
190
- readonly text: string;
191
- };
192
- }
193
- /** One declared argument of an MCP prompt, surfaced in `prompts/list`. */
194
- export interface PromptArgument {
195
- readonly name: string;
196
- readonly description?: string;
197
- readonly required?: boolean;
198
- }
199
- /** An app-declared MCP resource — read-only data an agent can fetch through `nifra mcp`. */
200
- export interface McpResourceDescriptor {
201
- readonly uri: string;
202
- readonly name: string;
203
- readonly description?: string;
204
- readonly mimeType?: string;
205
- readonly read: () => MaybePromise<string | {
206
- readonly text: string;
207
- readonly mimeType?: string;
208
- }>;
209
- }
210
- /** An app-declared MCP prompt — a reusable prompt template an agent can fetch through `nifra mcp`. */
211
- export interface McpPromptDescriptor {
212
- readonly name: string;
213
- readonly description: string;
214
- readonly arguments?: readonly PromptArgument[];
215
- readonly handler: (args: Record<string, string>) => MaybePromise<readonly PromptMessage[]>;
216
- }
217
- /**
218
- * The handle `listen()` returns — the slice of Bun's server nifra holds and exposes.
219
- * Declared explicitly (rather than `ReturnType<typeof Bun.serve>`) so the public type
220
- * surface doesn't leak the ambient `Bun` global into consumers' `.d.ts` resolution.
221
- */
222
- export interface RunningServer {
223
- readonly port: number;
224
- readonly hostname: string;
225
- readonly pendingRequests: number;
226
- stop(closeActiveConnections?: boolean): void;
227
- }
228
- /**
229
- * A bundle of lifecycle hooks applied together via {@link Server.use} — the unit
230
- * `@nifrajs/middleware` ships (cors, security headers, rate-limit). Every hook is
231
- * optional and wired to its lifecycle point. Middleware is context-agnostic (sees
232
- * the base `Context`); `use` does no context-type merging — the full type-merging
233
- * plugin system is deferred, and `.use` is reserved as its future entry point.
234
- */
235
- export interface Middleware {
236
- readonly name?: string;
237
- readonly onRequest?: (req: Request, platform?: Platform) => MaybePromise<OnRequestResult>;
238
- readonly around?: <T>(context: Context, next: () => MaybePromise<T>) => MaybePromise<T>;
239
- readonly beforeHandle?: (context: Context) => MaybePromise<unknown>;
240
- readonly afterHandle?: (result: unknown, context: Context) => MaybePromise<unknown>;
241
- readonly onResponse?: (response: Response, req: Request) => MaybePromise<Response>;
242
- readonly onResponseFinalized?: (outcome: ResponseFinalization, req: Request) => MaybePromise<void>;
243
- readonly onError?: (error: unknown, context: Context) => MaybePromise<unknown>;
244
- }
245
- /** The terminal response-pipeline outcome observed after every transforming `onResponse` hook. */
246
- export interface ResponseFinalization {
247
- /** The final response, or the last response available when a transformation failed. */
248
- readonly response: Response;
249
- /** A response-hook failure. Terminal observers run fail-open before the error is rethrown. */
250
- readonly error?: unknown;
251
- }
84
+ export type { AdmissionController, AdmissionDecision, McpPromptDescriptor, McpResourceDescriptor, Middleware, PromptArgument, PromptMessage, ResponseFinalization, RouteDescriptor, RunningServer, ServerOptions, ToolAnnotations, };
252
85
  export type AnyServer = Server<any, any>;
253
- /**
254
- * A nifra **plugin**: a function that augments an app — calling `use`/`derive`/`decorate` and/or
255
- * registering routes — and returns it. Because `derive`/`decorate` are type-threaded, an **inline**
256
- * `app.use((a) => a.derive(...).decorate(...))` carries the added context to handlers defined after
257
- * it (the `use` overload is generic over the concrete `this`). Wrap with {@link definePlugin} to
258
- * attach a name for idempotent dedupe (applying the same named plugin twice — e.g. transitively — is
259
- * a no-op).
260
- */
261
- export type NifraPlugin<In extends AnyServer = AnyServer, Out extends AnyServer = In> = ((app: In) => Out) & {
262
- readonly pluginName?: string;
263
- };
264
- /**
265
- * A named type-identity plugin built with {@link defineIdentityPlugin}. It returns the same concrete
266
- * server type it receives, preserving the caller's typed registry and context across `.use()` while
267
- * still allowing the plugin to register runtime hooks or handlers.
268
- */
269
- export type IdentityPlugin = (<S extends AnyServer>(app: S) => S) & {
270
- readonly pluginName?: string;
271
- };
272
- /**
273
- * Name + ergonomics for a plugin that **adds typed context** (`derive`/`decorate`). `app.use(myPlugin)`
274
- * applies it once; a second `use` of the same name is skipped (idempotent), so plugins can depend on each
275
- * other without double-registering hooks.
276
- *
277
- * ```ts
278
- * export const requestId = definePlugin("requestId", (app) => app.derive(() => ({ requestId: uuid() })))
279
- * app.use(requestId) // downstream handlers see c.requestId
280
- * ```
281
- *
282
- * FOOTGUN: only use this for a plugin that adds context. For a plugin that **mounts routes/hooks but adds
283
- * NO context** (an auth router, an audit logger), use {@link defineRouterPlugin} ({@link defineIdentityPlugin}).
284
- * `definePlugin((app) => app.get(...))` infers `app: Server<any, any>`, so `.use()` returns `Server<any, any>`
285
- * and your whole typed client silently collapses to `any` — no type error, no runtime error.
286
- */
287
- export declare function definePlugin<In extends AnyServer, Out extends AnyServer>(name: string, apply: (app: In) => Out): NifraPlugin<In, Out>;
288
- /**
289
- * Define a type-**identity** plugin: it registers routes/hooks as a side effect but returns the app with
290
- * its `Registry` + `Context` UNCHANGED. Use this (not {@link definePlugin}) for any plugin that doesn't
291
- * add context types — e.g. one mounting an auth handler. It threads the caller's *concrete* server type
292
- * through `use`, so routes declared after `app.use(plugin)` keep their types.
293
- *
294
- * Why a dedicated helper: `definePlugin((app) => app)` infers `app: Server<any, any>`, so `use` returns
295
- * `Server<any, any>` and the whole typed client collapses to `any`. The explicit generic return type here
296
- * (which a plain `Object.assign` can't preserve) is what keeps `use` returning the precise server type.
297
- *
298
- * ```ts
299
- * export const audit = defineIdentityPlugin("audit", (app) => app.onResponse(logResponse))
300
- * const api = server().get("/a", h).use(audit).get("/b", h) // /a AND /b stay typed
301
- * ```
302
- */
303
- export declare function defineIdentityPlugin(name: string, apply: <S extends AnyServer>(app: S) => S): IdentityPlugin;
304
- /**
305
- * Alias of {@link defineIdentityPlugin} with a name that says what it's FOR: a plugin that **mounts
306
- * routes/hooks but adds no context type** (an auth router, an audit logger). Use this — not
307
- * {@link definePlugin} — for any such plugin, or the typed client silently collapses to `any`. The
308
- * "identity" in {@link defineIdentityPlugin} refers to the type-identity it preserves; `defineRouterPlugin`
309
- * is the same thing under a clearer name.
310
- *
311
- * Mount routes as a **side effect**, then return the app unchanged (registering via `.get`/`.post` would
312
- * change the type away from the identity `S`; the mounted routes run but aren't in the caller's typed
313
- * registry — that's the trade that keeps everything else typed):
314
- *
315
- * ```ts
316
- * export const scim = defineRouterPlugin("scim", (app) => {
317
- * app.get("/scim/v2/Users", listUsers) // side effect: mounted at runtime
318
- * return app // return S unchanged → routes added after .use(scim) stay typed
319
- * })
320
- * const api = server().get("/a", h).use(scim).get("/b", h) // /a AND /b stay typed
321
- * ```
322
- */
323
- export declare const defineRouterPlugin: typeof defineIdentityPlugin;
324
- interface UrlParts {
325
- readonly pathname: string;
326
- readonly search: string;
327
- }
328
- export declare function urlPartsOf(url: string): UrlParts;
329
- export declare function pathnameOf(url: string): string;
330
- export declare function searchOf(url: string): string;
331
- /** A query value: a single occurrence is a string; a repeated key promotes to a string[] so an
332
- * array query schema (`t.array(t.string())`) can validate `?tag=a&tag=b` — last-wins silently
333
- * dropped values before (audit 2026-06). Single-occurrence keys stay plain strings, so existing
334
- * `t.string()` schemas are untouched; a repeated key against a string schema now FAILS validation
335
- * (an explicit 400 beats silently picking one). */
336
- export type QueryValue = string | string[];
337
- export declare function queryObjectOf(search: string): Record<string, QueryValue>;
338
- /**
339
- * What {@link Server.resolveNode} returns: either a plain-data render the `@nifrajs/node` adapter writes
340
- * to the socket directly (`kind: "json"` — status + headers + cookies + a pre-stringified body, **no**
341
- * undici `Response` built or drained), a marked buffered response body (`kind: "body"` — e.g.
342
- * @nifrajs/web's non-deferred SSR HTML), or a `Response` (`kind: "response"`) for everything else
343
- * (redirects, 404/405/errors, unmarked or streaming bodies). Internal to the nifra↔node bridge.
344
- */
345
- export type NodeServeOutcome = {
346
- readonly kind: "response";
347
- readonly response: Response;
348
- } | {
349
- readonly kind: "json";
350
- readonly status: number;
351
- /** `c.set.headers` backing, or `undefined` when the handler never set a header. */
352
- readonly headers: Readonly<Record<string, string>> | undefined;
353
- /** Queued `Set-Cookie` lines, or `undefined`; the adapter emits one header line each. */
354
- readonly cookies: readonly string[] | undefined;
355
- /** The JSON body already stringified, or `null` for an empty (204) response. */
356
- readonly body: string | null;
357
- } | {
358
- readonly kind: "body";
359
- readonly status: number;
360
- readonly headers: Readonly<Record<string, string | readonly string[]>> | undefined;
361
- readonly body: string | Uint8Array;
86
+ export { defineIdentityPlugin, definePlugin, defineRouterPlugin, type NifraPlugin, } from "./plugin.js";
87
+ export type { IdentityPlugin };
88
+ export { pathnameOf, urlPartsOf } from "./http.js";
89
+ export { type QueryValue, queryObjectOf, searchOf };
90
+ /** `ctx.set` carrying the lazy backings (`_headers`, `_cookies`) so `toResponse` can skip allocating
91
+ * anything when no handler touched `c.set.*`. Server-internal. */
92
+ export type CtxSet = ResponseControls & {
93
+ _headers?: Record<string, string>;
94
+ /** Accumulated `Set-Cookie` values — a list, since a `Record` would collapse multiple cookies. */
95
+ _cookies?: string[];
362
96
  };
363
97
  /**
364
98
  * The inline server. Routes are chainable and fully type-inferred. `derive`/
@@ -369,7 +103,7 @@ export type NodeServeOutcome = {
369
103
  * .get("/me", (c) => c.user) // c.user + c.db are typed
370
104
  */
371
105
  export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyContext> {
372
- private readonly router;
106
+ private readonly catalog;
373
107
  /** WebSocket routes, matched separately at upgrade time (a GET + `Upgrade: websocket`). */
374
108
  private readonly wsRouter;
375
109
  private wsRouteCount;
@@ -377,10 +111,11 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
377
111
  * Created by the first `app.ws()` via the `@nifrajs/core/ws` runtime — `undefined` until then, so a
378
112
  * no-WebSocket app never constructs (or bundles) it. */
379
113
  private topics;
380
- private readonly routeList;
381
114
  private readonly maxBodyBytes;
382
115
  private readonly wsMaxPayloadBytes;
383
116
  private readonly requestTimeoutMs;
117
+ /** Opt-in caller-IP trust declaration; `undefined` = socket peer only, no forwarded header believed. */
118
+ private readonly clientIpTrust;
384
119
  private readonly acceptInboundDeadlines;
385
120
  private readonly maxInboundDeadlineMs;
386
121
  private readonly deadlineAdmissionOptions;
@@ -388,15 +123,25 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
388
123
  /** Capacity-admission gate; `undefined` = off (the request path pays nothing). */
389
124
  private readonly capacityGate;
390
125
  private readonly onCapabilityUse;
391
- /** Effect-ledger defaults pinned at construction; per-route `ledgered` copies add route identity. */
392
- private readonly effectLedger;
393
- /** App-wide idempotency store (explicit option, else a lazily-created in-process default). */
394
- private readonly configuredIdempotencyStore;
395
- private lazyIdempotencyStore;
126
+ /** The installed effect-ledger runtime (owns the sink + per-route resolution + settle), or
127
+ * `undefined` when the effect-ledger plugin is not installed. */
128
+ private effectLedgerRuntime;
129
+ /** The installed idempotency runtime (owns the app-wide default store + the dedupe lane), or
130
+ * `undefined` when the idempotency plugin is not installed. */
131
+ private idempotencyRuntime;
132
+ /** Installed opt-in runtime for `.tool()`/`.resource()`/`.prompt()`; `undefined` until `.use(mcp())`. */
133
+ private mcpRuntime;
134
+ /** Installed Node-direct renderer for direct `resolveNode()` callers; `undefined` until `.use(nodeDirect())`. */
135
+ private nodeOutcomeRuntime;
136
+ /** Installed streaming runtime for `.sse()` routes; `undefined` until `.use(streaming())`. */
137
+ private sseRuntime;
138
+ /** Installed WebSocket runtime for `.ws()` routes; `undefined` until `.use(websocket())`. */
139
+ private wsRuntime;
396
140
  private readonly logger;
397
141
  /** App-wide validation-error fallback; a route's own `schema.onValidationError` takes precedence. */
398
142
  private readonly defaultOnValidationError?;
399
143
  private bunServer;
144
+ private sealed;
400
145
  private readonly derives;
401
146
  private readonly decorations;
402
147
  private readonly beforeHandleHooks;
@@ -416,14 +161,8 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
416
161
  /** App-declared MCP resources / prompts (via {@link resource} / {@link prompt}), read by `nifra mcp`. */
417
162
  private readonly mcpResourceList;
418
163
  private readonly mcpPromptList;
419
- /**
420
- * Every registered route's raw router entry + descriptor, in order — the replay source for
421
- * {@link merge}. The entry already carries the chains captured at registration (derives,
422
- * decorations, before/after/around/onError), so a merged route behaves exactly as it did on
423
- * the server that defined it.
424
- */
425
- private readonly rawEntries;
426
164
  constructor(options?: ServerOptions);
165
+ private assertConfigurable;
427
166
  /** Add a per-request, computed context extension for subsequent routes. */
428
167
  derive<D extends object>(fn: (context: Context & Ctx) => MaybePromise<D>): Server<R, Ctx & D>;
429
168
  /** Add a static context value for subsequent routes. */
@@ -480,7 +219,8 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
480
219
  * JSON-serialized into the SSE `data:` field. The typed client sees the marker and grows a
481
220
  * `.subscribe(onEvent)` for the route with the same payload type — end-to-end typed streaming.
482
221
  *
483
- * const app = server().sse("/feed", { sse: t.object({ id: t.integer(), title: t.string() }) },
222
+ * import { streaming } from "@nifrajs/core/sse" // .use(streaming()) enables .sse()
223
+ * const app = server().use(streaming()).sse("/feed", { sse: t.object({ id: t.integer(), title: t.string() }) },
484
224
  * async (c, stream) => {
485
225
  * stream.send({ id: 1, title: "hello" }) // typed
486
226
  * await waitForDisconnect(stream.signal)
@@ -504,6 +244,19 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
504
244
  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>;
505
245
  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>;
506
246
  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>;
247
+ /**
248
+ * Declare an **MCP tool** an agent can call (via `nifra mcp`, or a mounted MCP endpoint): a typed
249
+ * `POST /_nifra/tool/<name>` route whose `input`/`output` schemas contract the call and surface in
250
+ * `tools/list`. Requires `.use(mcp())` - without it, `.tool()` is a registration error, so an
251
+ * ordinary HTTP app never bundles the MCP wiring. Siblings: {@link resource}, {@link prompt}.
252
+ *
253
+ * import { mcp } from "@nifrajs/core/mcp"
254
+ * const app = server().use(mcp()).tool(
255
+ * "search",
256
+ * { description: "Search posts", input: t.object({ q: t.string() }) },
257
+ * ({ q }) => findPosts(q),
258
+ * )
259
+ */
507
260
  tool<Name extends string, S extends {
508
261
  description: string;
509
262
  input: StandardSchemaV1;
@@ -545,9 +298,15 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
545
298
  * adapter (`listen()`, `@nifrajs/node`, `@nifrajs/deno`, `toFetchHandler`) — not by bare `app.fetch`, which
546
299
  * has no socket (a WS path through `app.fetch` is a normal HTTP response).
547
300
  *
301
+ * The route also enters the type-level registry (under the pseudo-method `"WS"`), so the typed
302
+ * client grows a `.ws()` handle for it: `messageSchema` types what the client may `send`,
303
+ * `sendSchema` types the frames it receives. Passing explicit type arguments (`ws<MyData>(…)`)
304
+ * defeats path-literal inference and skips the registry entry - the route still serves, it is just
305
+ * invisible to `client<App>`; prefer typing `data` via `upgrade()`'s return.
306
+ *
548
307
  * app.ws("/chat", { open: (ws) => ws.send("hi"), message: (ws, data) => ws.send(data) })
549
308
  */
550
- ws<Data = unknown, Schema extends StandardSchemaV1 | undefined = undefined>(path: string, handler: WebSocketHandler<Data, EnvOf<Ctx>, Schema>): this;
309
+ ws<Data = unknown, Schema extends StandardSchemaV1 | undefined = undefined, Send extends StandardSchemaV1 | undefined = undefined, Path extends string = string>(path: Path, handler: WebSocketHandler<Data, EnvOf<Ctx>, Schema, Send>): string extends Path ? Server<R, Ctx> : Server<AddRoute<R, "WS", Path, WsRouteInfoFor<Path, Schema, Send>>, Ctx>;
551
310
  /**
552
311
  * Broadcast `data` to every WebSocket connection subscribed to `topic` (via `ws.subscribe(topic)`).
553
312
  * In-process and **single-instance** (see {@link TopicRegistry}) — a multi-instance deploy must bridge
@@ -561,10 +320,31 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
561
320
  * is the "compiled", order-scoped per-route chain.
562
321
  */
563
322
  register(method: Method, path: string, schema: RouteSchema | undefined, handler: (context: never) => unknown): void;
564
- /** Resolve a route's declared idempotency into a pinned store + defaults, or `undefined` when off. */
565
- private resolveIdempotency;
566
- /** The app-wide idempotency store: the configured one, else a lazily-created shared in-process store. */
567
- private defaultIdempotencyStore;
323
+ /** Register a contract/group route batch atomically. Every route captures the same current chain it
324
+ * would capture through {@link register}; no route becomes visible unless the full batch validates. */
325
+ registerBatch(routes: readonly {
326
+ readonly method: Method;
327
+ readonly path: string;
328
+ readonly schema: RouteSchema | undefined;
329
+ readonly handler: (context: never) => unknown;
330
+ }[]): void;
331
+ private prepareRoute;
332
+ /** Collapse route-invariant lifecycle decisions into one runner at registration. The request path
333
+ * performs no eligibility ladder: it supplies request state to this already-selected plan. */
334
+ private compileExecutionPlan;
335
+ /** The idempotency lane's bridge back into the normal matched lanes, resolved to a concrete Response
336
+ * (the lane buffers the body, then replays it through the route's real validation + handler). */
337
+ private idempotencyRunLanes;
338
+ /** @internal Symbol-keyed install seam for the `idempotency()` plugin. Off the public typed surface. */
339
+ [INSTALL_IDEMPOTENCY](runtime: IdempotencyRuntime): void;
340
+ /** @internal Symbol-keyed install seam for the `mcp()` plugin. Off the public typed surface. */
341
+ [INSTALL_MCP](runtime: McpRuntime): void;
342
+ /** @internal Symbol-keyed install seam for the `nodeDirect()` plugin. Off the public typed surface. */
343
+ [INSTALL_NODE_DIRECT](runtime: NodeOutcomeRuntime): void;
344
+ /** @internal Symbol-keyed install seam for the `streaming()` plugin. Off the public typed surface. */
345
+ [INSTALL_SSE](runtime: SseRuntime): void;
346
+ /** @internal Symbol-keyed install seam for the `websocket()` plugin. Off the public typed surface. */
347
+ [INSTALL_WS](runtime: WsRuntime): void;
568
348
  /**
569
349
  * Merge another server's routes into this one — the composition escape hatch for large apps.
570
350
  *
@@ -585,6 +365,9 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
585
365
  * those on the parent).
586
366
  */
587
367
  merge<R2 extends Registry, Ctx2>(other: Server<R2, Ctx2>): Server<R & R2, Ctx>;
368
+ /** A fused renderer closes over runtime services to keep its seven-argument JSC fast path. Merging
369
+ * rebinds that closure once to the executing server; generic plans already receive the runtime. */
370
+ private bindFusedRuntime;
588
371
  /**
589
372
  * Enumerate the registered routes (method, path, input schemas), in registration
590
373
  * order. Powers `toOpenAPI` and other introspection; the router trie itself no
@@ -621,7 +404,7 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
621
404
  * render differs.
622
405
  */
623
406
  resolveNode(req: Request, platform?: Platform<EnvOf<Ctx>>): MaybePromise<NodeServeOutcome>;
624
- resolveNodeSource(source: RequestSource, platform?: Platform<EnvOf<Ctx>>): MaybePromise<NodeServeOutcome>;
407
+ resolveNodeSource(source: RequestSource, platform?: Platform<EnvOf<Ctx>>, suppliedRuntime?: NodeOutcomeRuntime): MaybePromise<NodeServeOutcome>;
625
408
  /**
626
409
  * Resolve a WebSocket upgrade — the seam every serving adapter uses. Returns `pass` (not a WS
627
410
  * upgrade for a registered route → handle as normal HTTP), `reject` (a WS route matched but
@@ -642,6 +425,9 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
642
425
  * produces the 503. The Web `fetch` and `resolveNode` are thin callers over this one routing +
643
426
  * context + lifecycle implementation — no duplication across the trust boundary.
644
427
  */
428
+ /** Apply the `clientIp` trust declaration to the adapter's raw socket peer, returning a platform
429
+ * whose `clientIp` is the derived caller. Only called when a trust declaration is configured. */
430
+ private deriveClientIp;
645
431
  private dispatch;
646
432
  /**
647
433
  * onRequest short-circuit path. Synchronous as long as every hook returns synchronously (the
@@ -657,36 +443,22 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
657
443
  private takeResponseRequest;
658
444
  /**
659
445
  * Route → build context → run. Synchronous through to the handler for a **bare** route
660
- * ({@link RouteEntry.bare}), so a sync handler produces its result with zero promise allocations;
446
+ * (selected by its compiled plan), so a sync handler produces its result with zero promise allocations;
661
447
  * routes with validation/hooks keep the full async {@link runLifecycle}, unchanged.
662
448
  */
663
449
  private routeAndRun;
664
450
  /** Run a route that has already been matched by the runtime or Nifra's portable router. */
665
451
  private runMatched;
666
- /**
667
- * Idempotency dedupe lane. Reads the `Idempotency-Key`, fingerprints the request, and consults the
668
- * store: a fresh key runs the handler (via {@link runMatchedLanes} with the body buffered) and stores
669
- * the response; a repeat key replays it without re-running; a key reused with a different body or an
670
- * in-flight duplicate is rejected. Every branch materializes a `Response`, then adapts to `T` via the
671
- * caller's `wrapResponse`, so it works on the Web fetch path and the Node-native outcome path alike.
672
- */
673
- private runIdempotent;
674
- /** Run a matched route through its selected execution lane (fused/bare/bodyOnly/queryOnly/lifecycle). */
452
+ /** Supply request-specific deadline state to the route's precompiled execution plan. */
675
453
  private runMatchedLanes;
676
- /**
677
- * Seal a ledgered request and deliver the sealed ledger to the sink. An empty ledger (declared
678
- * capabilities, none exercised) is not delivered — there is nothing to audit. This post-effect
679
- * ledger is observational: transactional/durable audit belongs in
680
- * the owned effect transaction, so a sink outage must not turn a successful effect into a retryable
681
- * 500 that can duplicate it.
682
- */
683
- private settleEffectLedger;
454
+ /** @internal Symbol-keyed install seam for the `effectLedger()` plugin. Off the public typed surface. */
455
+ [INSTALL_EFFECT_LEDGER](runtime: EffectLedgerRuntime): void;
684
456
  /** The narrowest bare route: a syntactic `() => ...` handler cannot observe the context argument, so
685
457
  * successful requests can skip allocating `RequestContext`. Errors still allocate one for logging. */
686
458
  private runContextlessBare;
687
459
  private contextlessBareError;
688
460
  /**
689
- * The synchronous fast path for a {@link RouteEntry.bare} route: apply static decorations, call the
461
+ * The synchronous fast path selected by a route's execution plan: apply static decorations, call the
690
462
  * handler, render the result — **no `await`** unless the handler itself returns a promise. It mirrors
691
463
  * the bare slice of {@link runLifecycle} (which a bare route would otherwise no-op through) and shares
692
464
  * {@link logRequestError}; a bare route has no `onError` hooks, so error handling is fully synchronous
@@ -697,7 +469,7 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
697
469
  /** Bare-route error rendering — identical to {@link runLifecycle}'s catch minus the (absent) onError
698
470
  * loop: a thrown `Response` is returned as deliberate control flow; anything else is logged + 500. */
699
471
  /**
700
- * Build a route's fused Web lane (see {@link RouteEntry.fusedWeb}). Composition happens once at
472
+ * Build a route's fused Web renderer. Composition happens once at
701
473
  * registration; the returned closure is what every request to the route runs. Behavior is
702
474
  * byte-identical to the generic `runBare`/`runContextlessBare` + `toResponse` pair — same
703
475
  * decoration order, same error routing (thrown `Response` = control flow; anything else logs and
@@ -751,6 +523,9 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
751
523
  * bare fast path ({@link bareError}) so both record the same fields. Never throws; never leaks. */
752
524
  private logRequestError;
753
525
  private readAndValidateBody;
526
+ /** Apply validation and its recovery hook on the generic lifecycle lane. Recovery is completed
527
+ * before derives/beforeHandle run, matching the body-only and query-only execution lanes. */
528
+ private applyLifecycleValidation;
754
529
  /**
755
530
  * Read the body as text, capped at `maxBodyBytes`. Rejects (`null`) on a
756
531
  * `Content-Length` over the cap *before* buffering, and aborts mid-stream once the
@@ -766,6 +541,9 @@ export declare class Server<R extends Registry = EmptyRegistry, Ctx = EmptyConte
766
541
  * length-less bodies fall through to the streaming byte-cap guard below.
767
542
  */
768
543
  private readBoundedJson;
544
+ /** Adapt one route's compiled execution plan to Bun's already-matched request shape. Route
545
+ * semantics remain in the plan; this closure only supplies native params and deadline fallback. */
546
+ private compileBunNativeHandler;
769
547
  /** Compile portable route registrations into Bun's native route table. Apps with request-rewrite
770
548
  * hooks or WebSockets retain the single portable dispatcher because those features must run before
771
549
  * route selection/upgrade. Named wildcards also stay on the fallback until Bun exposes their raw
@@ -859,5 +637,4 @@ export declare function toFetchHandler<Env = unknown>(app: {
859
637
  fetch(request: Request, env: Env, ctx: ExecutionContext): MaybePromise<Response>;
860
638
  scheduled?(controller: ScheduledController, env: Env, ctx: ExecutionContext): MaybePromise<void>;
861
639
  };
862
- export {};
863
640
  //# sourceMappingURL=server.d.ts.map