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
@@ -44,7 +44,7 @@ function makeStreamAdapter(
44
44
  }),
45
45
  stream: vi.fn(async (_req: AdapterRequest): Promise<AdapterStreamResponse> => ({
46
46
  status: 200,
47
- headers: {},
47
+ headers: new Headers(),
48
48
  body: makeAsyncIterable(items ?? []),
49
49
  ...response,
50
50
  })),
@@ -265,7 +265,7 @@ describe('executeStream', () => {
265
265
  request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
266
266
  stream: vi.fn(async (req: AdapterRequest): Promise<AdapterStreamResponse> => {
267
267
  capturedHeaders.push(req.headers ?? {})
268
- return { status: 200, headers: {}, body: makeAsyncIterable([]) }
268
+ return { status: 200, headers: new Headers(), body: makeAsyncIterable([]) }
269
269
  }),
270
270
  }
271
271
 
@@ -287,7 +287,7 @@ describe('executeStream', () => {
287
287
  request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
288
288
  stream: vi.fn(async (): Promise<AdapterStreamResponse> => {
289
289
  order.push('adapter')
290
- return { status: 200, headers: {}, body: makeAsyncIterable([]) }
290
+ return { status: 200, headers: new Headers(), body: makeAsyncIterable([]) }
291
291
  }),
292
292
  }
293
293
  const hooks: ClientHooks = {
@@ -303,7 +303,7 @@ describe('executeStream', () => {
303
303
  request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
304
304
  stream: vi.fn(async (): Promise<AdapterStreamResponse> => ({
305
305
  status: 403,
306
- headers: {},
306
+ headers: new Headers(),
307
307
  body: makeAsyncIterable([]),
308
308
  })),
309
309
  }
@@ -368,7 +368,7 @@ describe('executeStream', () => {
368
368
  request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
369
369
  stream: vi.fn(async (req: AdapterRequest): Promise<AdapterStreamResponse> => {
370
370
  observedSignal = req.signal
371
- return { status: 200, headers: {}, body: makeAsyncIterable([]) }
371
+ return { status: 200, headers: new Headers(), body: makeAsyncIterable([]) }
372
372
  }),
373
373
  }
374
374
 
@@ -406,7 +406,7 @@ describe('executeStream', () => {
406
406
  request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
407
407
  stream: vi.fn(async (req: AdapterRequest): Promise<AdapterStreamResponse> => {
408
408
  capturedUrls.push(req.url)
409
- return { status: 200, headers: {}, body: makeAsyncIterable([]) }
409
+ return { status: 200, headers: new Headers(), body: makeAsyncIterable([]) }
410
410
  }),
411
411
  }
412
412
 
@@ -427,13 +427,45 @@ describe('executeStream', () => {
427
427
  request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
428
428
  stream: vi.fn(async (req: AdapterRequest): Promise<AdapterStreamResponse> => {
429
429
  observedMeta = req.meta
430
- return { status: 200, headers: {}, body: makeAsyncIterable([]) }
430
+ return { status: 200, headers: new Headers(), body: makeAsyncIterable([]) }
431
431
  }),
432
432
  }
433
433
 
434
434
  await run({ adapter, options: { meta: { traceId: 'stream-trace' } as never } })
435
435
  expect(observedMeta).toEqual({ traceId: 'stream-trace' })
436
436
  })
437
+
438
+ // ── TypedStream.headers (responseHeadersDeclared) ──
439
+
440
+ it('TypedStream.headers is populated when route declares res.headers', async () => {
441
+ const responseHeaders = new Headers({ 'x-stream-id': 'stream-abc' })
442
+ const adapter: ClientAdapter = {
443
+ request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
444
+ stream: vi.fn(async (): Promise<AdapterStreamResponse> => ({
445
+ status: 200,
446
+ headers: responseHeaders,
447
+ body: makeAsyncIterable([]),
448
+ })),
449
+ }
450
+
451
+ const stream = await run<string, void>({
452
+ adapter,
453
+ descriptor: makeDescriptor({ responseHeadersDeclared: true }),
454
+ })
455
+ for await (const _ of stream) { /* drain */ }
456
+
457
+ expect(stream.headers).toBe(responseHeaders)
458
+ expect(stream.headers?.get('x-stream-id')).toBe('stream-abc')
459
+ })
460
+
461
+ it('TypedStream.headers is undefined when route does not declare res.headers', async () => {
462
+ const adapter = makeStreamAdapter()
463
+
464
+ const stream = await run<string, void>({ adapter })
465
+ for await (const _ of stream) { /* drain */ }
466
+
467
+ expect(stream.headers).toBeUndefined()
468
+ })
437
469
  })
438
470
 
439
471
  // ── executeStream classifier integration ──────────────────
@@ -225,12 +225,18 @@ export async function executeStream<TYield, TReturn = void>(
225
225
  throw finalError
226
226
  }
227
227
 
228
+ // Convert the platform Headers object to a plain record for the hooks/error
229
+ // context (AdapterResponse.headers is Record<string, string>). The raw
230
+ // platform Headers object is kept separately for TypedStream.headers.
231
+ const headersRecord: Record<string, string> = {}
232
+ streamResponse.headers.forEach((value, key) => { headersRecord[key] = value })
233
+
228
234
  // Build an AdapterResponse shape for the hooks. For success the body is null
229
235
  // (the actual data flows through the async iterable); for non-2xx the adapter
230
236
  // eagerly parses the JSON response body and surfaces it via `errorBody`.
231
237
  const responseForHooks: AdapterResponse = {
232
238
  status: streamResponse.status,
233
- headers: streamResponse.headers,
239
+ headers: headersRecord,
234
240
  body: streamResponse.errorBody ?? null,
235
241
  }
236
242
 
@@ -261,10 +267,18 @@ export async function executeStream<TYield, TReturn = void>(
261
267
  // 8. Return the typed stream — pass the registry/descriptor through so
262
268
  // mid-stream `event: 'error'` items dispatch through the same registry as
263
269
  // RPC/API calls.
264
- return createTypedStream<TYield, TReturn>(streamResponse.body, descriptor.streamMode, {
270
+ const typedStream = createTypedStream<TYield, TReturn>(streamResponse.body, descriptor.streamMode, {
265
271
  errorRegistry,
266
272
  errorStatus: responseForHooks.status,
267
273
  procedureName: descriptor.name,
268
274
  scope: descriptor.scope,
269
275
  })
276
+
277
+ // Wire the initial response headers onto the stream when the route declares
278
+ // res.headers. The platform Headers object is passed directly from the adapter.
279
+ if (descriptor.responseHeadersDeclared) {
280
+ typedStream.headers = streamResponse.headers
281
+ }
282
+
283
+ return typedStream
270
284
  }
@@ -3,7 +3,7 @@ import { describe, expect, test } from 'vitest'
3
3
  import { Type } from 'typebox'
4
4
  import { Procedures } from '../index.js'
5
5
  import { APIConfig } from '../implementations/types.js'
6
- import { HonoAPIAppBuilder, defineErrorTaxonomy } from '../implementations/http/hono-api/index.js'
6
+ import { HonoAppBuilder, defineErrorTaxonomy } from '../implementations/http/hono/index.js'
7
7
  import { createClient } from './index.js'
8
8
  import { ClientRequestError } from './errors.js'
9
9
  import type { ClientAdapter, ErrorRegistry } from './types.js'
@@ -110,102 +110,12 @@ function honoAdapter(app: HonoAppLike): ClientAdapter {
110
110
  // ---------------------------------------------------------------------------
111
111
 
112
112
  describe('typed error dispatch — end-to-end', () => {
113
- function buildApp() {
114
- const API = Procedures<{}, APIConfig>()
115
- API.Create(
116
- 'GetUser',
117
- {
118
- path: '/users/:id',
119
- method: 'get',
120
- schema: {
121
- input: { pathParams: Type.Object({ id: Type.String() }) },
122
- returnType: Type.Object({ id: Type.String() }),
123
- },
124
- },
125
- async (_ctx, { pathParams }) => {
126
- if (pathParams.id === 'missing') {
127
- throw new UseCaseError('User not found', `no user with id=${pathParams.id}`)
128
- }
129
- return { id: pathParams.id }
130
- }
131
- )
132
- return new HonoAPIAppBuilder({ errors: appErrors }).register(API, () => ({})).build()
133
- }
134
-
135
- test('server-thrown UseCaseError arrives on client as a typed class instance', async () => {
136
- const app = buildApp()
137
- const api = createClient({
138
- adapter: honoAdapter(app),
139
- basePath: '',
140
- errorRegistry,
141
- scopes: (client) => ({
142
- getUser: (id: string) =>
143
- client.call<{ id: string }>({
144
- name: 'GetUser',
145
- scope: 'users',
146
- path: '/users/:id',
147
- method: 'get',
148
- kind: 'api',
149
- params: { pathParams: { id } },
150
- }),
151
- }),
152
- })
153
-
154
- await expect(api.getUser('missing')).rejects.toBeInstanceOf(ApiUseCaseError)
155
- try {
156
- await api.getUser('missing')
157
- } catch (err) {
158
- expect(err).toBeInstanceOf(ApiUseCaseError)
159
- expect(err).toBeInstanceOf(Error)
160
- expect((err as ApiUseCaseError).status).toBe(422)
161
- expect((err as ApiUseCaseError).procedureName).toBe('GetUser')
162
- expect((err as ApiUseCaseError).message).toBe('User not found')
163
- expect((err as ApiUseCaseError).body.name).toBe('UseCaseError')
164
- }
165
- })
113
+ // migrated to schema.req in Phase 3+ (CreateHttp)
114
+ test.todo('server-thrown UseCaseError arrives on client as a typed class instance')
166
115
 
167
- test('unregistered error body falls back to ClientRequestError', async () => {
168
- const app = buildApp()
169
- // Omit the registry so dispatch can't match; client sees the raw
170
- // transport error instead of a typed class.
171
- const api = createClient({
172
- adapter: honoAdapter(app),
173
- basePath: '',
174
- scopes: (client) => ({
175
- getUser: (id: string) =>
176
- client.call<{ id: string }>({
177
- name: 'GetUser',
178
- scope: 'users',
179
- path: '/users/:id',
180
- method: 'get',
181
- kind: 'api',
182
- params: { pathParams: { id } },
183
- }),
184
- }),
185
- })
186
-
187
- await expect(api.getUser('missing')).rejects.toBeInstanceOf(ClientRequestError)
188
- })
189
-
190
- test('success responses are not disturbed by dispatch logic', async () => {
191
- const app = buildApp()
192
- const api = createClient({
193
- adapter: honoAdapter(app),
194
- basePath: '',
195
- errorRegistry,
196
- scopes: (client) => ({
197
- getUser: (id: string) =>
198
- client.call<{ id: string }>({
199
- name: 'GetUser',
200
- scope: 'users',
201
- path: '/users/:id',
202
- method: 'get',
203
- kind: 'api',
204
- params: { pathParams: { id } },
205
- }),
206
- }),
207
- })
116
+ // migrated to schema.req in Phase 3+ (CreateHttp)
117
+ test.todo('unregistered error body falls back to ClientRequestError')
208
118
 
209
- await expect(api.getUser('u_42')).resolves.toEqual({ id: 'u_42' })
210
- })
119
+ // migrated to schema.req in Phase 3+ (CreateHttp)
120
+ test.todo('success responses are not disturbed by dispatch logic')
211
121
  })
@@ -99,7 +99,11 @@ export interface AdapterResponse {
99
99
 
100
100
  export interface AdapterStreamResponse {
101
101
  status: number
102
- headers: Record<string, string>
102
+ /**
103
+ * Platform `Headers` object populated by the adapter. Passed through to
104
+ * `TypedStream.headers` when the route declares `res.headers`.
105
+ */
106
+ headers: Headers
103
107
  body: AsyncIterable<unknown>
104
108
  /**
105
109
  * Populated when `status` is non-2xx — the parsed response body. Surfaced so
@@ -144,12 +148,19 @@ export interface CallDescriptor {
144
148
  scope: string
145
149
  path: string
146
150
  method: string
147
- kind: 'rpc' | 'api' | 'stream'
151
+ kind: 'rpc' | 'api' | 'stream' | 'http-stream'
148
152
  params: unknown
153
+ /**
154
+ * Set by codegen when the route declares `schema.res.headers`. Drives
155
+ * whether `executeCall` returns bare body (`undefined`/false) or a
156
+ * `{ body, headers }` envelope (`true`). Mirrors the `HttpReturn<TRes>`
157
+ * conditional return shape on the server side.
158
+ */
159
+ responseHeadersDeclared?: boolean
149
160
  }
150
161
 
151
162
  export interface StreamDescriptor extends CallDescriptor {
152
- kind: 'stream'
163
+ kind: 'stream' | 'http-stream'
153
164
  streamMode: 'sse' | 'text'
154
165
  }
155
166
 
@@ -163,6 +174,13 @@ export interface TypedStream<TYield, TReturn = void> extends AsyncIterable<TYiel
163
174
  * since resolution depends on the async generator running to completion.
164
175
  */
165
176
  result: Promise<TReturn>
177
+ /**
178
+ * Platform `Headers` from the initial stream response. Populated only when
179
+ * the route declares `res.headers` (i.e. `responseHeadersDeclared: true` on
180
+ * the descriptor). Undefined otherwise — consumers without declared response
181
+ * headers should not rely on this field.
182
+ */
183
+ headers?: Headers
166
184
  }
167
185
 
168
186
  // ── Request Options ──────────────────────────────────────
@@ -9,30 +9,32 @@
9
9
  "scope": "users",
10
10
  "method": "GET",
11
11
  "fullPath": "/users/:id",
12
- "schema": {
13
- "input": {
12
+ "jsonSchema": {
13
+ "req": {
14
14
  "pathParams": {
15
15
  "type": "object",
16
16
  "properties": { "id": { "type": "string" } },
17
17
  "required": ["id"]
18
18
  }
19
19
  },
20
- "returnType": {
21
- "type": "object",
22
- "properties": {
23
- "id": { "type": "string" },
24
- "name": { "type": "string" },
25
- "created-at": { "type": "string", "format": "date-time" },
26
- "address": {
27
- "type": "object",
28
- "properties": {
29
- "street": { "type": "string" },
30
- "city": { "type": "string" }
31
- },
32
- "required": ["street", "city"]
33
- }
34
- },
35
- "required": ["id", "name", "created-at", "address"]
20
+ "res": {
21
+ "body": {
22
+ "type": "object",
23
+ "properties": {
24
+ "id": { "type": "string" },
25
+ "name": { "type": "string" },
26
+ "created-at": { "type": "string", "format": "date-time" },
27
+ "address": {
28
+ "type": "object",
29
+ "properties": {
30
+ "street": { "type": "string" },
31
+ "city": { "type": "string" }
32
+ },
33
+ "required": ["street", "city"]
34
+ }
35
+ },
36
+ "required": ["id", "name", "created-at", "address"]
37
+ }
36
38
  }
37
39
  },
38
40
  "errors": ["NotFound"]
@@ -43,8 +45,8 @@
43
45
  "scope": "users",
44
46
  "method": "POST",
45
47
  "fullPath": "/users",
46
- "schema": {
47
- "input": {
48
+ "jsonSchema": {
49
+ "req": {
48
50
  "body": {
49
51
  "oneOf": [
50
52
  {
@@ -67,10 +69,12 @@
67
69
  ]
68
70
  }
69
71
  },
70
- "returnType": {
71
- "type": "object",
72
- "properties": { "id": { "type": "string" } },
73
- "required": ["id"]
72
+ "res": {
73
+ "body": {
74
+ "type": "object",
75
+ "properties": { "id": { "type": "string" } },
76
+ "required": ["id"]
77
+ }
74
78
  }
75
79
  },
76
80
  "errors": ["ValidationError"]
@@ -81,8 +85,8 @@
81
85
  "scope": "users",
82
86
  "method": "GET",
83
87
  "fullPath": "/users",
84
- "schema": {
85
- "input": {
88
+ "jsonSchema": {
89
+ "req": {
86
90
  "query": {
87
91
  "type": "object",
88
92
  "properties": {
@@ -91,22 +95,99 @@
91
95
  }
92
96
  }
93
97
  },
94
- "returnType": {
95
- "type": "object",
96
- "properties": {
97
- "items": {
98
- "type": "array",
98
+ "res": {
99
+ "body": {
100
+ "type": "object",
101
+ "properties": {
99
102
  "items": {
100
- "type": "object",
101
- "properties": {
102
- "id": { "type": "string" },
103
- "name": { "type": "string" }
104
- },
105
- "required": ["id", "name"]
103
+ "type": "array",
104
+ "items": {
105
+ "type": "object",
106
+ "properties": {
107
+ "id": { "type": "string" },
108
+ "name": { "type": "string" }
109
+ },
110
+ "required": ["id", "name"]
111
+ }
106
112
  }
113
+ },
114
+ "required": ["items"]
115
+ }
116
+ }
117
+ },
118
+ "errors": []
119
+ },
120
+ {
121
+ "kind": "api",
122
+ "name": "DownloadUser",
123
+ "scope": "users",
124
+ "method": "GET",
125
+ "fullPath": "/users/:id/download",
126
+ "jsonSchema": {
127
+ "req": {
128
+ "pathParams": {
129
+ "type": "object",
130
+ "properties": { "id": { "type": "string" } },
131
+ "required": ["id"]
132
+ }
133
+ },
134
+ "res": {
135
+ "body": {
136
+ "type": "object",
137
+ "properties": { "url": { "type": "string" } },
138
+ "required": ["url"]
139
+ },
140
+ "headers": {
141
+ "type": "object",
142
+ "properties": {
143
+ "x-download-token": { "type": "string" },
144
+ "content-disposition": { "type": "string" }
145
+ },
146
+ "required": ["x-download-token"]
147
+ }
148
+ }
149
+ },
150
+ "errors": []
151
+ },
152
+ {
153
+ "kind": "http-stream",
154
+ "name": "WatchUsers",
155
+ "scope": "users",
156
+ "method": "GET",
157
+ "fullPath": "/users/watch",
158
+ "streamMode": "sse",
159
+ "jsonSchema": {
160
+ "req": {
161
+ "query": {
162
+ "type": "object",
163
+ "properties": {
164
+ "status": { "type": "string", "enum": ["active", "inactive"] }
107
165
  }
166
+ }
167
+ },
168
+ "res": {
169
+ "headers": {
170
+ "type": "object",
171
+ "properties": {
172
+ "x-stream-id": { "type": "string" }
173
+ },
174
+ "required": ["x-stream-id"]
175
+ }
176
+ },
177
+ "yield": {
178
+ "type": "object",
179
+ "properties": {
180
+ "id": { "type": "string" },
181
+ "event": { "type": "string", "enum": ["created", "updated", "deleted"] }
182
+ },
183
+ "required": ["id", "event"]
184
+ },
185
+ "returnType": {
186
+ "type": "object",
187
+ "properties": {
188
+ "count": { "type": "integer" }
108
189
  },
109
- "required": ["items"]
190
+ "required": ["count"]
110
191
  }
111
192
  },
112
193
  "errors": []