ts-procedures 8.6.0 → 9.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 (627) hide show
  1. package/README.md +166 -101
  2. package/agent_config/claude-code/.claude-plugin/plugin.json +1 -1
  3. package/agent_config/claude-code/agents/ts-procedures-architect.md +11 -10
  4. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +25 -12
  5. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +10 -12
  6. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +141 -45
  7. package/agent_config/claude-code/skills/ts-procedures/checklist.md +7 -6
  8. package/agent_config/claude-code/skills/ts-procedures/patterns.md +45 -6
  9. package/agent_config/claude-code/skills/ts-procedures/templates/client.md +1 -1
  10. package/agent_config/claude-code/skills/ts-procedures/templates/hono.md +1 -1
  11. package/agent_config/copilot/copilot-instructions.md +50 -33
  12. package/agent_config/cursor/cursorrules +50 -33
  13. package/build/adapters/astro/astro-context.js.map +1 -0
  14. package/build/adapters/astro/create-handler.js.map +1 -0
  15. package/build/adapters/astro/index.js.map +1 -0
  16. package/build/{implementations/http → adapters}/astro/index.test.js +1 -1
  17. package/build/adapters/astro/index.test.js.map +1 -0
  18. package/build/adapters/astro/rewrite-request.js.map +1 -0
  19. package/build/adapters/hono/envelope-parity.test.js +98 -0
  20. package/build/adapters/hono/envelope-parity.test.js.map +1 -0
  21. package/build/{implementations/http → adapters}/hono/handlers/http-stream.d.ts +1 -1
  22. package/build/adapters/hono/handlers/http-stream.js +55 -0
  23. package/build/adapters/hono/handlers/http-stream.js.map +1 -0
  24. package/build/{implementations/http → adapters}/hono/handlers/http-stream.test.js +1 -1
  25. package/build/adapters/hono/handlers/http-stream.test.js.map +1 -0
  26. package/build/{implementations/http → adapters}/hono/handlers/http.d.ts +1 -1
  27. package/build/adapters/hono/handlers/http.js +50 -0
  28. package/build/adapters/hono/handlers/http.js.map +1 -0
  29. package/build/{implementations/http → adapters}/hono/handlers/http.test.js +1 -1
  30. package/build/adapters/hono/handlers/http.test.js.map +1 -0
  31. package/build/{implementations/http → adapters}/hono/handlers/rpc.d.ts +2 -2
  32. package/build/adapters/hono/handlers/rpc.js +23 -0
  33. package/build/adapters/hono/handlers/rpc.js.map +1 -0
  34. package/build/{implementations/http → adapters}/hono/handlers/rpc.test.js +1 -1
  35. package/build/adapters/hono/handlers/rpc.test.js.map +1 -0
  36. package/build/adapters/hono/handlers/stream.d.ts +12 -0
  37. package/build/adapters/hono/handlers/stream.js +89 -0
  38. package/build/adapters/hono/handlers/stream.js.map +1 -0
  39. package/build/{implementations/http → adapters}/hono/handlers/stream.test.js +3 -2
  40. package/build/adapters/hono/handlers/stream.test.js.map +1 -0
  41. package/build/{implementations/http → adapters}/hono/index.d.ts +24 -12
  42. package/build/{implementations/http → adapters}/hono/index.js +19 -8
  43. package/build/adapters/hono/index.js.map +1 -0
  44. package/build/{implementations/http → adapters}/hono/index.test.js +2 -4
  45. package/build/adapters/hono/index.test.js.map +1 -0
  46. package/build/{implementations/http → adapters/hono}/on-request-error.test.js +2 -2
  47. package/build/adapters/hono/on-request-error.test.js.map +1 -0
  48. package/build/adapters/hono/request.d.ts +7 -0
  49. package/build/adapters/hono/request.js +22 -0
  50. package/build/adapters/hono/request.js.map +1 -0
  51. package/build/{implementations/http → adapters/hono}/route-errors.test.js +4 -4
  52. package/build/adapters/hono/route-errors.test.js.map +1 -0
  53. package/build/adapters/hono/types.d.ts +55 -0
  54. package/build/adapters/hono/types.js +19 -0
  55. package/build/adapters/hono/types.js.map +1 -0
  56. package/build/client/freeze.test.js +39 -0
  57. package/build/client/freeze.test.js.map +1 -0
  58. package/build/client/typed-error-dispatch.test.js +2 -2
  59. package/build/client/typed-error-dispatch.test.js.map +1 -1
  60. package/build/codegen/__fixtures__/make-envelope.d.ts +1 -1
  61. package/build/codegen/bin/cli.d.ts +5 -0
  62. package/build/codegen/bin/cli.js +139 -182
  63. package/build/codegen/bin/cli.js.map +1 -1
  64. package/build/codegen/bin/cli.test.js +12 -2
  65. package/build/codegen/bin/cli.test.js.map +1 -1
  66. package/build/codegen/bin/flag-specs.d.ts +9 -0
  67. package/build/codegen/bin/flag-specs.js +33 -31
  68. package/build/codegen/bin/flag-specs.js.map +1 -1
  69. package/build/codegen/bin/flag-specs.test.js +14 -1
  70. package/build/codegen/bin/flag-specs.test.js.map +1 -1
  71. package/build/codegen/collect-models.d.ts +1 -1
  72. package/build/codegen/emit/api-route.d.ts +8 -0
  73. package/build/codegen/emit/api-route.js +156 -0
  74. package/build/codegen/emit/api-route.js.map +1 -0
  75. package/build/codegen/emit/context.d.ts +30 -0
  76. package/build/codegen/emit/context.js +2 -0
  77. package/build/codegen/emit/context.js.map +1 -0
  78. package/build/codegen/emit/declarations.d.ts +24 -0
  79. package/build/codegen/emit/declarations.js +48 -0
  80. package/build/codegen/emit/declarations.js.map +1 -0
  81. package/build/codegen/emit/format-types.d.ts +61 -0
  82. package/build/codegen/emit/format-types.js +188 -0
  83. package/build/codegen/emit/format-types.js.map +1 -0
  84. package/build/codegen/emit/http-stream-route.d.ts +7 -0
  85. package/build/codegen/emit/http-stream-route.js +138 -0
  86. package/build/codegen/emit/http-stream-route.js.map +1 -0
  87. package/build/codegen/emit/route-shared.d.ts +35 -0
  88. package/build/codegen/emit/route-shared.js +88 -0
  89. package/build/codegen/emit/route-shared.js.map +1 -0
  90. package/build/codegen/emit/rpc-route.d.ts +7 -0
  91. package/build/codegen/emit/rpc-route.js +37 -0
  92. package/build/codegen/emit/rpc-route.js.map +1 -0
  93. package/build/codegen/emit/scope-file.d.ts +39 -0
  94. package/build/codegen/emit/scope-file.js +166 -0
  95. package/build/codegen/emit/scope-file.js.map +1 -0
  96. package/build/codegen/emit/stream-route.d.ts +7 -0
  97. package/build/codegen/emit/stream-route.js +62 -0
  98. package/build/codegen/emit/stream-route.js.map +1 -0
  99. package/build/codegen/emit-errors.d.ts +1 -1
  100. package/build/codegen/emit-errors.integration.test.js +1 -1
  101. package/build/codegen/emit-errors.integration.test.js.map +1 -1
  102. package/build/codegen/emit-scope.d.ts +13 -30
  103. package/build/codegen/emit-scope.js +15 -844
  104. package/build/codegen/emit-scope.js.map +1 -1
  105. package/build/codegen/goldens.test.js +69 -0
  106. package/build/codegen/goldens.test.js.map +1 -0
  107. package/build/codegen/group-routes.d.ts +1 -1
  108. package/build/codegen/pipeline.d.ts +1 -1
  109. package/build/codegen/resolve-envelope.d.ts +1 -1
  110. package/build/codegen/targets/_shared/error-schemas.d.ts +1 -1
  111. package/build/codegen/targets/_shared/route-slots.d.ts +1 -1
  112. package/build/codegen/targets/_shared/target-run.d.ts +1 -1
  113. package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +1 -1
  114. package/build/codegen/targets/swift/emit-route-swift.d.ts +1 -1
  115. package/build/core/create-http-stream.d.ts +50 -0
  116. package/build/core/create-http-stream.js +108 -0
  117. package/build/core/create-http-stream.js.map +1 -0
  118. package/build/{create-http-stream.test.js → core/create-http-stream.test.js} +1 -1
  119. package/build/core/create-http-stream.test.js.map +1 -0
  120. package/build/core/create-http.d.ts +51 -0
  121. package/build/core/create-http.js +65 -0
  122. package/build/core/create-http.js.map +1 -0
  123. package/build/{create-http.test.js → core/create-http.test.js} +13 -4
  124. package/build/core/create-http.test.js.map +1 -0
  125. package/build/core/create-stream.d.ts +26 -0
  126. package/build/core/create-stream.js +80 -0
  127. package/build/core/create-stream.js.map +1 -0
  128. package/build/{create-stream.test.js → core/create-stream.test.js} +23 -28
  129. package/build/core/create-stream.test.js.map +1 -0
  130. package/build/core/create.d.ts +22 -0
  131. package/build/core/create.js +71 -0
  132. package/build/core/create.js.map +1 -0
  133. package/build/{create.test.js → core/create.test.js} +25 -46
  134. package/build/core/create.test.js.map +1 -0
  135. package/build/core/definition-site.d.ts +24 -0
  136. package/build/{stack-utils.js → core/definition-site.js} +20 -20
  137. package/build/core/definition-site.js.map +1 -0
  138. package/build/{stack-utils.test.js → core/definition-site.test.js} +12 -3
  139. package/build/core/definition-site.test.js.map +1 -0
  140. package/build/{errors.d.ts → core/errors.d.ts} +19 -8
  141. package/build/{errors.js → core/errors.js} +21 -26
  142. package/build/core/errors.js.map +1 -0
  143. package/build/core/errors.test.js.map +1 -0
  144. package/build/core/factory-options.test.js +82 -0
  145. package/build/core/factory-options.test.js.map +1 -0
  146. package/build/core/http-route.d.ts +13 -0
  147. package/build/core/http-route.js +54 -0
  148. package/build/core/http-route.js.map +1 -0
  149. package/build/core/internal.d.ts +72 -0
  150. package/build/core/internal.js +128 -0
  151. package/build/core/internal.js.map +1 -0
  152. package/build/{migration.test.js → core/migration.test.js} +17 -1
  153. package/build/core/migration.test.js.map +1 -0
  154. package/build/core/procedures.d.ts +143 -0
  155. package/build/core/procedures.js +64 -0
  156. package/build/core/procedures.js.map +1 -0
  157. package/build/{index.test.js → core/procedures.test.js} +14 -11
  158. package/build/core/procedures.test.js.map +1 -0
  159. package/build/core/types.d.ts +182 -0
  160. package/build/{schema → core}/types.js.map +1 -1
  161. package/build/exports.d.ts +31 -11
  162. package/build/exports.js +23 -8
  163. package/build/exports.js.map +1 -1
  164. package/build/schema/adapter.d.ts +35 -0
  165. package/build/schema/adapter.js +13 -0
  166. package/build/schema/adapter.js.map +1 -0
  167. package/build/schema/adapter.test.js +53 -0
  168. package/build/schema/adapter.test.js.map +1 -0
  169. package/build/schema/compile.d.ts +37 -0
  170. package/build/schema/compile.js +38 -0
  171. package/build/schema/compile.js.map +1 -0
  172. package/build/schema/compile.test.js +78 -0
  173. package/build/schema/compile.test.js.map +1 -0
  174. package/build/schema/compute-schema.d.ts +47 -37
  175. package/build/schema/compute-schema.js +86 -29
  176. package/build/schema/compute-schema.js.map +1 -1
  177. package/build/schema/compute-schema.test.js +158 -40
  178. package/build/schema/compute-schema.test.js.map +1 -1
  179. package/build/schema/json-schema.d.ts +17 -0
  180. package/build/schema/json-schema.js +2 -0
  181. package/build/schema/json-schema.js.map +1 -0
  182. package/build/schema/typebox.d.ts +11 -0
  183. package/build/schema/typebox.js +24 -0
  184. package/build/schema/typebox.js.map +1 -0
  185. package/build/schema/typebox.test.js +34 -0
  186. package/build/schema/typebox.test.js.map +1 -0
  187. package/build/server/context.d.ts +8 -0
  188. package/build/server/context.js +7 -0
  189. package/build/server/context.js.map +1 -0
  190. package/build/server/context.test.js +16 -0
  191. package/build/server/context.test.js.map +1 -0
  192. package/build/{doc-envelope.d.ts → server/doc-envelope.d.ts} +1 -1
  193. package/build/server/doc-envelope.js.map +1 -0
  194. package/build/server/doc-envelope.test.d.ts +1 -0
  195. package/build/server/doc-envelope.test.js.map +1 -0
  196. package/build/{implementations/http → server}/doc-registry.d.ts +7 -2
  197. package/build/{implementations/http → server}/doc-registry.js +9 -5
  198. package/build/server/doc-registry.js.map +1 -0
  199. package/build/server/doc-registry.test.d.ts +1 -0
  200. package/build/{implementations/http → server}/doc-registry.test.js +27 -24
  201. package/build/server/doc-registry.test.js.map +1 -0
  202. package/build/server/docs/docs.test.d.ts +1 -0
  203. package/build/server/docs/docs.test.js +237 -0
  204. package/build/server/docs/docs.test.js.map +1 -0
  205. package/build/{implementations/http/hono → server}/docs/http-doc.d.ts +2 -2
  206. package/build/{implementations/http/hono → server}/docs/http-doc.js +1 -1
  207. package/build/server/docs/http-doc.js.map +1 -0
  208. package/build/{implementations/http/hono → server}/docs/http-stream-doc.d.ts +2 -2
  209. package/build/{implementations/http/hono → server}/docs/http-stream-doc.js +1 -1
  210. package/build/server/docs/http-stream-doc.js.map +1 -0
  211. package/build/{implementations/http/hono → server}/docs/rpc-doc.d.ts +2 -2
  212. package/build/{implementations/http/hono → server}/docs/rpc-doc.js +1 -1
  213. package/build/server/docs/rpc-doc.js.map +1 -0
  214. package/build/{implementations/http/hono → server}/docs/stream-doc.d.ts +2 -2
  215. package/build/{implementations/http/hono → server}/docs/stream-doc.js +1 -1
  216. package/build/server/docs/stream-doc.js.map +1 -0
  217. package/build/server/errors/dispatch.d.ts +96 -0
  218. package/build/{implementations/http/error-dispatch.js → server/errors/dispatch.js} +20 -10
  219. package/build/server/errors/dispatch.js.map +1 -0
  220. package/build/server/errors/dispatch.test.d.ts +1 -0
  221. package/build/server/errors/dispatch.test.js +418 -0
  222. package/build/server/errors/dispatch.test.js.map +1 -0
  223. package/build/{implementations/http/error-taxonomy.d.ts → server/errors/taxonomy.d.ts} +8 -17
  224. package/build/{implementations/http/error-taxonomy.js → server/errors/taxonomy.js} +6 -15
  225. package/build/server/errors/taxonomy.js.map +1 -0
  226. package/build/server/errors/taxonomy.test.d.ts +1 -0
  227. package/build/{implementations/http/error-taxonomy.test.js → server/errors/taxonomy.test.js} +45 -39
  228. package/build/server/errors/taxonomy.test.js.map +1 -0
  229. package/build/server/index.d.ts +29 -0
  230. package/build/server/index.js +27 -0
  231. package/build/server/index.js.map +1 -0
  232. package/build/server/no-framework-imports.test.d.ts +1 -0
  233. package/build/server/no-framework-imports.test.js +40 -0
  234. package/build/server/no-framework-imports.test.js.map +1 -0
  235. package/build/{implementations/http/hono/path.d.ts → server/paths.d.ts} +2 -3
  236. package/build/{implementations/http/hono/path.js → server/paths.js} +1 -1
  237. package/build/server/paths.js.map +1 -0
  238. package/build/server/paths.test.d.ts +1 -0
  239. package/build/server/paths.test.js +111 -0
  240. package/build/server/paths.test.js.map +1 -0
  241. package/build/server/request/params.d.ts +29 -0
  242. package/build/server/request/params.js +43 -0
  243. package/build/server/request/params.js.map +1 -0
  244. package/build/server/request/params.test.d.ts +1 -0
  245. package/build/server/request/params.test.js +91 -0
  246. package/build/server/request/params.test.js.map +1 -0
  247. package/build/server/request/query.d.ts +9 -0
  248. package/build/server/request/query.js +22 -0
  249. package/build/server/request/query.js.map +1 -0
  250. package/build/server/request/query.test.d.ts +1 -0
  251. package/build/server/request/query.test.js +60 -0
  252. package/build/server/request/query.test.js.map +1 -0
  253. package/build/server/sse.d.ts +70 -0
  254. package/build/server/sse.js +94 -0
  255. package/build/server/sse.js.map +1 -0
  256. package/build/server/sse.test.d.ts +1 -0
  257. package/build/server/sse.test.js +98 -0
  258. package/build/server/sse.test.js.map +1 -0
  259. package/build/{implementations → server}/types.d.ts +17 -15
  260. package/build/{implementations → server}/types.js.map +1 -1
  261. package/docs/astro-adapter.md +8 -9
  262. package/docs/client-and-codegen.md +4 -4
  263. package/docs/client-error-handling.md +5 -5
  264. package/docs/codegen-kotlin.md +2 -3
  265. package/docs/codegen-swift.md +1 -2
  266. package/docs/core.md +135 -54
  267. package/docs/http-integrations.md +58 -6
  268. package/docs/migration-v8-to-v9.md +192 -0
  269. package/docs/plans/2026-06-09-v9-rewrite.md +130 -0
  270. package/docs/specs/2026-06-09-v9-rewrite-design.md +221 -0
  271. package/docs/streaming.md +12 -0
  272. package/package.json +23 -47
  273. package/src/{implementations/http → adapters}/astro/index.test.ts +2 -2
  274. package/src/adapters/hono/__fixtures__/parity-envelope.json +389 -0
  275. package/src/adapters/hono/envelope-parity.test.ts +126 -0
  276. package/src/{implementations/http → adapters}/hono/handlers/http-stream.test.ts +1 -1
  277. package/src/adapters/hono/handlers/http-stream.ts +73 -0
  278. package/src/{implementations/http → adapters}/hono/handlers/http.test.ts +1 -1
  279. package/src/adapters/hono/handlers/http.ts +70 -0
  280. package/src/{implementations/http → adapters}/hono/handlers/rpc.test.ts +2 -2
  281. package/src/adapters/hono/handlers/rpc.ts +39 -0
  282. package/src/{implementations/http → adapters}/hono/handlers/stream.test.ts +4 -3
  283. package/src/{implementations/http → adapters}/hono/handlers/stream.ts +19 -92
  284. package/src/{implementations/http → adapters}/hono/index.test.ts +14 -16
  285. package/src/{implementations/http → adapters}/hono/index.ts +35 -30
  286. package/src/{implementations/http → adapters/hono}/on-request-error.test.ts +3 -3
  287. package/src/adapters/hono/request.ts +28 -0
  288. package/src/{implementations/http → adapters/hono}/route-errors.test.ts +5 -5
  289. package/src/{implementations/http → adapters}/hono/types.ts +43 -20
  290. package/src/client/freeze.test.ts +41 -0
  291. package/src/client/typed-error-dispatch.test.ts +3 -3
  292. package/src/codegen/__fixtures__/make-envelope.ts +1 -1
  293. package/src/codegen/__fixtures__/models-envelope.json +310 -0
  294. package/src/codegen/__goldens__/MANIFEST.json +85 -0
  295. package/src/codegen/__goldens__/kotlin-default--models/Billing.kt +112 -0
  296. package/src/codegen/__goldens__/kotlin-default--models/BillingReports.kt +26 -0
  297. package/src/codegen/__goldens__/kotlin-default--models/Orders.kt +88 -0
  298. package/src/codegen/__goldens__/kotlin-default--users/Users.kt +189 -0
  299. package/src/codegen/__goldens__/swift-default--models/Billing.swift +97 -0
  300. package/src/codegen/__goldens__/swift-default--models/BillingReports.swift +20 -0
  301. package/src/codegen/__goldens__/swift-default--models/Orders.swift +81 -0
  302. package/src/codegen/__goldens__/swift-default--users/Users.swift +204 -0
  303. package/src/codegen/__goldens__/ts-default--models/_client.ts +1319 -0
  304. package/src/codegen/__goldens__/ts-default--models/_errors.ts +90 -0
  305. package/src/codegen/__goldens__/ts-default--models/_models.ts +10 -0
  306. package/src/codegen/__goldens__/ts-default--models/_types.ts +502 -0
  307. package/src/codegen/__goldens__/ts-default--models/billing-reports.ts +29 -0
  308. package/src/codegen/__goldens__/ts-default--models/billing.ts +67 -0
  309. package/src/codegen/__goldens__/ts-default--models/index.ts +48 -0
  310. package/src/codegen/__goldens__/ts-default--models/orders.ts +80 -0
  311. package/src/codegen/__goldens__/ts-default--users/_client.ts +1319 -0
  312. package/src/codegen/__goldens__/ts-default--users/_errors.ts +90 -0
  313. package/src/codegen/__goldens__/ts-default--users/_types.ts +502 -0
  314. package/src/codegen/__goldens__/ts-default--users/index.ts +38 -0
  315. package/src/codegen/__goldens__/ts-default--users/users.ts +169 -0
  316. package/src/codegen/__goldens__/ts-external-runtime--models/_errors.ts +90 -0
  317. package/src/codegen/__goldens__/ts-external-runtime--models/_models.ts +10 -0
  318. package/src/codegen/__goldens__/ts-external-runtime--models/billing-reports.ts +29 -0
  319. package/src/codegen/__goldens__/ts-external-runtime--models/billing.ts +67 -0
  320. package/src/codegen/__goldens__/ts-external-runtime--models/index.ts +48 -0
  321. package/src/codegen/__goldens__/ts-external-runtime--models/orders.ts +80 -0
  322. package/src/codegen/__goldens__/ts-external-runtime--users/_errors.ts +90 -0
  323. package/src/codegen/__goldens__/ts-external-runtime--users/index.ts +38 -0
  324. package/src/codegen/__goldens__/ts-external-runtime--users/users.ts +169 -0
  325. package/src/codegen/__goldens__/ts-flat--models/_client.ts +1319 -0
  326. package/src/codegen/__goldens__/ts-flat--models/_errors.ts +87 -0
  327. package/src/codegen/__goldens__/ts-flat--models/_models.ts +10 -0
  328. package/src/codegen/__goldens__/ts-flat--models/_types.ts +502 -0
  329. package/src/codegen/__goldens__/ts-flat--models/billing-reports.ts +28 -0
  330. package/src/codegen/__goldens__/ts-flat--models/billing.ts +51 -0
  331. package/src/codegen/__goldens__/ts-flat--models/index.ts +42 -0
  332. package/src/codegen/__goldens__/ts-flat--models/orders.ts +73 -0
  333. package/src/codegen/__goldens__/ts-flat--users/_client.ts +1319 -0
  334. package/src/codegen/__goldens__/ts-flat--users/_errors.ts +87 -0
  335. package/src/codegen/__goldens__/ts-flat--users/_types.ts +502 -0
  336. package/src/codegen/__goldens__/ts-flat--users/index.ts +34 -0
  337. package/src/codegen/__goldens__/ts-flat--users/users.ts +126 -0
  338. package/src/codegen/__goldens__/ts-no-share-models--models/_client.ts +1319 -0
  339. package/src/codegen/__goldens__/ts-no-share-models--models/_errors.ts +90 -0
  340. package/src/codegen/__goldens__/ts-no-share-models--models/_types.ts +502 -0
  341. package/src/codegen/__goldens__/ts-no-share-models--models/billing-reports.ts +29 -0
  342. package/src/codegen/__goldens__/ts-no-share-models--models/billing.ts +111 -0
  343. package/src/codegen/__goldens__/ts-no-share-models--models/index.ts +48 -0
  344. package/src/codegen/__goldens__/ts-no-share-models--models/orders.ts +112 -0
  345. package/src/codegen/__goldens__/ts-no-share-models--users/_client.ts +1319 -0
  346. package/src/codegen/__goldens__/ts-no-share-models--users/_errors.ts +90 -0
  347. package/src/codegen/__goldens__/ts-no-share-models--users/_types.ts +502 -0
  348. package/src/codegen/__goldens__/ts-no-share-models--users/index.ts +38 -0
  349. package/src/codegen/__goldens__/ts-no-share-models--users/users.ts +169 -0
  350. package/src/codegen/__goldens__/ts-shared-models-module--models/_client.ts +1319 -0
  351. package/src/codegen/__goldens__/ts-shared-models-module--models/_errors.ts +90 -0
  352. package/src/codegen/__goldens__/ts-shared-models-module--models/_models.ts +7 -0
  353. package/src/codegen/__goldens__/ts-shared-models-module--models/_types.ts +502 -0
  354. package/src/codegen/__goldens__/ts-shared-models-module--models/billing-reports.ts +29 -0
  355. package/src/codegen/__goldens__/ts-shared-models-module--models/billing.ts +67 -0
  356. package/src/codegen/__goldens__/ts-shared-models-module--models/index.ts +48 -0
  357. package/src/codegen/__goldens__/ts-shared-models-module--models/orders.ts +80 -0
  358. package/src/codegen/bin/cli.test.ts +13 -2
  359. package/src/codegen/bin/cli.ts +181 -144
  360. package/src/codegen/bin/flag-specs.test.ts +16 -1
  361. package/src/codegen/bin/flag-specs.ts +43 -31
  362. package/src/codegen/bundle-size.test.ts +1 -1
  363. package/src/codegen/collect-models.ts +1 -1
  364. package/src/codegen/e2e.test.ts +1 -1
  365. package/src/codegen/emit/api-route.ts +184 -0
  366. package/src/codegen/emit/context.ts +32 -0
  367. package/src/codegen/emit/declarations.ts +49 -0
  368. package/src/codegen/emit/format-types.ts +232 -0
  369. package/src/codegen/emit/http-stream-route.ts +162 -0
  370. package/src/codegen/emit/route-shared.ts +102 -0
  371. package/src/codegen/emit/rpc-route.ts +49 -0
  372. package/src/codegen/emit/scope-file.ts +226 -0
  373. package/src/codegen/emit/stream-route.ts +81 -0
  374. package/src/codegen/emit-errors.integration.test.ts +2 -2
  375. package/src/codegen/emit-errors.test.ts +1 -1
  376. package/src/codegen/emit-errors.ts +1 -1
  377. package/src/codegen/emit-scope.test.ts +2 -2
  378. package/src/codegen/emit-scope.ts +15 -1048
  379. package/src/codegen/goldens.test.ts +89 -0
  380. package/src/codegen/group-routes.test.ts +1 -1
  381. package/src/codegen/group-routes.ts +1 -1
  382. package/src/codegen/pipeline.test.ts +1 -1
  383. package/src/codegen/pipeline.ts +1 -1
  384. package/src/codegen/resolve-envelope.test.ts +1 -1
  385. package/src/codegen/resolve-envelope.ts +1 -1
  386. package/src/codegen/targets/_shared/error-schemas.test.ts +1 -1
  387. package/src/codegen/targets/_shared/error-schemas.ts +1 -1
  388. package/src/codegen/targets/_shared/route-slots.test.ts +1 -1
  389. package/src/codegen/targets/_shared/route-slots.ts +1 -1
  390. package/src/codegen/targets/_shared/target-run.ts +1 -1
  391. package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +1 -1
  392. package/src/codegen/targets/kotlin/emit-route-kotlin.ts +1 -1
  393. package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +1 -1
  394. package/src/codegen/targets/swift/access-level.test.ts +1 -1
  395. package/src/codegen/targets/swift/emit-route-swift.test.ts +1 -1
  396. package/src/codegen/targets/swift/emit-route-swift.ts +1 -1
  397. package/src/codegen/targets/swift/emit-scope-swift.test.ts +1 -1
  398. package/src/codegen/targets/ts/shared-models.test.ts +1 -1
  399. package/src/{create-http-stream.test.ts → core/create-http-stream.test.ts} +1 -1
  400. package/src/core/create-http-stream.ts +207 -0
  401. package/src/{create-http.test.ts → core/create-http.test.ts} +15 -4
  402. package/src/core/create-http.ts +126 -0
  403. package/src/{create-stream.test.ts → core/create-stream.test.ts} +28 -31
  404. package/src/core/create-stream.ts +142 -0
  405. package/src/{create.test.ts → core/create.test.ts} +25 -57
  406. package/src/core/create.ts +121 -0
  407. package/src/{stack-utils.test.ts → core/definition-site.test.ts} +14 -3
  408. package/src/{stack-utils.ts → core/definition-site.ts} +20 -23
  409. package/src/{errors.test.ts → core/errors.test.ts} +1 -1
  410. package/src/{errors.ts → core/errors.ts} +30 -28
  411. package/src/core/factory-options.test.ts +112 -0
  412. package/src/core/http-route.ts +73 -0
  413. package/src/core/internal.ts +203 -0
  414. package/src/{migration.test.ts → core/migration.test.ts} +23 -1
  415. package/src/{index.test.ts → core/procedures.test.ts} +13 -11
  416. package/src/core/procedures.ts +75 -0
  417. package/src/core/types.ts +195 -0
  418. package/src/exports.ts +60 -11
  419. package/src/schema/adapter.test.ts +58 -0
  420. package/src/schema/adapter.ts +45 -0
  421. package/src/schema/compile.test.ts +95 -0
  422. package/src/schema/compile.ts +64 -0
  423. package/src/schema/compute-schema.test.ts +222 -41
  424. package/src/schema/compute-schema.ts +145 -71
  425. package/src/schema/json-schema.ts +21 -0
  426. package/src/schema/typebox.test.ts +40 -0
  427. package/src/schema/typebox.ts +27 -0
  428. package/src/server/context.test.ts +22 -0
  429. package/src/server/context.ts +18 -0
  430. package/src/{doc-envelope.test.ts → server/doc-envelope.test.ts} +2 -2
  431. package/src/{doc-envelope.ts → server/doc-envelope.ts} +1 -1
  432. package/src/{implementations/http → server}/doc-registry.test.ts +32 -26
  433. package/src/{implementations/http → server}/doc-registry.ts +11 -7
  434. package/src/server/docs/docs.test.ts +287 -0
  435. package/src/{implementations/http/hono → server}/docs/http-doc.ts +3 -3
  436. package/src/{implementations/http/hono → server}/docs/http-stream-doc.ts +3 -3
  437. package/src/{implementations/http/hono → server}/docs/rpc-doc.ts +3 -3
  438. package/src/{implementations/http/hono → server}/docs/stream-doc.ts +3 -3
  439. package/src/server/errors/dispatch.test.ts +450 -0
  440. package/src/server/errors/dispatch.ts +189 -0
  441. package/src/{implementations/http/error-taxonomy.test.ts → server/errors/taxonomy.test.ts} +45 -39
  442. package/src/{implementations/http/error-taxonomy.ts → server/errors/taxonomy.ts} +8 -17
  443. package/src/server/index.ts +29 -0
  444. package/src/server/no-framework-imports.test.ts +43 -0
  445. package/src/server/paths.test.ts +141 -0
  446. package/src/{implementations/http/hono/path.ts → server/paths.ts} +2 -13
  447. package/src/server/request/params.test.ts +143 -0
  448. package/src/server/request/params.ts +68 -0
  449. package/src/server/request/query.test.ts +70 -0
  450. package/src/server/request/query.ts +24 -0
  451. package/src/server/sse.test.ts +113 -0
  452. package/src/server/sse.ts +117 -0
  453. package/src/{implementations → server}/types.ts +17 -16
  454. package/build/create-http-stream.d.ts +0 -58
  455. package/build/create-http-stream.js +0 -122
  456. package/build/create-http-stream.js.map +0 -1
  457. package/build/create-http-stream.test.js.map +0 -1
  458. package/build/create-http.d.ts +0 -49
  459. package/build/create-http.js +0 -108
  460. package/build/create-http.js.map +0 -1
  461. package/build/create-http.test.js.map +0 -1
  462. package/build/create-stream.d.ts +0 -35
  463. package/build/create-stream.js +0 -123
  464. package/build/create-stream.js.map +0 -1
  465. package/build/create-stream.test.js.map +0 -1
  466. package/build/create.d.ts +0 -28
  467. package/build/create.js +0 -82
  468. package/build/create.js.map +0 -1
  469. package/build/create.test.js.map +0 -1
  470. package/build/doc-envelope.js.map +0 -1
  471. package/build/doc-envelope.test.js.map +0 -1
  472. package/build/errors.js.map +0 -1
  473. package/build/errors.test.js.map +0 -1
  474. package/build/implementations/http/astro/astro-context.js.map +0 -1
  475. package/build/implementations/http/astro/create-handler.js.map +0 -1
  476. package/build/implementations/http/astro/index.js.map +0 -1
  477. package/build/implementations/http/astro/index.test.js.map +0 -1
  478. package/build/implementations/http/astro/rewrite-request.js.map +0 -1
  479. package/build/implementations/http/doc-registry.js.map +0 -1
  480. package/build/implementations/http/doc-registry.test.js.map +0 -1
  481. package/build/implementations/http/error-dispatch.d.ts +0 -76
  482. package/build/implementations/http/error-dispatch.js.map +0 -1
  483. package/build/implementations/http/error-dispatch.test.js +0 -254
  484. package/build/implementations/http/error-dispatch.test.js.map +0 -1
  485. package/build/implementations/http/error-taxonomy.js.map +0 -1
  486. package/build/implementations/http/error-taxonomy.test.js.map +0 -1
  487. package/build/implementations/http/hono/docs/http-doc.js.map +0 -1
  488. package/build/implementations/http/hono/docs/http-stream-doc.js.map +0 -1
  489. package/build/implementations/http/hono/docs/rpc-doc.js.map +0 -1
  490. package/build/implementations/http/hono/docs/stream-doc.js.map +0 -1
  491. package/build/implementations/http/hono/handlers/http-stream.js +0 -123
  492. package/build/implementations/http/hono/handlers/http-stream.js.map +0 -1
  493. package/build/implementations/http/hono/handlers/http-stream.test.js.map +0 -1
  494. package/build/implementations/http/hono/handlers/http.js +0 -110
  495. package/build/implementations/http/hono/handlers/http.js.map +0 -1
  496. package/build/implementations/http/hono/handlers/http.test.js.map +0 -1
  497. package/build/implementations/http/hono/handlers/rpc.js +0 -32
  498. package/build/implementations/http/hono/handlers/rpc.js.map +0 -1
  499. package/build/implementations/http/hono/handlers/rpc.test.js.map +0 -1
  500. package/build/implementations/http/hono/handlers/stream.d.ts +0 -23
  501. package/build/implementations/http/hono/handlers/stream.js +0 -147
  502. package/build/implementations/http/hono/handlers/stream.js.map +0 -1
  503. package/build/implementations/http/hono/handlers/stream.test.js.map +0 -1
  504. package/build/implementations/http/hono/index.js.map +0 -1
  505. package/build/implementations/http/hono/index.test.js.map +0 -1
  506. package/build/implementations/http/hono/path.js.map +0 -1
  507. package/build/implementations/http/hono/path.test.js +0 -83
  508. package/build/implementations/http/hono/path.test.js.map +0 -1
  509. package/build/implementations/http/hono/types.d.ts +0 -51
  510. package/build/implementations/http/hono/types.js.map +0 -1
  511. package/build/implementations/http/on-request-error.test.js.map +0 -1
  512. package/build/implementations/http/route-errors.test.js.map +0 -1
  513. package/build/index.d.ts +0 -175
  514. package/build/index.js +0 -47
  515. package/build/index.js.map +0 -1
  516. package/build/index.test.js.map +0 -1
  517. package/build/migration.test.js.map +0 -1
  518. package/build/schema/extract-json-schema.d.ts +0 -2
  519. package/build/schema/extract-json-schema.js +0 -12
  520. package/build/schema/extract-json-schema.js.map +0 -1
  521. package/build/schema/extract-json-schema.test.js +0 -23
  522. package/build/schema/extract-json-schema.test.js.map +0 -1
  523. package/build/schema/parser.d.ts +0 -36
  524. package/build/schema/parser.js +0 -210
  525. package/build/schema/parser.js.map +0 -1
  526. package/build/schema/parser.test.js +0 -120
  527. package/build/schema/parser.test.js.map +0 -1
  528. package/build/schema/resolve-schema-lib.d.ts +0 -12
  529. package/build/schema/resolve-schema-lib.js +0 -11
  530. package/build/schema/resolve-schema-lib.js.map +0 -1
  531. package/build/schema/resolve-schema-lib.test.js +0 -17
  532. package/build/schema/resolve-schema-lib.test.js.map +0 -1
  533. package/build/schema/types.d.ts +0 -8
  534. package/build/schema/types.js +0 -2
  535. package/build/stack-utils.d.ts +0 -25
  536. package/build/stack-utils.js.map +0 -1
  537. package/build/stack-utils.test.js.map +0 -1
  538. package/build/types.d.ts +0 -142
  539. package/build/types.js +0 -2
  540. package/build/types.js.map +0 -1
  541. package/docs/decisions/2026-06-02-monorepo-split-evaluation.md +0 -80
  542. package/docs/handoffs/2026-06-08-dx-round2-declines.md +0 -45
  543. package/docs/handoffs/ajsc-named-type-collision.md +0 -134
  544. package/docs/handoffs/ajsc-named-type-support.md +0 -181
  545. package/docs/handoffs/shared-models-auto-resolve-response.md +0 -181
  546. package/docs/npm-workspaces-migration-plan.md +0 -611
  547. package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +0 -886
  548. package/docs/superpowers/plans/2026-04-24-kotlin-codegen-target.md +0 -1265
  549. package/docs/superpowers/plans/2026-04-25-ajsc-v7-kotlin-polish.md +0 -1993
  550. package/docs/superpowers/plans/2026-04-29-safe-result-api.md +0 -2293
  551. package/docs/superpowers/plans/2026-05-07-astro-adapter.md +0 -1391
  552. package/docs/superpowers/plans/2026-05-08-create-http.md +0 -3355
  553. package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +0 -3365
  554. package/docs/superpowers/plans/2026-06-05-dx-feedback-round.md +0 -1292
  555. package/docs/superpowers/plans/2026-06-06-shared-models-convention-and-diagnostics.md +0 -659
  556. package/docs/superpowers/plans/2026-06-08-codegen-dx-surfacing.md +0 -428
  557. package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +0 -401
  558. package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +0 -314
  559. package/docs/superpowers/specs/2026-04-25-swift-codegen-design.md +0 -264
  560. package/docs/superpowers/specs/2026-04-29-safe-result-api-design.md +0 -324
  561. package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +0 -252
  562. package/docs/superpowers/specs/2026-05-08-create-http-design.md +0 -409
  563. package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +0 -411
  564. package/docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md +0 -285
  565. package/docs/superpowers/specs/2026-06-08-dx-feedback-round-2-design.md +0 -376
  566. package/src/create-http-stream.ts +0 -191
  567. package/src/create-http.ts +0 -210
  568. package/src/create-stream.ts +0 -228
  569. package/src/create.ts +0 -172
  570. package/src/implementations/http/README.md +0 -390
  571. package/src/implementations/http/error-dispatch.test.ts +0 -283
  572. package/src/implementations/http/error-dispatch.ts +0 -176
  573. package/src/implementations/http/hono/handlers/http-stream.ts +0 -152
  574. package/src/implementations/http/hono/handlers/http.ts +0 -145
  575. package/src/implementations/http/hono/handlers/rpc.ts +0 -54
  576. package/src/implementations/http/hono/path.test.ts +0 -96
  577. package/src/index.ts +0 -101
  578. package/src/schema/extract-json-schema.test.ts +0 -25
  579. package/src/schema/extract-json-schema.ts +0 -15
  580. package/src/schema/parser.test.ts +0 -182
  581. package/src/schema/parser.ts +0 -265
  582. package/src/schema/resolve-schema-lib.test.ts +0 -19
  583. package/src/schema/resolve-schema-lib.ts +0 -29
  584. package/src/schema/types.ts +0 -20
  585. package/src/types.ts +0 -133
  586. /package/build/{implementations/http → adapters}/astro/astro-context.d.ts +0 -0
  587. /package/build/{implementations/http → adapters}/astro/astro-context.js +0 -0
  588. /package/build/{implementations/http → adapters}/astro/create-handler.d.ts +0 -0
  589. /package/build/{implementations/http → adapters}/astro/create-handler.js +0 -0
  590. /package/build/{implementations/http → adapters}/astro/index.d.ts +0 -0
  591. /package/build/{implementations/http → adapters}/astro/index.js +0 -0
  592. /package/build/{implementations/http → adapters}/astro/index.test.d.ts +0 -0
  593. /package/build/{implementations/http → adapters}/astro/rewrite-request.d.ts +0 -0
  594. /package/build/{implementations/http → adapters}/astro/rewrite-request.js +0 -0
  595. /package/build/{create-http-stream.test.d.ts → adapters/hono/envelope-parity.test.d.ts} +0 -0
  596. /package/build/{implementations/http → adapters}/hono/handlers/http-stream.test.d.ts +0 -0
  597. /package/build/{implementations/http → adapters}/hono/handlers/http.test.d.ts +0 -0
  598. /package/build/{implementations/http → adapters}/hono/handlers/rpc.test.d.ts +0 -0
  599. /package/build/{implementations/http → adapters}/hono/handlers/stream.test.d.ts +0 -0
  600. /package/build/{implementations/http → adapters}/hono/index.test.d.ts +0 -0
  601. /package/build/{implementations/http → adapters/hono}/on-request-error.test.d.ts +0 -0
  602. /package/build/{implementations/http → adapters/hono}/route-errors.test.d.ts +0 -0
  603. /package/build/{create-http.test.d.ts → client/freeze.test.d.ts} +0 -0
  604. /package/build/{create-stream.test.d.ts → codegen/goldens.test.d.ts} +0 -0
  605. /package/build/{create.test.d.ts → core/create-http-stream.test.d.ts} +0 -0
  606. /package/build/{doc-envelope.test.d.ts → core/create-http.test.d.ts} +0 -0
  607. /package/build/{errors.test.d.ts → core/create-stream.test.d.ts} +0 -0
  608. /package/build/{implementations/http/doc-registry.test.d.ts → core/create.test.d.ts} +0 -0
  609. /package/build/{implementations/http/error-dispatch.test.d.ts → core/definition-site.test.d.ts} +0 -0
  610. /package/build/{implementations/http/error-taxonomy.test.d.ts → core/errors.test.d.ts} +0 -0
  611. /package/build/{errors.test.js → core/errors.test.js} +0 -0
  612. /package/build/{implementations/http/hono/path.test.d.ts → core/factory-options.test.d.ts} +0 -0
  613. /package/build/{migration.test.d.ts → core/migration.test.d.ts} +0 -0
  614. /package/build/{index.test.d.ts → core/procedures.test.d.ts} +0 -0
  615. /package/build/{implementations/http/hono → core}/types.js +0 -0
  616. /package/build/schema/{extract-json-schema.test.d.ts → adapter.test.d.ts} +0 -0
  617. /package/build/schema/{parser.test.d.ts → compile.test.d.ts} +0 -0
  618. /package/build/schema/{resolve-schema-lib.test.d.ts → typebox.test.d.ts} +0 -0
  619. /package/build/{stack-utils.test.d.ts → server/context.test.d.ts} +0 -0
  620. /package/build/{doc-envelope.js → server/doc-envelope.js} +0 -0
  621. /package/build/{doc-envelope.test.js → server/doc-envelope.test.js} +0 -0
  622. /package/build/{implementations → server}/types.js +0 -0
  623. /package/src/{implementations/http → adapters}/astro/README.md +0 -0
  624. /package/src/{implementations/http → adapters}/astro/astro-context.ts +0 -0
  625. /package/src/{implementations/http → adapters}/astro/create-handler.ts +0 -0
  626. /package/src/{implementations/http → adapters}/astro/index.ts +0 -0
  627. /package/src/{implementations/http → adapters}/astro/rewrite-request.ts +0 -0
