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
@@ -1,622 +0,0 @@
1
- import { Hono, Context } from 'hono'
2
- import { streamSSE, streamText } from 'hono/streaming'
3
- import { kebabCase } from 'es-toolkit/string'
4
- import { castArray } from 'es-toolkit/compat'
5
- import { TStreamProcedureRegistration } from '../../../index.js'
6
- import { ExtractConfig, ExtractContext, ProceduresFactory, RPCConfig } from '../../types.js'
7
- import { HonoStreamFactoryItem, StreamHttpRouteDoc, StreamMode } from './types.js'
8
- import { ProcedureValidationError } from '../../../errors.js'
9
- import {
10
- ErrorTaxonomy,
11
- ErrorTaxonomyEntry,
12
- UnknownErrorConfig,
13
- defineErrorTaxonomy,
14
- resolveErrorResponse,
15
- } from '../error-taxonomy.js'
16
-
17
- export type { StreamHttpRouteDoc, StreamMode }
18
- export { defineErrorTaxonomy }
19
- export type { ErrorTaxonomy, ErrorTaxonomyEntry, UnknownErrorConfig }
20
-
21
- /**
22
- * Context passed to the `onRequestError` observer. `raw` is the Hono
23
- * `Context` for the in-flight request — cast at the use site if you need
24
- * framework-specific fields.
25
- */
26
- export type OnRequestErrorContext = {
27
- err: unknown
28
- procedure: TStreamProcedureRegistration
29
- raw: Context
30
- }
31
-
32
- export type SSEOptions = {
33
- event?: string
34
- id?: string
35
- retry?: number
36
- }
37
-
38
- const sseMetadata = new WeakMap<object, SSEOptions>()
39
-
40
- export function sse<T extends object>(data: T, options?: SSEOptions): T {
41
- sseMetadata.set(data, options ?? {})
42
- return data
43
- }
44
-
45
- function getSSEMeta(value: unknown): SSEOptions | undefined {
46
- if (typeof value === 'object' && value !== null) {
47
- return sseMetadata.get(value)
48
- }
49
- return undefined
50
- }
51
-
52
- /**
53
- * Result from onMidStreamError callback.
54
- * @property data - The data to write as the SSE `data:` field content (should match yieldType schema)
55
- * @property closeStream - Whether to close the stream after writing (defaults to true)
56
- */
57
- export type MidStreamErrorResult<TErrorData = unknown> = {
58
- data: TErrorData
59
- closeStream?: boolean
60
- }
61
-
62
- export type HonoStreamAppBuilderConfig<TErrorData = unknown> = {
63
- /**
64
- * An existing Hono application instance to use.
65
- * If not provided, a new instance will be created.
66
- */
67
- app?: Hono
68
- /** Optional path prefix for all stream routes. */
69
- pathPrefix?: string
70
- /** Default stream mode for all routes. Defaults to 'sse'. */
71
- defaultStreamMode?: StreamMode
72
- onRequestStart?: (c: Context) => void
73
- onRequestEnd?: (c: Context) => void
74
- onStreamStart?: (procedure: TStreamProcedureRegistration, c: Context, streamMode: StreamMode) => void
75
- onStreamEnd?: (procedure: TStreamProcedureRegistration, c: Context, streamMode: StreamMode) => void
76
- /**
77
- * Declarative error-to-response mapping (one of the two peer error modes).
78
- * Thrown error classes map to status codes + bodies declaratively. The
79
- * taxonomy applies to BOTH pre-stream errors (where the status code is
80
- * honored) AND mid-stream errors (where only the body shape is honored —
81
- * the HTTP status is already committed once streaming starts; the body is
82
- * written as the SSE `event: 'error'` data, or a JSON line in text mode,
83
- * for the client's error registry to dispatch). See hono-api for the full
84
- * taxonomy contract.
85
- */
86
- errors?: ErrorTaxonomy
87
- /**
88
- * Fallback serializer when a taxonomy is configured but no entry matches.
89
- */
90
- unknownError?: UnknownErrorConfig
91
- /**
92
- * Imperative pre-stream error callback — the other peer error mode.
93
- * Receives every pre-stream error directly and returns the HTTP response.
94
- * Use this instead of `errors` when you want full control over the response
95
- * shape, or alongside it for the tail of errors the taxonomy doesn't cover.
96
- *
97
- * Mid-stream errors always go through `onMidStreamError`.
98
- */
99
- onError?: (
100
- procedure: TStreamProcedureRegistration,
101
- c: Context,
102
- error: ProcedureValidationError | Error
103
- ) => Response | Promise<Response>
104
- /**
105
- * Cross-cutting observer — fires for every caught pre-stream error, BEFORE
106
- * dispatch to the taxonomy or `onError`. Awaited. Cannot mutate the
107
- * response. Intended for logging, tracing, and metrics (Sentry, Datadog,
108
- * OpenTelemetry). Any error thrown inside the observer is swallowed and
109
- * logged so the primary dispatch flow is never disrupted.
110
- */
111
- onRequestError?: (ctx: OnRequestErrorContext) => void | Promise<void>
112
- /**
113
- * Called for errors DURING streaming (generator throws).
114
- * Return value is written to the stream as a yield.
115
- * Should return a value matching your yieldType schema (e.g., error variant of a union).
116
- * Return undefined to use default behavior (writes { error: message }).
117
- *
118
- * Use sse() to attach SSE metadata (event, id, retry) to the error data object.
119
- *
120
- * @returns { data, closeStream? } - data to yield, whether to close after (default true)
121
- */
122
- onMidStreamError?: (
123
- procedure: TStreamProcedureRegistration,
124
- c: Context,
125
- error: Error
126
- ) => MidStreamErrorResult<TErrorData> | undefined
127
- }
128
-
129
- /**
130
- * Builder class for creating a Hono application with streaming RPC routes.
131
- *
132
- * Usage:
133
- * const StreamRPC = Procedures<StreamContext, RPCConfig>()
134
- *
135
- * const streamApp = new HonoStreamAppBuilder()
136
- * .register(StreamRPC, (c): Promise<StreamContext> => { /* context resolution logic * / })
137
- * .build();
138
- *
139
- * const app = streamApp.app; // Hono application
140
- * const docs = streamApp.docs; // Stream route documentation
141
- */
142
- export class HonoStreamAppBuilder<TErrorData = unknown> {
143
- /**
144
- * Constructor for HonoStreamAppBuilder.
145
- */
146
- constructor(readonly config?: HonoStreamAppBuilderConfig<TErrorData>) {
147
- if (config?.app) {
148
- this._app = config.app
149
- }
150
-
151
- if (config?.onRequestStart) {
152
- this._app.use('*', async (c, next) => {
153
- config.onRequestStart!(c)
154
- await next()
155
- })
156
- }
157
- }
158
-
159
- /**
160
- * Generates the stream route path based on the RPC configuration.
161
- */
162
- static makeStreamHttpRoutePath({
163
- name,
164
- config,
165
- prefix,
166
- }: {
167
- name: string
168
- prefix?: string
169
- config: RPCConfig
170
- }) {
171
- const normalizedPrefix = prefix ? (prefix.startsWith('/') ? prefix : `/${prefix}`) : ''
172
-
173
- return `${normalizedPrefix}/${castArray(config.scope).map(kebabCase).join('/')}/${kebabCase(name)}/${String(config.version).trim()}`
174
- }
175
-
176
- /**
177
- * Instance method wrapper for makeStreamHttpRoutePath that uses the builder's pathPrefix.
178
- */
179
- makeStreamHttpRoutePath(name: string, config: RPCConfig): string {
180
- return HonoStreamAppBuilder.makeStreamHttpRoutePath({
181
- name,
182
- config,
183
- prefix: this.config?.pathPrefix,
184
- })
185
- }
186
-
187
- private factories: HonoStreamFactoryItem<any>[] = []
188
-
189
- private _app: Hono = new Hono()
190
- private _docs: (StreamHttpRouteDoc & object)[] = []
191
- private _skipped: { name: string; reason: string }[] = []
192
-
193
- get app(): Hono {
194
- return this._app
195
- }
196
-
197
- get docs(): StreamHttpRouteDoc[] {
198
- return this._docs
199
- }
200
-
201
- /**
202
- * Procedures that were skipped at `build()` time because they don't fit
203
- * this builder (e.g. non-streaming procedures registered against the
204
- * stream builder). Surfaced via `DocSource.skippedProcedures` so
205
- * DocRegistry can warn about coverage gaps.
206
- */
207
- get skippedProcedures(): { name: string; reason: string }[] {
208
- return this._skipped
209
- }
210
-
211
- /**
212
- * Registers a procedure factory with its context.
213
- * Only streaming procedures (created with CreateStream) will be registered.
214
- */
215
- register<TFactory extends ProceduresFactory>(
216
- factory: TFactory,
217
- factoryContext:
218
- | ExtractContext<TFactory>
219
- | ((c: Context) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>),
220
- options?: {
221
- streamMode?: StreamMode
222
- extendProcedureDoc?: (params: {
223
- base: StreamHttpRouteDoc
224
- procedure: TStreamProcedureRegistration<any, ExtractConfig<TFactory>>
225
- }) => Record<string, any>
226
- }
227
- ): this {
228
- this.factories.push({
229
- factory,
230
- factoryContext,
231
- streamMode: options?.streamMode,
232
- extendProcedureDoc: options?.extendProcedureDoc,
233
- } as HonoStreamFactoryItem<any>)
234
- return this
235
- }
236
-
237
- /**
238
- * Creates a route handler for streaming procedures.
239
- */
240
- private createStreamHandler(
241
- procedure: TStreamProcedureRegistration,
242
- factoryContext: HonoStreamFactoryItem['factoryContext'],
243
- streamMode: StreamMode
244
- ) {
245
- return async (c: Context) => {
246
- try {
247
- const context =
248
- typeof factoryContext === 'function' ? await factoryContext(c) : factoryContext
249
-
250
- // GET: query params, POST: JSON body
251
- const params =
252
- c.req.method === 'GET'
253
- ? Object.fromEntries(new URL(c.req.url).searchParams)
254
- : await c.req.json().catch(() => ({}))
255
-
256
- // Validate params BEFORE starting the stream. Throw so both the
257
- // validation path and the outer-catch (context resolution) path flow
258
- // through one dispatch site.
259
- if (procedure.config.validation?.params) {
260
- const { errors } = procedure.config.validation.params(params)
261
- if (errors) {
262
- throw new ProcedureValidationError(
263
- procedure.name,
264
- `Validation error for ${procedure.name}`,
265
- errors
266
- )
267
- }
268
- }
269
-
270
- if (this.config?.onStreamStart) {
271
- this.config.onStreamStart(procedure, c, streamMode)
272
- }
273
-
274
- if (streamMode === 'sse') {
275
- return this.handleSSEStream(procedure, context, params, c)
276
- } else {
277
- return this.handleTextStream(procedure, context, params, c)
278
- }
279
- } catch (error) {
280
- // Observer fires first — cross-cutting, cannot alter dispatch or the
281
- // response. Swallow any throw from the observer so the primary flow
282
- // never breaks because of instrumentation.
283
- if (this.config?.onRequestError) {
284
- try {
285
- await this.config.onRequestError({ err: error, procedure, raw: c })
286
- } catch (observerErr) {
287
- console.error('[ts-procedures hono-stream] onRequestError threw — swallowed:', observerErr)
288
- }
289
- }
290
-
291
- // Dispatch: taxonomy → onError → hard default. The two modes are
292
- // peers; developers configure whichever fits their app.
293
- if (this.config?.errors || this.config?.unknownError) {
294
- const resolved = resolveErrorResponse({
295
- err: error,
296
- userTaxonomy: this.config.errors,
297
- unknownError: this.config.unknownError,
298
- procedure,
299
- raw: c,
300
- })
301
- if (resolved) {
302
- await resolved.runOnCatch()
303
- return c.json(resolved.body, resolved.statusCode as never)
304
- }
305
- }
306
- if (this.config?.onError) {
307
- return this.config.onError(procedure, c, error as Error)
308
- }
309
- return c.json({ error: (error as Error).message }, 500)
310
- }
311
- }
312
- }
313
-
314
- /**
315
- * Handles SSE streaming mode.
316
- */
317
- private handleSSEStream(
318
- procedure: TStreamProcedureRegistration,
319
- context: any,
320
- params: any,
321
- c: Context
322
- ) {
323
- return streamSSE(c, async (stream) => {
324
- // Pass isPrevalidated: true since we already validated params in createStreamHandler
325
- const generator = procedure.handler({ ...context, signal: c.req.raw.signal, isPrevalidated: true }, params)
326
-
327
- stream.onAbort(async () => {
328
- await generator.return(undefined)
329
- })
330
-
331
- let eventId = 0
332
- try {
333
- const iterator = generator[Symbol.asyncIterator]()
334
- let iterResult = await iterator.next()
335
-
336
- while (!iterResult.done) {
337
- const value = iterResult.value
338
- const currentId = eventId++
339
- const meta = getSSEMeta(value)
340
-
341
- const data =
342
- typeof value === 'string'
343
- ? value
344
- : value != null
345
- ? JSON.stringify(value)
346
- : ''
347
-
348
- await stream.writeSSE({
349
- data,
350
- event: meta?.event ?? procedure.name,
351
- id: meta?.id ?? String(currentId),
352
- ...(meta?.retry !== undefined && { retry: meta.retry }),
353
- })
354
-
355
- iterResult = await iterator.next()
356
- }
357
-
358
- // Send return value as a special 'return' event (if present)
359
- if (iterResult.value !== undefined) {
360
- const returnData =
361
- typeof iterResult.value === 'string'
362
- ? iterResult.value
363
- : JSON.stringify(iterResult.value)
364
-
365
- await stream.writeSSE({
366
- data: returnData,
367
- event: 'return',
368
- id: String(eventId++),
369
- })
370
- }
371
- } catch (error) {
372
- // Dispatch order mirrors hono-rpc: taxonomy → onMidStreamError → default.
373
- // The HTTP status is already committed (200 OK headers were sent the
374
- // moment streaming started), so the taxonomy here only drives the
375
- // wire-protocol body shape — clients dispatch through the same error
376
- // registry as RPC/API responses by reading `data.name`.
377
- let errorData: unknown
378
- let sseEventOverride: string | undefined
379
- let sseIdOverride: string | undefined
380
- let sseRetryOverride: number | undefined
381
- let runOnCatch: (() => Promise<void>) | undefined
382
-
383
- if (this.config?.errors || this.config?.unknownError) {
384
- const resolved = resolveErrorResponse({
385
- err: error,
386
- userTaxonomy: this.config.errors,
387
- unknownError: this.config.unknownError,
388
- procedure,
389
- raw: c,
390
- })
391
- if (resolved) {
392
- errorData = resolved.body
393
- sseEventOverride = 'error'
394
- runOnCatch = resolved.runOnCatch
395
- }
396
- }
397
-
398
- if (errorData === undefined && this.config?.onMidStreamError) {
399
- const errorResult: MidStreamErrorResult<TErrorData> | undefined =
400
- this.config.onMidStreamError(procedure, c, error as Error)
401
- if (errorResult?.data !== undefined) {
402
- errorData = errorResult.data
403
- sseEventOverride = procedure.name
404
- }
405
- }
406
-
407
- if (errorData === undefined) {
408
- errorData = { error: (error as Error).message }
409
- sseEventOverride = 'error'
410
- }
411
-
412
- const sseMeta = getSSEMeta(errorData)
413
-
414
- await stream.writeSSE({
415
- data: typeof errorData === 'string' ? errorData : JSON.stringify(errorData),
416
- event: sseMeta?.event ?? sseEventOverride ?? 'error',
417
- id: sseMeta?.id ?? sseIdOverride ?? String(eventId++),
418
- ...((sseMeta?.retry ?? sseRetryOverride) !== undefined && {
419
- retry: (sseMeta?.retry ?? sseRetryOverride) as number,
420
- }),
421
- })
422
-
423
- if (runOnCatch) {
424
- await runOnCatch()
425
- }
426
-
427
- // closeStream defaults to true if not specified
428
- // (stream closes naturally after this handler completes)
429
- } finally {
430
- if (this.config?.onStreamEnd) {
431
- this.config.onStreamEnd(procedure, c, 'sse')
432
- }
433
- if (this.config?.onRequestEnd) {
434
- this.config.onRequestEnd(c)
435
- }
436
- }
437
- })
438
- }
439
-
440
- /**
441
- * Handles text streaming mode.
442
- */
443
- private handleTextStream(
444
- procedure: TStreamProcedureRegistration,
445
- context: any,
446
- params: any,
447
- c: Context
448
- ) {
449
- return streamText(c, async (stream) => {
450
- // Pass isPrevalidated: true since we already validated params in createStreamHandler
451
- const generator = procedure.handler({ ...context, signal: c.req.raw.signal, isPrevalidated: true }, params)
452
-
453
- stream.onAbort(async () => {
454
- await generator.return(undefined)
455
- })
456
-
457
- try {
458
- for await (const value of generator) {
459
- await stream.writeln(JSON.stringify(value))
460
- }
461
- } catch (error) {
462
- // Same dispatch order as SSE — taxonomy first, onMidStreamError next,
463
- // hard default last. Text streams have no event/id metadata, so we
464
- // only forward the body bytes.
465
- let errorData: unknown
466
- let runOnCatch: (() => Promise<void>) | undefined
467
-
468
- if (this.config?.errors || this.config?.unknownError) {
469
- const resolved = resolveErrorResponse({
470
- err: error,
471
- userTaxonomy: this.config.errors,
472
- unknownError: this.config.unknownError,
473
- procedure,
474
- raw: c,
475
- })
476
- if (resolved) {
477
- errorData = resolved.body
478
- runOnCatch = resolved.runOnCatch
479
- }
480
- }
481
-
482
- if (errorData === undefined && this.config?.onMidStreamError) {
483
- const errorResult: MidStreamErrorResult<TErrorData> | undefined =
484
- this.config.onMidStreamError(procedure, c, error as Error)
485
- if (errorResult?.data !== undefined) {
486
- errorData = errorResult.data
487
- }
488
- }
489
-
490
- if (errorData === undefined) {
491
- errorData = { error: (error as Error).message }
492
- }
493
-
494
- await stream.writeln(JSON.stringify(errorData))
495
-
496
- if (runOnCatch) {
497
- await runOnCatch()
498
- }
499
- } finally {
500
- if (this.config?.onStreamEnd) {
501
- this.config.onStreamEnd(procedure, c, 'text')
502
- }
503
- if (this.config?.onRequestEnd) {
504
- this.config.onRequestEnd(c)
505
- }
506
- }
507
- })
508
- }
509
-
510
- /**
511
- * Builds and returns the Hono application with registered streaming routes.
512
- */
513
- build(): Hono {
514
- this.factories.forEach(({ factory, factoryContext, streamMode, extendProcedureDoc }) => {
515
- const mode = streamMode ?? this.config?.defaultStreamMode ?? 'sse'
516
-
517
- const procedures = factory.getProcedures()
518
-
519
- // Track non-streaming procedures so DocRegistry can warn about coverage
520
- // gaps (e.g. a regular procedure registered with this builder will get
521
- // dropped here and needs to be registered with HonoRPCAppBuilder).
522
- for (const p of procedures as { name: string; isStream?: boolean }[]) {
523
- if (p.isStream !== true) {
524
- const reason =
525
- 'Non-streaming procedure registered with HonoStreamAppBuilder — register it with HonoRPCAppBuilder (or HonoApiAppBuilder) instead.'
526
- this._skipped.push({ name: p.name, reason })
527
- console.warn(
528
- `[ts-procedures hono-stream] Skipping procedure "${p.name}": ${reason}`
529
- )
530
- }
531
- }
532
-
533
- procedures
534
- .filter(
535
- (p: { isStream?: boolean }): p is TStreamProcedureRegistration => p.isStream === true
536
- )
537
- .forEach((procedure: TStreamProcedureRegistration<any, RPCConfig>) => {
538
- const route = this.buildStreamHttpRouteDoc(procedure, mode, extendProcedureDoc)
539
-
540
- this._docs.push(route)
541
-
542
- const handler = this.createStreamHandler(procedure, factoryContext, mode)
543
-
544
- // Register both GET and POST handlers
545
- this._app.get(route.path, handler)
546
- this._app.post(route.path, handler)
547
- })
548
- })
549
-
550
- return this._app
551
- }
552
-
553
- /**
554
- * Generates the Stream HTTP route documentation for the given procedure.
555
- */
556
- private buildStreamHttpRouteDoc(
557
- procedure: TStreamProcedureRegistration<any, RPCConfig>,
558
- streamMode: StreamMode,
559
- extendProcedureDoc?: HonoStreamFactoryItem['extendProcedureDoc']
560
- ): StreamHttpRouteDoc {
561
- const { config } = procedure
562
- const path = HonoStreamAppBuilder.makeStreamHttpRoutePath({
563
- name: procedure.name,
564
- config,
565
- prefix: this.config?.pathPrefix,
566
- })
567
- // POST first so codegen (which uses `methods[0]`) defaults to POST. POST is
568
- // the canonical method for streaming procedures because it can carry a body
569
- // for params; GET is the supplementary method for query-string callers.
570
- const methods = ['post', 'get'] as const
571
- const jsonSchema: { params?: Record<string, unknown>; yieldType?: Record<string, unknown>; returnType?: Record<string, unknown> } = {}
572
-
573
- if (config.schema?.params) {
574
- jsonSchema.params = config.schema.params
575
- }
576
- if (streamMode === 'sse') {
577
- jsonSchema.yieldType = {
578
- type: 'object',
579
- description: 'SSE message envelope. The data field contains the procedure yield value.',
580
- required: ['data', 'event', 'id'],
581
- properties: {
582
- data: config.schema?.yieldType ?? {},
583
- event: { type: 'string' },
584
- id: { type: 'string' },
585
- retry: { type: 'number' },
586
- },
587
- }
588
- } else if (config.schema?.yieldType) {
589
- // Text mode: pass through as-is
590
- jsonSchema.yieldType = config.schema.yieldType
591
- }
592
- if (config.schema?.returnType) {
593
- jsonSchema.returnType = config.schema.returnType
594
- }
595
-
596
- const base: StreamHttpRouteDoc = {
597
- kind: 'stream',
598
- name: procedure.name,
599
- version: config.version,
600
- scope: config.scope,
601
- path,
602
- methods: [...methods],
603
- streamMode,
604
- jsonSchema,
605
- }
606
-
607
- if (config.errors && config.errors.length > 0) {
608
- base.errors = [...config.errors]
609
- }
610
-
611
- let extendedDoc: object = {}
612
-
613
- if (extendProcedureDoc) {
614
- extendedDoc = extendProcedureDoc({ base, procedure })
615
- }
616
-
617
- return {
618
- ...extendedDoc,
619
- ...base,
620
- }
621
- }
622
- }
@@ -1,20 +0,0 @@
1
- import { Context } from 'hono'
2
- import { TStreamProcedureRegistration } from '../../../index.js'
3
- import { ExtractConfig, ExtractContext } from '../../types.js'
4
-
5
- export type { StreamHttpRouteDoc } from '../../types.js'
6
- export type { StreamMode } from '../../types.js'
7
-
8
- import type { StreamHttpRouteDoc, StreamMode } from '../../types.js'
9
-
10
- export type HonoStreamFactoryItem<TFactory = any> = {
11
- factory: TFactory
12
- factoryContext:
13
- | ExtractContext<TFactory>
14
- | ((c: Context) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>)
15
- streamMode?: StreamMode
16
- extendProcedureDoc?: (params: {
17
- base: StreamHttpRouteDoc
18
- procedure: TStreamProcedureRegistration<any, ExtractConfig<TFactory>>
19
- }) => Record<string, any>
20
- }