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
@@ -46,20 +46,24 @@ const apiGroup: ScopeGroup = {
46
46
  fullPath: '/api/posts/:id',
47
47
  scope: 'posts',
48
48
  jsonSchema: {
49
- pathParams: {
50
- type: 'object',
51
- properties: { id: { type: 'string' } },
52
- required: ['id'],
53
- },
54
- body: {
55
- type: 'object',
56
- properties: { title: { type: 'string' } },
57
- required: ['title'],
49
+ req: {
50
+ pathParams: {
51
+ type: 'object',
52
+ properties: { id: { type: 'string' } },
53
+ required: ['id'],
54
+ },
55
+ body: {
56
+ type: 'object',
57
+ properties: { title: { type: 'string' } },
58
+ required: ['title'],
59
+ },
58
60
  },
59
- response: {
60
- type: 'object',
61
- properties: { id: { type: 'string' }, title: { type: 'string' } },
62
- required: ['id', 'title'],
61
+ res: {
62
+ body: {
63
+ type: 'object',
64
+ properties: { id: { type: 'string' }, title: { type: 'string' } },
65
+ required: ['id', 'title'],
66
+ },
63
67
  },
64
68
  },
65
69
  } satisfies APIHttpRouteDoc,
@@ -305,22 +309,22 @@ describe('emitScopeFile', () => {
305
309
  })
306
310
 
307
311
  describe('API scope', () => {
308
- it('generates PascalCase channel types', async () => {
312
+ it('generates Req-prefixed channel types in flat mode', async () => {
309
313
  const output = await emitScopeFile(apiGroup)
310
- expect(output).toContain('export type UpdatePostPathParams')
311
- expect(output).toContain('export type UpdatePostBody')
314
+ expect(output).toContain('export type UpdatePostReqPathParams')
315
+ expect(output).toContain('export type UpdatePostReqBody')
312
316
  })
313
317
 
314
- it('composes structured params type', async () => {
318
+ it('composes structured Req type in flat mode', async () => {
315
319
  const output = await emitScopeFile(apiGroup)
316
- expect(output).toContain('export type UpdatePostParams =')
317
- expect(output).toContain('pathParams: UpdatePostPathParams')
318
- expect(output).toContain('body: UpdatePostBody')
320
+ expect(output).toContain('export type UpdatePostReq =')
321
+ expect(output).toContain('pathParams: UpdatePostReqPathParams')
322
+ expect(output).toContain('body: UpdatePostReqBody')
319
323
  })
320
324
 
321
- it('exports response type', async () => {
325
+ it('exports response body type with Response prefix in flat mode', async () => {
322
326
  const output = await emitScopeFile(apiGroup)
323
- expect(output).toContain('export type UpdatePostResponse')
327
+ expect(output).toContain('export type UpdatePostResponseBody')
324
328
  })
325
329
 
326
330
  it('callable uses client.bindCallable with kind: api', async () => {
@@ -443,24 +447,27 @@ describe('emitScopeFile', () => {
443
447
  })
444
448
 
445
449
  describe('API scope', () => {
446
- it('wraps channel types and structured Params in namespace', async () => {
450
+ it('wraps channel types in nested Req/Response sub-namespaces inside route namespace', async () => {
447
451
  const output = await emitScopeFile(apiGroup, { namespaceTypes: true })
448
452
  expect(output).toContain('export namespace Posts {')
449
- expect(output).toContain(' export namespace UpdatePost {')
453
+ expect(output).toContain('export namespace UpdatePost {')
454
+ expect(output).toContain('export namespace Req {')
455
+ expect(output).toContain('export namespace Response {')
450
456
  expect(output).toContain('export type PathParams =')
451
457
  expect(output).toContain('export type Body =')
452
- expect(output).toContain('export type Response =')
458
+ // Response body is emitted inside Response sub-namespace
459
+ expect(output).toContain('export type Body =')
453
460
  })
454
461
 
455
- it('structured Params uses namespace-local channel names', async () => {
462
+ it('callable uses Req sub-namespace as params type', async () => {
456
463
  const output = await emitScopeFile(apiGroup, { namespaceTypes: true })
457
- expect(output).toContain('export type Params = { pathParams: PathParams; body: Body }')
464
+ expect(output).toContain('Posts.UpdatePost.Req')
458
465
  })
459
466
 
460
467
  it('callable references fully qualified namespace types', async () => {
461
468
  const output = await emitScopeFile(apiGroup, { namespaceTypes: true })
462
- // New emission: UpdatePost: client.bindCallable<Posts.UpdatePost.Params, Posts.UpdatePost.Response>({...})
463
- expect(output).toContain('client.bindCallable<Posts.UpdatePost.Params, Posts.UpdatePost.Response>')
469
+ // Params type is Posts.UpdatePost.Req; return is Posts.UpdatePost.Response.Body (body only)
470
+ expect(output).toContain('client.bindCallable<Posts.UpdatePost.Req, Posts.UpdatePost.Response.Body>')
464
471
  })
465
472
  })
466
473
 
@@ -778,20 +785,24 @@ const apiGroupWithErrors: ScopeGroup = {
778
785
  scope: 'posts',
779
786
  errors: ['NotFound'],
780
787
  jsonSchema: {
781
- pathParams: {
782
- type: 'object',
783
- properties: { id: { type: 'string' } },
784
- required: ['id'],
785
- },
786
- body: {
787
- type: 'object',
788
- properties: { title: { type: 'string' } },
789
- required: ['title'],
788
+ req: {
789
+ pathParams: {
790
+ type: 'object',
791
+ properties: { id: { type: 'string' } },
792
+ required: ['id'],
793
+ },
794
+ body: {
795
+ type: 'object',
796
+ properties: { title: { type: 'string' } },
797
+ required: ['title'],
798
+ },
790
799
  },
791
- response: {
792
- type: 'object',
793
- properties: { id: { type: 'string' }, title: { type: 'string' } },
794
- required: ['id', 'title'],
800
+ res: {
801
+ body: {
802
+ type: 'object',
803
+ properties: { id: { type: 'string' }, title: { type: 'string' } },
804
+ required: ['id', 'title'],
805
+ },
795
806
  },
796
807
  },
797
808
  } satisfies APIHttpRouteDoc,
@@ -810,20 +821,24 @@ const apiGroupNoErrors: ScopeGroup = {
810
821
  fullPath: '/api/posts/:id',
811
822
  scope: 'posts',
812
823
  jsonSchema: {
813
- pathParams: {
814
- type: 'object',
815
- properties: { id: { type: 'string' } },
816
- required: ['id'],
817
- },
818
- body: {
819
- type: 'object',
820
- properties: { title: { type: 'string' } },
821
- required: ['title'],
824
+ req: {
825
+ pathParams: {
826
+ type: 'object',
827
+ properties: { id: { type: 'string' } },
828
+ required: ['id'],
829
+ },
830
+ body: {
831
+ type: 'object',
832
+ properties: { title: { type: 'string' } },
833
+ required: ['title'],
834
+ },
822
835
  },
823
- response: {
824
- type: 'object',
825
- properties: { id: { type: 'string' }, title: { type: 'string' } },
826
- required: ['id', 'title'],
836
+ res: {
837
+ body: {
838
+ type: 'object',
839
+ properties: { id: { type: 'string' }, title: { type: 'string' } },
840
+ required: ['id', 'title'],
841
+ },
827
842
  },
828
843
  },
829
844
  } satisfies APIHttpRouteDoc,
@@ -837,7 +852,7 @@ describe('emitScopeFile .safe sibling on API', () => {
837
852
  errorKeys: new Set(['NotFound']),
838
853
  serviceName: 'Api',
839
854
  })
840
- // With errors: uses bindCallableTyped<Params, Response, Errors>
855
+ // With errors: uses bindCallableTyped<Req, ReturnType, Errors>
841
856
  expect(out).toContain('client.bindCallableTyped<')
842
857
  // No Object.assign in generated output
843
858
  expect(out).not.toContain('Object.assign')
@@ -848,7 +863,7 @@ describe('emitScopeFile .safe sibling on API', () => {
848
863
  namespaceTypes: true,
849
864
  serviceName: 'Api',
850
865
  })
851
- // Without errors: uses bindCallable<Params, Response>
866
+ // Without errors: uses bindCallable<Req, ReturnType>
852
867
  expect(out).toContain('client.bindCallable<')
853
868
  // No Object.assign in generated output
854
869
  expect(out).not.toContain('Object.assign')
@@ -861,7 +876,8 @@ describe('emitScopeFile .safe sibling on API', () => {
861
876
  serviceName: 'Api',
862
877
  })
863
878
  // errorsRef in namespace mode is the route's Errors type alias: Scope.Route.Errors
864
- expect(out).toContain('client.bindCallableTyped<Posts.UpdatePost.Params, Posts.UpdatePost.Response, Posts.UpdatePost.Errors>')
879
+ // Params is Req sub-namespace; return is Response.Body (body-only)
880
+ expect(out).toContain('client.bindCallableTyped<Posts.UpdatePost.Req, Posts.UpdatePost.Response.Body, Posts.UpdatePost.Errors>')
865
881
  })
866
882
 
867
883
  it('flat mode: uses route Errors type alias as third type arg', async () => {
@@ -871,7 +887,8 @@ describe('emitScopeFile .safe sibling on API', () => {
871
887
  serviceName: 'Api',
872
888
  })
873
889
  // errorsRef in flat mode is the injected UpdatePostErrors type alias
874
- expect(out).toContain('client.bindCallableTyped<UpdatePostParams, UpdatePostResponse, UpdatePostErrors>')
890
+ // Params is UpdatePostReq; return is UpdatePostResponseBody (body-only)
891
+ expect(out).toContain('client.bindCallableTyped<UpdatePostReq, UpdatePostResponseBody, UpdatePostErrors>')
875
892
  })
876
893
 
877
894
  it('route with errors uses bindCallableTyped', async () => {
@@ -965,39 +982,43 @@ const apiGroupScopeCollision: ScopeGroup = {
965
982
  fullPath: '/api/keys',
966
983
  scope: 'keys',
967
984
  jsonSchema: {
968
- // Body has a property literally named `scope` whose value is an object —
969
- // ajsc with inlineTypes:false extracts `export type Scope = {...}`.
970
- body: {
971
- type: 'object',
972
- properties: {
973
- scope: {
974
- type: 'object',
975
- properties: { resource: { type: 'string' }, action: { type: 'string' } },
976
- required: ['resource', 'action'],
985
+ req: {
986
+ // Body has a property literally named `scope` whose value is an object —
987
+ // ajsc with inlineTypes:false extracts `export type Scope = {...}`.
988
+ body: {
989
+ type: 'object',
990
+ properties: {
991
+ scope: {
992
+ type: 'object',
993
+ properties: { resource: { type: 'string' }, action: { type: 'string' } },
994
+ required: ['resource', 'action'],
995
+ },
977
996
  },
997
+ required: ['scope'],
978
998
  },
979
- required: ['scope'],
980
999
  },
981
- // Response also contains a property named `scope`, but with a DIFFERENT
982
- // shape (additional `grantedAt` field). ajsc extracts a second
983
- // `export type Scope = {...}` whose body string differs from the body's
984
- // `Scope`, so the dedup-by-string-equality in formatTypes does not
985
- // collapse them both end up in the namespace.
986
- response: {
987
- type: 'object',
988
- properties: {
989
- id: { type: 'string' },
990
- scope: {
991
- type: 'object',
992
- properties: {
993
- resource: { type: 'string' },
994
- action: { type: 'string' },
995
- grantedAt: { type: 'string' },
1000
+ res: {
1001
+ // Response also contains a property named `scope`, but with a DIFFERENT
1002
+ // shape (additional `grantedAt` field). ajsc extracts a second
1003
+ // `export type Scope = {...}` whose body string differs from the body's
1004
+ // `Scope`, so the dedup-by-string-equality in formatTypes does not
1005
+ // collapse them — both end up in the namespace.
1006
+ body: {
1007
+ type: 'object',
1008
+ properties: {
1009
+ id: { type: 'string' },
1010
+ scope: {
1011
+ type: 'object',
1012
+ properties: {
1013
+ resource: { type: 'string' },
1014
+ action: { type: 'string' },
1015
+ grantedAt: { type: 'string' },
1016
+ },
1017
+ required: ['resource', 'action', 'grantedAt'],
996
1018
  },
997
- required: ['resource', 'action', 'grantedAt'],
998
1019
  },
1020
+ required: ['id', 'scope'],
999
1021
  },
1000
- required: ['id', 'scope'],
1001
1022
  },
1002
1023
  },
1003
1024
  } satisfies APIHttpRouteDoc,
@@ -1016,26 +1037,30 @@ const apiGroupParamsCollision: ScopeGroup = {
1016
1037
  fullPath: '/api/schemas',
1017
1038
  scope: 'schemas',
1018
1039
  jsonSchema: {
1019
- // Body has a property literally named `params` — ajsc extracts
1020
- // `export type Params = {...}`. emitApiRoute then injects an additional
1021
- // `export type Params = { body: Body }` for the structured channel
1022
- // params. Two `Params` declarations in the same namespace.
1023
- body: {
1024
- type: 'object',
1025
- properties: {
1026
- name: { type: 'string' },
1027
- params: {
1028
- type: 'object',
1029
- properties: { kind: { type: 'string' } },
1030
- required: ['kind'],
1040
+ req: {
1041
+ // Body has a property literally named `params` ajsc extracts
1042
+ // `export type Params = {...}`. With the new nested Req namespace,
1043
+ // the `Params` sub-type lives inside `Req {}` and does not collide
1044
+ // with the route-level structured type (which no longer exists as `Params`).
1045
+ body: {
1046
+ type: 'object',
1047
+ properties: {
1048
+ name: { type: 'string' },
1049
+ params: {
1050
+ type: 'object',
1051
+ properties: { kind: { type: 'string' } },
1052
+ required: ['kind'],
1053
+ },
1031
1054
  },
1055
+ required: ['name', 'params'],
1032
1056
  },
1033
- required: ['name', 'params'],
1034
1057
  },
1035
- response: {
1036
- type: 'object',
1037
- properties: { id: { type: 'string' } },
1038
- required: ['id'],
1058
+ res: {
1059
+ body: {
1060
+ type: 'object',
1061
+ properties: { id: { type: 'string' } },
1062
+ required: ['id'],
1063
+ },
1039
1064
  },
1040
1065
  },
1041
1066
  } satisfies APIHttpRouteDoc,
@@ -1056,17 +1081,23 @@ describe('emitScopeFile (bug repro: duplicate identifiers in namespace)', () =>
1056
1081
  expect(countTypeDeclarations(out, 'Params')).toBe(1)
1057
1082
  })
1058
1083
 
1059
- it('does not emit two `export type Scope` inside a route namespace when body and response have differently-shaped `scope` properties (API)', async () => {
1084
+ it('does not emit two `export type Scope` for API routes with `scope` properties in both req and res (now in separate sub-namespaces)', async () => {
1060
1085
  const out = await emitScopeFile(apiGroupScopeCollision, { namespaceTypes: true })
1061
- // Two extracted `Scope` sub-types with differing bodies → string-equality
1062
- // dedup does not collapse them, both get emitted.
1063
- expect(countTypeDeclarations(out, 'Scope')).toBe(1)
1086
+ // With the new Req/Response sub-namespace structure, the body's `scope`
1087
+ // sub-type lives in `Req {}` and the response's `scope` sub-type lives in
1088
+ // `Response {}` — they're in separate namespaces and do not collide. Each
1089
+ // sub-namespace can have at most one `Scope` (the rename guard still applies
1090
+ // within each sub-namespace). Total count across the file can be 2 (one in
1091
+ // each sub-namespace).
1092
+ expect(countTypeDeclarations(out, 'Scope')).toBeGreaterThanOrEqual(1)
1093
+ expect(countTypeDeclarations(out, 'Scope')).toBeLessThanOrEqual(2)
1064
1094
  })
1065
1095
 
1066
- it('does not emit two `export type Params` inside a route namespace when the body has a `params` property (API)', async () => {
1096
+ it('does not emit two `export type Params` inside the Req sub-namespace when the body has a `params` property (API)', async () => {
1067
1097
  const out = await emitScopeFile(apiGroupParamsCollision, { namespaceTypes: true })
1068
- // One extracted `Params` (from body.params property) + one injected `Params`
1069
- // (the structured channel composer) collision.
1098
+ // One extracted `Params` (from body.params property) lives inside `Req {}`.
1099
+ // No separate structured Params injection at the route level any more.
1100
+ // The rename guard inside formatSubNamespace ensures at most 1 `Params` in Req.
1070
1101
  expect(countTypeDeclarations(out, 'Params')).toBe(1)
1071
1102
  })
1072
1103
  })
@@ -1115,3 +1146,257 @@ describe('emitScopeFile streams omit .safe sibling', () => {
1115
1146
  expect(out).not.toMatch(/WatchEvents\s*:\s*client\.bindCallable/)
1116
1147
  })
1117
1148
  })
1149
+
1150
+ // ---------------------------------------------------------------------------
1151
+ // API conditional return type (body-only, headers-only, both, void)
1152
+ // ---------------------------------------------------------------------------
1153
+
1154
+ import type { HttpStreamRouteDoc } from '../implementations/types.js'
1155
+
1156
+ const apiGroupBodyOnly: ScopeGroup = {
1157
+ scopeKey: 'reports',
1158
+ camelCase: 'reports',
1159
+ routes: [
1160
+ {
1161
+ kind: 'api',
1162
+ name: 'GetReport',
1163
+ path: '/reports/:id',
1164
+ method: 'get',
1165
+ fullPath: '/api/reports/:id',
1166
+ scope: 'reports',
1167
+ jsonSchema: {
1168
+ req: { pathParams: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
1169
+ res: { body: { type: 'object', properties: { data: { type: 'string' } }, required: ['data'] } },
1170
+ },
1171
+ } satisfies APIHttpRouteDoc,
1172
+ ],
1173
+ }
1174
+
1175
+ const apiGroupBothBodyAndHeaders: ScopeGroup = {
1176
+ scopeKey: 'downloads',
1177
+ camelCase: 'downloads',
1178
+ routes: [
1179
+ {
1180
+ kind: 'api',
1181
+ name: 'GetDownload',
1182
+ path: '/downloads/:id',
1183
+ method: 'get',
1184
+ fullPath: '/api/downloads/:id',
1185
+ scope: 'downloads',
1186
+ jsonSchema: {
1187
+ req: { pathParams: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
1188
+ res: {
1189
+ body: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] },
1190
+ headers: { type: 'object', properties: { 'x-token': { type: 'string' } }, required: ['x-token'] },
1191
+ },
1192
+ },
1193
+ } satisfies APIHttpRouteDoc,
1194
+ ],
1195
+ }
1196
+
1197
+ const apiGroupHeadersOnly: ScopeGroup = {
1198
+ scopeKey: 'preflight',
1199
+ camelCase: 'preflight',
1200
+ routes: [
1201
+ {
1202
+ kind: 'api',
1203
+ name: 'GetPreflight',
1204
+ path: '/preflight',
1205
+ method: 'get',
1206
+ fullPath: '/api/preflight',
1207
+ scope: 'preflight',
1208
+ jsonSchema: {
1209
+ req: {},
1210
+ res: { headers: { type: 'object', properties: { 'x-allowed': { type: 'string' } }, required: ['x-allowed'] } },
1211
+ },
1212
+ } satisfies APIHttpRouteDoc,
1213
+ ],
1214
+ }
1215
+
1216
+ const apiGroupNoBody: ScopeGroup = {
1217
+ scopeKey: 'pings',
1218
+ camelCase: 'pings',
1219
+ routes: [
1220
+ {
1221
+ kind: 'api',
1222
+ name: 'Ping',
1223
+ path: '/ping',
1224
+ method: 'get',
1225
+ fullPath: '/api/ping',
1226
+ scope: 'pings',
1227
+ jsonSchema: { req: {}, res: {} },
1228
+ } satisfies APIHttpRouteDoc,
1229
+ ],
1230
+ }
1231
+
1232
+ describe('emitScopeFile API conditional return type', () => {
1233
+ describe('flat mode', () => {
1234
+ it('body-only: return type is the body type', async () => {
1235
+ const out = await emitScopeFile(apiGroupBodyOnly)
1236
+ expect(out).toContain('export type GetReportResponseBody =')
1237
+ expect(out).toContain('client.bindCallable<GetReportReq, GetReportResponseBody>')
1238
+ })
1239
+
1240
+ it('body + headers: return type is { body; headers }', async () => {
1241
+ const out = await emitScopeFile(apiGroupBothBodyAndHeaders)
1242
+ expect(out).toContain('export type GetDownloadResponseBody =')
1243
+ expect(out).toContain('export type GetDownloadResponseHeaders =')
1244
+ expect(out).toContain('{ body: GetDownloadResponseBody; headers: GetDownloadResponseHeaders }')
1245
+ })
1246
+
1247
+ it('headers-only: return type is { headers }', async () => {
1248
+ const out = await emitScopeFile(apiGroupHeadersOnly)
1249
+ expect(out).toContain('export type GetPreflightResponseHeaders =')
1250
+ expect(out).toContain('{ headers: GetPreflightResponseHeaders }')
1251
+ })
1252
+
1253
+ it('neither body nor headers: return type is void', async () => {
1254
+ const out = await emitScopeFile(apiGroupNoBody)
1255
+ expect(out).toContain('client.bindCallable<unknown, void>')
1256
+ })
1257
+ })
1258
+
1259
+ describe('namespace mode', () => {
1260
+ it('body-only: return type references Response.Body', async () => {
1261
+ const out = await emitScopeFile(apiGroupBodyOnly, { namespaceTypes: true })
1262
+ expect(out).toContain('export namespace Response {')
1263
+ expect(out).toContain('client.bindCallable<Reports.GetReport.Req, Reports.GetReport.Response.Body>')
1264
+ })
1265
+
1266
+ it('body + headers: return type is { body: Response.Body; headers: Response.Headers }', async () => {
1267
+ const out = await emitScopeFile(apiGroupBothBodyAndHeaders, { namespaceTypes: true })
1268
+ expect(out).toContain('export namespace Response {')
1269
+ expect(out).toContain('export type Body =')
1270
+ expect(out).toContain('export type Headers =')
1271
+ expect(out).toContain('{ body: Downloads.GetDownload.Response.Body; headers: Downloads.GetDownload.Response.Headers }')
1272
+ })
1273
+
1274
+ it('headers-only: return type is { headers: Response.Headers }', async () => {
1275
+ const out = await emitScopeFile(apiGroupHeadersOnly, { namespaceTypes: true })
1276
+ expect(out).toContain('{ headers: Preflight.GetPreflight.Response.Headers }')
1277
+ })
1278
+
1279
+ it('neither body nor headers: return type is void', async () => {
1280
+ const out = await emitScopeFile(apiGroupNoBody, { namespaceTypes: true })
1281
+ expect(out).toContain('client.bindCallable<unknown, void>')
1282
+ })
1283
+ })
1284
+ })
1285
+
1286
+ describe('emitScopeFile API responseHeadersDeclared descriptor field', () => {
1287
+ it('emits responseHeadersDeclared: true when res.headers is present', async () => {
1288
+ const out = await emitScopeFile(apiGroupBothBodyAndHeaders)
1289
+ expect(out).toContain('responseHeadersDeclared: true')
1290
+ })
1291
+
1292
+ it('does NOT emit responseHeadersDeclared when res.headers is absent', async () => {
1293
+ const out = await emitScopeFile(apiGroupBodyOnly)
1294
+ expect(out).not.toContain('responseHeadersDeclared')
1295
+ })
1296
+ })
1297
+
1298
+ // ---------------------------------------------------------------------------
1299
+ // http-stream kind emission
1300
+ // ---------------------------------------------------------------------------
1301
+
1302
+ const httpStreamGroup: ScopeGroup = {
1303
+ scopeKey: 'feed',
1304
+ camelCase: 'feed',
1305
+ routes: [
1306
+ {
1307
+ kind: 'http-stream',
1308
+ name: 'StreamFeed',
1309
+ path: '/feed/stream',
1310
+ method: 'get',
1311
+ fullPath: '/api/feed/stream',
1312
+ scope: 'feed',
1313
+ streamMode: 'sse',
1314
+ jsonSchema: {
1315
+ req: {
1316
+ query: {
1317
+ type: 'object',
1318
+ properties: { cursor: { type: 'string' } },
1319
+ },
1320
+ },
1321
+ res: {
1322
+ headers: {
1323
+ type: 'object',
1324
+ properties: { 'x-session': { type: 'string' } },
1325
+ required: ['x-session'],
1326
+ },
1327
+ },
1328
+ yield: {
1329
+ type: 'object',
1330
+ properties: { message: { type: 'string' } },
1331
+ required: ['message'],
1332
+ },
1333
+ returnType: {
1334
+ type: 'object',
1335
+ properties: { total: { type: 'number' } },
1336
+ required: ['total'],
1337
+ },
1338
+ },
1339
+ } satisfies HttpStreamRouteDoc,
1340
+ ],
1341
+ }
1342
+
1343
+ describe('emitScopeFile http-stream kind', () => {
1344
+ describe('flat mode', () => {
1345
+ it('emits Req-prefixed query type', async () => {
1346
+ const out = await emitScopeFile(httpStreamGroup)
1347
+ expect(out).toContain('export type StreamFeedReqQuery =')
1348
+ })
1349
+
1350
+ it('emits ResponseHeaders type', async () => {
1351
+ const out = await emitScopeFile(httpStreamGroup)
1352
+ expect(out).toContain('export type StreamFeedResponseHeaders =')
1353
+ })
1354
+
1355
+ it('emits Yield and ReturnType types', async () => {
1356
+ const out = await emitScopeFile(httpStreamGroup)
1357
+ expect(out).toContain('export type StreamFeedYield =')
1358
+ expect(out).toContain('export type StreamFeedReturnType =')
1359
+ })
1360
+
1361
+ it('callable returns TypedStream<Yield, ReturnType>', async () => {
1362
+ const out = await emitScopeFile(httpStreamGroup)
1363
+ expect(out).toContain('TypedStream<StreamFeedYield, StreamFeedReturnType>')
1364
+ })
1365
+
1366
+ it('callable uses kind: http-stream in descriptor', async () => {
1367
+ const out = await emitScopeFile(httpStreamGroup)
1368
+ expect(out).toContain("kind: 'http-stream'")
1369
+ })
1370
+
1371
+ it('imports TypedStream', async () => {
1372
+ const out = await emitScopeFile(httpStreamGroup)
1373
+ expect(out).toContain('TypedStream')
1374
+ })
1375
+ })
1376
+
1377
+ describe('namespace mode', () => {
1378
+ it('emits Req sub-namespace with query type', async () => {
1379
+ const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
1380
+ expect(out).toContain('export namespace Req {')
1381
+ expect(out).toContain('export type Query =')
1382
+ })
1383
+
1384
+ it('emits Response sub-namespace with Headers type', async () => {
1385
+ const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
1386
+ expect(out).toContain('export namespace Response {')
1387
+ expect(out).toContain('export type Headers =')
1388
+ })
1389
+
1390
+ it('emits Yield and ReturnType in route namespace', async () => {
1391
+ const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
1392
+ expect(out).toContain('export type Yield =')
1393
+ expect(out).toContain('export type ReturnType =')
1394
+ })
1395
+
1396
+ it('callable uses qualified Req and TypedStream types', async () => {
1397
+ const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
1398
+ expect(out).toContain('Feed.StreamFeed.Req')
1399
+ expect(out).toContain('TypedStream<Feed.StreamFeed.Yield, Feed.StreamFeed.ReturnType>')
1400
+ })
1401
+ })
1402
+ })