@@ -1,1049 +1,16 @@
1
- import type { ScopeGroup } from './group-routes.js'
2
- import type {
3
- RPCHttpRouteDoc,
4
- APIHttpRouteDoc,
5
- StreamHttpRouteDoc,
6
- HttpStreamRouteDoc,
7
- } from '../implementations/types.js'
8
- import {
9
- jsonSchemaToTypeString,
10
- jsonSchemaToTypeBody,
11
- jsonSchemaToTypeBodyWithRefs,
12
- jsonSchemaToExtractedTypes,
13
- renameExtractedTypes,
14
- extractedDeclName,
15
- type AjscOptions,
16
- type ExtractedTypeOutput,
17
- } from './emit-types.js'
18
- import { substituteModelRefs } from './model-refs.js'
19
- import { CODEGEN_HEADER } from './constants.js'
20
- import { toPascalCase } from './naming.js'
21
-
22
- // ---------------------------------------------------------------------------
23
- // Types
24
- // ---------------------------------------------------------------------------
25
-
26
- export interface EmitScopeOptions {
27
- ajsc?: AjscOptions
28
- clientImportPath?: string
29
- namespaceTypes?: boolean
30
- /** Service identifier used to namespace generated error types (defaults to 'Api'). */
31
- serviceName?: string
32
- /**
33
- * Error keys present in the generated `_errors.ts`. Routes may list keys
34
- * that aren't emitted (e.g. no schema); those are filtered out at emit time
35
- * so generated code never references undefined types.
36
- */
37
- errorKeys?: Set<string>
38
- /**
39
- * Maps a model `$id` to its shared model type name (built by the run module
40
- * from ALL models — generated and imported). When present and non-empty,
41
- * `$id` subschemas are rewritten to `x-named-type` nodes before ajsc; ajsc
42
- * emits bare references and reports them via `referencedNamedTypes`, which
43
- * drives the `import type { … } from './_models'` line. Absent/empty →
44
- * identical inlining behaviour (the conversion wrappers short-circuit so
45
- * output is byte-identical for envelopes without models).
46
- */
47
- idToModelName?: Map<string, string>
48
- }
49
-
50
- interface RouteChunks {
51
- typeDeclarations: string[]
52
- callable: string
53
- hasStream: boolean
54
- /** True when this route emitted an `Errors` type (drives the `_errors` import at the top of the scope file). */
55
- hasErrors: boolean
56
- }
57
-
58
- interface EmitRouteContext {
59
- ajsc?: AjscOptions
60
- namespaceTypes: boolean
61
- scopePascal: string
62
- serviceName: string
63
- errorKeys?: Set<string>
64
- /** `$id` → shared model type name; drives the substitute wrappers. */
65
- idToModelName?: Map<string, string>
66
- /**
67
- * Accumulates the model names ajsc reported as `referencedNamedTypes` across
68
- * every route in the scope. Drives the `import type { … } from './_models'`
69
- * line assembled in `emitScopeFile`.
70
- */
71
- referencedModels: Set<string>
72
- }
73
-
74
- // ---------------------------------------------------------------------------
75
- // Helpers
76
- // ---------------------------------------------------------------------------
77
-
78
1
  /**
79
- * Infers the route kind when the `kind` discriminant is missing.
80
- * This provides backward compatibility with servers running older ts-procedures
81
- * versions that don't set `kind` on route docs.
82
- */
83
- function inferRouteKind(route: Record<string, unknown>): 'rpc' | 'api' | 'stream' | 'http-stream' {
84
- if ('streamMode' in route && 'fullPath' in route) return 'http-stream'
85
- if ('streamMode' in route) return 'stream'
86
- if ('fullPath' in route) return 'api'
87
- return 'rpc'
88
- }
89
-
90
- /**
91
- * Checks whether a JSON schema looks like an SSE envelope.
92
- * SSE envelopes have properties: data, event, id (and optionally retry).
93
- */
94
- function isSseEnvelope(schema: Record<string, unknown>): boolean {
95
- const props = schema.properties
96
- if (props == null || typeof props !== 'object') return false
97
- const keys = Object.keys(props as Record<string, unknown>)
98
- return keys.includes('data') && keys.includes('event') && keys.includes('id')
99
- }
100
-
101
- /**
102
- * Unwraps an SSE envelope schema to return the inner `data` property schema.
103
- * If not an SSE envelope, returns the schema unchanged.
104
- */
105
- function unwrapSseEnvelope(
106
- schema: Record<string, unknown>
107
- ): Record<string, unknown> {
108
- if (!isSseEnvelope(schema)) return schema
109
- const props = schema.properties as Record<string, Record<string, unknown>>
110
- const dataSchema = props['data']
111
- return dataSchema ?? schema
112
- }
113
-
114
- /**
115
- * Returns the PascalCase display name for a route, appending `V{version}`
116
- * when version > 1. Version 1 produces no suffix for backward compatibility.
117
- */
118
- function versionedPascal(name: string, version: number | undefined): string {
119
- const pascal = toPascalCase(name)
120
- if (version != null && version > 1) return `${pascal}V${version}`
121
- return pascal
122
- }
123
-
124
- // ---------------------------------------------------------------------------
125
- // Shared type formatting helpers
126
- // ---------------------------------------------------------------------------
127
-
128
- /** Indent each line of a multi-line string by a given prefix. */
129
- function indent(text: string, prefix: string): string {
130
- return text.split('\n').map((line) => (line ? prefix + line : line)).join('\n')
131
- }
132
-
133
- // ---------------------------------------------------------------------------
134
- // Shared-model conversion wrappers (ajsc x-named-type)
135
- // ---------------------------------------------------------------------------
136
-
137
- /**
138
- * Drop-in replacement for `jsonSchemaToExtractedTypes` that, when shared models
139
- * are in play, rewrites every `$id` subschema into an `x-named-type` node before
140
- * ajsc. ajsc emits each as a bare verbatim reference and reports it via
141
- * `referencedNamedTypes`; this wrapper folds those names into
142
- * `ctx.referencedModels` so `emitScopeFile` can build the `_models` import.
143
- *
144
- * Short-circuits to the exact original call when no models are configured, so
145
- * output is byte-identical for envelopes without `$id` models.
146
- */
147
- async function convertExtracted(
148
- schema: Record<string, unknown>,
149
- ctx: EmitRouteContext,
150
- ): Promise<ExtractedTypeOutput | undefined> {
151
- if (ctx.idToModelName == null || ctx.idToModelName.size === 0) {
152
- return jsonSchemaToExtractedTypes(schema, ctx.ajsc)
153
- }
154
- const { schema: sub } = substituteModelRefs(schema, ctx.idToModelName)
155
- const result = await jsonSchemaToExtractedTypes(sub, ctx.ajsc)
156
- if (result != null) {
157
- assertNoModelNameCollision(result)
158
- for (const name of result.referencedNamedTypes) ctx.referencedModels.add(name)
159
- }
160
- return result
161
- }
162
-
163
- /**
164
- * Guards ajsc's documented `x-named-type` collision caveat. If a shared-model
165
- * name (referenced via `x-named-type`) also matches the name ajsc derives for a
166
- * sibling *structural* sub-type, ajsc silently MERGES them — the model reference
167
- * resolves to the unrelated structural type. The merge happens inside ajsc's own
168
- * pass, so it can't be disentangled afterward (a post-hoc rename would rewrite
169
- * the model reference too, producing a silently-wrong type). Instead we detect
170
- * the collision — a name present in BOTH `referencedNamedTypes` AND
171
- * `extractedTypeNames` (ajsc's own documented collision signal) — and fail fast
172
- * with an actionable message. The throw is wrapped with the route's name/scope
173
- * by `emitScopeFile`.
174
- */
175
- function assertNoModelNameCollision(result: ExtractedTypeOutput): void {
176
- if (result.referencedNamedTypes.length === 0 || result.extractedTypeNames.length === 0) return
177
- const extracted = new Set(result.extractedTypeNames)
178
- const collisions = result.referencedNamedTypes.filter((name) => extracted.has(name))
179
- if (collisions.length === 0) return
180
- const names = collisions.map((n) => `'${n}'`).join(', ')
181
- throw new Error(
182
- `shared model ${names} collides with a generated type of the same name derived ` +
183
- `from a property in this route's schema — ajsc would silently merge them. Rename ` +
184
- `the colliding property, or change the model's $id/title so the generated names differ`,
185
- )
186
- }
187
-
188
- /**
189
- * Drop-in replacement for `jsonSchemaToTypeBody` mirroring {@link convertExtracted}
190
- * for the bare-body (flat / merged-alias) conversion path.
191
- */
192
- async function convertBody(
193
- schema: Record<string, unknown>,
194
- ctx: EmitRouteContext,
195
- ): Promise<string | undefined> {
196
- if (ctx.idToModelName == null || ctx.idToModelName.size === 0) {
197
- return jsonSchemaToTypeBody(schema, ctx.ajsc)
198
- }
199
- const { schema: sub } = substituteModelRefs(schema, ctx.idToModelName)
200
- const result = await jsonSchemaToTypeBodyWithRefs(sub, ctx.ajsc)
201
- if (result == null) return undefined
202
- for (const name of result.referencedNamedTypes) ctx.referencedModels.add(name)
203
- return result.body
204
- }
205
-
206
- /**
207
- * Tracks extracted declarations emitted into a single namespace, guarding
208
- * against duplicate identifiers (defense-in-depth on top of the rename pass).
209
- *
210
- * - Exact-string duplicates (the same sub-type extracted from two schemas) are
211
- * silently skipped.
212
- * - A same-name-but-different-body declaration is a genuine collision the
213
- * rename pass failed to resolve; emitting it would produce an opaque
214
- * `TS2300: Duplicate identifier` in the consumer's build. We fail fast at
215
- * codegen with a message that names the offending identifier instead.
216
- *
217
- * Returns the indented declaration line to push, or `null` when it should be
218
- * skipped (exact duplicate).
219
- */
220
- class DeclarationCollector {
221
- private readonly seenStrings = new Set<string>()
222
- private readonly seenNames = new Map<string, string>()
223
-
224
- constructor(private readonly context: string) {}
225
-
226
- /** Returns the indented line to emit, or `null` for an exact duplicate. */
227
- accept(decl: string, indentPrefix: string): string | null {
228
- if (this.seenStrings.has(decl)) return null
229
- const name = extractedDeclName(decl)
230
- if (name != null) {
231
- if (this.seenNames.has(name)) {
232
- throw new Error(
233
- `[ts-procedures-codegen] duplicate identifier '${name}' while emitting ${this.context}. ` +
234
- `An extracted sub-type collided with another of the same name and could not be renamed. ` +
235
- `This is a codegen bug — please report it with the offending schema.`,
236
- )
237
- }
238
- this.seenNames.set(name, decl)
239
- }
240
- this.seenStrings.add(decl)
241
- return indent(decl, indentPrefix)
242
- }
243
- }
244
-
245
- interface NamedType {
246
- /** Short name for namespace mode (e.g., 'Params', 'Response'). */
247
- shortName: string
248
- /** Schema to convert, or undefined if not present. */
249
- schema: Record<string, unknown> | undefined
250
- }
251
-
252
- interface FormattedTypes {
253
- /** Type declarations to add to the file. */
254
- declarations: string[]
255
- /** Map of shortName → qualified type reference (for callables). */
256
- refs: Record<string, string>
257
- }
258
-
259
- /**
260
- * Converts multiple schemas into type declarations and type references.
261
- * In flat mode: `export type ${routePascal}${shortName} = <body>`
262
- * In namespace mode: extracted sub-types + named types inside `export namespace ${routePascal} { ... }`
263
- *
264
- * `extraReserved` lets the caller pre-reserve identifier names that the route
265
- * will inject AFTER formatTypes returns (e.g. emitApiRoute's structured
266
- * `Params`). Without this, an ajsc-extracted sub-type could shadow the
267
- * injected one and produce duplicate `export type Params` declarations.
268
- */
269
- async function formatTypes(
270
- routePascal: string,
271
- types: NamedType[],
272
- ctx: EmitRouteContext,
273
- extraReserved?: ReadonlySet<string>,
274
- ): Promise<FormattedTypes> {
275
- const declarations: string[] = []
276
- const refs: Record<string, string> = {}
277
-
278
- if (ctx.namespaceTypes) {
279
- const nsLines: string[] = []
280
- const collector = new DeclarationCollector(`namespace ${routePascal}`)
281
-
282
- // Pre-reserve every name the route will declare itself (each shortName +
283
- // any caller-supplied extras). Extracted sub-types whose names land in
284
- // this set get renamed (e.g. `Params` → `Params_`) by renameExtractedTypes,
285
- // and the body string is patched in lockstep so the reference still
286
- // resolves. The set is mutated as we go, so a sub-type renamed in schema A
287
- // is also reserved against schema B.
288
- const taken = new Set<string>(extraReserved ?? [])
289
- for (const t of types) {
290
- if (t.schema != null) taken.add(t.shortName)
291
- }
292
-
293
- for (const { shortName, schema } of types) {
294
- if (schema == null) continue
295
-
296
- const rawResult = await convertExtracted(schema, ctx)
297
- if (rawResult == null) continue
298
-
299
- const result = renameExtractedTypes(rawResult, taken)
300
-
301
- // Collect extracted sub-types (dedupe exact dups; throw on real collisions)
302
- for (const decl of result.declarations) {
303
- const line = collector.accept(decl, ' ')
304
- if (line != null) nsLines.push(line)
305
- }
306
-
307
- nsLines.push(` export type ${shortName} = ${result.body}`)
308
- refs[shortName] = `${ctx.scopePascal}.${routePascal}.${shortName}`
309
- }
310
-
311
- if (nsLines.length > 0) {
312
- declarations.push(` export namespace ${routePascal} {\n${nsLines.join('\n')}\n }`)
313
- }
314
- } else {
315
- for (const { shortName, schema } of types) {
316
- if (schema == null) continue
317
-
318
- const flatName = `${routePascal}${shortName}`
319
- const body = await convertBody(schema, ctx)
320
- if (body == null) continue
321
-
322
- declarations.push(`export type ${flatName} = ${body}`)
323
- refs[shortName] = flatName
324
- }
325
- }
326
-
327
- return { declarations, refs }
328
- }
329
-
330
- // ---------------------------------------------------------------------------
331
- // Route-level Errors union injection
332
- // ---------------------------------------------------------------------------
333
-
334
- /**
335
- * Builds the body of an `Errors` type union from the route's declared error
336
- * keys. Filters to keys actually emitted in `_errors.ts` so generated code
337
- * never references undefined types.
338
- *
339
- * In namespace mode the union uses qualified names (`ApiErrors.UseCaseError`);
340
- * in flat mode it uses the bundled wildcard import alias (`_errors.UseCaseError`).
341
- * Returns `null` when no keys remain.
342
- */
343
- function buildErrorUnion(
344
- routeErrors: string[] | undefined,
345
- ctx: EmitRouteContext
346
- ): string | null {
347
- if (!routeErrors || routeErrors.length === 0) return null
348
- const available = ctx.errorKeys
349
- const filtered = available ? routeErrors.filter((k) => available.has(k)) : routeErrors
350
- if (filtered.length === 0) return null
351
- const qualify = ctx.namespaceTypes
352
- ? (k: string) => `${toPascalCase(ctx.serviceName)}Errors.${k}`
353
- : (k: string) => `_errors.${k}`
354
- return filtered.map(qualify).join(' | ')
355
- }
356
-
357
- /**
358
- * Injects `export type Errors = ...` into an existing route namespace block
359
- * (namespace mode) or appends a flat `export type ${pascal}Errors = ...` in
360
- * flat mode. Mutates the `declarations` array in place and returns whether an
361
- * injection happened.
362
- */
363
- function injectRouteErrors(
364
- declarations: string[],
365
- routePascal: string,
366
- errorUnion: string | null,
367
- namespaceTypes: boolean
368
- ): boolean {
369
- if (!errorUnion) return false
370
- if (namespaceTypes) {
371
- const lastIdx = declarations.length - 1
372
- if (lastIdx < 0) return false
373
- const lastDecl = declarations[lastIdx]!
374
- const closingIdx = lastDecl.lastIndexOf(' }')
375
- if (closingIdx === -1) return false
376
- declarations[lastIdx] =
377
- lastDecl.slice(0, closingIdx) +
378
- ` export type Errors = ${errorUnion}\n` +
379
- lastDecl.slice(closingIdx)
380
- return true
381
- }
382
- declarations.push(`export type ${routePascal}Errors = ${errorUnion}`)
383
- return true
384
- }
385
-
386
- // ---------------------------------------------------------------------------
387
- // Route emitters
388
- // ---------------------------------------------------------------------------
389
-
390
- /**
391
- * Builds the multi-line JSDoc comment for a route callable. Surfaces the
392
- * second `options` argument (the per-call AbortSignal/timeout seam — DX #8) and,
393
- * for routes that declare typed errors, points at the route's `Errors` type and
394
- * how to narrow it on the throwing path (DX #10). Indented for placement inside
395
- * the bind-object (4 spaces).
396
- */
397
- function buildCallableJsDoc(opts: {
398
- methodLabel: string
399
- path: string
400
- errorsRef: string | null
401
- }): string {
402
- const lines = [
403
- ` /**`,
404
- ` * ${opts.methodLabel} ${opts.path}`,
405
- ` *`,
406
- ` * @param options Optional per-call {@link ProcedureCallOptions} —`,
407
- ` * \`signal\` (cancel on dispose), \`timeout\`, \`headers\`, \`basePath\`.`,
408
- ]
409
- if (opts.errorsRef) {
410
- lines.push(
411
- ` * @throws Declared typed errors: {@link ${opts.errorsRef}}. Narrow with`,
412
- ` * \`instanceof\` on the throwing path, or call \`.safe()\` for a \`Result\`.`,
413
- )
414
- }
415
- lines.push(` */`)
416
- return lines.join('\n')
417
- }
418
-
419
- async function emitRpcRoute(route: RPCHttpRouteDoc, ctx: EmitRouteContext): Promise<RouteChunks> {
420
- const pascal = versionedPascal(route.name, route.version)
421
-
422
- const { declarations, refs } = await formatTypes(pascal, [
423
- { shortName: 'Params', schema: route.jsonSchema.body },
424
- { shortName: 'Response', schema: route.jsonSchema.response },
425
- ], ctx)
426
-
427
- const paramsTypeName = refs['Params'] ?? 'void'
428
- const responseTypeName = refs['Response'] ?? 'unknown'
429
- const scopeStr = Array.isArray(route.scope) ? route.scope.join('-') : route.scope
430
-
431
- const errorUnion = buildErrorUnion(route.errors, ctx)
432
- const hasErrors = errorUnion !== null
433
- const errorsRef = ctx.namespaceTypes
434
- ? `${ctx.scopePascal}.${pascal}.Errors`
435
- : `${pascal}Errors`
436
- const helperCall = hasErrors
437
- ? `client.bindCallableTyped<${paramsTypeName}, ${responseTypeName}, ${errorsRef}>`
438
- : `client.bindCallable<${paramsTypeName}, ${responseTypeName}>`
439
-
440
- const callable = [
441
- buildCallableJsDoc({
442
- methodLabel: route.method.toUpperCase(),
443
- path: route.path,
444
- errorsRef: hasErrors ? errorsRef : null,
445
- }),
446
- ` ${pascal}: ${helperCall}({`,
447
- ` name: '${pascal}',`,
448
- ` scope: '${scopeStr}',`,
449
- ` path: '${route.path}',`,
450
- ` method: '${route.method}',`,
451
- ` kind: 'rpc',`,
452
- ` }),`,
453
- ].join('\n')
454
-
455
- const hasErrorsInjected = injectRouteErrors(declarations, pascal, errorUnion, ctx.namespaceTypes)
456
-
457
- return { typeDeclarations: declarations, callable, hasStream: false, hasErrors: hasErrorsInjected }
458
- }
459
-
460
- /**
461
- * Formats a group of named types into a nested sub-namespace block (for namespace mode).
462
- * Returns an array of lines to be inserted into the parent namespace, and a map of
463
- * shortName → qualified type reference for use in callables.
464
- *
465
- * In flat mode, returns declarations like `export type ${prefix}${shortName} = ...`
466
- * and refs like `${prefix}${shortName}`.
467
- */
468
- async function formatSubNamespace(
469
- routePascal: string,
470
- nsName: string, // e.g. 'Req' or 'Response'
471
- types: NamedType[],
472
- ctx: EmitRouteContext,
473
- taken: Set<string>,
474
- ): Promise<{ nsBlock: string | null; refs: Record<string, string> }> {
475
- const refs: Record<string, string> = {}
476
- const nsLines: string[] = []
477
- const collector = new DeclarationCollector(`namespace ${routePascal}.${nsName}`)
478
-
479
- // Pre-reserve short names to prevent sub-type extraction collision
480
- for (const t of types) {
481
- if (t.schema != null) taken.add(t.shortName)
482
- }
483
-
484
- for (const { shortName, schema } of types) {
485
- if (schema == null) continue
486
-
487
- if (ctx.namespaceTypes) {
488
- const rawResult = await convertExtracted(schema, ctx)
489
- if (rawResult == null) continue
490
-
491
- const result = renameExtractedTypes(rawResult, taken)
492
-
493
- for (const decl of result.declarations) {
494
- const line = collector.accept(decl, ' ')
495
- if (line != null) nsLines.push(line)
496
- }
497
-
498
- nsLines.push(` export type ${shortName} = ${result.body}`)
499
- refs[shortName] = `${ctx.scopePascal}.${routePascal}.${nsName}.${shortName}`
500
- } else {
501
- const flatName = `${routePascal}${nsName}${shortName}`
502
- const body = await convertBody(schema, ctx)
503
- if (body == null) continue
504
- refs[shortName] = flatName
505
- }
506
- }
507
-
508
- if (ctx.namespaceTypes) {
509
- if (nsLines.length === 0) return { nsBlock: null, refs }
510
- const nsBlock = ` export namespace ${nsName} {\n${nsLines.join('\n')}\n }`
511
- return { nsBlock, refs }
512
- }
513
-
514
- return { nsBlock: null, refs }
515
- }
516
-
517
- /**
518
- * Builds the conditional return type string for an API or http-stream callable.
519
- *
520
- * - Both body + headers → `{ body: <Body>; headers: <Headers> }`
521
- * - Only body → `<Body>`
522
- * - Only headers → `{ headers: <Headers> }`
523
- * - Neither → `void`
524
- */
525
- function buildApiReturnType(
526
- bodyRef: string | undefined,
527
- headersRef: string | undefined,
528
- ): string {
529
- if (bodyRef && headersRef) {
530
- return `{ body: ${bodyRef}; headers: ${headersRef} }`
531
- }
532
- if (bodyRef) return bodyRef
533
- if (headersRef) return `{ headers: ${headersRef} }`
534
- return 'void'
535
- }
536
-
537
- async function emitApiRoute(route: APIHttpRouteDoc, ctx: EmitRouteContext): Promise<RouteChunks> {
538
- const pascal = toPascalCase(route.name)
539
- const req = route.jsonSchema.req ?? {}
540
- const res = route.jsonSchema.res ?? {}
541
-
542
- // Request channels
543
- const reqChannelKeys = ['pathParams', 'query', 'body', 'headers'] as const
544
- const reqTypes: NamedType[] = []
545
- const presentChannels: string[] = []
546
-
547
- for (const channel of reqChannelKeys) {
548
- const schema = req[channel]
549
- if (schema != null) {
550
- reqTypes.push({ shortName: toPascalCase(channel), schema })
551
- presentChannels.push(channel)
552
- }
553
- }
554
-
555
- // Response slots
556
- const resTypes: NamedType[] = [
557
- { shortName: 'Body', schema: res.body },
558
- { shortName: 'Headers', schema: res.headers },
559
- ]
560
-
561
- const scopeStr = route.scope ?? 'default'
562
- const errorUnion = buildErrorUnion(route.errors, ctx)
563
- const hasErrors = errorUnion !== null
564
- const errorsRef = ctx.namespaceTypes
565
- ? `${ctx.scopePascal}.${pascal}.Errors`
566
- : `${pascal}Errors`
567
-
568
- const declarations: string[] = []
569
- let paramsTypeName = 'void'
570
- let returnTypeName = 'void'
571
-
572
- // Track reserved names across all sub-namespaces. Model names are reserved
573
- // so an ajsc-extracted sub-type can't silently merge with a referenced model.
574
- const taken = new Set<string>(['Req', 'Response'])
575
-
576
- if (ctx.namespaceTypes) {
577
- // Namespace mode: emit nested Req {} and Response {} namespaces inside route namespace.
578
- // Also emit merged type aliases `export type Req = { ... }` and `export type Response = ...`
579
- // so they can be used as type arguments to bindCallable (TS requires a TYPE, not a namespace).
580
- const nsLines: string[] = []
581
-
582
- const { nsBlock: reqBlock, refs: reqRefs } = await formatSubNamespace(
583
- pascal, 'Req', reqTypes, ctx, taken
584
- )
585
- if (reqBlock) {
586
- nsLines.push(reqBlock)
587
- // Merged type alias for Req so it can be used as a generic type arg
588
- const reqFields = presentChannels
589
- .map((ch) => `${ch}: Req.${toPascalCase(ch)}`)
590
- .join('; ')
591
- nsLines.push(` export type Req = { ${reqFields} }`)
592
- }
593
-
594
- const { nsBlock: resBlock, refs: resRefs } = await formatSubNamespace(
595
- pascal, 'Response', resTypes, ctx, taken
596
- )
597
- if (resBlock) {
598
- nsLines.push(resBlock)
599
- // No merged Response type alias needed: we reference Response.Body / Response.Headers
600
- // directly in the return type string, which are namespace-qualified paths (valid).
601
- }
602
-
603
- // Emit Errors type last (injected by injectRouteErrors below)
604
- // Build the route namespace block
605
- if (nsLines.length > 0) {
606
- declarations.push(` export namespace ${pascal} {\n${nsLines.join('\n\n')}\n }`)
607
- }
608
-
609
- // Params type: use the merged Req type alias
610
- if (presentChannels.length > 0) {
611
- paramsTypeName = `${ctx.scopePascal}.${pascal}.Req`
612
- }
613
-
614
- // Return type
615
- const bodyRef = resRefs['Body']
616
- const headersRef = resRefs['Headers']
617
- returnTypeName = buildApiReturnType(bodyRef, headersRef)
618
- } else {
619
- // Flat mode: emit individual types prefixed with route + sub-namespace name
620
- for (const { shortName, schema } of reqTypes) {
621
- if (schema == null) continue
622
- const flatName = `${pascal}Req${shortName}`
623
- const body = await convertBody(schema, ctx)
624
- if (body == null) continue
625
- declarations.push(`export type ${flatName} = ${body}`)
626
- }
627
-
628
- // Flat mode: compose structured Req type
629
- if (presentChannels.length > 0) {
630
- const structureFields = presentChannels
631
- .map((ch) => `${ch}: ${pascal}Req${toPascalCase(ch)}`)
632
- .join('; ')
633
- declarations.push(`export type ${pascal}Req = { ${structureFields} }`)
634
- paramsTypeName = `${pascal}Req`
635
- }
636
-
637
- // Flat mode: emit response types
638
- let bodyRef: string | undefined
639
- let headersRef: string | undefined
640
-
641
- for (const { shortName, schema } of resTypes) {
642
- if (schema == null) continue
643
- const flatName = `${pascal}Response${shortName}`
644
- const body = await convertBody(schema, ctx)
645
- if (body == null) continue
646
- declarations.push(`export type ${flatName} = ${body}`)
647
- if (shortName === 'Body') bodyRef = flatName
648
- if (shortName === 'Headers') headersRef = flatName
649
- }
650
-
651
- returnTypeName = buildApiReturnType(bodyRef, headersRef)
652
-
653
- // Flat mode errors
654
- if (errorUnion) {
655
- declarations.push(`export type ${pascal}Errors = ${errorUnion}`)
656
- }
657
- }
658
-
659
- const responseHeadersDeclared = res.headers != null
660
- const helperCall = hasErrors
661
- ? `client.bindCallableTyped<${paramsTypeName}, ${returnTypeName}, ${errorsRef}>`
662
- : `client.bindCallable<${paramsTypeName}, ${returnTypeName}>`
663
-
664
- const descriptorLines = [
665
- ` name: '${route.name}',`,
666
- ` scope: '${scopeStr}',`,
667
- ` path: '${route.fullPath}',`,
668
- ` method: '${route.method}',`,
669
- ` kind: 'api',`,
670
- ...(responseHeadersDeclared ? [` responseHeadersDeclared: true,`] : []),
671
- ]
672
-
673
- const callable = [
674
- buildCallableJsDoc({
675
- methodLabel: route.method.toUpperCase(),
676
- path: route.fullPath,
677
- errorsRef: hasErrors ? errorsRef : null,
678
- }),
679
- ` ${route.name}: ${helperCall}({`,
680
- ...descriptorLines,
681
- ` }),`,
682
- ].join('\n')
683
-
684
- const hasErrorsInjected = ctx.namespaceTypes
685
- ? injectRouteErrors(declarations, pascal, errorUnion, ctx.namespaceTypes)
686
- : errorUnion !== null // flat mode already emitted errors above
687
-
688
- return { typeDeclarations: declarations, callable, hasStream: false, hasErrors: hasErrorsInjected }
689
- }
690
-
691
- async function emitHttpStreamRoute(route: HttpStreamRouteDoc, ctx: EmitRouteContext): Promise<RouteChunks> {
692
- const pascal = toPascalCase(route.name)
693
- const req = route.jsonSchema.req ?? {}
694
- const res = route.jsonSchema.res ?? {}
695
-
696
- // Request channels
697
- const reqChannelKeys = ['pathParams', 'query', 'body', 'headers'] as const
698
- const reqTypes: NamedType[] = []
699
- const presentChannels: string[] = []
700
-
701
- for (const channel of reqChannelKeys) {
702
- const schema = req[channel]
703
- if (schema != null) {
704
- reqTypes.push({ shortName: toPascalCase(channel), schema })
705
- presentChannels.push(channel)
706
- }
707
- }
708
-
709
- // Yield + ReturnType
710
- const yieldSchema = route.jsonSchema.yield
711
- const returnSchema = route.jsonSchema.returnType
712
- const resHeadersSchema = res.headers
713
-
714
- const scopeStr = route.scope ?? 'default'
715
- const declarations: string[] = []
716
- let paramsTypeName = 'void'
717
- let yieldTypeName = 'unknown'
718
- let returnTypeName = 'void'
719
-
720
- const taken = new Set<string>(['Req', 'Response', 'Yield', 'ReturnType'])
721
-
722
- if (ctx.namespaceTypes) {
723
- const nsLines: string[] = []
724
-
725
- // Req sub-namespace
726
- const { nsBlock: reqBlock, refs: reqRefs } = await formatSubNamespace(
727
- pascal, 'Req', reqTypes, ctx, taken
728
- )
729
- if (reqBlock) {
730
- nsLines.push(reqBlock)
731
- // Merged type alias so Req can be used as a generic type arg (same pattern as emitApiRoute)
732
- const reqFields = presentChannels
733
- .map((ch) => `${ch}: Req.${toPascalCase(ch)}`)
734
- .join('; ')
735
- nsLines.push(` export type Req = { ${reqFields} }`)
736
- }
737
-
738
- // Response sub-namespace (headers only for http-stream)
739
- const resTypes: NamedType[] = [{ shortName: 'Headers', schema: resHeadersSchema }]
740
- const { nsBlock: resBlock } = await formatSubNamespace(
741
- pascal, 'Response', resTypes, ctx, taken
742
- )
743
- if (resBlock) nsLines.push(resBlock)
744
-
745
- // Yield and ReturnType directly in the route namespace
746
- const directTypes: NamedType[] = [
747
- { shortName: 'Yield', schema: yieldSchema },
748
- { shortName: 'ReturnType', schema: returnSchema },
749
- ]
750
- const collector = new DeclarationCollector(`namespace ${pascal}`)
751
- for (const { shortName, schema } of directTypes) {
752
- if (schema == null) continue
753
- const rawResult = await convertExtracted(schema, ctx)
754
- if (rawResult == null) continue
755
- const result = renameExtractedTypes(rawResult, taken)
756
- for (const decl of result.declarations) {
757
- const line = collector.accept(decl, ' ')
758
- if (line != null) nsLines.push(line)
759
- }
760
- nsLines.push(` export type ${shortName} = ${result.body}`)
761
- }
762
-
763
- if (nsLines.length > 0) {
764
- declarations.push(` export namespace ${pascal} {\n${nsLines.join('\n')}\n }`)
765
- }
766
-
767
- if (presentChannels.length > 0) {
768
- paramsTypeName = `${ctx.scopePascal}.${pascal}.Req`
769
- }
770
-
771
- if (yieldSchema != null) yieldTypeName = `${ctx.scopePascal}.${pascal}.Yield`
772
- if (returnSchema != null) returnTypeName = `${ctx.scopePascal}.${pascal}.ReturnType`
773
- } else {
774
- // Flat mode
775
- for (const { shortName, schema } of reqTypes) {
776
- if (schema == null) continue
777
- const flatName = `${pascal}Req${shortName}`
778
- const body = await convertBody(schema, ctx)
779
- if (body == null) continue
780
- declarations.push(`export type ${flatName} = ${body}`)
781
- }
782
-
783
- if (presentChannels.length > 0) {
784
- const structureFields = presentChannels
785
- .map((ch) => `${ch}: ${pascal}Req${toPascalCase(ch)}`)
786
- .join('; ')
787
- declarations.push(`export type ${pascal}Req = { ${structureFields} }`)
788
- paramsTypeName = `${pascal}Req`
789
- }
790
-
791
- if (resHeadersSchema != null) {
792
- const body = await convertBody(resHeadersSchema, ctx)
793
- if (body != null) declarations.push(`export type ${pascal}ResponseHeaders = ${body}`)
794
- }
795
-
796
- if (yieldSchema != null) {
797
- const body = await convertBody(yieldSchema, ctx)
798
- if (body != null) {
799
- declarations.push(`export type ${pascal}Yield = ${body}`)
800
- yieldTypeName = `${pascal}Yield`
801
- }
802
- }
803
-
804
- if (returnSchema != null) {
805
- const body = await convertBody(returnSchema, ctx)
806
- if (body != null) {
807
- declarations.push(`export type ${pascal}ReturnType = ${body}`)
808
- returnTypeName = `${pascal}ReturnType`
809
- }
810
- }
811
- }
812
-
813
- const callable = [
814
- buildCallableJsDoc({
815
- methodLabel: route.method.toUpperCase(),
816
- path: route.fullPath,
817
- errorsRef: null,
818
- }),
819
- ` ${route.name}(req: ${paramsTypeName}, options?: ProcedureCallOptions): TypedStream<${yieldTypeName}, ${returnTypeName}> {`,
820
- ` return client.stream<${yieldTypeName}, ${returnTypeName}>({`,
821
- ` name: '${route.name}',`,
822
- ` scope: '${scopeStr}',`,
823
- ` path: '${route.fullPath}',`,
824
- ` method: '${route.method}',`,
825
- ` kind: 'http-stream',`,
826
- ` streamMode: '${route.streamMode}',`,
827
- ` params: req,`,
828
- ` }, options)`,
829
- ` },`,
830
- ].join('\n')
831
-
832
- const hasErrors = injectRouteErrors(
833
- declarations,
834
- pascal,
835
- buildErrorUnion(route.errors, ctx),
836
- ctx.namespaceTypes
837
- )
838
-
839
- return { typeDeclarations: declarations, callable, hasStream: true, hasErrors }
840
- }
841
-
842
- async function emitStreamRoute(route: StreamHttpRouteDoc, ctx: EmitRouteContext): Promise<RouteChunks> {
843
- const pascal = versionedPascal(route.name, route.version)
844
-
845
- // Unwrap SSE envelope from yieldType
846
- let yieldSchema = route.jsonSchema.yieldType
847
- if (yieldSchema != null && route.streamMode === 'sse' && isSseEnvelope(yieldSchema)) {
848
- yieldSchema = unwrapSseEnvelope(yieldSchema)
849
- }
850
-
851
- const { declarations, refs } = await formatTypes(pascal, [
852
- { shortName: 'Params', schema: route.jsonSchema.params },
853
- { shortName: 'Yield', schema: yieldSchema },
854
- { shortName: 'Return', schema: route.jsonSchema.returnType },
855
- ], ctx)
856
-
857
- const paramsTypeName = refs['Params'] ?? 'void'
858
- const yieldTypeName = refs['Yield'] ?? 'unknown'
859
- const returnTypeName = refs['Return'] ?? 'void'
860
- const scopeStr = Array.isArray(route.scope) ? route.scope.join('-') : route.scope
861
-
862
- const callable = [
863
- buildCallableJsDoc({
864
- methodLabel: route.methods.map((m) => m.toUpperCase()).join('|'),
865
- path: route.path,
866
- errorsRef: null,
867
- }),
868
- ` ${pascal}(params: ${paramsTypeName}, options?: ProcedureCallOptions): TypedStream<${yieldTypeName}, ${returnTypeName}> {`,
869
- ` return client.stream<${yieldTypeName}, ${returnTypeName}>({`,
870
- ` name: '${pascal}',`,
871
- ` scope: '${scopeStr}',`,
872
- ` path: '${route.path}',`,
873
- ` method: '${route.methods[0] ?? 'get'}',`,
874
- ` kind: 'stream',`,
875
- ` streamMode: '${route.streamMode}',`,
876
- ` params,`,
877
- ` }, options)`,
878
- ` },`,
879
- ].join('\n')
880
-
881
- const hasErrors = injectRouteErrors(
882
- declarations,
883
- pascal,
884
- buildErrorUnion(route.errors, ctx),
885
- ctx.namespaceTypes
886
- )
887
-
888
- return { typeDeclarations: declarations, callable, hasStream: true, hasErrors }
889
- }
890
-
891
- // ---------------------------------------------------------------------------
892
- // emitScopeFile
893
- // ---------------------------------------------------------------------------
894
-
895
- /**
896
- * Generates a complete TypeScript scope file for a ScopeGroup.
897
- *
898
- * When `namespaceTypes` is true, types are wrapped in nested TypeScript namespaces
899
- * and ajsc runs with `inlineTypes: false` so formatting options (enumStyle, depluralize,
900
- * jsdoc, etc.) produce extracted sub-types inside each namespace.
901
- */
902
- export async function emitScopeFile(
903
- group: ScopeGroup,
904
- options?: EmitScopeOptions,
905
- ): Promise<string> {
906
- const {
907
- ajsc: ajscOpts,
908
- clientImportPath = 'ts-procedures/client',
909
- namespaceTypes = false,
910
- serviceName = 'Api',
911
- errorKeys,
912
- idToModelName,
913
- } = options ?? {}
914
-
915
- const pascal = toPascalCase(group.camelCase)
916
- const referencedModels = new Set<string>()
917
- const ctx: EmitRouteContext = {
918
- ajsc: ajscOpts,
919
- namespaceTypes,
920
- scopePascal: pascal,
921
- serviceName,
922
- errorKeys,
923
- idToModelName,
924
- referencedModels,
925
- }
926
-
927
- const allTypeDeclarations: string[] = []
928
- const callables: string[] = []
929
- let hasStream = false
930
- let scopeHasErrors = false
931
-
932
- for (const route of group.routes) {
933
- let chunks: RouteChunks
934
- const kind = route.kind ?? inferRouteKind(route as Record<string, unknown>)
935
-
936
- try {
937
- if (kind === 'rpc') {
938
- chunks = await emitRpcRoute(route as RPCHttpRouteDoc, ctx)
939
- } else if (kind === 'api') {
940
- chunks = await emitApiRoute(route as APIHttpRouteDoc, ctx)
941
- } else if (kind === 'stream') {
942
- chunks = await emitStreamRoute(route as StreamHttpRouteDoc, ctx)
943
- } else if (kind === 'http-stream') {
944
- chunks = await emitHttpStreamRoute(route as HttpStreamRouteDoc, ctx)
945
- } else {
946
- throw new Error(`Unknown route kind "${kind}"`)
947
- }
948
- } catch (err) {
949
- const msg = err instanceof Error ? err.message : String(err)
950
- throw new Error(
951
- `[ts-procedures-codegen] Failed to emit route "${route.name}" (kind: ${kind}, scope: ${group.scopeKey}): ${msg}`
952
- )
953
- }
954
-
955
- allTypeDeclarations.push(...chunks.typeDeclarations)
956
- callables.push(chunks.callable)
957
- if (chunks.hasStream) hasStream = true
958
- if (chunks.hasErrors) scopeHasErrors = true
959
- }
960
-
961
- // Build client import line — Result/ResultNoTyped are no longer referenced
962
- // directly in scope files; the bindCallable helpers infer them from ClientInstance.
963
- const clientImports = hasStream
964
- ? `import type { ClientInstance, ProcedureCallOptions, TypedStream } from '${clientImportPath}'`
965
- : `import type { ClientInstance, ProcedureCallOptions } from '${clientImportPath}'`
966
-
967
- // Build _errors import line when at least one route emits an Errors union.
968
- // Namespace mode uses the qualified `${Service}Errors` namespace; flat mode
969
- // pulls classes in via a wildcard alias (`_errors.UseCaseError`).
970
- let errorsImport = ''
971
- if (scopeHasErrors) {
972
- if (namespaceTypes) {
973
- errorsImport = `import type { ${toPascalCase(serviceName)}Errors } from './_errors'`
974
- } else {
975
- errorsImport = `import type * as _errors from './_errors'`
976
- }
977
- }
978
-
979
- const callablesBlock = callables.join('\n\n')
980
-
981
- // Assemble the type + callable section for this scope. ajsc already emitted
982
- // bare model references (via `x-named-type`); `convertExtracted` / `convertBody`
983
- // folded the reported names into `ctx.referencedModels`, which drives the
984
- // `_models` import below. No post-pass over the body is needed.
985
- let body: string
986
- if (namespaceTypes) {
987
- // Namespace mode: types AND `bindScope` live inside `export namespace ${pascal}`.
988
- // Putting the function inside the namespace makes the merged symbol
989
- // value+type, which lets `index.ts` use `export import ${pascal} = …`
990
- // under `verbatimModuleSyntax: true`. (A type-only namespace would trip
991
- // TS1269/TS1288.) Consumers call `${Pascal}.bindScope(client)`; the
992
- // generated `index.ts` factory wires this internally.
993
- const callableLines = indent(callablesBlock, ' ')
994
- const namespaceMembers = [
995
- ...(allTypeDeclarations.length > 0 ? [allTypeDeclarations.join('\n\n')] : []),
996
- [
997
- ' /** Binds every callable in this scope to a configured client. */',
998
- ' export function bindScope(client: ClientInstance) {',
999
- ' return {',
1000
- callableLines,
1001
- ' }',
1002
- ' }',
1003
- ].join('\n'),
1004
- ].join('\n\n')
1005
-
1006
- body = [
1007
- `export namespace ${pascal} {`,
1008
- namespaceMembers,
1009
- '}',
1010
- '',
1011
- ].join('\n')
1012
- } else {
1013
- // Flat mode: types at module level, `bind${pascal}Scope` standalone.
1014
- const typesBlock = allTypeDeclarations.length > 0
1015
- ? allTypeDeclarations.join('\n') + '\n'
1016
- : ''
1017
-
1018
- body = [
1019
- '// ── Types ────────────────────────────────────────',
1020
- '',
1021
- typesBlock,
1022
- '// ── Callables ────────────────────────────────────',
1023
- '',
1024
- `export function bind${pascal}Scope(client: ClientInstance) {`,
1025
- ' return {',
1026
- callablesBlock,
1027
- ' }',
1028
- '}',
1029
- '',
1030
- ].join('\n')
1031
- }
1032
-
1033
- // Build the shared-models import line when any route referenced a `$id` model.
1034
- // Matches the `_errors` import convention exactly: `'./_models'` with no `.js`.
1035
- let modelsImport = ''
1036
- if (referencedModels.size > 0) {
1037
- const names = [...referencedModels].sort().join(', ')
1038
- modelsImport = `import type { ${names} } from './_models'`
1039
- }
1040
-
1041
- const importsBlock = [clientImports, errorsImport, modelsImport].filter(Boolean).join('\n')
1042
-
1043
- return [
1044
- CODEGEN_HEADER,
1045
- importsBlock,
1046
- '',
1047
- body,
1048
- ].join('\n')
1049
- }
2
+ * Thin re-export barrel for the TS scope emitter, which now lives in
3
+ * `./emit/`. Kept so existing imports of `./emit-scope.js` keep working.
4
+ *
5
+ * Module layout:
6
+ * - `emit/scope-file.ts` — `emitScopeFile` orchestration + assembly
7
+ * - `emit/rpc-route.ts` — `kind: 'rpc'` emitter
8
+ * - `emit/api-route.ts` — `kind: 'api'` emitter
9
+ * - `emit/stream-route.ts` — `kind: 'stream'` emitter
10
+ * - `emit/http-stream-route.ts` — `kind: 'http-stream'` emitter
11
+ * - `emit/format-types.ts` — schema→type conversion + formatTypes helpers
12
+ * - `emit/declarations.ts` — `indent` + `DeclarationCollector`
13
+ * - `emit/route-shared.ts` — errors-union / JSDoc / naming helpers
14
+ * - `emit/context.ts` — shared `EmitRouteContext` / `RouteChunks`
15
+ */
16
+ export { emitScopeFile, type EmitScopeOptions } from './emit/scope-file.js'