@nifrajs/core 3.3.0 → 4.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 (331) hide show
  1. package/README.md +26 -0
  2. package/dist/assurance.d.ts +7 -0
  3. package/dist/assurance.d.ts.map +1 -1
  4. package/dist/assurance.js +39 -3
  5. package/dist/assurance.js.map +1 -1
  6. package/dist/capabilities.d.ts +20 -2
  7. package/dist/capabilities.d.ts.map +1 -1
  8. package/dist/capabilities.js +47 -15
  9. package/dist/capabilities.js.map +1 -1
  10. package/dist/diff.d.ts +6 -1
  11. package/dist/diff.d.ts.map +1 -1
  12. package/dist/diff.js +36 -2
  13. package/dist/diff.js.map +1 -1
  14. package/dist/durable-adapters.d.ts.map +1 -1
  15. package/dist/durable-adapters.js +16 -3
  16. package/dist/durable-adapters.js.map +1 -1
  17. package/dist/durable-effect.d.ts.map +1 -1
  18. package/dist/durable-effect.js +6 -3
  19. package/dist/durable-effect.js.map +1 -1
  20. package/dist/edge-kit.d.ts +2 -0
  21. package/dist/edge-kit.d.ts.map +1 -1
  22. package/dist/edge-kit.js +2 -0
  23. package/dist/edge-kit.js.map +1 -1
  24. package/dist/effect-lifecycle.d.ts.map +1 -1
  25. package/dist/effect-lifecycle.js +2 -1
  26. package/dist/effect-lifecycle.js.map +1 -1
  27. package/dist/effect-scope.d.ts.map +1 -1
  28. package/dist/effect-scope.js +1 -1
  29. package/dist/effect-scope.js.map +1 -1
  30. package/dist/errors.d.ts +1 -1
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js.map +1 -1
  33. package/dist/evidence.d.ts +36 -1
  34. package/dist/evidence.d.ts.map +1 -1
  35. package/dist/evidence.js +219 -13
  36. package/dist/evidence.js.map +1 -1
  37. package/dist/idempotency.d.ts +31 -0
  38. package/dist/idempotency.d.ts.map +1 -1
  39. package/dist/idempotency.js +48 -9
  40. package/dist/idempotency.js.map +1 -1
  41. package/dist/index.d.ts +9 -6
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +6 -3
  44. package/dist/index.js.map +1 -1
  45. package/dist/internal/code-unit-order.d.ts +7 -0
  46. package/dist/internal/code-unit-order.d.ts.map +1 -0
  47. package/dist/internal/code-unit-order.js +9 -0
  48. package/dist/internal/code-unit-order.js.map +1 -0
  49. package/dist/internal/durable-saga-protocol.d.ts.map +1 -1
  50. package/dist/internal/durable-saga-protocol.js +2 -0
  51. package/dist/internal/durable-saga-protocol.js.map +1 -1
  52. package/dist/internal/route-catalog.d.ts +6 -6
  53. package/dist/internal/route-catalog.d.ts.map +1 -1
  54. package/dist/internal/route-catalog.js +20 -18
  55. package/dist/internal/route-catalog.js.map +1 -1
  56. package/dist/internal/route-compiler.d.ts +7 -6
  57. package/dist/internal/route-compiler.d.ts.map +1 -1
  58. package/dist/internal/route-compiler.js +36 -4
  59. package/dist/internal/route-compiler.js.map +1 -1
  60. package/dist/internal/route-execution.d.ts +7 -3
  61. package/dist/internal/route-execution.d.ts.map +1 -1
  62. package/dist/internal/route-execution.js.map +1 -1
  63. package/dist/internal/route-lanes.d.ts +1 -0
  64. package/dist/internal/route-lanes.d.ts.map +1 -1
  65. package/dist/internal/route-lanes.js +34 -29
  66. package/dist/internal/route-lanes.js.map +1 -1
  67. package/dist/internal/route-program.d.ts +8 -2
  68. package/dist/internal/route-program.d.ts.map +1 -1
  69. package/dist/internal/route-program.js +222 -20
  70. package/dist/internal/route-program.js.map +1 -1
  71. package/dist/ledger.d.ts.map +1 -1
  72. package/dist/ledger.js +8 -5
  73. package/dist/ledger.js.map +1 -1
  74. package/dist/manifest.d.ts +1 -1
  75. package/dist/manifest.d.ts.map +1 -1
  76. package/dist/manifest.js +52 -46
  77. package/dist/manifest.js.map +1 -1
  78. package/dist/mount.d.ts +36 -0
  79. package/dist/mount.d.ts.map +1 -1
  80. package/dist/mount.js +32 -0
  81. package/dist/mount.js.map +1 -1
  82. package/dist/reflection.d.ts +26 -0
  83. package/dist/reflection.d.ts.map +1 -1
  84. package/dist/reflection.js +65 -1
  85. package/dist/reflection.js.map +1 -1
  86. package/dist/response-observer.d.ts +1 -1
  87. package/dist/response-observer.d.ts.map +1 -1
  88. package/dist/response-observer.js.map +1 -1
  89. package/dist/router/overlap.d.ts +23 -0
  90. package/dist/router/overlap.d.ts.map +1 -0
  91. package/dist/router/overlap.js +224 -0
  92. package/dist/router/overlap.js.map +1 -0
  93. package/dist/router/pattern.d.ts +117 -2
  94. package/dist/router/pattern.d.ts.map +1 -1
  95. package/dist/router/pattern.js +283 -23
  96. package/dist/router/pattern.js.map +1 -1
  97. package/dist/router/router.d.ts +18 -4
  98. package/dist/router/router.d.ts.map +1 -1
  99. package/dist/router/router.js +51 -44
  100. package/dist/router/router.js.map +1 -1
  101. package/dist/server/auth.d.ts +53 -0
  102. package/dist/server/auth.d.ts.map +1 -0
  103. package/dist/server/auth.js +9 -0
  104. package/dist/server/auth.js.map +1 -0
  105. package/dist/server/bare-error-lane.d.ts +12 -7
  106. package/dist/server/bare-error-lane.d.ts.map +1 -1
  107. package/dist/server/bare-error-lane.js +13 -11
  108. package/dist/server/bare-error-lane.js.map +1 -1
  109. package/dist/server/body-lane.d.ts +20 -2
  110. package/dist/server/body-lane.d.ts.map +1 -1
  111. package/dist/server/body-lane.js +26 -7
  112. package/dist/server/body-lane.js.map +1 -1
  113. package/dist/server/body-parser.d.ts +68 -0
  114. package/dist/server/body-parser.d.ts.map +1 -0
  115. package/dist/server/body-parser.js +172 -0
  116. package/dist/server/body-parser.js.map +1 -0
  117. package/dist/server/body.d.ts.map +1 -1
  118. package/dist/server/body.js +62 -7
  119. package/dist/server/body.js.map +1 -1
  120. package/dist/server/client-ip.d.ts +8 -0
  121. package/dist/server/client-ip.d.ts.map +1 -1
  122. package/dist/server/client-ip.js +8 -0
  123. package/dist/server/client-ip.js.map +1 -1
  124. package/dist/server/context.d.ts +71 -6
  125. package/dist/server/context.d.ts.map +1 -1
  126. package/dist/server/contract.d.ts +15 -5
  127. package/dist/server/contract.d.ts.map +1 -1
  128. package/dist/server/contract.js +25 -4
  129. package/dist/server/contract.js.map +1 -1
  130. package/dist/server/cookies.d.ts +4 -1
  131. package/dist/server/cookies.d.ts.map +1 -1
  132. package/dist/server/cookies.js +40 -2
  133. package/dist/server/cookies.js.map +1 -1
  134. package/dist/server/core-copies.d.ts +2 -0
  135. package/dist/server/core-copies.d.ts.map +1 -0
  136. package/dist/server/core-copies.js +20 -0
  137. package/dist/server/core-copies.js.map +1 -0
  138. package/dist/server/edge.d.ts.map +1 -1
  139. package/dist/server/edge.js +2 -0
  140. package/dist/server/edge.js.map +1 -1
  141. package/dist/server/http.d.ts +11 -0
  142. package/dist/server/http.d.ts.map +1 -1
  143. package/dist/server/http.js +36 -0
  144. package/dist/server/http.js.map +1 -1
  145. package/dist/server/idempotency-lane.d.ts +6 -0
  146. package/dist/server/idempotency-lane.d.ts.map +1 -1
  147. package/dist/server/idempotency-lane.js +130 -73
  148. package/dist/server/idempotency-lane.js.map +1 -1
  149. package/dist/server/install.d.ts +4 -0
  150. package/dist/server/install.d.ts.map +1 -1
  151. package/dist/server/install.js +4 -0
  152. package/dist/server/install.js.map +1 -1
  153. package/dist/server/methods.d.ts +45 -0
  154. package/dist/server/methods.d.ts.map +1 -0
  155. package/dist/server/methods.js +37 -0
  156. package/dist/server/methods.js.map +1 -0
  157. package/dist/server/multipart.d.ts +63 -0
  158. package/dist/server/multipart.d.ts.map +1 -0
  159. package/dist/server/multipart.js +374 -0
  160. package/dist/server/multipart.js.map +1 -0
  161. package/dist/server/node-outcome-hook.d.ts +20 -0
  162. package/dist/server/node-outcome-hook.d.ts.map +1 -1
  163. package/dist/server/node-outcome-hook.js +15 -6
  164. package/dist/server/node-outcome-hook.js.map +1 -1
  165. package/dist/server/not-found-answer.d.ts +40 -0
  166. package/dist/server/not-found-answer.d.ts.map +1 -0
  167. package/dist/server/not-found-answer.js +56 -0
  168. package/dist/server/not-found-answer.js.map +1 -0
  169. package/dist/server/not-found.d.ts +23 -0
  170. package/dist/server/not-found.d.ts.map +1 -0
  171. package/dist/server/not-found.js +81 -0
  172. package/dist/server/not-found.js.map +1 -0
  173. package/dist/server/plugin.d.ts +26 -1
  174. package/dist/server/plugin.d.ts.map +1 -1
  175. package/dist/server/plugin.js.map +1 -1
  176. package/dist/server/proto-guard.d.ts +18 -5
  177. package/dist/server/proto-guard.d.ts.map +1 -1
  178. package/dist/server/proto-guard.js +114 -9
  179. package/dist/server/proto-guard.js.map +1 -1
  180. package/dist/server/query.d.ts +7 -0
  181. package/dist/server/query.d.ts.map +1 -1
  182. package/dist/server/query.js +47 -0
  183. package/dist/server/query.js.map +1 -1
  184. package/dist/server/registry.d.ts +13 -2
  185. package/dist/server/registry.d.ts.map +1 -1
  186. package/dist/server/request-context.d.ts +11 -0
  187. package/dist/server/request-context.d.ts.map +1 -1
  188. package/dist/server/request-context.js +33 -19
  189. package/dist/server/request-context.js.map +1 -1
  190. package/dist/server/request-lineage.d.ts +8 -0
  191. package/dist/server/request-lineage.d.ts.map +1 -0
  192. package/dist/server/request-lineage.js +14 -0
  193. package/dist/server/request-lineage.js.map +1 -0
  194. package/dist/server/request-target.d.ts +31 -0
  195. package/dist/server/request-target.d.ts.map +1 -0
  196. package/dist/server/request-target.js +44 -0
  197. package/dist/server/request-target.js.map +1 -0
  198. package/dist/server/respond.d.ts +1 -1
  199. package/dist/server/respond.d.ts.map +1 -1
  200. package/dist/server/respond.js +16 -4
  201. package/dist/server/respond.js.map +1 -1
  202. package/dist/server/response-contract-lane.d.ts +19 -1
  203. package/dist/server/response-contract-lane.d.ts.map +1 -1
  204. package/dist/server/response-contract-lane.js +59 -14
  205. package/dist/server/response-contract-lane.js.map +1 -1
  206. package/dist/server/response-observer-runtime.d.ts +9 -5
  207. package/dist/server/response-observer-runtime.d.ts.map +1 -1
  208. package/dist/server/response-observer-runtime.js +10 -6
  209. package/dist/server/response-observer-runtime.js.map +1 -1
  210. package/dist/server/runtime-core.d.ts +2 -0
  211. package/dist/server/runtime-core.d.ts.map +1 -1
  212. package/dist/server/runtime-core.js.map +1 -1
  213. package/dist/server/server-types.d.ts +55 -3
  214. package/dist/server/server-types.d.ts.map +1 -1
  215. package/dist/server/server.d.ts +194 -19
  216. package/dist/server/server.d.ts.map +1 -1
  217. package/dist/server/server.js +1342 -274
  218. package/dist/server/server.js.map +1 -1
  219. package/dist/server/sse.d.ts.map +1 -1
  220. package/dist/server/sse.js +10 -7
  221. package/dist/server/sse.js.map +1 -1
  222. package/dist/server/static-headers.d.ts +3 -0
  223. package/dist/server/static-headers.d.ts.map +1 -1
  224. package/dist/server/static-headers.js +19 -1
  225. package/dist/server/static-headers.js.map +1 -1
  226. package/dist/server/transport-lane.d.ts +8 -0
  227. package/dist/server/transport-lane.d.ts.map +1 -1
  228. package/dist/server/transport-lane.js +9 -8
  229. package/dist/server/transport-lane.js.map +1 -1
  230. package/dist/server/validation.d.ts.map +1 -1
  231. package/dist/server/validation.js +5 -1
  232. package/dist/server/validation.js.map +1 -1
  233. package/dist/server/websocket.d.ts +10 -3
  234. package/dist/server/websocket.d.ts.map +1 -1
  235. package/dist/server/websocket.js +7 -4
  236. package/dist/server/websocket.js.map +1 -1
  237. package/dist/server/ws-bun.d.ts +2 -0
  238. package/dist/server/ws-bun.d.ts.map +1 -1
  239. package/dist/server/ws-bun.js +6 -3
  240. package/dist/server/ws-bun.js.map +1 -1
  241. package/dist/server/ws-hook.d.ts +2 -1
  242. package/dist/server/ws-hook.d.ts.map +1 -1
  243. package/dist/server.d.ts +8 -5
  244. package/dist/server.d.ts.map +1 -1
  245. package/dist/server.js +5 -2
  246. package/dist/server.js.map +1 -1
  247. package/dist/single-copy-register.js +5 -0
  248. package/dist/single-copy-register.js.map +1 -1
  249. package/dist/single-copy.d.ts +46 -3
  250. package/dist/single-copy.d.ts.map +1 -1
  251. package/dist/single-copy.js +125 -12
  252. package/dist/single-copy.js.map +1 -1
  253. package/dist/tool-contract.d.ts +23 -0
  254. package/dist/tool-contract.d.ts.map +1 -1
  255. package/dist/tool-contract.js +37 -3
  256. package/dist/tool-contract.js.map +1 -1
  257. package/dist/transport-codec.d.ts.map +1 -1
  258. package/dist/transport-codec.js +2 -1
  259. package/dist/transport-codec.js.map +1 -1
  260. package/package.json +27 -2
  261. package/src/assurance.ts +52 -2
  262. package/src/capabilities.ts +81 -22
  263. package/src/diff.ts +54 -7
  264. package/src/durable-adapters.ts +20 -3
  265. package/src/durable-effect.ts +10 -3
  266. package/src/edge-kit.ts +7 -0
  267. package/src/effect-lifecycle.ts +5 -1
  268. package/src/effect-scope.ts +2 -1
  269. package/src/errors.ts +3 -0
  270. package/src/evidence.ts +302 -12
  271. package/src/idempotency.ts +80 -9
  272. package/src/index.ts +41 -3
  273. package/src/internal/code-unit-order.ts +8 -0
  274. package/src/internal/durable-saga-protocol.ts +2 -0
  275. package/src/internal/route-catalog.ts +19 -18
  276. package/src/internal/route-compiler.ts +62 -13
  277. package/src/internal/route-execution.ts +13 -3
  278. package/src/internal/route-lanes.ts +37 -28
  279. package/src/internal/route-program.ts +434 -20
  280. package/src/ledger.ts +11 -5
  281. package/src/manifest.ts +52 -46
  282. package/src/mount.ts +62 -0
  283. package/src/reflection.ts +84 -1
  284. package/src/response-observer.ts +1 -0
  285. package/src/router/overlap.ts +258 -0
  286. package/src/router/pattern.ts +349 -25
  287. package/src/router/router.ts +76 -51
  288. package/src/server/auth.ts +75 -0
  289. package/src/server/bare-error-lane.ts +12 -11
  290. package/src/server/body-lane.ts +46 -6
  291. package/src/server/body-parser.ts +241 -0
  292. package/src/server/body.ts +82 -9
  293. package/src/server/client-ip.ts +9 -0
  294. package/src/server/context.ts +219 -12
  295. package/src/server/contract.ts +54 -9
  296. package/src/server/cookies.ts +34 -2
  297. package/src/server/core-copies.ts +21 -0
  298. package/src/server/edge.ts +2 -0
  299. package/src/server/http.ts +34 -0
  300. package/src/server/idempotency-lane.ts +147 -72
  301. package/src/server/install.ts +6 -0
  302. package/src/server/methods.ts +143 -0
  303. package/src/server/multipart.ts +424 -0
  304. package/src/server/node-outcome-hook.ts +40 -7
  305. package/src/server/not-found-answer.ts +109 -0
  306. package/src/server/not-found.ts +121 -0
  307. package/src/server/plugin.ts +45 -1
  308. package/src/server/proto-guard.ts +128 -9
  309. package/src/server/query.ts +49 -0
  310. package/src/server/registry.ts +17 -2
  311. package/src/server/request-context.ts +44 -16
  312. package/src/server/request-lineage.ts +15 -0
  313. package/src/server/request-target.ts +46 -0
  314. package/src/server/respond.ts +14 -3
  315. package/src/server/response-contract-lane.ts +97 -16
  316. package/src/server/response-observer-runtime.ts +37 -8
  317. package/src/server/runtime-core.ts +2 -0
  318. package/src/server/server-types.ts +61 -2
  319. package/src/server/server.ts +1798 -355
  320. package/src/server/sse.ts +10 -8
  321. package/src/server/static-headers.ts +22 -1
  322. package/src/server/transport-lane.ts +17 -9
  323. package/src/server/validation.ts +7 -1
  324. package/src/server/websocket.ts +23 -9
  325. package/src/server/ws-bun.ts +8 -3
  326. package/src/server/ws-hook.ts +2 -1
  327. package/src/server.ts +39 -2
  328. package/src/single-copy-register.ts +5 -0
  329. package/src/single-copy.ts +160 -12
  330. package/src/tool-contract.ts +66 -3
  331. package/src/transport-codec.ts +2 -1
