ts-procedures 7.2.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 +139 -53
  5. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +208 -231
  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 +36 -48
  12. package/agent_config/cursor/cursorrules +36 -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 +125 -115
  153. package/build/index.js +10 -222
  154. package/build/index.js.map +1 -1
  155. package/build/index.test.js +30 -822
  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 +34 -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 +35 -1091
  248. package/src/index.ts +50 -474
  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
@@ -0,0 +1,147 @@
1
+ import type { Context, Hono } from 'hono'
2
+ import type { THttpProcedureRegistration } from '../../../../types.js'
3
+ import type { HttpMethod } from '../../../types.js'
4
+ import { dispatchPreStreamError } from '../../error-dispatch.js'
5
+ import { buildHttpRouteDoc } from '../docs/http-doc.js'
6
+ import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, QueryParser } from '../types.js'
7
+
8
+ const BODY_METHODS: HttpMethod[] = ['post', 'put', 'patch']
9
+
10
+ function defaultSuccessStatus(method: HttpMethod): number {
11
+ switch (method) {
12
+ case 'post': return 201
13
+ case 'delete': return 204
14
+ default: return 200
15
+ }
16
+ }
17
+
18
+ function parseQueryNative(queryString: string): Record<string, unknown> {
19
+ const sp = new URLSearchParams(queryString)
20
+ const result: Record<string, unknown> = {}
21
+ for (const key of new Set(sp.keys())) {
22
+ const values = sp.getAll(key)
23
+ result[key] = values.length > 1 ? values : values[0]
24
+ }
25
+ return result
26
+ }
27
+
28
+ function extractQuery(url: string, parser: QueryParser): Record<string, unknown> {
29
+ const q = url.indexOf('?')
30
+ if (q === -1) return {}
31
+ const raw = url.slice(q + 1)
32
+ return raw ? parser(raw) : {}
33
+ }
34
+
35
+ async function extractParams(
36
+ c: Context,
37
+ method: HttpMethod,
38
+ reqSchema: Record<string, unknown>,
39
+ parser: QueryParser,
40
+ ): Promise<Record<string, unknown>> {
41
+ const params: Record<string, unknown> = {}
42
+ for (const channel of Object.keys(reqSchema)) {
43
+ switch (channel) {
44
+ case 'pathParams':
45
+ params.pathParams = c.req.param()
46
+ break
47
+ case 'query':
48
+ params.query = extractQuery(c.req.url, parser)
49
+ break
50
+ case 'body':
51
+ if (BODY_METHODS.includes(method)) {
52
+ params.body = await c.req.json().catch(() => ({}))
53
+ }
54
+ break
55
+ case 'headers': {
56
+ const obj: Record<string, string> = {}
57
+ c.req.raw.headers.forEach((v, k) => { obj[k] = v })
58
+ params.headers = obj
59
+ break
60
+ }
61
+ default:
62
+ params[channel] = undefined
63
+ }
64
+ }
65
+ return params
66
+ }
67
+
68
+ export function installHttpRoute(params: {
69
+ app: Hono
70
+ procedure: THttpProcedureRegistration<any>
71
+ factoryItem: HonoFactoryItem
72
+ cfg: HonoAppBuilderConfig
73
+ docs: DocAccumulator
74
+ }): void {
75
+ const { app, procedure, factoryItem, cfg, docs } = params
76
+ const queryParser = cfg.api?.queryParser ?? parseQueryNative
77
+
78
+ const route = buildHttpRouteDoc(
79
+ procedure,
80
+ cfg.pathPrefix,
81
+ factoryItem.extendProcedureDoc as any,
82
+ )
83
+ docs.push(route)
84
+
85
+ const successStatus = procedure.config.successStatus ?? defaultSuccessStatus(procedure.config.method)
86
+ const reqSchema = procedure.config.schema?.req
87
+
88
+ app.on(procedure.config.method.toUpperCase(), route.fullPath, async (c: Context) => {
89
+ try {
90
+ const context =
91
+ typeof factoryItem.factoryContext === 'function'
92
+ ? await (factoryItem.factoryContext as (c: Context) => any)(c)
93
+ : factoryItem.factoryContext
94
+
95
+ const reqParams = reqSchema
96
+ ? await extractParams(c, procedure.config.method, reqSchema, queryParser)
97
+ : undefined
98
+
99
+ const result = await procedure.handler(
100
+ { ...context, signal: c.req.raw.signal },
101
+ reqParams,
102
+ )
103
+
104
+ cfg.api?.onSuccess?.(procedure, c)
105
+
106
+ // 204 No Content — no body, just optional headers
107
+ if (successStatus === 204) {
108
+ if (result && typeof result === 'object' && 'headers' in result && (result as any).headers) {
109
+ for (const [k, v] of Object.entries((result as any).headers as Record<string, string>)) {
110
+ c.header(k, v)
111
+ }
112
+ }
113
+ return c.body(null, 204)
114
+ }
115
+
116
+ let body: unknown = result
117
+ let headers: Record<string, string> | undefined
118
+
119
+ if (result && typeof result === 'object' && 'body' in result && 'headers' in result) {
120
+ body = (result as any).body
121
+ headers = (result as any).headers as Record<string, string>
122
+ } else if (result && typeof result === 'object' && 'headers' in result && !('body' in result)) {
123
+ for (const [k, v] of Object.entries((result as any).headers as Record<string, string>)) {
124
+ c.header(k, v)
125
+ }
126
+ return c.body(null, successStatus as any)
127
+ } else if (result && typeof result === 'object' && 'body' in result && !('headers' in result)) {
128
+ body = (result as any).body
129
+ }
130
+
131
+ if (headers) for (const [k, v] of Object.entries(headers)) c.header(k, v)
132
+ return c.json(body, successStatus as any)
133
+ } catch (error) {
134
+ return dispatchPreStreamError({
135
+ err: error,
136
+ procedure,
137
+ raw: c,
138
+ cfg: {
139
+ errors: cfg.errors,
140
+ unknownError: cfg.unknownError,
141
+ onError: cfg.onError,
142
+ onRequestError: cfg.onRequestError,
143
+ },
144
+ })
145
+ }
146
+ })
147
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test, vi } from 'vitest'
2
+ import { Hono } from 'hono'
3
+ import { Type } from 'typebox'
4
+ import { Procedures } from '../../../../index.js'
5
+ import type { RPCConfig } from '../../../types.js'
6
+ import { installRpcRoute } from './rpc.js'
7
+
8
+ function buildApp(register: (factory: ReturnType<typeof Procedures<{ userId: string }, RPCConfig>>) => void, cfg: any = {}) {
9
+ const RPC = Procedures<{ userId: string }, RPCConfig>()
10
+ register(RPC)
11
+ const app = new Hono()
12
+ const docs: any[] = []
13
+ for (const proc of RPC.getProcedures().values()) {
14
+ if (proc.kind !== 'rpc') continue
15
+ installRpcRoute({
16
+ app,
17
+ procedure: proc as any,
18
+ factoryItem: { factory: RPC, factoryContext: () => ({ userId: '123' }) },
19
+ cfg,
20
+ docs,
21
+ })
22
+ }
23
+ return { app, docs }
24
+ }
25
+
26
+ describe('installRpcRoute', () => {
27
+ test('mounts POST route at scope/name/version path', async () => {
28
+ const { app, docs } = buildApp((RPC) => {
29
+ RPC.Create('Echo', {
30
+ scope: 'echo',
31
+ version: 1,
32
+ schema: { params: Type.Object({ msg: Type.String() }) },
33
+ }, async (_ctx, params) => params)
34
+ })
35
+
36
+ const res = await app.request('/echo/echo/1', {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify({ msg: 'hi' }),
40
+ })
41
+ expect(res.status).toBe(200)
42
+ expect(await res.json()).toEqual({ msg: 'hi' })
43
+ expect(docs).toHaveLength(1)
44
+ expect(docs[0].kind).toBe('rpc')
45
+ expect(docs[0].path).toBe('/echo/echo/1')
46
+ })
47
+
48
+ test('rpc.onSuccess fires after handler', async () => {
49
+ const onSuccess = vi.fn()
50
+ const { app } = buildApp((RPC) => {
51
+ RPC.Create('Ping', { scope: 'p', version: 1 }, async () => ({ ok: true }))
52
+ }, { rpc: { onSuccess } })
53
+
54
+ await app.request('/p/ping/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
55
+ expect(onSuccess).toHaveBeenCalledOnce()
56
+ })
57
+
58
+ test('thrown error → dispatchPreStreamError default 500', async () => {
59
+ const { app } = buildApp((RPC) => {
60
+ RPC.Create('Boom', { scope: 'b', version: 1 }, async () => { throw new Error('boom') })
61
+ })
62
+
63
+ const res = await app.request('/b/boom/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
64
+ expect(res.status).toBe(500)
65
+ const body = await res.json()
66
+ expect(body.error).toContain('boom')
67
+ })
68
+
69
+ test('AbortSignal is injected into ctx', async () => {
70
+ let receivedSignal: AbortSignal | undefined
71
+ const { app } = buildApp((RPC) => {
72
+ RPC.Create('Sig', { scope: 's', version: 1 }, async (ctx) => {
73
+ receivedSignal = ctx.signal
74
+ return {}
75
+ })
76
+ })
77
+
78
+ await app.request('/s/sig/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
79
+ expect(receivedSignal).toBeInstanceOf(AbortSignal)
80
+ })
81
+ })
@@ -0,0 +1,54 @@
1
+ import type { Context, Hono } from 'hono'
2
+ import type { TProcedureRegistration } from '../../../../types.js'
3
+ import type { RPCConfig } from '../../../types.js'
4
+ import { dispatchPreStreamError } from '../../error-dispatch.js'
5
+ import { buildRpcRouteDoc } from '../docs/rpc-doc.js'
6
+ import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
7
+
8
+ export function installRpcRoute(params: {
9
+ app: Hono
10
+ procedure: TProcedureRegistration<any, RPCConfig>
11
+ factoryItem: HonoFactoryItem
12
+ cfg: HonoAppBuilderConfig
13
+ docs: DocAccumulator
14
+ }): void {
15
+ const { app, procedure, factoryItem, cfg, docs } = params
16
+
17
+ const route = buildRpcRouteDoc(
18
+ procedure,
19
+ cfg.pathPrefix,
20
+ factoryItem.extendProcedureDoc as any,
21
+ )
22
+ docs.push(route)
23
+
24
+ app.post(route.path, async (c: Context) => {
25
+ try {
26
+ const context =
27
+ typeof factoryItem.factoryContext === 'function'
28
+ ? await (factoryItem.factoryContext as (c: Context) => any)(c)
29
+ : factoryItem.factoryContext
30
+
31
+ const body = await c.req.json().catch(() => ({}))
32
+ const result = await procedure.handler(
33
+ { ...context, signal: c.req.raw.signal },
34
+ body,
35
+ )
36
+
37
+ cfg.rpc?.onSuccess?.(procedure, c)
38
+
39
+ return c.json(result)
40
+ } catch (error) {
41
+ return dispatchPreStreamError({
42
+ err: error,
43
+ procedure,
44
+ raw: c,
45
+ cfg: {
46
+ errors: cfg.errors,
47
+ unknownError: cfg.unknownError,
48
+ onError: cfg.onError,
49
+ onRequestError: cfg.onRequestError,
50
+ },
51
+ })
52
+ }
53
+ })
54
+ }
@@ -0,0 +1,198 @@
1
+ import { describe, expect, test, vi } from 'vitest'
2
+ import { Hono } from 'hono'
3
+ import { Type } from 'typebox'
4
+ import { Procedures } from '../../../../index.js'
5
+ import type { RPCConfig } from '../../../types.js'
6
+ import { installRpcStreamRoute, sse } from './stream.js'
7
+
8
+ function buildApp(setup: (P: ReturnType<typeof Procedures<{ uid: string }, RPCConfig>>) => void, cfg: any = {}) {
9
+ const P = Procedures<{ uid: string }, RPCConfig>()
10
+ setup(P)
11
+ const app = new Hono()
12
+ const docs: any[] = []
13
+ for (const proc of P.getProcedures().values()) {
14
+ if (proc.kind !== 'rpc-stream') continue
15
+ installRpcStreamRoute({
16
+ app,
17
+ procedure: proc as any,
18
+ factoryItem: { factory: P, factoryContext: () => ({ uid: 'u1' }) },
19
+ cfg,
20
+ docs,
21
+ streamMode: cfg.stream?.defaultStreamMode ?? 'sse',
22
+ })
23
+ }
24
+ return { app, docs }
25
+ }
26
+
27
+ describe('installRpcStreamRoute', () => {
28
+ test('SSE: yields are written as data events; final return as event:return', async () => {
29
+ const { app, docs } = buildApp((P) => {
30
+ P.CreateStream('Counts', { scope: 'c', version: 1 }, async function* () {
31
+ yield 1
32
+ yield 2
33
+ return 'done'
34
+ })
35
+ })
36
+ expect(docs[0].kind).toBe('stream')
37
+ expect(docs[0].path).toBe('/c/counts/1')
38
+
39
+ const res = await app.request('/c/counts/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
40
+ expect(res.status).toBe(200)
41
+ const text = await res.text()
42
+ expect(text).toContain('data: 1')
43
+ expect(text).toContain('data: 2')
44
+ expect(text).toContain('event: return')
45
+ expect(text).toContain('data: done')
46
+ })
47
+
48
+ test('mid-stream throw → dispatchMidStreamError default { error }', async () => {
49
+ const { app } = buildApp((P) => {
50
+ P.CreateStream('Boom', { scope: 'b', version: 1 }, async function* () {
51
+ yield 'first'
52
+ throw new Error('mid-boom')
53
+ })
54
+ })
55
+ const res = await app.request('/b/boom/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
56
+ const text = await res.text()
57
+ expect(text).toContain('event: error')
58
+ expect(text).toContain('"error":"mid-boom"')
59
+ })
60
+
61
+ test('pre-stream validation error → 400 via dispatchPreStreamError', async () => {
62
+ const { app } = buildApp((P) => {
63
+ P.CreateStream('NeedsParams', {
64
+ scope: 'n', version: 1,
65
+ schema: { params: Type.Object({ id: Type.String() }) },
66
+ }, async function* () { yield 'ok' })
67
+ })
68
+ const res = await app.request('/n/needs-params/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
69
+ expect(res.status).toBe(400)
70
+ const body = await res.json()
71
+ expect(body.name).toBe('ProcedureValidationError')
72
+ })
73
+
74
+ test('onStreamStart and onStreamEnd fire', async () => {
75
+ const onStreamStart = vi.fn()
76
+ const onStreamEnd = vi.fn()
77
+ const { app } = buildApp((P) => {
78
+ P.CreateStream('Ev', { scope: 'e', version: 1 }, async function* () { yield 'x' })
79
+ }, { stream: { onStreamStart, onStreamEnd } })
80
+
81
+ const res = await app.request('/e/ev/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
82
+ // Consume the stream body so the SSE writer's finally block runs
83
+ await res.text()
84
+ expect(onStreamStart).toHaveBeenCalled()
85
+ expect(onStreamEnd).toHaveBeenCalled()
86
+ })
87
+
88
+ test('validateYields: true → invalid yield surfaces ProcedureYieldValidationError on the wire', async () => {
89
+ const { app } = buildApp((P) => {
90
+ P.CreateStream(
91
+ 'BadYield',
92
+ {
93
+ scope: 'y', version: 1,
94
+ schema: { yieldType: Type.Object({ id: Type.Number() }) },
95
+ validateYields: true,
96
+ },
97
+ async function* () {
98
+ yield { id: 1 } // valid
99
+ yield { id: 'not-a-number' } as any // invalid → throws ProcedureYieldValidationError
100
+ },
101
+ )
102
+ })
103
+
104
+ const res = await app.request('/y/bad-yield/1', {
105
+ method: 'POST',
106
+ body: '{}',
107
+ headers: { 'Content-Type': 'application/json' },
108
+ })
109
+ // Stream is already open by the time validation fails on the second yield;
110
+ // status is committed at 200 and the error shows up as an SSE error event.
111
+ expect(res.status).toBe(200)
112
+ const text = await res.text()
113
+ expect(text).toContain('event: error')
114
+ expect(text).toContain('"name":"ProcedureYieldValidationError"')
115
+ expect(text).toContain('"procedureName":"BadYield"')
116
+ // First valid yield still made it onto the wire.
117
+ expect(text).toContain('data: {"id":1}')
118
+ })
119
+
120
+ test('sse() helper propagates event/id/retry into SSE wire output', async () => {
121
+ const { app } = buildApp((P) => {
122
+ P.CreateStream('Tagged', { scope: 'tagged', version: 1 }, async function* () {
123
+ yield sse({ count: 1 }, { event: 'tick', id: 'evt-1', retry: 5000 })
124
+ })
125
+ })
126
+
127
+ const res = await app.request('/tagged/tagged/1', {
128
+ method: 'POST',
129
+ body: '{}',
130
+ headers: { 'Content-Type': 'application/json' },
131
+ })
132
+ const text = await res.text()
133
+ expect(text).toContain('event: tick')
134
+ expect(text).toContain('id: evt-1')
135
+ expect(text).toContain('retry: 5000')
136
+ expect(text).toContain('data: {"count":1}')
137
+ })
138
+
139
+ test('AbortSignal: natural completion → ctx.signal.reason === "stream-completed"', async () => {
140
+ let capturedSignal: AbortSignal | undefined
141
+ const { app } = buildApp((P) => {
142
+ P.CreateStream('Natural', { scope: 'sig', version: 1 }, async function* (ctx) {
143
+ capturedSignal = ctx.signal
144
+ yield 'a'
145
+ yield 'b'
146
+ return 'done'
147
+ })
148
+ })
149
+
150
+ const res = await app.request('/sig/natural/1', {
151
+ method: 'POST',
152
+ body: '{}',
153
+ headers: { 'Content-Type': 'application/json' },
154
+ })
155
+ // Drain the body so the wrappedHandler's finally block runs.
156
+ await res.text()
157
+
158
+ expect(capturedSignal).toBeInstanceOf(AbortSignal)
159
+ expect(capturedSignal!.aborted).toBe(true)
160
+ expect(capturedSignal!.reason).toBe('stream-completed')
161
+ })
162
+
163
+ test('AbortSignal: client disconnect (request signal aborted) → ctx.signal.reason !== "stream-completed"', async () => {
164
+ // NOTE: We can't directly simulate a real socket disconnect through
165
+ // `app.request()`. The closest test-friendly proxy: pre-abort the request
166
+ // signal so `c.req.raw.signal` is already aborted by the time the
167
+ // wrappedHandler in create-stream.ts builds the combined signal via
168
+ // `AbortSignal.any([incomingSignal, internalController.signal])`. The
169
+ // pre-existing reason wins over the internal 'stream-completed' that fires
170
+ // later in the wrappedHandler's finally clause.
171
+ let capturedSignal: AbortSignal | undefined
172
+ const { app } = buildApp((P) => {
173
+ P.CreateStream('Disconnect', { scope: 'sig', version: 1 }, async function* (ctx) {
174
+ capturedSignal = ctx.signal
175
+ yield 'first'
176
+ })
177
+ })
178
+
179
+ const ac = new AbortController()
180
+ ac.abort('client-gone')
181
+
182
+ const req = new Request('http://localhost/sig/disconnect/1', {
183
+ method: 'POST',
184
+ body: '{}',
185
+ headers: { 'Content-Type': 'application/json' },
186
+ signal: ac.signal,
187
+ })
188
+ const res = await app.request(req)
189
+ // Drain the body in case anything was emitted before the abort propagated.
190
+ await res.text().catch(() => '')
191
+
192
+ expect(capturedSignal).toBeInstanceOf(AbortSignal)
193
+ expect(capturedSignal!.aborted).toBe(true)
194
+ // The disconnect reason wins over the internal 'stream-completed' signal,
195
+ // so handlers can distinguish "client gave up" from "we finished normally".
196
+ expect(capturedSignal!.reason).not.toBe('stream-completed')
197
+ })
198
+ })
@@ -0,0 +1,208 @@
1
+ import type { Context, Hono } from 'hono'
2
+ import { streamSSE, streamText } from 'hono/streaming'
3
+ import type { TStreamProcedureRegistration } from '../../../../types.js'
4
+ import type { RPCConfig, StreamMode } from '../../../types.js'
5
+ import { ProcedureValidationError } from '../../../../errors.js'
6
+ import { dispatchMidStreamError, dispatchPreStreamError } from '../../error-dispatch.js'
7
+ import { buildStreamRouteDoc } from '../docs/stream-doc.js'
8
+ import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
9
+
10
+ export type SSEOptions = {
11
+ event?: string
12
+ id?: string
13
+ retry?: number
14
+ }
15
+
16
+ const sseMetadata = new WeakMap<object, SSEOptions>()
17
+
18
+ /**
19
+ * Marks an object yield as an SSE event with custom metadata. Stream handlers
20
+ * read this metadata to set the SSE `event:`, `id:`, and `retry:` fields.
21
+ */
22
+ export function sse<T extends object>(data: T, options?: SSEOptions): T {
23
+ sseMetadata.set(data, options ?? {})
24
+ return data
25
+ }
26
+
27
+ function getSSEMeta(value: unknown): SSEOptions | undefined {
28
+ if (typeof value === 'object' && value !== null) {
29
+ return sseMetadata.get(value as object)
30
+ }
31
+ return undefined
32
+ }
33
+
34
+ export type { MidStreamErrorResult } from '../../error-dispatch.js'
35
+
36
+ export function installRpcStreamRoute(params: {
37
+ app: Hono
38
+ procedure: TStreamProcedureRegistration<any, RPCConfig>
39
+ factoryItem: HonoFactoryItem
40
+ cfg: HonoAppBuilderConfig
41
+ docs: DocAccumulator
42
+ streamMode: StreamMode
43
+ }): void {
44
+ const { app, procedure, factoryItem, cfg, docs, streamMode } = params
45
+
46
+ const route = buildStreamRouteDoc(
47
+ procedure,
48
+ streamMode,
49
+ cfg.pathPrefix,
50
+ factoryItem.extendProcedureDoc as any,
51
+ )
52
+ docs.push(route)
53
+
54
+ const handler = async (c: Context) => {
55
+ try {
56
+ const context =
57
+ typeof factoryItem.factoryContext === 'function'
58
+ ? await (factoryItem.factoryContext as (c: Context) => any)(c)
59
+ : factoryItem.factoryContext
60
+
61
+ const reqParams =
62
+ c.req.method === 'GET'
63
+ ? Object.fromEntries(new URL(c.req.url).searchParams)
64
+ : await c.req.json().catch(() => ({}))
65
+
66
+ // Pre-stream validation — throw so the catch routes through dispatchPreStreamError.
67
+ if ((procedure.config as any).validation?.params) {
68
+ const { errors } = (procedure.config as any).validation.params(reqParams)
69
+ if (errors) {
70
+ throw new ProcedureValidationError(
71
+ procedure.name,
72
+ `Validation error for ${procedure.name}`,
73
+ errors,
74
+ )
75
+ }
76
+ }
77
+
78
+ cfg.stream?.onStreamStart?.(procedure, c, streamMode)
79
+
80
+ return streamMode === 'sse'
81
+ ? handleSSE(procedure, context, reqParams, c, cfg)
82
+ : handleText(procedure, context, reqParams, c, cfg)
83
+ } catch (error) {
84
+ return dispatchPreStreamError({
85
+ err: error,
86
+ procedure,
87
+ raw: c,
88
+ cfg: {
89
+ errors: cfg.errors,
90
+ unknownError: cfg.unknownError,
91
+ onError: cfg.onError,
92
+ onRequestError: cfg.onRequestError,
93
+ },
94
+ })
95
+ }
96
+ }
97
+
98
+ app.get(route.path, handler)
99
+ app.post(route.path, handler)
100
+ }
101
+
102
+ function handleSSE(
103
+ procedure: TStreamProcedureRegistration<any, RPCConfig>,
104
+ context: any,
105
+ reqParams: any,
106
+ c: Context,
107
+ cfg: HonoAppBuilderConfig,
108
+ ) {
109
+ return streamSSE(c, async (stream) => {
110
+ const generator = procedure.handler(
111
+ { ...context, signal: c.req.raw.signal, isPrevalidated: true } as any,
112
+ reqParams,
113
+ )
114
+ stream.onAbort(async () => { await generator.return(undefined) })
115
+
116
+ let eventId = 0
117
+ try {
118
+ const iterator = generator[Symbol.asyncIterator]()
119
+ let it = await iterator.next()
120
+
121
+ while (!it.done) {
122
+ const value = it.value
123
+ const meta = getSSEMeta(value)
124
+ const data =
125
+ typeof value === 'string' ? value
126
+ : value != null ? JSON.stringify(value)
127
+ : ''
128
+
129
+ await stream.writeSSE({
130
+ data,
131
+ event: meta?.event ?? procedure.name,
132
+ id: meta?.id ?? String(eventId++),
133
+ ...(meta?.retry !== undefined && { retry: meta.retry }),
134
+ })
135
+ it = await iterator.next()
136
+ }
137
+
138
+ if (it.value !== undefined) {
139
+ const data = typeof it.value === 'string' ? it.value : JSON.stringify(it.value)
140
+ await stream.writeSSE({ data, event: 'return', id: String(eventId++) })
141
+ }
142
+ } catch (error) {
143
+ const dispatched = await dispatchMidStreamError({
144
+ err: error,
145
+ procedure,
146
+ raw: c,
147
+ cfg: {
148
+ errors: cfg.errors,
149
+ unknownError: cfg.unknownError,
150
+ onMidStreamError: cfg.stream?.onMidStreamError,
151
+ onRequestError: cfg.onRequestError,
152
+ },
153
+ })
154
+
155
+ const meta = getSSEMeta(dispatched.data)
156
+ await stream.writeSSE({
157
+ data: typeof dispatched.data === 'string' ? dispatched.data : JSON.stringify(dispatched.data),
158
+ event: meta?.event ?? dispatched.sseEvent ?? 'error',
159
+ id: meta?.id ?? String(eventId++),
160
+ ...(meta?.retry !== undefined && { retry: meta.retry }),
161
+ })
162
+
163
+ if (dispatched.runOnCatch) await dispatched.runOnCatch()
164
+ } finally {
165
+ cfg.stream?.onStreamEnd?.(procedure, c, 'sse')
166
+ cfg.onRequestEnd?.(c)
167
+ }
168
+ })
169
+ }
170
+
171
+ function handleText(
172
+ procedure: TStreamProcedureRegistration<any, RPCConfig>,
173
+ context: any,
174
+ reqParams: any,
175
+ c: Context,
176
+ cfg: HonoAppBuilderConfig,
177
+ ) {
178
+ return streamText(c, async (stream) => {
179
+ const generator = procedure.handler(
180
+ { ...context, signal: c.req.raw.signal, isPrevalidated: true } as any,
181
+ reqParams,
182
+ )
183
+ stream.onAbort(async () => { await generator.return(undefined) })
184
+
185
+ try {
186
+ for await (const value of generator) {
187
+ await stream.writeln(JSON.stringify(value))
188
+ }
189
+ } catch (error) {
190
+ const dispatched = await dispatchMidStreamError({
191
+ err: error,
192
+ procedure,
193
+ raw: c,
194
+ cfg: {
195
+ errors: cfg.errors,
196
+ unknownError: cfg.unknownError,
197
+ onMidStreamError: cfg.stream?.onMidStreamError,
198
+ onRequestError: cfg.onRequestError,
199
+ },
200
+ })
201
+ await stream.writeln(JSON.stringify(dispatched.data))
202
+ if (dispatched.runOnCatch) await dispatched.runOnCatch()
203
+ } finally {
204
+ cfg.stream?.onStreamEnd?.(procedure, c, 'text')
205
+ cfg.onRequestEnd?.(c)
206
+ }
207
+ })
208
+ }