@nifrajs/core 2.14.1 → 3.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.
Files changed (226) hide show
  1. package/README.md +25 -0
  2. package/dist/assurance.d.ts +16 -8
  3. package/dist/assurance.d.ts.map +1 -1
  4. package/dist/assurance.js +33 -0
  5. package/dist/assurance.js.map +1 -1
  6. package/dist/durable-adapters.d.ts.map +1 -1
  7. package/dist/durable-adapters.js +26 -3
  8. package/dist/durable-adapters.js.map +1 -1
  9. package/dist/durable-approval.d.ts +56 -0
  10. package/dist/durable-approval.d.ts.map +1 -0
  11. package/dist/durable-approval.js +233 -0
  12. package/dist/durable-approval.js.map +1 -0
  13. package/dist/durable-effect.d.ts +42 -0
  14. package/dist/durable-effect.d.ts.map +1 -0
  15. package/dist/durable-effect.js +234 -0
  16. package/dist/durable-effect.js.map +1 -0
  17. package/dist/durable-execution.d.ts +8 -346
  18. package/dist/durable-execution.d.ts.map +1 -1
  19. package/dist/durable-execution.js +7 -1150
  20. package/dist/durable-execution.js.map +1 -1
  21. package/dist/durable-saga.d.ts +94 -0
  22. package/dist/durable-saga.d.ts.map +1 -0
  23. package/dist/durable-saga.js +550 -0
  24. package/dist/durable-saga.js.map +1 -0
  25. package/dist/edge-kit.d.ts +27 -0
  26. package/dist/edge-kit.d.ts.map +1 -0
  27. package/dist/edge-kit.js +24 -0
  28. package/dist/edge-kit.js.map +1 -0
  29. package/dist/errors.d.ts +1 -1
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js.map +1 -1
  32. package/dist/index.d.ts +23 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +18 -2
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal/durable-approval-protocol.d.ts +1 -1
  37. package/dist/internal/durable-approval-protocol.d.ts.map +1 -1
  38. package/dist/internal/durable-effect-protocol.d.ts +1 -1
  39. package/dist/internal/durable-effect-protocol.d.ts.map +1 -1
  40. package/dist/internal/durable-saga-protocol.d.ts +1 -1
  41. package/dist/internal/durable-saga-protocol.d.ts.map +1 -1
  42. package/dist/internal/durable-shared.d.ts +26 -0
  43. package/dist/internal/durable-shared.d.ts.map +1 -0
  44. package/dist/internal/durable-shared.js +131 -0
  45. package/dist/internal/durable-shared.js.map +1 -0
  46. package/dist/internal/durable-types.d.ts +159 -0
  47. package/dist/internal/durable-types.d.ts.map +1 -0
  48. package/dist/internal/durable-types.js +2 -0
  49. package/dist/internal/durable-types.js.map +1 -0
  50. package/dist/internal/route-compiler.d.ts +61 -0
  51. package/dist/internal/route-compiler.d.ts.map +1 -0
  52. package/dist/internal/route-compiler.js +121 -0
  53. package/dist/internal/route-compiler.js.map +1 -0
  54. package/dist/internal/route-execution.d.ts +17 -9
  55. package/dist/internal/route-execution.d.ts.map +1 -1
  56. package/dist/internal/route-execution.js +4 -18
  57. package/dist/internal/route-execution.js.map +1 -1
  58. package/dist/internal/route-lanes.d.ts +35 -0
  59. package/dist/internal/route-lanes.d.ts.map +1 -0
  60. package/dist/internal/route-lanes.js +150 -0
  61. package/dist/internal/route-lanes.js.map +1 -0
  62. package/dist/internal/route-program.d.ts +67 -0
  63. package/dist/internal/route-program.d.ts.map +1 -0
  64. package/dist/internal/route-program.js +199 -0
  65. package/dist/internal/route-program.js.map +1 -0
  66. package/dist/response-observer.d.ts +25 -0
  67. package/dist/response-observer.d.ts.map +1 -0
  68. package/dist/response-observer.js +40 -0
  69. package/dist/response-observer.js.map +1 -0
  70. package/dist/security/baseline.d.ts +68 -0
  71. package/dist/security/baseline.d.ts.map +1 -0
  72. package/dist/security/baseline.js +118 -0
  73. package/dist/security/baseline.js.map +1 -0
  74. package/dist/server/bare-error-lane.d.ts +26 -0
  75. package/dist/server/bare-error-lane.d.ts.map +1 -0
  76. package/dist/server/bare-error-lane.js +60 -0
  77. package/dist/server/bare-error-lane.js.map +1 -0
  78. package/dist/server/body-lane.d.ts +13 -0
  79. package/dist/server/body-lane.d.ts.map +1 -0
  80. package/dist/server/body-lane.js +134 -0
  81. package/dist/server/body-lane.js.map +1 -0
  82. package/dist/server/body.d.ts +7 -0
  83. package/dist/server/body.d.ts.map +1 -1
  84. package/dist/server/body.js +7 -0
  85. package/dist/server/body.js.map +1 -1
  86. package/dist/server/bridge-markers.d.ts +25 -0
  87. package/dist/server/bridge-markers.d.ts.map +1 -0
  88. package/dist/server/bridge-markers.js +25 -0
  89. package/dist/server/bridge-markers.js.map +1 -0
  90. package/dist/server/context.d.ts +6 -1
  91. package/dist/server/context.d.ts.map +1 -1
  92. package/dist/server/contract.d.ts +40 -14
  93. package/dist/server/contract.d.ts.map +1 -1
  94. package/dist/server/contract.js +7 -0
  95. package/dist/server/contract.js.map +1 -1
  96. package/dist/server/cookies.d.ts.map +1 -1
  97. package/dist/server/cookies.js +16 -2
  98. package/dist/server/cookies.js.map +1 -1
  99. package/dist/server/header-case.d.ts.map +1 -1
  100. package/dist/server/header-case.js +2 -1
  101. package/dist/server/header-case.js.map +1 -1
  102. package/dist/server/http.d.ts +36 -3
  103. package/dist/server/http.d.ts.map +1 -1
  104. package/dist/server/http.js +49 -3
  105. package/dist/server/http.js.map +1 -1
  106. package/dist/server/install.d.ts +6 -8
  107. package/dist/server/install.d.ts.map +1 -1
  108. package/dist/server/install.js +7 -0
  109. package/dist/server/install.js.map +1 -1
  110. package/dist/server/node-outcome-hook.d.ts +2 -1
  111. package/dist/server/node-outcome-hook.d.ts.map +1 -1
  112. package/dist/server/node-outcome-hook.js.map +1 -1
  113. package/dist/server/node-outcome.d.ts +17 -2
  114. package/dist/server/node-outcome.d.ts.map +1 -1
  115. package/dist/server/node-outcome.js +37 -3
  116. package/dist/server/node-outcome.js.map +1 -1
  117. package/dist/server/plugin.d.ts +2 -2
  118. package/dist/server/plugin.d.ts.map +1 -1
  119. package/dist/server/plugin.js +6 -1
  120. package/dist/server/plugin.js.map +1 -1
  121. package/dist/server/query.d.ts +7 -1
  122. package/dist/server/query.d.ts.map +1 -1
  123. package/dist/server/query.js +8 -3
  124. package/dist/server/query.js.map +1 -1
  125. package/dist/server/registry.d.ts +40 -12
  126. package/dist/server/registry.d.ts.map +1 -1
  127. package/dist/server/request-context.d.ts +2 -13
  128. package/dist/server/request-context.d.ts.map +1 -1
  129. package/dist/server/request-context.js +18 -159
  130. package/dist/server/request-context.js.map +1 -1
  131. package/dist/server/respond.d.ts.map +1 -1
  132. package/dist/server/respond.js +58 -4
  133. package/dist/server/respond.js.map +1 -1
  134. package/dist/server/response-contract-lane.d.ts.map +1 -1
  135. package/dist/server/response-contract-lane.js +5 -2
  136. package/dist/server/response-contract-lane.js.map +1 -1
  137. package/dist/server/response-observer-runtime.d.ts +20 -0
  138. package/dist/server/response-observer-runtime.d.ts.map +1 -0
  139. package/dist/server/response-observer-runtime.js +95 -0
  140. package/dist/server/response-observer-runtime.js.map +1 -0
  141. package/dist/server/runtime-core.d.ts +76 -0
  142. package/dist/server/runtime-core.d.ts.map +1 -1
  143. package/dist/server/runtime-core.js +42 -1
  144. package/dist/server/runtime-core.js.map +1 -1
  145. package/dist/server/server-types.d.ts +6 -1
  146. package/dist/server/server-types.d.ts.map +1 -1
  147. package/dist/server/server.d.ts +158 -116
  148. package/dist/server/server.d.ts.map +1 -1
  149. package/dist/server/server.js +707 -844
  150. package/dist/server/server.js.map +1 -1
  151. package/dist/server/validation.d.ts +5 -0
  152. package/dist/server/validation.d.ts.map +1 -0
  153. package/dist/server/validation.js +13 -0
  154. package/dist/server/validation.js.map +1 -0
  155. package/dist/server/websocket.d.ts +20 -4
  156. package/dist/server/websocket.d.ts.map +1 -1
  157. package/dist/server/websocket.js +81 -11
  158. package/dist/server/websocket.js.map +1 -1
  159. package/dist/server/ws-bun.d.ts +12 -4
  160. package/dist/server/ws-bun.d.ts.map +1 -1
  161. package/dist/server/ws-bun.js +50 -16
  162. package/dist/server/ws-bun.js.map +1 -1
  163. package/dist/server/ws-hook.d.ts +3 -2
  164. package/dist/server/ws-hook.d.ts.map +1 -1
  165. package/dist/server.d.ts +3 -2
  166. package/dist/server.d.ts.map +1 -1
  167. package/dist/server.js +2 -1
  168. package/dist/server.js.map +1 -1
  169. package/dist/single-copy-register.d.ts +2 -0
  170. package/dist/single-copy-register.d.ts.map +1 -0
  171. package/dist/single-copy-register.js +24 -0
  172. package/dist/single-copy-register.js.map +1 -0
  173. package/dist/single-copy.d.ts +156 -0
  174. package/dist/single-copy.d.ts.map +1 -0
  175. package/dist/single-copy.js +472 -0
  176. package/dist/single-copy.js.map +1 -0
  177. package/package.json +28 -3
  178. package/src/assurance.ts +58 -1
  179. package/src/durable-adapters.ts +20 -0
  180. package/src/durable-approval.ts +281 -0
  181. package/src/durable-effect.ts +300 -0
  182. package/src/durable-execution.ts +8 -1627
  183. package/src/durable-saga.ts +744 -0
  184. package/src/edge-kit.ts +27 -0
  185. package/src/errors.ts +1 -0
  186. package/src/index.ts +129 -2
  187. package/src/internal/durable-approval-protocol.ts +1 -1
  188. package/src/internal/durable-effect-protocol.ts +1 -1
  189. package/src/internal/durable-saga-protocol.ts +1 -1
  190. package/src/internal/durable-shared.ts +157 -0
  191. package/src/internal/durable-types.ts +194 -0
  192. package/src/internal/route-compiler.ts +234 -0
  193. package/src/internal/route-execution.ts +53 -96
  194. package/src/internal/route-lanes.ts +221 -0
  195. package/src/internal/route-program.ts +404 -0
  196. package/src/response-observer.ts +64 -0
  197. package/src/security/baseline.ts +172 -0
  198. package/src/server/bare-error-lane.ts +77 -0
  199. package/src/server/body-lane.ts +203 -0
  200. package/src/server/body.ts +8 -0
  201. package/src/server/bridge-markers.ts +24 -0
  202. package/src/server/context.ts +6 -1
  203. package/src/server/contract.ts +70 -20
  204. package/src/server/cookies.ts +16 -2
  205. package/src/server/header-case.ts +3 -1
  206. package/src/server/http.ts +53 -3
  207. package/src/server/install.ts +14 -0
  208. package/src/server/node-outcome-hook.ts +2 -1
  209. package/src/server/node-outcome.ts +44 -3
  210. package/src/server/plugin.ts +11 -5
  211. package/src/server/query.ts +10 -4
  212. package/src/server/registry.ts +71 -16
  213. package/src/server/request-context.ts +13 -216
  214. package/src/server/respond.ts +71 -3
  215. package/src/server/response-contract-lane.ts +6 -2
  216. package/src/server/response-observer-runtime.ts +135 -0
  217. package/src/server/runtime-core.ts +124 -1
  218. package/src/server/server-types.ts +6 -1
  219. package/src/server/server.ts +1446 -1485
  220. package/src/server/validation.ts +19 -0
  221. package/src/server/websocket.ts +127 -17
  222. package/src/server/ws-bun.ts +67 -15
  223. package/src/server/ws-hook.ts +3 -2
  224. package/src/server.ts +8 -2
  225. package/src/single-copy-register.ts +24 -0
  226. package/src/single-copy.ts +526 -0