package/src/diff.ts CHANGED
@@ -18,6 +18,7 @@
18
18
  * yield `info` - never a silent pass presented as proof.
19
19
  */
20
20
 
21
+ import { type ProjectEvidenceSnapshot, reflectedRoutesFromEvidence } from "./evidence.ts"
21
22
  import {
22
23
  type JsonSchema,
23
24
  type ReflectedRoute,
@@ -33,8 +34,10 @@ export interface SchemaSnapshot {
33
34
 
34
35
  export interface RouteSnapshotSchema {
35
36
  readonly headers?: SchemaSnapshot
37
+ readonly cookies?: SchemaSnapshot
36
38
  readonly body?: SchemaSnapshot
37
39
  readonly query?: SchemaSnapshot
40
+ readonly params?: SchemaSnapshot
38
41
  readonly response?: SchemaSnapshot
39
42
  readonly sse?: SchemaSnapshot
40
43
  readonly errors?: Readonly<Record<string, SchemaSnapshot>>
@@ -54,7 +57,16 @@ export interface RouteChange {
54
57
  readonly method: string
55
58
  readonly path: string
56
59
  /** Which part of the contract changed; "route" for add/remove of the whole route. */
57
- readonly section: "route" | "headers" | "body" | "query" | "response" | "sse" | "errors"
60
+ readonly section:
61
+ | "route"
62
+ | "headers"
63
+ | "cookies"
64
+ | "body"
65
+ | "query"
66
+ | "params"
67
+ | "response"
68
+ | "sse"
69
+ | "errors"
58
70
  /** The top-level field (or error status) the change is about, when field-granular. */
59
71
  readonly field?: string
60
72
  readonly message: string
@@ -87,9 +99,19 @@ const schemaSnapshot = (
87
99
  * `JSON.stringify` unchanged.
88
100
  */
89
101
  export function snapshotRoutes(source: unknown): readonly RouteSnapshot[] {
102
+ // Preserve the historical registration order of this low-level helper. The canonical evidence
103
+ // seam intentionally sorts routes for persistence, while callers using snapshotRoutes directly
104
+ // may use the order to compare a live reflection with an older baseline.
90
105
  return reflectRoutes(source).map((route) => snapshotRoute(route))
91
106
  }
92
107
 
108
+ /** Snapshot routes from an existing canonical project-evidence pass. */
109
+ export function snapshotRoutesFromEvidence(
110
+ evidence: ProjectEvidenceSnapshot,
111
+ ): readonly RouteSnapshot[] {
112
+ return reflectedRoutesFromEvidence(evidence).map((route) => snapshotRoute(route))
113
+ }
114
+
93
115
  const snapshotRoute = (route: ReflectedRoute): RouteSnapshot => {
94
116
  const schema = route.schema
95
117
  if (schema === undefined) return { method: route.method, path: route.path }
@@ -100,13 +122,17 @@ const snapshotRoute = (route: ReflectedRoute): RouteSnapshot => {
100
122
  }
101
123
  const body = schemaSnapshot(schema.body)
102
124
  const headers = schemaSnapshot(schema.headers)
125
+ const cookies = schemaSnapshot(schema.cookies)
103
126
  const query = schemaSnapshot(schema.query)
127
+ const params = schemaSnapshot(schema.params)
104
128
  const response = schemaSnapshot(schema.response)
105
129
  const sse = schemaSnapshot(schema.sse)
106
130
  const snapped: RouteSnapshotSchema = {
107
131
  ...(headers !== undefined ? { headers } : {}),
132
+ ...(cookies !== undefined ? { cookies } : {}),
108
133
  ...(body !== undefined ? { body } : {}),
109
134
  ...(query !== undefined ? { query } : {}),
135
+ ...(params !== undefined ? { params } : {}),
110
136
  ...(response !== undefined ? { response } : {}),
111
137
  ...(sse !== undefined ? { sse } : {}),
112
138
  ...(Object.keys(errors).length > 0 ? { errors } : {}),
@@ -197,19 +223,40 @@ const fieldMap = (
197
223
  return new Map(snapshot.fields.map((field) => [field.name, field]))
198
224
  }
199
225
 
226
+ /**
227
+ * An object schema without the parts the field diff already covers: its `properties`, and the
228
+ * `required` names of declared fields. What is left (`additionalProperties`, the `$defs` a field's
229
+ * `$ref` points into, a required name no field declares) changes the contract as surely as a field.
230
+ */
231
+ const envelopeOf = (schema: JsonSchema): JsonSchema => {
232
+ if (typeof schema !== "object") return schema
233
+ const { properties, required, ...rest } = schema
234
+ const declared = typeof properties === "object" && properties !== null ? properties : {}
235
+ const undeclared = Array.isArray(required)
236
+ ? required.filter((name) => typeof name !== "string" || !Object.hasOwn(declared, name))
237
+ : required
238
+ return undeclared === undefined || (Array.isArray(undeclared) && undeclared.length === 0)
239
+ ? rest
240
+ : { ...rest, required: undeclared }
241
+ }
242
+
200
243
  const diffFields = (ctx: SectionContext, before: SchemaSnapshot, after: SchemaSnapshot): void => {
244
+ const beforeSchema = before.jsonSchema as JsonSchema
245
+ const afterSchema = after.jsonSchema as JsonSchema
201
246
  const beforeFields = fieldMap(before)
202
247
  const afterFields = fieldMap(after)
203
248
  if (beforeFields === undefined || afterFields === undefined) {
204
249
  // Non-object schemas: compare the whole JSON Schema in one step.
205
- const severity = compareJsonSchema(
206
- ctx.direction,
207
- before.jsonSchema as JsonSchema,
208
- after.jsonSchema as JsonSchema,
209
- )
250
+ const severity = compareJsonSchema(ctx.direction, beforeSchema, afterSchema)
210
251
  if (severity !== undefined) push(ctx, severity, `${ctx.section} schema changed`)
211
252
  return
212
253
  }
254
+ const envelope = compareJsonSchema(
255
+ ctx.direction,
256
+ envelopeOf(beforeSchema),
257
+ envelopeOf(afterSchema),
258
+ )
259
+ if (envelope !== undefined) push(ctx, envelope, `${ctx.section} schema changed`)
213
260
  for (const [name, beforeField] of beforeFields) {
214
261
  const afterField = afterFields.get(name)
215
262
  if (afterField === undefined) {
@@ -356,7 +403,7 @@ export function diffRouteSnapshots(
356
403
  const path = beforeRoute.path
357
404
  const beforeSchema = beforeRoute.schema ?? {}
358
405
  const afterSchema = afterRoute.schema ?? {}
359
- for (const section of ["headers", "body", "query"] as const) {
406
+ for (const section of ["headers", "cookies", "body", "query", "params"] as const) {
360
407
  diffSchemaSection(
361
408
  { method, path, section, direction: "request", changes },
362
409
  beforeSchema[section],
@@ -222,7 +222,8 @@ export class MemoryDurableRecordBackend implements DurableRecordBackend {
222
222
  if (!key.startsWith(prefix) || !input.states.includes(value.state ?? "")) return false
223
223
  return input.updatedBefore === undefined || (value.updatedAt ?? 0) <= input.updatedBefore
224
224
  })
225
- .sort(([a], [b]) => a.localeCompare(b))
225
+ // Code-unit order, the order the cursor comparison below uses (and SQLite's BINARY collation).
226
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
226
227
  .filter(([key]) => input.cursor === undefined || key > prefix + input.cursor)
227
228
  const page = records.slice(0, input.limit)
228
229
  return Object.freeze({
@@ -382,6 +383,14 @@ export async function runDurableExecutionAdapterConformance(
382
383
  version: 1,
383
384
  }
384
385
  requireConformance(await adapter.sagas.create(saga), "saga create rejected")
386
+ requireConformance(
387
+ !(await adapter.sagas.compareAndSet({
388
+ sagaId,
389
+ version: 1,
390
+ record: { ...saga, sagaId: `${sagaId}-other`, state: "completed", updatedAt: 2, version: 2 },
391
+ })),
392
+ "saga CAS accepted a record for another saga",
393
+ )
385
394
  requireConformance(
386
395
  await adapter.sagas.compareAndSet({
387
396
  sagaId,
@@ -914,11 +923,19 @@ export class DurableObjectRecordBackend implements DurableRecordBackend {
914
923
  }
915
924
  if (selected.length > input.limit) break
916
925
  }
917
- const hasMore = selected.length > input.limit || listed.size > input.limit
918
926
  const page = selected.slice(0, input.limit)
927
+ // A full page resumes after its own last record: the matching record that overflowed it was
928
+ // visited but not returned, so the cursor must not pass it. Otherwise every listed key was
929
+ // returned or filtered out, and the next page starts after the last one visited.
930
+ const cursor =
931
+ selected.length > input.limit
932
+ ? page.at(-1)?.id
933
+ : listed.size > input.limit
934
+ ? lastVisited
935
+ : undefined
919
936
  return Object.freeze({
920
937
  records: Object.freeze(page.map(({ record }) => clone(record))),
921
- ...(hasMore && lastVisited !== undefined ? { cursor: lastVisited } : {}),
938
+ ...(cursor === undefined ? {} : { cursor }),
922
939
  })
923
940
  }
924
941
  async acquireLease(
@@ -57,6 +57,7 @@ export function createDurableEffectJournal(
57
57
  if (record === undefined) throw new DurableEffectTransitionError(effectId, "missing")
58
58
  if (
59
59
  record.effectId !== effectId ||
60
+ typeof record.capability !== "string" ||
60
61
  !validCapabilityId(record.capability) ||
61
62
  !["admission", "executing", "committed", "failed", "unknown"].includes(record.state) ||
62
63
  !Number.isSafeInteger(record.version) ||
@@ -69,7 +70,10 @@ export function createDurableEffectJournal(
69
70
  if (record.createdAt > record.updatedAt)
70
71
  throw new TypeError(`durable effect ${effectId}: updatedAt precedes createdAt`)
71
72
  if (record.target !== undefined) assertToken(record.target, "stored durable effect target", 128)
72
- if (record.digest !== undefined && !/^[0-9a-f]{64}$/u.test(record.digest))
73
+ if (
74
+ record.digest !== undefined &&
75
+ (typeof record.digest !== "string" || !/^[0-9a-f]{64}$/u.test(record.digest))
76
+ )
73
77
  throw new TypeError(`durable effect ${effectId}: store returned an invalid digest`)
74
78
  if (record.tenantId !== undefined)
75
79
  assertToken(record.tenantId, "stored durable effect tenantId")
@@ -77,7 +81,10 @@ export function createDurableEffectJournal(
77
81
  assertToken(record.principalId, "stored durable effect principalId")
78
82
  if ((record.tenantId === undefined) !== (record.principalId === undefined))
79
83
  throw new TypeError(`durable effect ${effectId}: store returned a partial identity`)
80
- if (record.errorCode !== undefined && !ERROR_CODE.test(record.errorCode))
84
+ if (
85
+ record.errorCode !== undefined &&
86
+ (typeof record.errorCode !== "string" || !ERROR_CODE.test(record.errorCode))
87
+ )
81
88
  throw new TypeError(`durable effect ${effectId}: store returned an invalid error code`)
82
89
  return record
83
90
  }
@@ -145,7 +152,7 @@ export function createDurableEffectJournal(
145
152
  await transition(effectId, "executing", "committed")
146
153
  },
147
154
  async failed(effectId, input) {
148
- if (!ERROR_CODE.test(input.errorCode))
155
+ if (typeof input.errorCode !== "string" || !ERROR_CODE.test(input.errorCode))
149
156
  throw new TypeError("durable effect errorCode is invalid")
150
157
  const record = await current(effectId)
151
158
  const to = input.began ? "unknown" : "failed"
package/src/edge-kit.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  * - {@link toResponse} render a {@link ResponseResult} (e.g. a 413 / 415 rejection) to a `Response`
12
12
  * - {@link plainError} the structured rejection envelope the lane returns
13
13
  * - {@link queryObjectOf} / {@link searchOf} query parse without a full WHATWG URL construction
14
+ * - {@link answerNotFound} run a not-found handler under the same rules (404 only, no 2xx, fail closed)
14
15
  *
15
16
  * Everything here carries only structure - bytes, guards, and envelopes - never routing, lifecycle,
16
17
  * or app state. It is the interface, not the framework.
@@ -18,6 +19,12 @@
18
19
 
19
20
  export { readBodyFramed } from "./server/body-lane.ts"
20
21
  export { plainError } from "./server/http.ts"
22
+ export {
23
+ answerNotFound,
24
+ type NotFoundHandler,
25
+ type NotFoundInput,
26
+ notFoundInput,
27
+ } from "./server/not-found-answer.ts"
21
28
  export type { ProtoPoisoning } from "./server/proto-guard.ts"
22
29
  export { type QueryValue, queryObjectOf, searchOf } from "./server/query.ts"
23
30
  export { toResponse } from "./server/respond.ts"
@@ -93,7 +93,11 @@ export function emitEffectLifecycle(
93
93
  if (input.attempt !== undefined && (!Number.isSafeInteger(input.attempt) || input.attempt < 1)) {
94
94
  return
95
95
  }
96
- if (input.errorCode !== undefined && !ERROR_CODE.test(input.errorCode)) return
96
+ if (
97
+ input.errorCode !== undefined &&
98
+ (typeof input.errorCode !== "string" || !ERROR_CODE.test(input.errorCode))
99
+ )
100
+ return
97
101
  const event: EffectLifecycleEvent = Object.freeze({
98
102
  effectId: input.effectId,
99
103
  capability: input.capability,
@@ -108,7 +108,8 @@ function codeFor(
108
108
  typeof configured === "function"
109
109
  ? configured(error, began)
110
110
  : (configured ?? (began ? "execution_failed" : "admission_failed"))
111
- if (!ERROR_CODE.test(code)) throw new TypeError("effect errorCode is invalid")
111
+ if (typeof code !== "string" || !ERROR_CODE.test(code))
112
+ throw new TypeError("effect errorCode is invalid")
112
113
  return code
113
114
  }
114
115
 
package/src/errors.ts CHANGED
@@ -27,7 +27,10 @@ export type RouteConfigErrorCode =
27
27
  | "INVALID_ASSURANCE"
28
28
  | "INVALID_IDEMPOTENCY"
29
29
  | "INVALID_WS_SEND_VALIDATION"
30
+ | "INVALID_WIRE"
31
+ | "INVALID_VALIDATION_ORDER"
30
32
  | "INVALID_BODY_LIMIT"
33
+ | "PLUGIN_RECONFIGURED"
31
34
 
32
35
  /**
33
36
  * Thrown at route registration when a route is misconfigured. This is the