ts-procedures 7.3.0 → 8.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 (325) hide show
  1. package/README.md +65 -3
  2. package/agent_config/claude-code/agents/ts-procedures-architect.md +6 -8
  3. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +30 -33
  4. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +104 -53
  5. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +205 -232
  6. package/agent_config/claude-code/skills/ts-procedures/patterns.md +80 -153
  7. package/agent_config/claude-code/skills/ts-procedures-review/SKILL.md +1 -1
  8. package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +4 -5
  9. package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +4 -7
  10. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono.md +223 -0
  11. package/agent_config/copilot/copilot-instructions.md +34 -48
  12. package/agent_config/cursor/cursorrules +34 -48
  13. package/build/client/call.js +4 -1
  14. package/build/client/call.js.map +1 -1
  15. package/build/client/call.test.js +23 -0
  16. package/build/client/call.test.js.map +1 -1
  17. package/build/client/fetch-adapter.js +3 -1
  18. package/build/client/fetch-adapter.js.map +1 -1
  19. package/build/client/fetch-adapter.test.js +11 -1
  20. package/build/client/fetch-adapter.test.js.map +1 -1
  21. package/build/client/index.test.js +7 -7
  22. package/build/client/index.test.js.map +1 -1
  23. package/build/client/request-builder.d.ts +1 -1
  24. package/build/client/request-builder.js +2 -2
  25. package/build/client/request-builder.js.map +1 -1
  26. package/build/client/stream.js +13 -2
  27. package/build/client/stream.js.map +1 -1
  28. package/build/client/stream.test.js +32 -7
  29. package/build/client/stream.test.js.map +1 -1
  30. package/build/client/typed-error-dispatch.test.js +8 -92
  31. package/build/client/typed-error-dispatch.test.js.map +1 -1
  32. package/build/client/types.d.ts +21 -3
  33. package/build/codegen/bin/cli.js +0 -0
  34. package/build/codegen/e2e.test.js +87 -23
  35. package/build/codegen/e2e.test.js.map +1 -1
  36. package/build/codegen/emit-errors.integration.test.js +1 -1
  37. package/build/codegen/emit-errors.integration.test.js.map +1 -1
  38. package/build/codegen/emit-scope.js +308 -47
  39. package/build/codegen/emit-scope.js.map +1 -1
  40. package/build/codegen/emit-scope.test.js +363 -110
  41. package/build/codegen/emit-scope.test.js.map +1 -1
  42. package/build/codegen/pipeline.test.js +7 -7
  43. package/build/codegen/pipeline.test.js.map +1 -1
  44. package/build/codegen/resolve-envelope.js +1 -1
  45. package/build/codegen/resolve-envelope.js.map +1 -1
  46. package/build/codegen/resolve-envelope.test.js +5 -5
  47. package/build/codegen/resolve-envelope.test.js.map +1 -1
  48. package/build/codegen/targets/_shared/route-slots.d.ts +8 -3
  49. package/build/codegen/targets/_shared/route-slots.js +49 -8
  50. package/build/codegen/targets/_shared/route-slots.js.map +1 -1
  51. package/build/codegen/targets/_shared/route-slots.test.js +99 -26
  52. package/build/codegen/targets/_shared/route-slots.test.js.map +1 -1
  53. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +88 -17
  54. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -1
  55. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +9 -6
  56. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -1
  57. package/build/codegen/targets/kotlin/integration.test.js +6 -0
  58. package/build/codegen/targets/kotlin/integration.test.js.map +1 -1
  59. package/build/codegen/targets/swift/access-level.test.js +8 -11
  60. package/build/codegen/targets/swift/access-level.test.js.map +1 -1
  61. package/build/codegen/targets/swift/emit-route-swift.test.js +91 -20
  62. package/build/codegen/targets/swift/emit-route-swift.test.js.map +1 -1
  63. package/build/codegen/targets/swift/emit-scope-swift.test.js +12 -9
  64. package/build/codegen/targets/swift/emit-scope-swift.test.js.map +1 -1
  65. package/build/codegen/targets/swift/integration.test.js +6 -0
  66. package/build/codegen/targets/swift/integration.test.js.map +1 -1
  67. package/build/create-http-stream.d.ts +58 -0
  68. package/build/create-http-stream.js +122 -0
  69. package/build/create-http-stream.js.map +1 -0
  70. package/build/create-http-stream.test.js +88 -0
  71. package/build/create-http-stream.test.js.map +1 -0
  72. package/build/create-http.d.ts +49 -0
  73. package/build/create-http.js +108 -0
  74. package/build/create-http.js.map +1 -0
  75. package/build/create-http.test.js +137 -0
  76. package/build/create-http.test.js.map +1 -0
  77. package/build/create-stream.d.ts +35 -0
  78. package/build/create-stream.js +123 -0
  79. package/build/create-stream.js.map +1 -0
  80. package/build/create-stream.test.js +428 -0
  81. package/build/create-stream.test.js.map +1 -0
  82. package/build/create.d.ts +28 -0
  83. package/build/create.js +82 -0
  84. package/build/create.js.map +1 -0
  85. package/build/create.test.js +483 -0
  86. package/build/create.test.js.map +1 -0
  87. package/build/exports.d.ts +2 -0
  88. package/build/implementations/http/astro/index.test.js +20 -12
  89. package/build/implementations/http/astro/index.test.js.map +1 -1
  90. package/build/implementations/http/doc-registry.js +1 -1
  91. package/build/implementations/http/doc-registry.js.map +1 -1
  92. package/build/implementations/http/doc-registry.test.js +36 -5
  93. package/build/implementations/http/doc-registry.test.js.map +1 -1
  94. package/build/implementations/http/error-dispatch.d.ts +76 -0
  95. package/build/implementations/http/error-dispatch.js +77 -0
  96. package/build/implementations/http/error-dispatch.js.map +1 -0
  97. package/build/implementations/http/error-dispatch.test.js +254 -0
  98. package/build/implementations/http/error-dispatch.test.js.map +1 -0
  99. package/build/implementations/http/error-taxonomy.d.ts +5 -5
  100. package/build/implementations/http/hono/docs/http-doc.d.ts +6 -0
  101. package/build/implementations/http/hono/docs/http-doc.js +42 -0
  102. package/build/implementations/http/hono/docs/http-doc.js.map +1 -0
  103. package/build/implementations/http/hono/docs/http-stream-doc.d.ts +6 -0
  104. package/build/implementations/http/hono/docs/http-stream-doc.js +40 -0
  105. package/build/implementations/http/hono/docs/http-stream-doc.js.map +1 -0
  106. package/build/implementations/http/hono/docs/rpc-doc.d.ts +6 -0
  107. package/build/implementations/http/hono/docs/rpc-doc.js +24 -0
  108. package/build/implementations/http/hono/docs/rpc-doc.js.map +1 -0
  109. package/build/implementations/http/hono/docs/stream-doc.d.ts +6 -0
  110. package/build/implementations/http/hono/docs/stream-doc.js +42 -0
  111. package/build/implementations/http/hono/docs/stream-doc.js.map +1 -0
  112. package/build/implementations/http/hono/handlers/http-stream.d.ts +10 -0
  113. package/build/implementations/http/hono/handlers/http-stream.js +123 -0
  114. package/build/implementations/http/hono/handlers/http-stream.js.map +1 -0
  115. package/build/implementations/http/hono/handlers/http-stream.test.js +128 -0
  116. package/build/implementations/http/hono/handlers/http-stream.test.js.map +1 -0
  117. package/build/implementations/http/hono/handlers/http.d.ts +10 -0
  118. package/build/implementations/http/hono/handlers/http.js +115 -0
  119. package/build/implementations/http/hono/handlers/http.js.map +1 -0
  120. package/build/implementations/http/hono/handlers/http.test.js +118 -0
  121. package/build/implementations/http/hono/handlers/http.test.js.map +1 -0
  122. package/build/implementations/http/hono/handlers/rpc.d.ts +11 -0
  123. package/build/implementations/http/hono/handlers/rpc.js +32 -0
  124. package/build/implementations/http/hono/handlers/rpc.js.map +1 -0
  125. package/build/implementations/http/hono/handlers/rpc.test.js +73 -0
  126. package/build/implementations/http/hono/handlers/rpc.test.js.map +1 -0
  127. package/build/implementations/http/hono/handlers/stream.d.ts +23 -0
  128. package/build/implementations/http/hono/handlers/stream.js +147 -0
  129. package/build/implementations/http/hono/handlers/stream.js.map +1 -0
  130. package/build/implementations/http/hono/handlers/stream.test.d.ts +1 -0
  131. package/build/implementations/http/hono/handlers/stream.test.js +177 -0
  132. package/build/implementations/http/hono/handlers/stream.test.js.map +1 -0
  133. package/build/implementations/http/hono/index.d.ts +57 -0
  134. package/build/implementations/http/hono/index.js +149 -0
  135. package/build/implementations/http/hono/index.js.map +1 -0
  136. package/build/implementations/http/hono/index.test.d.ts +1 -0
  137. package/build/implementations/http/hono/index.test.js +274 -0
  138. package/build/implementations/http/hono/index.test.js.map +1 -0
  139. package/build/implementations/http/hono/path.d.ts +17 -0
  140. package/build/implementations/http/hono/path.js +39 -0
  141. package/build/implementations/http/hono/path.js.map +1 -0
  142. package/build/implementations/http/hono/path.test.d.ts +1 -0
  143. package/build/implementations/http/hono/path.test.js +83 -0
  144. package/build/implementations/http/hono/path.test.js.map +1 -0
  145. package/build/implementations/http/hono/types.d.ts +51 -0
  146. package/build/implementations/http/hono/types.js.map +1 -0
  147. package/build/implementations/http/on-request-error.test.js +6 -96
  148. package/build/implementations/http/on-request-error.test.js.map +1 -1
  149. package/build/implementations/http/route-errors.test.js +11 -59
  150. package/build/implementations/http/route-errors.test.js.map +1 -1
  151. package/build/implementations/types.d.ts +43 -9
  152. package/build/index.d.ts +124 -124
  153. package/build/index.js +10 -221
  154. package/build/index.js.map +1 -1
  155. package/build/index.test.js +20 -919
  156. package/build/index.test.js.map +1 -1
  157. package/build/migration.test.d.ts +1 -0
  158. package/build/migration.test.js +34 -0
  159. package/build/migration.test.js.map +1 -0
  160. package/build/schema/compute-schema.d.ts +11 -3
  161. package/build/schema/compute-schema.js +13 -7
  162. package/build/schema/compute-schema.js.map +1 -1
  163. package/build/schema/parser.d.ts +11 -3
  164. package/build/schema/parser.js +49 -9
  165. package/build/schema/parser.js.map +1 -1
  166. package/build/stack-utils.js +8 -0
  167. package/build/stack-utils.js.map +1 -1
  168. package/build/types.d.ts +142 -0
  169. package/build/types.js.map +1 -0
  170. package/docs/astro-adapter.md +5 -5
  171. package/docs/core.md +15 -17
  172. package/docs/http-integrations.md +83 -170
  173. package/docs/streaming.md +3 -60
  174. package/docs/superpowers/plans/2026-05-07-astro-adapter.md +2 -7
  175. package/docs/superpowers/plans/2026-05-08-create-http.md +3355 -0
  176. package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +3365 -0
  177. package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +1 -3
  178. package/docs/superpowers/specs/2026-05-08-create-http-design.md +409 -0
  179. package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +411 -0
  180. package/package.json +4 -22
  181. package/src/client/call.test.ts +26 -0
  182. package/src/client/call.ts +4 -1
  183. package/src/client/fetch-adapter.test.ts +14 -1
  184. package/src/client/fetch-adapter.ts +3 -1
  185. package/src/client/index.test.ts +7 -7
  186. package/src/client/request-builder.ts +2 -2
  187. package/src/client/stream.test.ts +39 -7
  188. package/src/client/stream.ts +16 -2
  189. package/src/client/typed-error-dispatch.test.ts +7 -97
  190. package/src/client/types.ts +21 -3
  191. package/src/codegen/__fixtures__/users-envelope.json +119 -38
  192. package/src/codegen/e2e.test.ts +98 -24
  193. package/src/codegen/emit-errors.integration.test.ts +1 -1
  194. package/src/codegen/emit-scope.test.ts +395 -110
  195. package/src/codegen/emit-scope.ts +350 -55
  196. package/src/codegen/pipeline.test.ts +7 -7
  197. package/src/codegen/resolve-envelope.test.ts +5 -5
  198. package/src/codegen/resolve-envelope.ts +1 -1
  199. package/src/codegen/targets/_shared/route-slots.test.ts +109 -26
  200. package/src/codegen/targets/_shared/route-slots.ts +48 -11
  201. package/src/codegen/targets/kotlin/__fixtures__/users-golden.kt +73 -0
  202. package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +100 -17
  203. package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +9 -6
  204. package/src/codegen/targets/kotlin/integration.test.ts +19 -0
  205. package/src/codegen/targets/swift/__fixtures__/users-golden.swift +79 -0
  206. package/src/codegen/targets/swift/access-level.test.ts +8 -11
  207. package/src/codegen/targets/swift/emit-route-swift.test.ts +103 -20
  208. package/src/codegen/targets/swift/emit-scope-swift.test.ts +12 -9
  209. package/src/codegen/targets/swift/integration.test.ts +17 -0
  210. package/src/create-http-stream.test.ts +97 -0
  211. package/src/create-http-stream.ts +191 -0
  212. package/src/create-http.test.ts +163 -0
  213. package/src/create-http.ts +211 -0
  214. package/src/create-stream.test.ts +565 -0
  215. package/src/create-stream.ts +228 -0
  216. package/src/create.test.ts +658 -0
  217. package/src/create.ts +172 -0
  218. package/src/exports.ts +2 -0
  219. package/src/implementations/http/README.md +135 -95
  220. package/src/implementations/http/astro/README.md +4 -5
  221. package/src/implementations/http/astro/index.test.ts +25 -18
  222. package/src/implementations/http/doc-registry.test.ts +42 -5
  223. package/src/implementations/http/doc-registry.ts +1 -1
  224. package/src/implementations/http/error-dispatch.test.ts +283 -0
  225. package/src/implementations/http/error-dispatch.ts +176 -0
  226. package/src/implementations/http/error-taxonomy.ts +5 -5
  227. package/src/implementations/http/hono/docs/http-doc.ts +43 -0
  228. package/src/implementations/http/hono/docs/http-stream-doc.ts +44 -0
  229. package/src/implementations/http/hono/docs/rpc-doc.ts +34 -0
  230. package/src/implementations/http/hono/docs/stream-doc.ts +53 -0
  231. package/src/implementations/http/hono/handlers/http-stream.test.ts +150 -0
  232. package/src/implementations/http/hono/handlers/http-stream.ts +152 -0
  233. package/src/implementations/http/hono/handlers/http.test.ts +130 -0
  234. package/src/implementations/http/hono/handlers/http.ts +147 -0
  235. package/src/implementations/http/hono/handlers/rpc.test.ts +81 -0
  236. package/src/implementations/http/hono/handlers/rpc.ts +54 -0
  237. package/src/implementations/http/hono/handlers/stream.test.ts +198 -0
  238. package/src/implementations/http/hono/handlers/stream.ts +208 -0
  239. package/src/implementations/http/hono/index.test.ts +329 -0
  240. package/src/implementations/http/hono/index.ts +204 -0
  241. package/src/implementations/http/hono/path.test.ts +96 -0
  242. package/src/implementations/http/hono/path.ts +59 -0
  243. package/src/implementations/http/hono/types.ts +93 -0
  244. package/src/implementations/http/on-request-error.test.ts +10 -116
  245. package/src/implementations/http/route-errors.test.ts +11 -77
  246. package/src/implementations/types.ts +44 -9
  247. package/src/index.test.ts +22 -1249
  248. package/src/index.ts +49 -485
  249. package/src/migration.test.ts +48 -0
  250. package/src/schema/compute-schema.ts +26 -12
  251. package/src/schema/parser.ts +62 -12
  252. package/src/stack-utils.ts +8 -0
  253. package/src/types.ts +133 -0
  254. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/express-rpc.md +0 -137
  255. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-api.md +0 -173
  256. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-rpc.md +0 -142
  257. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-stream.md +0 -147
  258. package/build/implementations/http/express-rpc/error-taxonomy.test.js +0 -83
  259. package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +0 -1
  260. package/build/implementations/http/express-rpc/index.d.ts +0 -125
  261. package/build/implementations/http/express-rpc/index.js +0 -216
  262. package/build/implementations/http/express-rpc/index.js.map +0 -1
  263. package/build/implementations/http/express-rpc/index.test.js +0 -684
  264. package/build/implementations/http/express-rpc/index.test.js.map +0 -1
  265. package/build/implementations/http/express-rpc/types.d.ts +0 -11
  266. package/build/implementations/http/express-rpc/types.js.map +0 -1
  267. package/build/implementations/http/hono-api/error-taxonomy.test.js +0 -137
  268. package/build/implementations/http/hono-api/error-taxonomy.test.js.map +0 -1
  269. package/build/implementations/http/hono-api/index.d.ts +0 -151
  270. package/build/implementations/http/hono-api/index.js +0 -344
  271. package/build/implementations/http/hono-api/index.js.map +0 -1
  272. package/build/implementations/http/hono-api/index.test.js +0 -992
  273. package/build/implementations/http/hono-api/index.test.js.map +0 -1
  274. package/build/implementations/http/hono-api/types.d.ts +0 -13
  275. package/build/implementations/http/hono-api/types.js.map +0 -1
  276. package/build/implementations/http/hono-rpc/error-taxonomy.test.js +0 -64
  277. package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +0 -1
  278. package/build/implementations/http/hono-rpc/index.d.ts +0 -130
  279. package/build/implementations/http/hono-rpc/index.js +0 -209
  280. package/build/implementations/http/hono-rpc/index.js.map +0 -1
  281. package/build/implementations/http/hono-rpc/index.test.js +0 -828
  282. package/build/implementations/http/hono-rpc/index.test.js.map +0 -1
  283. package/build/implementations/http/hono-rpc/types.d.ts +0 -11
  284. package/build/implementations/http/hono-rpc/types.js +0 -2
  285. package/build/implementations/http/hono-rpc/types.js.map +0 -1
  286. package/build/implementations/http/hono-stream/error-taxonomy.test.js +0 -159
  287. package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +0 -1
  288. package/build/implementations/http/hono-stream/index.d.ts +0 -171
  289. package/build/implementations/http/hono-stream/index.js +0 -415
  290. package/build/implementations/http/hono-stream/index.js.map +0 -1
  291. package/build/implementations/http/hono-stream/index.test.js +0 -1383
  292. package/build/implementations/http/hono-stream/index.test.js.map +0 -1
  293. package/build/implementations/http/hono-stream/types.d.ts +0 -15
  294. package/build/implementations/http/hono-stream/types.js +0 -2
  295. package/build/implementations/http/hono-stream/types.js.map +0 -1
  296. package/src/implementations/http/express-rpc/README.md +0 -280
  297. package/src/implementations/http/express-rpc/error-taxonomy.test.ts +0 -103
  298. package/src/implementations/http/express-rpc/index.test.ts +0 -957
  299. package/src/implementations/http/express-rpc/index.ts +0 -327
  300. package/src/implementations/http/express-rpc/types.ts +0 -16
  301. package/src/implementations/http/hono-api/README.md +0 -284
  302. package/src/implementations/http/hono-api/error-taxonomy.test.ts +0 -179
  303. package/src/implementations/http/hono-api/index.test.ts +0 -1341
  304. package/src/implementations/http/hono-api/index.ts +0 -519
  305. package/src/implementations/http/hono-api/types.ts +0 -16
  306. package/src/implementations/http/hono-rpc/README.md +0 -357
  307. package/src/implementations/http/hono-rpc/error-taxonomy.test.ts +0 -82
  308. package/src/implementations/http/hono-rpc/index.test.ts +0 -1107
  309. package/src/implementations/http/hono-rpc/index.ts +0 -320
  310. package/src/implementations/http/hono-rpc/types.ts +0 -16
  311. package/src/implementations/http/hono-stream/README.md +0 -559
  312. package/src/implementations/http/hono-stream/error-taxonomy.test.ts +0 -178
  313. package/src/implementations/http/hono-stream/index.test.ts +0 -1804
  314. package/src/implementations/http/hono-stream/index.ts +0 -622
  315. package/src/implementations/http/hono-stream/types.ts +0 -20
  316. /package/build/{implementations/http/express-rpc/error-taxonomy.test.d.ts → create-http-stream.test.d.ts} +0 -0
  317. /package/build/{implementations/http/express-rpc/index.test.d.ts → create-http.test.d.ts} +0 -0
  318. /package/build/{implementations/http/hono-api/error-taxonomy.test.d.ts → create-stream.test.d.ts} +0 -0
  319. /package/build/{implementations/http/hono-api/index.test.d.ts → create.test.d.ts} +0 -0
  320. /package/build/implementations/http/{hono-rpc/error-taxonomy.test.d.ts → error-dispatch.test.d.ts} +0 -0
  321. /package/build/implementations/http/{hono-rpc/index.test.d.ts → hono/handlers/http-stream.test.d.ts} +0 -0
  322. /package/build/implementations/http/{hono-stream/error-taxonomy.test.d.ts → hono/handlers/http.test.d.ts} +0 -0
  323. /package/build/implementations/http/{hono-stream/index.test.d.ts → hono/handlers/rpc.test.d.ts} +0 -0
  324. /package/build/implementations/http/{express-rpc → hono}/types.js +0 -0
  325. /package/build/{implementations/http/hono-api/types.js → types.js} +0 -0