@@ -5,12 +5,13 @@
5
5
  * its values) so `request-context`, `respond`, `node-outcome`, and the kernel form a cycle-free graph.
6
6
  */
7
7
  import { createUnboundedRequestBudget, type RequestBudget } from "../budget.ts"
8
+ import { NODE_BRIDGE_MARKER_KEYS } from "./bridge-markers.ts"
8
9
  import type { CtxSet, RequestSource } from "./server.ts"
9
10
 
10
11
  /** A handler returns a `Response` (used as-is) or any value (serialized to JSON). */
11
12
  export type HandlerResult = Response | unknown
12
13
 
13
- export const RESPONSE_RESULT = Symbol.for("nifra.response.result")
14
+ export const RESPONSE_RESULT = Symbol.for(NODE_BRIDGE_MARKER_KEYS.responseResult)
14
15
  export const CONTEXT_SET = Symbol("nifra.context.set")
15
16
  export const CONTEXT_SEARCH = Symbol("nifra.context.search")
16
17
 
@@ -28,6 +29,28 @@ export interface PreDecodedBody {
28
29
  readonly value: unknown
29
30
  }
30
31
 
32
+ /**
33
+ * A response described as plain data - the status, any headers of its own, and a body still in value
34
+ * form. A `ResponseResult` carrying one is rendered on the SAME lane a handler's plain return takes:
35
+ * `JSON.stringify` straight into the node writer's `kind: "json"` outcome, or the web lane's prebuilt
36
+ * JSON init. No `Response` is constructed anywhere on the node path.
37
+ *
38
+ * That matters because building one is the dominant cost of answering early. Measured on the rig, one
39
+ * server per shape, all five answering the same 401: `c.set.status` + a plain object 82802 req/s,
40
+ * `beforeHandle` returning that same object 81819, `return new Response(...)` 49668, `throw new
41
+ * Response(...)` 47505, the same throw from a `derive` 48746. The `Response` costs 40%; the throw
42
+ * around it costs another 4%; unwinding a lifecycle stage instead of a handler costs nothing
43
+ * measurable. So the fix for a slow rejection is not a faster throw - it is not allocating the
44
+ * `Response`.
45
+ */
46
+ export interface PlainRender {
47
+ readonly status: number
48
+ /** Headers belonging to this render. They win over anything the request left in `c.set.headers`. */
49
+ readonly headers?: Readonly<Record<string, string>>
50
+ /** The body as a value, serialized by the lane that renders it. `undefined` means no body. */
51
+ readonly body: unknown
52
+ }
53
+
31
54
  export interface ResponseResult {
32
55
  readonly [RESPONSE_RESULT]: true
33
56
  toResponse(): Response
@@ -36,6 +59,25 @@ export interface ResponseResult {
36
59
  readonly headers: Readonly<Record<string, string | readonly string[]>> | undefined
37
60
  readonly body: string | Uint8Array
38
61
  }
62
+ /** Present when this result can be rendered as plain data - see {@link PlainRender}. Every lane
63
+ * checks it before `toNodeBody`/`toResponse`, so a carrier that has one never builds a `Response`. */
64
+ readonly plain?: PlainRender
65
+ }
66
+
67
+ /**
68
+ * Type-only metadata carried by {@link status}. The symbol is declared, not created, so this brand
69
+ * adds no runtime property and no per-response allocation. It lets the registry distinguish a typed
70
+ * early response from an ordinary handler value while preserving the existing ResponseResult marker.
71
+ */
72
+ export declare const STATUS_RESPONSE_TYPE: unique symbol
73
+
74
+ /** A status-bearing response whose code and body remain visible to TypeScript. */
75
+ export interface StatusResponse<Code extends number = number, Body = unknown>
76
+ extends ResponseResult {
77
+ readonly [STATUS_RESPONSE_TYPE]: {
78
+ readonly code: Code
79
+ readonly body: Body
80
+ }
39
81
  }
