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
@@ -0,0 +1,565 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { describe, expect, it, test } from 'vitest'
3
+ import { Procedures } from './index.js'
4
+ import { v } from 'suretype'
5
+ import { Type } from 'typebox'
6
+ import { ProcedureError, ProcedureValidationError } from './errors.js'
7
+
8
+ describe('Streaming Procedures - CreateStream', () => {
9
+ test('CreateStream creates a streaming procedure', async () => {
10
+ const { CreateStream } = Procedures()
11
+
12
+ const { StreamTest, procedure, info } = CreateStream(
13
+ 'StreamTest',
14
+ {
15
+ description: 'Test streaming procedure',
16
+ schema: {
17
+ params: v.object({ count: v.number().required() }),
18
+ yieldType: v.object({ value: v.number().required() }),
19
+ },
20
+ },
21
+ async function* (ctx, params) {
22
+ for (let i = 0; i < params.count; i++) {
23
+ yield { value: i }
24
+ }
25
+ }
26
+ )
27
+
28
+ expect(StreamTest).toBeDefined()
29
+ expect(procedure).toBeDefined()
30
+ expect(info.isStream).toBe(true)
31
+ expect(info.name).toBe('StreamTest')
32
+ expect(info.description).toBe('Test streaming procedure')
33
+ expect(info.schema.params).toEqual({
34
+ type: 'object',
35
+ properties: { count: { type: 'number' } },
36
+ required: ['count'],
37
+ })
38
+ expect(info.schema.yieldType).toEqual({
39
+ type: 'object',
40
+ properties: { value: { type: 'number' } },
41
+ required: ['value'],
42
+ })
43
+
44
+ // Collect yielded values
45
+ const values: { value: number }[] = []
46
+ for await (const val of StreamTest({}, { count: 3 })) {
47
+ values.push(val)
48
+ }
49
+
50
+ expect(values).toEqual([{ value: 0 }, { value: 1 }, { value: 2 }])
51
+ })
52
+
53
+ test('CreateStream validates params', async () => {
54
+ const { CreateStream } = Procedures()
55
+
56
+ const { StreamWithParams } = CreateStream(
57
+ 'StreamWithParams',
58
+ {
59
+ schema: {
60
+ params: v.object({ name: v.string().required() }),
61
+ yieldType: v.string(),
62
+ },
63
+ },
64
+ async function* (ctx, params) {
65
+ yield params.name
66
+ }
67
+ )
68
+
69
+ // Missing required param should throw ProcedureValidationError
70
+ try {
71
+ // @ts-expect-error - intentionally passing invalid params
72
+ for await (const _val of StreamWithParams({}, {})) {
73
+ // Should not reach here
74
+ }
75
+ expect.fail('Should have thrown')
76
+ } catch (e: any) {
77
+ expect(e).toBeInstanceOf(ProcedureValidationError)
78
+ expect(e.message).toContain('Validation error for StreamWithParams')
79
+ }
80
+ })
81
+
82
+ test('CreateStream with validateYields validates each yielded value', async () => {
83
+ const { CreateStream } = Procedures()
84
+ const { ProcedureYieldValidationError } = await import('./errors.js')
85
+
86
+ const { StreamValidateYields } = CreateStream(
87
+ 'StreamValidateYields',
88
+ {
89
+ schema: {
90
+ yieldType: v.object({ id: v.number().required() }),
91
+ },
92
+ validateYields: true,
93
+ },
94
+ async function* () {
95
+ yield { id: 1 } // Valid
96
+ yield { id: 'not-a-number' } as any // Invalid - should throw
97
+ }
98
+ )
99
+
100
+ const values: any[] = []
101
+ try {
102
+ for await (const val of StreamValidateYields({}, {})) {
103
+ values.push(val)
104
+ }
105
+ expect.fail('Should have thrown on invalid yield')
106
+ } catch (e: any) {
107
+ expect(e).toBeInstanceOf(ProcedureYieldValidationError)
108
+ expect(e.message).toContain('Yield validation error for StreamValidateYields')
109
+ expect(values).toEqual([{ id: 1 }]) // First value was valid
110
+ }
111
+ })
112
+
113
+ test('CreateStream without validateYields does not validate yields', async () => {
114
+ const { CreateStream } = Procedures()
115
+
116
+ const { StreamNoValidate } = CreateStream(
117
+ 'StreamNoValidate',
118
+ {
119
+ schema: {
120
+ yieldType: v.object({ id: v.number().required() }),
121
+ },
122
+ // validateYields defaults to false
123
+ },
124
+ async function* () {
125
+ yield { id: 1 }
126
+ yield { id: 'not-a-number' } as any // Invalid but won't throw
127
+ }
128
+ )
129
+
130
+ const values: any[] = []
131
+ for await (const val of StreamNoValidate({}, {})) {
132
+ values.push(val)
133
+ }
134
+
135
+ expect(values).toEqual([{ id: 1 }, { id: 'not-a-number' }])
136
+ })
137
+
138
+ test('CreateStream ctx.error throws ProcedureError', async () => {
139
+ const { CreateStream } = Procedures()
140
+
141
+ const { StreamError } = CreateStream('StreamError', {}, async function* (ctx) {
142
+ yield 'first'
143
+ throw ctx.error('Custom stream error', { code: 'STREAM_ERR' })
144
+ })
145
+
146
+ const values: any[] = []
147
+ try {
148
+ for await (const val of StreamError({}, {})) {
149
+ values.push(val)
150
+ }
151
+ expect.fail('Should have thrown')
152
+ } catch (e: any) {
153
+ expect(e).toBeInstanceOf(ProcedureError)
154
+ expect(e.message).toBe('Custom stream error')
155
+ expect(e.procedureName).toBe('StreamError')
156
+ expect(e.meta).toEqual({ code: 'STREAM_ERR' })
157
+ expect(values).toEqual(['first'])
158
+ }
159
+ })
160
+
161
+ test('CreateStream provides ctx.signal for abort handling', async () => {
162
+ const { CreateStream } = Procedures()
163
+ let signalReceived = false
164
+
165
+ const { StreamWithSignal } = CreateStream('StreamWithSignal', {}, async function* (ctx) {
166
+ expect(ctx.signal).toBeDefined()
167
+ expect(ctx.signal).toBeInstanceOf(AbortSignal)
168
+ signalReceived = true
169
+ yield 'value'
170
+ })
171
+
172
+ for await (const _val of StreamWithSignal({}, {})) {
173
+ // consume
174
+ }
175
+
176
+ expect(signalReceived).toBe(true)
177
+ })
178
+
179
+ test('CreateStream onCreate callback receives isStream flag', () =>
180
+ new Promise<void>((done) => {
181
+ let receivedProc: any
182
+
183
+ const { CreateStream } = Procedures({
184
+ onCreate: (proc) => {
185
+ receivedProc = proc
186
+ },
187
+ })
188
+
189
+ CreateStream('OnCreateStream', {}, async function* () {
190
+ yield 'test'
191
+ })
192
+
193
+ expect(receivedProc).toBeDefined()
194
+ expect(receivedProc.isStream).toBe(true)
195
+ expect(receivedProc.name).toBe('OnCreateStream')
196
+ done()
197
+ }))
198
+
199
+ test('CreateStream duplicate registration throws', () => {
200
+ const { CreateStream } = Procedures()
201
+
202
+ CreateStream('DuplicateStream', {}, async function* () {
203
+ yield 'first'
204
+ })
205
+
206
+ expect(() => {
207
+ CreateStream('DuplicateStream', {}, async function* () {
208
+ yield 'second'
209
+ })
210
+ }).toThrow('Procedure with name DuplicateStream is already registered')
211
+ })
212
+
213
+ test('CreateStream error includes definition location', async () => {
214
+ const { CreateStream } = Procedures()
215
+
216
+ const { StreamErrorLocation } = CreateStream(
217
+ 'StreamErrorLocation',
218
+ {
219
+ schema: {
220
+ params: v.object({ id: v.number().required() }),
221
+ },
222
+ },
223
+ async function* () {
224
+ yield 'test'
225
+ }
226
+ )
227
+
228
+ try {
229
+ // @ts-expect-error - intentionally passing invalid params
230
+ for await (const _val of StreamErrorLocation({}, {})) {
231
+ // consume
232
+ }
233
+ expect.fail('Should have thrown')
234
+ } catch (e: any) {
235
+ expect(e.definedAt).toBeDefined()
236
+ expect(e.definedAt.file).toContain('create-stream.test.ts')
237
+ expect(e.stack).toContain('--- Procedure "StreamErrorLocation" defined at ---')
238
+ }
239
+ })
240
+
241
+ test('CreateStream with Typebox schema', async () => {
242
+ const { CreateStream } = Procedures()
243
+
244
+ const { TypeboxStream } = CreateStream(
245
+ 'TypeboxStream',
246
+ {
247
+ schema: {
248
+ params: Type.Object({ limit: Type.Number() }),
249
+ yieldType: Type.Object({ data: Type.String() }),
250
+ },
251
+ },
252
+ async function* (ctx, params) {
253
+ for (let i = 0; i < params.limit; i++) {
254
+ yield { data: `item-${i}` }
255
+ }
256
+ }
257
+ )
258
+
259
+ const values: any[] = []
260
+ for await (const val of TypeboxStream({}, { limit: 2 })) {
261
+ values.push(val)
262
+ }
263
+
264
+ expect(values).toEqual([{ data: 'item-0' }, { data: 'item-1' }])
265
+ })
266
+
267
+ test('CreateStream with context type', async () => {
268
+ interface StreamContext {
269
+ userId: string
270
+ }
271
+
272
+ const { CreateStream } = Procedures<StreamContext>()
273
+
274
+ const { ContextStream } = CreateStream('ContextStream', {}, async function* (ctx) {
275
+ // ctx should have both userId and error
276
+ expect(ctx.userId).toBe('user-123')
277
+ expect(ctx.error).toBeDefined()
278
+ expect(ctx.signal).toBeDefined()
279
+ yield ctx.userId
280
+ })
281
+
282
+ const values: any[] = []
283
+ for await (const val of ContextStream({ userId: 'user-123' }, {})) {
284
+ values.push(val)
285
+ }
286
+
287
+ expect(values).toEqual(['user-123'])
288
+ })
289
+
290
+ test('CreateStream rethrows the original error preserving class identity', async () => {
291
+ // The streaming wrapper must NOT box user errors inside ProcedureError —
292
+ // doing so would defeat route-declared typed-error dispatch on the client
293
+ // (the HTTP builder's taxonomy would see `ProcedureError` instead of the
294
+ // user's class). Stack annotation is added in place; class identity and
295
+ // custom properties are preserved.
296
+ class MyDomainError extends Error {
297
+ readonly name = 'MyDomainError'
298
+ readonly code = 'STREAM_FAIL'
299
+ }
300
+
301
+ const { CreateStream } = Procedures()
302
+ const originalError = new MyDomainError('Stream underlying error')
303
+
304
+ const { StreamCause } = CreateStream(
305
+ 'StreamCause',
306
+ {},
307
+ // eslint-disable-next-line require-yield
308
+ async function* () {
309
+ throw originalError
310
+ }
311
+ )
312
+
313
+ try {
314
+ for await (const _val of StreamCause({}, {})) {
315
+ // consume
316
+ }
317
+ expect.fail('Should have thrown')
318
+ } catch (e: any) {
319
+ expect(e).toBe(originalError)
320
+ expect(e).toBeInstanceOf(MyDomainError)
321
+ expect(e.code).toBe('STREAM_FAIL')
322
+ }
323
+ })
324
+
325
+ test('CreateStream propagates .return() to the user generator', async () => {
326
+ // Consumers that close a stream early (via `iterator.return()` or breaking
327
+ // out of for-await) must trigger the user generator's `finally` block so
328
+ // cleanup (db handles, subscriptions, signal-driven teardown) runs.
329
+ const { CreateStream } = Procedures()
330
+ let finallyRan = false
331
+
332
+ const { EarlyClose } = CreateStream(
333
+ 'EarlyClose',
334
+ {},
335
+ async function* () {
336
+ try {
337
+ yield 1
338
+ yield 2
339
+ yield 3
340
+ } finally {
341
+ finallyRan = true
342
+ }
343
+ }
344
+ )
345
+
346
+ const iter = EarlyClose({}, {})
347
+ const first = await iter.next()
348
+ expect(first.value).toBe(1)
349
+ await iter.return!(undefined)
350
+ expect(finallyRan).toBe(true)
351
+ })
352
+
353
+ test('CreateStream with extended config', () => {
354
+ interface ExtConfig {
355
+ scope: string
356
+ version: number
357
+ }
358
+
359
+ const { CreateStream } = Procedures<unknown, ExtConfig>()
360
+
361
+ const { info } = CreateStream(
362
+ 'ExtendedStream',
363
+ {
364
+ scope: 'api',
365
+ version: 1,
366
+ description: 'Extended config stream',
367
+ },
368
+ async function* () {
369
+ yield 'data'
370
+ }
371
+ )
372
+
373
+ expect(info.scope).toBe('api')
374
+ expect(info.version).toBe(1)
375
+ expect(info.description).toBe('Extended config stream')
376
+ expect(info.isStream).toBe(true)
377
+ })
378
+
379
+ test('CreateStream signal.aborted becomes true after generator completes', async () => {
380
+ const { CreateStream } = Procedures()
381
+ let capturedSignal: AbortSignal | null = null
382
+
383
+ const { AbortStream } = CreateStream('AbortStream', {}, async function* (ctx) {
384
+ capturedSignal = ctx.signal
385
+ yield 'value'
386
+ })
387
+
388
+ // Consume the generator
389
+ for await (const _val of AbortStream({}, {})) {
390
+ // consume
391
+ }
392
+
393
+ // After generator completes, signal should be aborted
394
+ expect(capturedSignal).not.toBeNull()
395
+ expect(capturedSignal!.aborted).toBe(true)
396
+ })
397
+
398
+ test('CreateStream signal.reason is stream-completed after normal completion', async () => {
399
+ const { CreateStream } = Procedures()
400
+ let capturedSignal: AbortSignal | null = null
401
+
402
+ const { ReasonStream } = CreateStream('ReasonStream', {}, async function* (ctx) {
403
+ capturedSignal = ctx.signal
404
+ yield 'value'
405
+ })
406
+
407
+ for await (const _val of ReasonStream({}, {})) {
408
+ // consume
409
+ }
410
+
411
+ expect(capturedSignal!.reason).toBe('stream-completed')
412
+ })
413
+
414
+ test('CreateStream combines external signal with internal signal', async () => {
415
+ const { CreateStream } = Procedures<{ signal: AbortSignal }>()
416
+ const externalAc = new AbortController()
417
+ let capturedSignal: AbortSignal | null = null
418
+
419
+ const { CombinedStream } = CreateStream('CombinedStream', {}, async function* (ctx) {
420
+ capturedSignal = ctx.signal
421
+ // Combined signal is a new object, not the raw external signal
422
+ expect(ctx.signal).not.toBe(externalAc.signal)
423
+ yield 'value'
424
+ })
425
+
426
+ // Abort external before consuming — combined signal should reflect it
427
+ externalAc.abort('client-disconnected')
428
+
429
+ for await (const _val of CombinedStream({ signal: externalAc.signal }, {})) {
430
+ // consume
431
+ }
432
+
433
+ expect(capturedSignal!.aborted).toBe(true)
434
+ // Reason comes from external abort, not internal 'stream-completed'
435
+ expect(capturedSignal!.reason).toBe('client-disconnected')
436
+ })
437
+ })
438
+
439
+ describe('isPrevalidated context property', () => {
440
+ test('CreateStream skips validation when ctx.isPrevalidated is true', async () => {
441
+ const { CreateStream } = Procedures()
442
+
443
+ const { StreamSkipValidation } = CreateStream(
444
+ 'StreamSkipValidation',
445
+ {
446
+ schema: {
447
+ params: v.object({ count: v.number().required() }),
448
+ },
449
+ },
450
+ async function* (ctx, params) {
451
+ yield { received: params }
452
+ }
453
+ )
454
+
455
+ // With isPrevalidated: true, validation is skipped even with invalid params
456
+ const values: any[] = []
457
+ for await (const val of StreamSkipValidation({ isPrevalidated: true }, {} as any)) {
458
+ values.push(val)
459
+ }
460
+
461
+ expect(values).toEqual([{ received: {} }])
462
+ })
463
+
464
+ test('CreateStream validates when ctx.isPrevalidated is false', async () => {
465
+ const { CreateStream } = Procedures()
466
+
467
+ const { StreamValidate } = CreateStream(
468
+ 'StreamValidate',
469
+ {
470
+ schema: {
471
+ params: v.object({ count: v.number().required() }),
472
+ },
473
+ },
474
+ async function* (ctx, params) {
475
+ yield { received: params }
476
+ }
477
+ )
478
+
479
+ // With isPrevalidated: false, validation should run
480
+ try {
481
+ for await (const _val of StreamValidate({ isPrevalidated: false }, {} as any)) {
482
+ // consume
483
+ }
484
+ expect.fail('Should have thrown validation error')
485
+ } catch (e: any) {
486
+ expect(e).toBeInstanceOf(ProcedureValidationError)
487
+ }
488
+ })
489
+
490
+ test('CreateStream validates when ctx.isPrevalidated is undefined', async () => {
491
+ const { CreateStream } = Procedures()
492
+
493
+ const { StreamValidateUndefined } = CreateStream(
494
+ 'StreamValidateUndefined',
495
+ {
496
+ schema: {
497
+ params: v.object({ value: v.string().required() }),
498
+ },
499
+ },
500
+ async function* (ctx, params) {
501
+ yield params
502
+ }
503
+ )
504
+
505
+ // Without isPrevalidated property, validation should run
506
+ try {
507
+ for await (const _val of StreamValidateUndefined({}, {} as any)) {
508
+ // consume
509
+ }
510
+ expect.fail('Should have thrown validation error')
511
+ } catch (e: any) {
512
+ expect(e).toBeInstanceOf(ProcedureValidationError)
513
+ }
514
+ })
515
+ })
516
+
517
+ describe('builder.config.noRuntimeValidation', () => {
518
+ test('CreateStream skips params validation when noRuntimeValidation is true', async () => {
519
+ const { CreateStream } = Procedures({ config: { noRuntimeValidation: true } })
520
+
521
+ const { StreamSkipParams } = CreateStream(
522
+ 'StreamSkipParams',
523
+ {
524
+ schema: {
525
+ params: Type.Object({ count: Type.Number() }),
526
+ },
527
+ },
528
+ async function* (_ctx, params) {
529
+ yield { received: params }
530
+ }
531
+ )
532
+
533
+ const values: any[] = []
534
+ for await (const val of StreamSkipParams({}, {} as any)) {
535
+ values.push(val)
536
+ }
537
+
538
+ expect(values).toEqual([{ received: {} }])
539
+ })
540
+
541
+ // migrated to schema.req in Phase 3+ (CreateHttpStream)
542
+ test.todo('CreateStream skips input channel validation when noRuntimeValidation is true')
543
+
544
+ test('CreateStream still validates when noRuntimeValidation is omitted', async () => {
545
+ const { CreateStream } = Procedures({ config: {} })
546
+
547
+ const { StreamValidateDefault } = CreateStream(
548
+ 'StreamValidateDefault',
549
+ {
550
+ schema: {
551
+ params: Type.Object({ count: Type.Number() }),
552
+ },
553
+ },
554
+ async function* (_ctx, params) {
555
+ yield params
556
+ }
557
+ )
558
+
559
+ await expect(async () => {
560
+ for await (const _v of StreamValidateDefault({}, {} as any)) {
561
+ // consume
562
+ }
563
+ }).rejects.toBeInstanceOf(ProcedureValidationError)
564
+ })
565
+ })