@@ -5,9 +5,8 @@ import { stripPrefix } from './rewrite-request.js'
5
5
  import { setAstroContext, getAstroContext } from './astro-context.js'
6
6
  import { createAstroHandler } from './create-handler.js'
7
7
  import { Procedures } from '../../../index.js'
8
- import { HonoAPIAppBuilder } from '../hono-api/index.js'
9
- import { HonoStreamAppBuilder } from '../hono-stream/index.js'
10
- import type { APIConfig, RPCConfig } from '../../types.js'
8
+ import { HonoAppBuilder } from '../hono/index.js'
9
+ import type { RPCConfig } from '../../types.js'
11
10
 
12
11
  // Minimal stand-in for Astro's APIContext for unit tests.
13
12
  function fakeApiContext(overrides: Record<string, unknown> = {}) {
@@ -23,15 +22,15 @@ function fakeApiContext(overrides: Record<string, unknown> = {}) {
23
22
  }
24
23
 
25
24
  function buildSimpleUserApi() {
26
- const API = Procedures<{ db: Map<string, { id: string; name: string }> }, APIConfig>()
27
- API.Create(
25
+ const API = Procedures<{ db: Map<string, { id: string; name: string }> }>()
26
+ API.CreateHttp(
28
27
  'GetUser',
29
28
  {
30
29
  path: '/users/:id',
31
30
  method: 'get',
32
31
  schema: {
33
- input: { pathParams: Type.Object({ id: Type.String() }) },
34
- returnType: Type.Object({ id: Type.String(), name: Type.String() }),
32
+ req: { pathParams: Type.Object({ id: Type.String() }) },
33
+ res: { body: Type.Object({ id: Type.String(), name: Type.String() }) },
35
34
  },
36
35
  },
37
36
  async (ctx, { pathParams }) => {
@@ -41,7 +40,7 @@ function buildSimpleUserApi() {
41
40
  }
42
41
  )
43
42
  const db = new Map([['1', { id: '1', name: 'Ada' }]])
44
- return new HonoAPIAppBuilder().register(API, () => ({ db })).build()
43
+ return new HonoAppBuilder().register(API, () => ({ db })).build()
45
44
  }
46
45
 
47
46
  describe('stripPrefix', () => {
@@ -199,14 +198,18 @@ describe('createAstroHandler — pathPrefix + getAstroContext', () => {
199
198
  })
200
199
 
201
200
  test('factory closure can read APIContext via getAstroContext', async () => {
202
- const API = Procedures<{ user: { id: string } | null }, APIConfig>()
203
- API.Create(
201
+ const API = Procedures<{ user: { id: string } | null }>()
202
+ API.CreateHttp(
204
203
  'WhoAmI',
205
- { path: '/whoami', method: 'get', schema: { returnType: Type.Object({ userId: Type.Union([Type.String(), Type.Null()]) }) } },
204
+ {
205
+ path: '/whoami',
206
+ method: 'get',
207
+ schema: { res: { body: Type.Object({ userId: Type.Union([Type.String(), Type.Null()]) }) } },
208
+ },
206
209
  async (ctx) => ({ userId: ctx.user?.id ?? null })
207
210
  )
208
211
 
209
- const app = new HonoAPIAppBuilder()
212
+ const app = new HonoAppBuilder()
210
213
  .register(API, (c) => {
211
214
  const astro = getAstroContext(c)
212
215
  return { user: (astro.locals as { user?: { id: string } }).user ?? null }
@@ -294,7 +297,7 @@ describe('createAstroHandler — streams', () => {
294
297
  }
295
298
  )
296
299
 
297
- const streamApp = new HonoStreamAppBuilder().register(STREAM, () => ({})).build()
300
+ const streamApp = new HonoAppBuilder().register(STREAM, () => ({})).build()
298
301
  const { ALL } = createAstroHandler({ apps: streamApp })
299
302
 
300
303
  const apiContext = fakeApiContext({
@@ -318,10 +321,14 @@ describe('createAstroHandler — abort signal', () => {
318
321
  test("aborting the incoming request aborts the procedure handler's ctx.signal", async () => {
319
322
  let observedSignal: AbortSignal | undefined
320
323
  const aborted = new Promise<void>((resolve) => {
321
- const API = Procedures<{}, APIConfig>()
322
- API.Create(
324
+ const API = Procedures<{}>()
325
+ API.CreateHttp(
323
326
  'Hang',
324
- { path: '/hang', method: 'get', schema: { returnType: Type.Object({ ok: Type.Boolean() }) } },
327
+ {
328
+ path: '/hang',
329
+ method: 'get',
330
+ schema: { res: { body: Type.Object({ ok: Type.Boolean() }) } },
331
+ },
325
332
  async (ctx) => {
326
333
  observedSignal = ctx.signal
327
334
  ctx.signal?.addEventListener('abort', () => resolve(), { once: true })
@@ -330,7 +337,7 @@ describe('createAstroHandler — abort signal', () => {
330
337
  return { ok: true }
331
338
  }
332
339
  )
333
- const app = new HonoAPIAppBuilder().register(API, () => ({})).build()
340
+ const app = new HonoAppBuilder().register(API, () => ({})).build()
334
341
  const { ALL } = createAstroHandler({ apps: app })
335
342
 
336
343
  const controller = new AbortController()
@@ -340,7 +347,7 @@ describe('createAstroHandler — abort signal', () => {
340
347
  })
341
348
 
342
349
  // Fire ALL but don't await — abort while the handler is still running.
343
- ;(ALL(apiContext) as Promise<Response>).catch(() => {})
350
+ Promise.resolve(ALL(apiContext)).catch(() => {})
344
351
  setTimeout(() => controller.abort(), 20)
345
352
  })
346
353
 
@@ -1,7 +1,8 @@
1
1
  import { describe, expect, it, test, vi, afterEach } from 'vitest'
2
2
  import { v } from 'suretype'
3
+ import { Type } from 'typebox'
3
4
  import { Procedures } from '../../index.js'
4
- import { HonoRPCAppBuilder } from './hono-rpc/index.js'
5
+ import { HonoAppBuilder } from './hono/index.js'
5
6
  import { DocRegistry } from './doc-registry.js'
6
7
  import { defineErrorTaxonomy } from './error-taxonomy.js'
7
8
  import type {
@@ -10,6 +11,7 @@ import type {
10
11
  RPCConfig,
11
12
  APIHttpRouteDoc,
12
13
  StreamHttpRouteDoc,
14
+ HttpStreamRouteDoc,
13
15
  DocSource,
14
16
  DocEnvelope,
15
17
  ErrorDoc,
@@ -35,7 +37,7 @@ const apiDoc: APIHttpRouteDoc = {
35
37
  path: '/users/:id',
36
38
  method: 'get',
37
39
  fullPath: '/api/users/:id',
38
- jsonSchema: { pathParams: { type: 'object' } },
40
+ jsonSchema: { req: { pathParams: { type: 'object' } } },
39
41
  }
40
42
 
41
43
  const streamDoc: StreamHttpRouteDoc = {
@@ -49,6 +51,16 @@ const streamDoc: StreamHttpRouteDoc = {
49
51
  jsonSchema: { yieldType: { type: 'object' } },
50
52
  }
51
53
 
54
+ const httpStreamDoc: HttpStreamRouteDoc = {
55
+ kind: 'http-stream',
56
+ name: 'Tail',
57
+ path: '/streams/logs',
58
+ method: 'get',
59
+ fullPath: '/streams/logs',
60
+ streamMode: 'sse',
61
+ jsonSchema: { yield: { type: 'object' } },
62
+ }
63
+
52
64
  function makeSource<T extends AnyHttpRouteDoc>(docs: T[]): DocSource<T> {
53
65
  return { docs }
54
66
  }
@@ -466,8 +478,13 @@ describe('DocRegistry', () => {
466
478
  expect(doc.kind).toBe('stream')
467
479
  })
468
480
 
481
+ it('httpStreamDoc fixture requires kind field', () => {
482
+ const doc = httpStreamDoc
483
+ expect(doc.kind).toBe('http-stream')
484
+ })
485
+
469
486
  it('narrows AnyHttpRouteDoc union via kind', () => {
470
- const routes: AnyHttpRouteDoc[] = [rpcDoc, apiDoc, streamDoc]
487
+ const routes: AnyHttpRouteDoc[] = [rpcDoc, apiDoc, streamDoc, httpStreamDoc]
471
488
  for (const route of routes) {
472
489
  expect(route.kind).toBeDefined()
473
490
  }
@@ -580,7 +597,27 @@ describe('DocRegistry', () => {
580
597
  })
581
598
  })
582
599
 
583
- test('accepts a real HonoRPCAppBuilder as source', () => {
600
+ it('aggregates http-stream routes from HonoAppBuilder', () => {
601
+ const procs = Procedures()
602
+ procs.CreateHttpStream('Tail', {
603
+ path: '/streams/logs',
604
+ method: 'get',
605
+ schema: {
606
+ req: { query: Type.Object({ source: Type.String() }) },
607
+ yield: Type.Object({ line: Type.String() }),
608
+ },
609
+ }, async function* () {})
610
+
611
+ const builder = new HonoAppBuilder().register(procs, {})
612
+ builder.build()
613
+
614
+ const registry = new DocRegistry().from(builder)
615
+ const envelope = registry.toJSON()
616
+ const route = envelope.routes.find((r) => r.name === 'Tail')
617
+ expect(route?.kind).toBe('http-stream')
618
+ })
619
+
620
+ test('accepts a real HonoAppBuilder as source', () => {
584
621
  const RPC = Procedures<{ userId: string }, RPCConfig>()
585
622
 
586
623
  RPC.Create(
@@ -589,7 +626,7 @@ describe('DocRegistry', () => {
589
626
  async (_ctx, params) => params,
590
627
  )
591
628
 
592
- const builder = new HonoRPCAppBuilder()
629
+ const builder = new HonoAppBuilder()
593
630
  builder.register(RPC, () => ({ userId: '123' }))
594
631
  builder.build()
595
632
 
@@ -89,7 +89,7 @@ export class DocRegistry {
89
89
  }
90
90
 
91
91
  // Surface coverage gaps — when a builder skipped procedures (e.g. a
92
- // streaming procedure registered with HonoRPCAppBuilder), the per-builder
92
+ // streaming procedure registered with HonoAppBuilder), the per-builder
93
93
  // warning fires once at `build()` time, but consumers who only call
94
94
  // `registry.toJSON()` (e.g. to dump the envelope to disk for codegen)
95
95
  // wouldn't see it without this aggregate. Single warning per call,
@@ -0,0 +1,283 @@
1
+ import { describe, expect, test, vi } from 'vitest'
2
+ import { Hono } from 'hono'
3
+ import { dispatchMidStreamError, dispatchPreStreamError } from './error-dispatch.js'
4
+ import { defineErrorTaxonomy } from './error-taxonomy.js'
5
+ import { ProcedureValidationError } from '../../errors.js'
6
+
7
+ function makeContext() {
8
+ return new Promise<import('hono').Context>((resolve) => {
9
+ const app = new Hono()
10
+ app.get('/', (c) => {
11
+ resolve(c)
12
+ return c.text('ok')
13
+ })
14
+ app.request('/')
15
+ })
16
+ }
17
+
18
+ const dummyProcedure = {
19
+ name: 'Test',
20
+ kind: 'rpc',
21
+ config: { scope: 'test', version: 1 },
22
+ handler: async () => undefined,
23
+ } as any
24
+
25
+ describe('dispatchPreStreamError', () => {
26
+ test('default taxonomy: ProcedureValidationError → 400', async () => {
27
+ const c = await makeContext()
28
+ const err = new ProcedureValidationError('Test', 'Validation error for Test', [
29
+ { instancePath: '/x', message: 'expected number' } as any,
30
+ ])
31
+
32
+ const res = await dispatchPreStreamError({
33
+ err,
34
+ procedure: dummyProcedure,
35
+ raw: c,
36
+ cfg: {},
37
+ })
38
+
39
+ expect(res.status).toBe(400)
40
+ const body = await res.json()
41
+ expect(body.name).toBe('ProcedureValidationError')
42
+ expect(body.procedureName).toBe('Test')
43
+ })
44
+
45
+ test('user taxonomy entry takes precedence', async () => {
46
+ const c = await makeContext()
47
+ class MyError extends Error {
48
+ constructor(public reason: string) { super(reason) }
49
+ }
50
+ const errors = defineErrorTaxonomy({
51
+ MyError: { class: MyError, statusCode: 422, toResponse: (e) => ({ name: 'MyError', reason: e.reason }) },
52
+ })
53
+
54
+ const res = await dispatchPreStreamError({
55
+ err: new MyError('boom'),
56
+ procedure: dummyProcedure,
57
+ raw: c,
58
+ cfg: { errors },
59
+ })
60
+
61
+ expect(res.status).toBe(422)
62
+ expect(await res.json()).toEqual({ name: 'MyError', reason: 'boom' })
63
+ })
64
+
65
+ test('falls back to onError imperative callback', async () => {
66
+ const c = await makeContext()
67
+ const onError = vi.fn().mockResolvedValue(c.json({ ok: false }, 503))
68
+
69
+ const res = await dispatchPreStreamError({
70
+ err: new Error('unmatched'),
71
+ procedure: dummyProcedure,
72
+ raw: c,
73
+ cfg: { onError: (proc, raw, e) => onError(proc, raw, e) },
74
+ })
75
+
76
+ expect(onError).toHaveBeenCalled()
77
+ expect(res.status).toBe(503)
78
+ })
79
+
80
+ test('hard default 500 when nothing matches', async () => {
81
+ const c = await makeContext()
82
+ const res = await dispatchPreStreamError({
83
+ err: new Error('boom'),
84
+ procedure: dummyProcedure,
85
+ raw: c,
86
+ cfg: {},
87
+ })
88
+ expect(res.status).toBe(500)
89
+ expect(await res.json()).toEqual({ error: 'boom' })
90
+ })
91
+
92
+ test('onRequestError observer fires before dispatch and is awaited', async () => {
93
+ const c = await makeContext()
94
+ const calls: string[] = []
95
+ const onRequestError = vi.fn(async () => {
96
+ calls.push('observer')
97
+ })
98
+ const errors = defineErrorTaxonomy({
99
+ AnyError: {
100
+ match: (e): e is Error => e instanceof Error,
101
+ statusCode: 500,
102
+ toResponse: () => {
103
+ calls.push('toResponse')
104
+ return { name: 'AnyError' }
105
+ },
106
+ },
107
+ })
108
+
109
+ await dispatchPreStreamError({
110
+ err: new Error('x'),
111
+ procedure: dummyProcedure,
112
+ raw: c,
113
+ cfg: { errors, onRequestError },
114
+ })
115
+
116
+ expect(calls).toEqual(['observer', 'toResponse'])
117
+ expect(onRequestError).toHaveBeenCalledWith({ err: expect.any(Error), procedure: dummyProcedure, raw: c })
118
+ })
119
+
120
+ test('observer throw is swallowed and logged', async () => {
121
+ const c = await makeContext()
122
+ const consoleErr = vi.spyOn(console, 'error').mockImplementation(() => {})
123
+ const onRequestError = vi.fn(() => { throw new Error('observer broke') })
124
+
125
+ const res = await dispatchPreStreamError({
126
+ err: new Error('x'),
127
+ procedure: dummyProcedure,
128
+ raw: c,
129
+ cfg: { onRequestError },
130
+ })
131
+
132
+ expect(res.status).toBe(500)
133
+ expect(consoleErr).toHaveBeenCalled()
134
+ consoleErr.mockRestore()
135
+ })
136
+
137
+ test('taxonomy onCatch is fully awaited before the response body is written', async () => {
138
+ const c = await makeContext()
139
+ const calls: string[] = []
140
+ class HookedError extends Error {}
141
+ const errors = defineErrorTaxonomy({
142
+ HookedError: {
143
+ class: HookedError,
144
+ statusCode: 500,
145
+ toResponse: () => ({ name: 'HookedError' }),
146
+ onCatch: async () => {
147
+ // Awaiting an async setTimeout here means a non-awaiting dispatcher
148
+ // would write the response BEFORE this push runs. The order we capture
149
+ // in `calls` reveals which branch happened.
150
+ await new Promise<void>((resolve) => setTimeout(resolve, 5))
151
+ calls.push('oncatch-done')
152
+ },
153
+ },
154
+ })
155
+
156
+ // Spy on c.json: when the dispatcher writes the response, record where in
157
+ // the timeline it happened relative to onCatch's push.
158
+ const originalJson = c.json.bind(c) as (...args: any[]) => Response
159
+ const jsonSpy = vi.fn((...args: any[]) => {
160
+ calls.push('response-write')
161
+ return originalJson(...args)
162
+ })
163
+ ;(c as any).json = jsonSpy
164
+
165
+ const res = await dispatchPreStreamError({
166
+ err: new HookedError('boom'),
167
+ procedure: dummyProcedure,
168
+ raw: c,
169
+ cfg: { errors },
170
+ })
171
+
172
+ expect(res.status).toBe(500)
173
+ expect(jsonSpy).toHaveBeenCalledTimes(1)
174
+ // If the dispatcher awaits onCatch first, 'oncatch-done' precedes
175
+ // 'response-write'. Without the await, the order would be reversed
176
+ // (or 'oncatch-done' would land after the dispatcher returns).
177
+ expect(calls).toEqual(['oncatch-done', 'response-write'])
178
+ })
179
+ })
180
+
181
+ const streamProcedure = {
182
+ name: 'Tail',
183
+ kind: 'rpc-stream',
184
+ config: { scope: 'logs', version: 1 },
185
+ handler: async function* () {},
186
+ } as any
187
+
188
+ describe('dispatchMidStreamError', () => {
189
+ test('uses taxonomy body when configured', async () => {
190
+ const c = await makeContext()
191
+ class MidErr extends Error {}
192
+ const errors = defineErrorTaxonomy({
193
+ MidErr: { class: MidErr, statusCode: 500, toResponse: () => ({ name: 'MidErr', detail: 'x' }) },
194
+ })
195
+
196
+ const result = await dispatchMidStreamError({
197
+ err: new MidErr('mid'),
198
+ procedure: streamProcedure,
199
+ raw: c,
200
+ cfg: { errors },
201
+ })
202
+
203
+ expect(result.data).toEqual({ name: 'MidErr', detail: 'x' })
204
+ expect(result.sseEvent).toBe('error')
205
+ expect(result.runOnCatch).toBeTypeOf('function')
206
+ })
207
+
208
+ test('falls back to onMidStreamError', async () => {
209
+ const c = await makeContext()
210
+ const result = await dispatchMidStreamError({
211
+ err: new Error('boom'),
212
+ procedure: streamProcedure,
213
+ raw: c,
214
+ cfg: {
215
+ onMidStreamError: () => ({ data: { kind: 'fail', msg: 'boom' } }),
216
+ },
217
+ })
218
+
219
+ expect(result.data).toEqual({ kind: 'fail', msg: 'boom' })
220
+ expect(result.sseEvent).toBe('Tail') // procedure name override
221
+ })
222
+
223
+ test('hard default { error: msg } when nothing matches', async () => {
224
+ const c = await makeContext()
225
+ const result = await dispatchMidStreamError({
226
+ err: new Error('plain'),
227
+ procedure: streamProcedure,
228
+ raw: c,
229
+ cfg: {},
230
+ })
231
+
232
+ expect(result.data).toEqual({ error: 'plain' })
233
+ expect(result.sseEvent).toBe('error')
234
+ })
235
+
236
+ test('onRequestError observer fires before dispatch (mid-stream form)', async () => {
237
+ const c = await makeContext()
238
+ const onRequestError = vi.fn(async () => {})
239
+
240
+ await dispatchMidStreamError({
241
+ err: new Error('x'),
242
+ procedure: streamProcedure,
243
+ raw: c,
244
+ cfg: { onRequestError },
245
+ })
246
+
247
+ expect(onRequestError).toHaveBeenCalled()
248
+ })
249
+
250
+ test('returns a runOnCatch function that is invokable and awaits the taxonomy hook', async () => {
251
+ const c = await makeContext()
252
+ const calls: string[] = []
253
+ class StreamErr extends Error {}
254
+ const errors = defineErrorTaxonomy({
255
+ StreamErr: {
256
+ class: StreamErr,
257
+ statusCode: 500,
258
+ toResponse: () => ({ name: 'StreamErr' }),
259
+ onCatch: async () => {
260
+ await new Promise<void>((resolve) => setTimeout(resolve, 5))
261
+ calls.push('oncatch-done')
262
+ },
263
+ },
264
+ })
265
+
266
+ // Mid-stream contract: the dispatcher does NOT call runOnCatch itself —
267
+ // the caller (stream handler) decides when to invoke it. Verify the shape
268
+ // and that awaiting it actually awaits the hook.
269
+ const result = await dispatchMidStreamError({
270
+ err: new StreamErr('mid'),
271
+ procedure: streamProcedure,
272
+ raw: c,
273
+ cfg: { errors },
274
+ })
275
+
276
+ expect(result.runOnCatch).toBeTypeOf('function')
277
+ // Hook has not yet run — caller hasn't invoked it.
278
+ expect(calls).toEqual([])
279
+
280
+ await result.runOnCatch!()
281
+ expect(calls).toEqual(['oncatch-done'])
282
+ })
283
+ })
@@ -0,0 +1,176 @@
1
+ import type { Context } from 'hono'
2
+ import {
3
+ resolveErrorResponse,
4
+ type ErrorTaxonomy,
5
+ type TAnyProcedureRegistration,
6
+ type UnknownErrorConfig,
7
+ } from './error-taxonomy.js'
8
+ import type { TStreamProcedureRegistration, THttpStreamProcedureRegistration } from '../../types.js'
9
+
10
+ /**
11
+ * Public alias of {@link TAnyProcedureRegistration} — re-exported here so the
12
+ * hono/ module can import it from one place without taking a direct dependency
13
+ * on error-taxonomy internals. Single source of truth lives in
14
+ * error-taxonomy.ts.
15
+ */
16
+ export type AnyProcedureRegistration = TAnyProcedureRegistration
17
+
18
+ export type OnRequestErrorContext = {
19
+ err: unknown
20
+ procedure: AnyProcedureRegistration
21
+ raw: Context
22
+ }
23
+
24
+ export type OnRequestErrorObserver = (
25
+ ctx: OnRequestErrorContext,
26
+ ) => void | Promise<void>
27
+
28
+ export type PreStreamOnError = (
29
+ procedure: AnyProcedureRegistration,
30
+ raw: Context,
31
+ err: Error,
32
+ ) => Response | Promise<Response>
33
+
34
+ export type PreStreamErrorConfig = {
35
+ errors?: ErrorTaxonomy
36
+ unknownError?: UnknownErrorConfig
37
+ onError?: PreStreamOnError
38
+ onRequestError?: OnRequestErrorObserver
39
+ }
40
+
41
+ /**
42
+ * Used by rpc, http, and the pre-stream guard in stream / http-stream
43
+ * handlers. Order: onRequestError observer (awaited, throws swallowed) →
44
+ * taxonomy resolver → imperative onError → hard default 500.
45
+ */
46
+ export async function dispatchPreStreamError(params: {
47
+ err: unknown
48
+ procedure: AnyProcedureRegistration
49
+ raw: Context
50
+ cfg: PreStreamErrorConfig
51
+ }): Promise<Response> {
52
+ const { err, procedure, raw, cfg } = params
53
+
54
+ if (cfg.onRequestError) {
55
+ try {
56
+ await cfg.onRequestError({ err, procedure, raw })
57
+ } catch (observerErr) {
58
+ console.error(
59
+ '[ts-procedures hono] onRequestError threw — swallowed:',
60
+ observerErr,
61
+ )
62
+ }
63
+ }
64
+
65
+ const resolved = resolveErrorResponse({
66
+ err,
67
+ userTaxonomy: cfg.errors,
68
+ unknownError: cfg.unknownError,
69
+ procedure,
70
+ raw,
71
+ })
72
+ if (resolved) {
73
+ await resolved.runOnCatch()
74
+ return raw.json(resolved.body, resolved.statusCode as never)
75
+ }
76
+
77
+ if (cfg.onError) {
78
+ return cfg.onError(procedure, raw, err as Error)
79
+ }
80
+
81
+ return raw.json({ error: (err as Error).message }, 500)
82
+ }
83
+
84
+ /**
85
+ * Return shape of an `onMidStreamError` callback.
86
+ *
87
+ * - `data`: the value to write to the open stream as the error payload.
88
+ * Decorate with the `sse(data, { event, id, retry })` helper from
89
+ * `hono/handlers/stream` to attach SSE metadata; the stream handler
90
+ * reads that metadata via `getSSEMeta` after dispatch.
91
+ * - `closeStream`: reserved for future use. The stream currently always
92
+ * closes naturally after the catch block returns; this flag is kept on
93
+ * the public type for forward compatibility but the dispatcher does not
94
+ * read it.
95
+ */
96
+ export type MidStreamErrorResult<TErrorData = unknown> = {
97
+ data: TErrorData
98
+ closeStream?: boolean
99
+ }
100
+
101
+ export type OnMidStreamError<TErrorData = unknown> = (
102
+ procedure: TStreamProcedureRegistration | THttpStreamProcedureRegistration<any>,
103
+ raw: Context,
104
+ err: Error,
105
+ ) => MidStreamErrorResult<TErrorData> | undefined
106
+
107
+ export type MidStreamErrorConfig<TErrorData = unknown> = {
108
+ errors?: ErrorTaxonomy
109
+ unknownError?: UnknownErrorConfig
110
+ onMidStreamError?: OnMidStreamError<TErrorData>
111
+ onRequestError?: OnRequestErrorObserver
112
+ }
113
+
114
+ export type DispatchedMidStreamError = {
115
+ data: unknown
116
+ sseEvent?: string
117
+ sseId?: string
118
+ sseRetry?: number
119
+ runOnCatch?: () => Promise<void>
120
+ }
121
+
122
+ /**
123
+ * Used inside SSE / text-stream generator catch blocks. Returns the bytes
124
+ * to write to the open stream — the HTTP status is already committed, so we
125
+ * can't return a Response. Order matches dispatchPreStreamError: observer
126
+ * (awaited, throws swallowed) → taxonomy → onMidStreamError → hard default.
127
+ */
128
+ export async function dispatchMidStreamError<TErrorData = unknown>(params: {
129
+ err: unknown
130
+ procedure: TStreamProcedureRegistration | THttpStreamProcedureRegistration<any>
131
+ raw: Context
132
+ cfg: MidStreamErrorConfig<TErrorData>
133
+ }): Promise<DispatchedMidStreamError> {
134
+ const { err, procedure, raw, cfg } = params
135
+
136
+ if (cfg.onRequestError) {
137
+ try {
138
+ await cfg.onRequestError({ err, procedure, raw })
139
+ } catch (observerErr) {
140
+ console.error(
141
+ '[ts-procedures hono] onRequestError (mid-stream) threw — swallowed:',
142
+ observerErr,
143
+ )
144
+ }
145
+ }
146
+
147
+ const resolved = resolveErrorResponse({
148
+ err,
149
+ userTaxonomy: cfg.errors,
150
+ unknownError: cfg.unknownError,
151
+ procedure,
152
+ raw,
153
+ })
154
+ if (resolved) {
155
+ return {
156
+ data: resolved.body,
157
+ sseEvent: 'error',
158
+ runOnCatch: resolved.runOnCatch,
159
+ }
160
+ }
161
+
162
+ if (cfg.onMidStreamError) {
163
+ const result = cfg.onMidStreamError(procedure, raw, err as Error)
164
+ if (result?.data !== undefined) {
165
+ return {
166
+ data: result.data,
167
+ sseEvent: procedure.name,
168
+ }
169
+ }
170
+ }
171
+
172
+ return {
173
+ data: { error: (err as Error).message },
174
+ sseEvent: 'error',
175
+ }
176
+ }
@@ -3,11 +3,11 @@ import {
3
3
  ProcedureValidationError,
4
4
  ProcedureYieldValidationError,
5
5
  } from '../../errors.js'
6
- import type { TProcedureRegistration, TStreamProcedureRegistration } from '../../index.js'
6
+ import type { TProcedureRegistration, TStreamProcedureRegistration, THttpProcedureRegistration, THttpStreamProcedureRegistration } from '../../index.js'
7
7
  import type { ErrorDoc } from '../types.js'
8
8
 
9
- /** Either a regular or stream procedure registration — accepted by taxonomy callbacks. */
10
- export type TAnyProcedureRegistration = TProcedureRegistration | TStreamProcedureRegistration
9
+ /** Any procedure registration — accepted by taxonomy callbacks. */
10
+ export type TAnyProcedureRegistration = TProcedureRegistration | TStreamProcedureRegistration | THttpProcedureRegistration<any> | THttpStreamProcedureRegistration<any>
11
11
 
12
12
  /**
13
13
  * An entry in an {@link ErrorTaxonomy}. Describes how to recognize a thrown
@@ -35,8 +35,8 @@ export type ErrorTaxonomyEntry<TError = any, TBody = unknown> = {
35
35
  toResponse?: (err: TError, meta: { key: string }) => TBody
36
36
  /**
37
37
  * Side effect on catch (logging, metrics). Awaited before the response is sent.
38
- * `raw` is the framework-specific request context (Hono `Context` /
39
- * `{ req, res }` for Express) — cast as needed.
38
+ * `raw` is the framework-specific request context (e.g., Hono `Context`)
39
+ * cast as needed.
40
40
  */
41
41
  onCatch?: (
42
42
  err: TError,