40
82
 
41
83
  export function isResponseResult(value: unknown): value is ResponseResult {
@@ -47,6 +89,87 @@ export function isResponseResult(value: unknown): value is ResponseResult {
47
89
  )
48
90
  }
49
91
 
92
+ /**
93
+ * The headers a plain render ships: its own on top of whatever the request left in `c.set.headers`,
94
+ * so an ambient header (a request id, say) survives an early exit the way it survives an ordinary
95
+ * return, and a header named at the exit site still wins.
96
+ *
97
+ * Always a fresh object when the render has headers of its own: the node writers mutate the record
98
+ * they are handed (content-type, content-length, cookies), and a `status(...)` value is commonly
99
+ * hoisted to module scope and answered from on every request.
100
+ */
101
+ export function plainRenderHeaders(
102
+ plain: PlainRender,
103
+ set: CtxSet,
104
+ ): Record<string, string> | undefined {
105
+ const own = plain.headers
106
+ if (own === undefined) return set._headers
107
+ return set._headers === undefined ? { ...own } : { ...set._headers, ...own }
108
+ }
109
+
110
+ /**
111
+ * Finish the request here, with this status and body, without building a `Response`.
112
+ *
113
+ * Returned or thrown, from a handler or from any lifecycle stage:
114
+ *
115
+ * ```ts
116
+ * app.derive((c) => {
117
+ * const user = sessionOf(c)
118
+ * if (user === undefined) return status(401, { error: "unauthorized" })
119
+ * return { user }
120
+ * })
121
+ * ```
122
+ *
123
+ * A `derive` is the reason this exists as a value rather than as a rule about `beforeHandle`: a
124
+ * `beforeHandle` already short-circuits by returning a value, but a `derive`'s return IS the context
125
+ * extension, so before this its only exit was `throw new Response(...)` - the most expensive way to
126
+ * say 401 (see {@link PlainRender} for the measurements). Returning it is preferred; throwing it
127
+ * carries the same cost as any other throw and stays supported so a guard helper called for effect
128
+ * (`requireSession(c)`) can still end the request from inside a call it makes.
129
+ *
130
+ * The body is serialized by the lane that renders it, exactly like a handler's plain return, so the
131
+ * response carries a `content-length` rather than falling to chunked, and queued cookies still apply.
132
+ */
133
+ export function status<const Code extends number>(
134
+ code: Code,
135
+ body?: undefined,
136
+ init?: { readonly headers?: Readonly<Record<string, string>> },
137
+ ): StatusResponse<Code, undefined>
138
+ export function status<const Code extends number, const Body>(
139
+ code: Code,
140
+ body: Body,
141
+ init?: { readonly headers?: Readonly<Record<string, string>> },
142
+ ): StatusResponse<Code, Body>
143
+ export function status(
144
+ code: number,
145
+ body?: unknown,
146
+ init?: { readonly headers?: Readonly<Record<string, string>> },
147
+ ): StatusResponse<number, unknown> {
148
+ // The range `Response` accepts, enforced HERE so the plain lane cannot outrun it. A plain render
149
+ // is written straight to the socket by the Node adapter, where `writeHead` takes 100-999 - so a
150
+ // status this rejects would otherwise ship on Node and throw on every Web runtime, from the same
151
+ // code. One integer comparison against the value already in a register; the divergence it closes
152
+ // is the kind that only shows up in the runtime an app did not test on.
153
+ if (!Number.isInteger(code) || code < 200 || code > 599) {
154
+ throw new RangeError(`[nifra] status(${String(code)}): HTTP status must be an integer 200-599`)
155
+ }
156
+ const headers = init?.headers
157
+ const plain: PlainRender =
158
+ headers === undefined ? { status: code, body } : { status: code, headers, body }
159
+ return {
160
+ [RESPONSE_RESULT]: true,
161
+ plain,
162
+ // Only the lanes that cannot take plain data reach this - an edge runtime handed the value by a
163
+ // caller outside the server, say. Built on demand, never on the lanes that matter.
164
+ toResponse(): Response {
165
+ const headers: Record<string, string> = { ...init?.headers }
166
+ if (body === undefined) return new Response(null, { status: code, headers })
167
+ headers["content-type"] ??= "application/json;charset=utf-8"
168
+ return new Response(JSON.stringify(body), { status: code, headers })
169
+ },
170
+ } as StatusResponse<number, unknown>
171
+ }
172
+
50
173
  /** The concrete `Request` for a source - itself when a real `Request` was passed (the Web path), or the
51
174
  * lazily-built one (the Node adapter). A real `Request` IS a `RequestSource`, so no wrapper is allocated
52
175
  * on the Web hot path. */
@@ -256,12 +256,17 @@ export interface RunningServer {
256
256
  readonly hostname: string
257
257
  readonly pendingRequests: number
258
258
  stop(closeActiveConnections?: boolean): void
259
+ /** Bun's native topic broadcast, present on the Bun `Server` handle. `app.publish` delegates to it
260
+ * in native-pubsub mode; absent on other runtimes' running-server views. */
261
+ publish?(topic: string, data: string | ArrayBufferView | ArrayBuffer): number
259
262
  }
260
263
 
261
264
  /**
262
265
  * A bundle of lifecycle hooks applied together via {@link Server.use} - the unit
263
266
  * `@nifrajs/middleware` ships (cors, security headers, rate-limit). Every hook is
264
- * optional and wired to its lifecycle point. Middleware is context-agnostic (sees
267
+ * optional and wired to its lifecycle point. Middleware carrying a portable response tier must be
268
+ * marked with `withResponseObserver()` from `@nifrajs/core/response-observer`, or the server must
269
+ * install `responseObserver()` before the bundle is applied. Middleware is context-agnostic (sees
265
270
  * the base `Context`); `use` does no context-type merging - the full type-merging
266
271
  * plugin system is deferred, and `.use` is reserved as its future entry point.
267
272
  */