ts-procedures 8.5.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 (635) 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-index.js +13 -0
  103. package/build/codegen/emit-index.js.map +1 -1
  104. package/build/codegen/emit-index.test.js +25 -0
  105. package/build/codegen/emit-index.test.js.map +1 -1
  106. package/build/codegen/emit-scope.d.ts +13 -30
  107. package/build/codegen/emit-scope.js +15 -807
  108. package/build/codegen/emit-scope.js.map +1 -1
  109. package/build/codegen/emit-scope.test.js +86 -4
  110. package/build/codegen/emit-scope.test.js.map +1 -1
  111. package/build/codegen/goldens.test.js +69 -0
  112. package/build/codegen/goldens.test.js.map +1 -0
  113. package/build/codegen/group-routes.d.ts +1 -1
  114. package/build/codegen/pipeline.d.ts +1 -1
  115. package/build/codegen/resolve-envelope.d.ts +1 -1
  116. package/build/codegen/targets/_shared/error-schemas.d.ts +1 -1
  117. package/build/codegen/targets/_shared/route-slots.d.ts +1 -1
  118. package/build/codegen/targets/_shared/target-run.d.ts +1 -1
  119. package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +1 -1
  120. package/build/codegen/targets/swift/emit-route-swift.d.ts +1 -1
  121. package/build/core/create-http-stream.d.ts +50 -0
  122. package/build/core/create-http-stream.js +108 -0
  123. package/build/core/create-http-stream.js.map +1 -0
  124. package/build/{create-http-stream.test.js → core/create-http-stream.test.js} +1 -1
  125. package/build/core/create-http-stream.test.js.map +1 -0
  126. package/build/core/create-http.d.ts +51 -0
  127. package/build/core/create-http.js +65 -0
  128. package/build/core/create-http.js.map +1 -0
  129. package/build/{create-http.test.js → core/create-http.test.js} +13 -4
  130. package/build/core/create-http.test.js.map +1 -0
  131. package/build/core/create-stream.d.ts +26 -0
  132. package/build/core/create-stream.js +80 -0
  133. package/build/core/create-stream.js.map +1 -0
  134. package/build/{create-stream.test.js → core/create-stream.test.js} +23 -28
  135. package/build/core/create-stream.test.js.map +1 -0
  136. package/build/core/create.d.ts +22 -0
  137. package/build/core/create.js +71 -0
  138. package/build/core/create.js.map +1 -0
  139. package/build/{create.test.js → core/create.test.js} +25 -46
  140. package/build/core/create.test.js.map +1 -0
  141. package/build/core/definition-site.d.ts +24 -0
  142. package/build/{stack-utils.js → core/definition-site.js} +20 -20
  143. package/build/core/definition-site.js.map +1 -0
  144. package/build/{stack-utils.test.js → core/definition-site.test.js} +12 -3
  145. package/build/core/definition-site.test.js.map +1 -0
  146. package/build/{errors.d.ts → core/errors.d.ts} +19 -8
  147. package/build/{errors.js → core/errors.js} +21 -26
  148. package/build/core/errors.js.map +1 -0
  149. package/build/core/errors.test.js.map +1 -0
  150. package/build/core/factory-options.test.js +82 -0
  151. package/build/core/factory-options.test.js.map +1 -0
  152. package/build/core/http-route.d.ts +13 -0
  153. package/build/core/http-route.js +54 -0
  154. package/build/core/http-route.js.map +1 -0
  155. package/build/core/internal.d.ts +72 -0
  156. package/build/core/internal.js +128 -0
  157. package/build/core/internal.js.map +1 -0
  158. package/build/{migration.test.js → core/migration.test.js} +17 -1
  159. package/build/core/migration.test.js.map +1 -0
  160. package/build/core/procedures.d.ts +143 -0
  161. package/build/core/procedures.js +64 -0
  162. package/build/core/procedures.js.map +1 -0
  163. package/build/{index.test.js → core/procedures.test.js} +14 -11
  164. package/build/core/procedures.test.js.map +1 -0
  165. package/build/core/types.d.ts +182 -0
  166. package/build/{schema → core}/types.js.map +1 -1
  167. package/build/exports.d.ts +31 -11
  168. package/build/exports.js +23 -8
  169. package/build/exports.js.map +1 -1
  170. package/build/schema/adapter.d.ts +35 -0
  171. package/build/schema/adapter.js +13 -0
  172. package/build/schema/adapter.js.map +1 -0
  173. package/build/schema/adapter.test.js +53 -0
  174. package/build/schema/adapter.test.js.map +1 -0
  175. package/build/schema/compile.d.ts +37 -0
  176. package/build/schema/compile.js +38 -0
  177. package/build/schema/compile.js.map +1 -0
  178. package/build/schema/compile.test.js +78 -0
  179. package/build/schema/compile.test.js.map +1 -0
  180. package/build/schema/compute-schema.d.ts +47 -37
  181. package/build/schema/compute-schema.js +86 -29
  182. package/build/schema/compute-schema.js.map +1 -1
  183. package/build/schema/compute-schema.test.js +158 -40
  184. package/build/schema/compute-schema.test.js.map +1 -1
  185. package/build/schema/json-schema.d.ts +17 -0
  186. package/build/schema/json-schema.js +2 -0
  187. package/build/schema/json-schema.js.map +1 -0
  188. package/build/schema/typebox.d.ts +11 -0
  189. package/build/schema/typebox.js +24 -0
  190. package/build/schema/typebox.js.map +1 -0
  191. package/build/schema/typebox.test.js +34 -0
  192. package/build/schema/typebox.test.js.map +1 -0
  193. package/build/server/context.d.ts +8 -0
  194. package/build/server/context.js +7 -0
  195. package/build/server/context.js.map +1 -0
  196. package/build/server/context.test.js +16 -0
  197. package/build/server/context.test.js.map +1 -0
  198. package/build/{doc-envelope.d.ts → server/doc-envelope.d.ts} +1 -1
  199. package/build/server/doc-envelope.js.map +1 -0
  200. package/build/server/doc-envelope.test.d.ts +1 -0
  201. package/build/server/doc-envelope.test.js.map +1 -0
  202. package/build/{implementations/http → server}/doc-registry.d.ts +7 -2
  203. package/build/{implementations/http → server}/doc-registry.js +9 -5
  204. package/build/server/doc-registry.js.map +1 -0
  205. package/build/server/doc-registry.test.d.ts +1 -0
  206. package/build/{implementations/http → server}/doc-registry.test.js +27 -24
  207. package/build/server/doc-registry.test.js.map +1 -0
  208. package/build/server/docs/docs.test.d.ts +1 -0
  209. package/build/server/docs/docs.test.js +237 -0
  210. package/build/server/docs/docs.test.js.map +1 -0
  211. package/build/{implementations/http/hono → server}/docs/http-doc.d.ts +2 -2
  212. package/build/{implementations/http/hono → server}/docs/http-doc.js +1 -1
  213. package/build/server/docs/http-doc.js.map +1 -0
  214. package/build/{implementations/http/hono → server}/docs/http-stream-doc.d.ts +2 -2
  215. package/build/{implementations/http/hono → server}/docs/http-stream-doc.js +1 -1
  216. package/build/server/docs/http-stream-doc.js.map +1 -0
  217. package/build/{implementations/http/hono → server}/docs/rpc-doc.d.ts +2 -2
  218. package/build/{implementations/http/hono → server}/docs/rpc-doc.js +1 -1
  219. package/build/server/docs/rpc-doc.js.map +1 -0
  220. package/build/{implementations/http/hono → server}/docs/stream-doc.d.ts +2 -2
  221. package/build/{implementations/http/hono → server}/docs/stream-doc.js +1 -1
  222. package/build/server/docs/stream-doc.js.map +1 -0
  223. package/build/server/errors/dispatch.d.ts +96 -0
  224. package/build/{implementations/http/error-dispatch.js → server/errors/dispatch.js} +20 -10
  225. package/build/server/errors/dispatch.js.map +1 -0
  226. package/build/server/errors/dispatch.test.d.ts +1 -0
  227. package/build/server/errors/dispatch.test.js +418 -0
  228. package/build/server/errors/dispatch.test.js.map +1 -0
  229. package/build/{implementations/http/error-taxonomy.d.ts → server/errors/taxonomy.d.ts} +8 -17
  230. package/build/{implementations/http/error-taxonomy.js → server/errors/taxonomy.js} +6 -15
  231. package/build/server/errors/taxonomy.js.map +1 -0
  232. package/build/server/errors/taxonomy.test.d.ts +1 -0
  233. package/build/{implementations/http/error-taxonomy.test.js → server/errors/taxonomy.test.js} +45 -39
  234. package/build/server/errors/taxonomy.test.js.map +1 -0
  235. package/build/server/index.d.ts +29 -0
  236. package/build/server/index.js +27 -0
  237. package/build/server/index.js.map +1 -0
  238. package/build/server/no-framework-imports.test.d.ts +1 -0
  239. package/build/server/no-framework-imports.test.js +40 -0
  240. package/build/server/no-framework-imports.test.js.map +1 -0
  241. package/build/{implementations/http/hono/path.d.ts → server/paths.d.ts} +2 -3
  242. package/build/{implementations/http/hono/path.js → server/paths.js} +1 -1
  243. package/build/server/paths.js.map +1 -0
  244. package/build/server/paths.test.d.ts +1 -0
  245. package/build/server/paths.test.js +111 -0
  246. package/build/server/paths.test.js.map +1 -0
  247. package/build/server/request/params.d.ts +29 -0
  248. package/build/server/request/params.js +43 -0
  249. package/build/server/request/params.js.map +1 -0
  250. package/build/server/request/params.test.d.ts +1 -0
  251. package/build/server/request/params.test.js +91 -0
  252. package/build/server/request/params.test.js.map +1 -0
  253. package/build/server/request/query.d.ts +9 -0
  254. package/build/server/request/query.js +22 -0
  255. package/build/server/request/query.js.map +1 -0
  256. package/build/server/request/query.test.d.ts +1 -0
  257. package/build/server/request/query.test.js +60 -0
  258. package/build/server/request/query.test.js.map +1 -0
  259. package/build/server/sse.d.ts +70 -0
  260. package/build/server/sse.js +94 -0
  261. package/build/server/sse.js.map +1 -0
  262. package/build/server/sse.test.d.ts +1 -0
  263. package/build/server/sse.test.js +98 -0
  264. package/build/server/sse.test.js.map +1 -0
  265. package/build/{implementations → server}/types.d.ts +17 -15
  266. package/build/{implementations → server}/types.js.map +1 -1
  267. package/docs/astro-adapter.md +8 -9
  268. package/docs/client-and-codegen.md +4 -4
  269. package/docs/client-error-handling.md +92 -5
  270. package/docs/codegen-kotlin.md +2 -3
  271. package/docs/codegen-swift.md +1 -2
  272. package/docs/core.md +135 -54
  273. package/docs/http-integrations.md +83 -6
  274. package/docs/migration-v8-to-v9.md +192 -0
  275. package/docs/plans/2026-06-09-v9-rewrite.md +130 -0
  276. package/docs/specs/2026-06-09-v9-rewrite-design.md +221 -0
  277. package/docs/streaming.md +12 -0
  278. package/package.json +23 -47
  279. package/src/{implementations/http → adapters}/astro/index.test.ts +2 -2
  280. package/src/adapters/hono/__fixtures__/parity-envelope.json +389 -0
  281. package/src/adapters/hono/envelope-parity.test.ts +126 -0
  282. package/src/{implementations/http → adapters}/hono/handlers/http-stream.test.ts +1 -1
  283. package/src/adapters/hono/handlers/http-stream.ts +73 -0
  284. package/src/{implementations/http → adapters}/hono/handlers/http.test.ts +1 -1
  285. package/src/adapters/hono/handlers/http.ts +70 -0
  286. package/src/{implementations/http → adapters}/hono/handlers/rpc.test.ts +2 -2
  287. package/src/adapters/hono/handlers/rpc.ts +39 -0
  288. package/src/{implementations/http → adapters}/hono/handlers/stream.test.ts +4 -3
  289. package/src/{implementations/http → adapters}/hono/handlers/stream.ts +19 -92
  290. package/src/{implementations/http → adapters}/hono/index.test.ts +14 -16
  291. package/src/{implementations/http → adapters}/hono/index.ts +35 -30
  292. package/src/{implementations/http → adapters/hono}/on-request-error.test.ts +3 -3
  293. package/src/adapters/hono/request.ts +28 -0
  294. package/src/{implementations/http → adapters/hono}/route-errors.test.ts +5 -5
  295. package/src/{implementations/http → adapters}/hono/types.ts +43 -20
  296. package/src/client/freeze.test.ts +41 -0
  297. package/src/client/typed-error-dispatch.test.ts +3 -3
  298. package/src/codegen/__fixtures__/make-envelope.ts +1 -1
  299. package/src/codegen/__fixtures__/models-envelope.json +310 -0
  300. package/src/codegen/__fixtures__/users-envelope.json +9 -0
  301. package/src/codegen/__goldens__/MANIFEST.json +85 -0
  302. package/src/codegen/__goldens__/kotlin-default--models/Billing.kt +112 -0
  303. package/src/codegen/__goldens__/kotlin-default--models/BillingReports.kt +26 -0
  304. package/src/codegen/__goldens__/kotlin-default--models/Orders.kt +88 -0
  305. package/src/codegen/__goldens__/kotlin-default--users/Users.kt +189 -0
  306. package/src/codegen/__goldens__/swift-default--models/Billing.swift +97 -0
  307. package/src/codegen/__goldens__/swift-default--models/BillingReports.swift +20 -0
  308. package/src/codegen/__goldens__/swift-default--models/Orders.swift +81 -0
  309. package/src/codegen/__goldens__/swift-default--users/Users.swift +204 -0
  310. package/src/codegen/__goldens__/ts-default--models/_client.ts +1319 -0
  311. package/src/codegen/__goldens__/ts-default--models/_errors.ts +90 -0
  312. package/src/codegen/__goldens__/ts-default--models/_models.ts +10 -0
  313. package/src/codegen/__goldens__/ts-default--models/_types.ts +502 -0
  314. package/src/codegen/__goldens__/ts-default--models/billing-reports.ts +29 -0
  315. package/src/codegen/__goldens__/ts-default--models/billing.ts +67 -0
  316. package/src/codegen/__goldens__/ts-default--models/index.ts +48 -0
  317. package/src/codegen/__goldens__/ts-default--models/orders.ts +80 -0
  318. package/src/codegen/__goldens__/ts-default--users/_client.ts +1319 -0
  319. package/src/codegen/__goldens__/ts-default--users/_errors.ts +90 -0
  320. package/src/codegen/__goldens__/ts-default--users/_types.ts +502 -0
  321. package/src/codegen/__goldens__/ts-default--users/index.ts +38 -0
  322. package/src/codegen/__goldens__/ts-default--users/users.ts +169 -0
  323. package/src/codegen/__goldens__/ts-external-runtime--models/_errors.ts +90 -0
  324. package/src/codegen/__goldens__/ts-external-runtime--models/_models.ts +10 -0
  325. package/src/codegen/__goldens__/ts-external-runtime--models/billing-reports.ts +29 -0
  326. package/src/codegen/__goldens__/ts-external-runtime--models/billing.ts +67 -0
  327. package/src/codegen/__goldens__/ts-external-runtime--models/index.ts +48 -0
  328. package/src/codegen/__goldens__/ts-external-runtime--models/orders.ts +80 -0
  329. package/src/codegen/__goldens__/ts-external-runtime--users/_errors.ts +90 -0
  330. package/src/codegen/__goldens__/ts-external-runtime--users/index.ts +38 -0
  331. package/src/codegen/__goldens__/ts-external-runtime--users/users.ts +169 -0
  332. package/src/codegen/__goldens__/ts-flat--models/_client.ts +1319 -0
  333. package/src/codegen/__goldens__/ts-flat--models/_errors.ts +87 -0
  334. package/src/codegen/__goldens__/ts-flat--models/_models.ts +10 -0
  335. package/src/codegen/__goldens__/ts-flat--models/_types.ts +502 -0
  336. package/src/codegen/__goldens__/ts-flat--models/billing-reports.ts +28 -0
  337. package/src/codegen/__goldens__/ts-flat--models/billing.ts +51 -0
  338. package/src/codegen/__goldens__/ts-flat--models/index.ts +42 -0
  339. package/src/codegen/__goldens__/ts-flat--models/orders.ts +73 -0
  340. package/src/codegen/__goldens__/ts-flat--users/_client.ts +1319 -0
  341. package/src/codegen/__goldens__/ts-flat--users/_errors.ts +87 -0
  342. package/src/codegen/__goldens__/ts-flat--users/_types.ts +502 -0
  343. package/src/codegen/__goldens__/ts-flat--users/index.ts +34 -0
  344. package/src/codegen/__goldens__/ts-flat--users/users.ts +126 -0
  345. package/src/codegen/__goldens__/ts-no-share-models--models/_client.ts +1319 -0
  346. package/src/codegen/__goldens__/ts-no-share-models--models/_errors.ts +90 -0
  347. package/src/codegen/__goldens__/ts-no-share-models--models/_types.ts +502 -0
  348. package/src/codegen/__goldens__/ts-no-share-models--models/billing-reports.ts +29 -0
  349. package/src/codegen/__goldens__/ts-no-share-models--models/billing.ts +111 -0
  350. package/src/codegen/__goldens__/ts-no-share-models--models/index.ts +48 -0
  351. package/src/codegen/__goldens__/ts-no-share-models--models/orders.ts +112 -0
  352. package/src/codegen/__goldens__/ts-no-share-models--users/_client.ts +1319 -0
  353. package/src/codegen/__goldens__/ts-no-share-models--users/_errors.ts +90 -0
  354. package/src/codegen/__goldens__/ts-no-share-models--users/_types.ts +502 -0
  355. package/src/codegen/__goldens__/ts-no-share-models--users/index.ts +38 -0
  356. package/src/codegen/__goldens__/ts-no-share-models--users/users.ts +169 -0
  357. package/src/codegen/__goldens__/ts-shared-models-module--models/_client.ts +1319 -0
  358. package/src/codegen/__goldens__/ts-shared-models-module--models/_errors.ts +90 -0
  359. package/src/codegen/__goldens__/ts-shared-models-module--models/_models.ts +7 -0
  360. package/src/codegen/__goldens__/ts-shared-models-module--models/_types.ts +502 -0
  361. package/src/codegen/__goldens__/ts-shared-models-module--models/billing-reports.ts +29 -0
  362. package/src/codegen/__goldens__/ts-shared-models-module--models/billing.ts +67 -0
  363. package/src/codegen/__goldens__/ts-shared-models-module--models/index.ts +48 -0
  364. package/src/codegen/__goldens__/ts-shared-models-module--models/orders.ts +80 -0
  365. package/src/codegen/bin/cli.test.ts +13 -2
  366. package/src/codegen/bin/cli.ts +181 -144
  367. package/src/codegen/bin/flag-specs.test.ts +16 -1
  368. package/src/codegen/bin/flag-specs.ts +43 -31
  369. package/src/codegen/bundle-size.test.ts +1 -1
  370. package/src/codegen/collect-models.ts +1 -1
  371. package/src/codegen/e2e.test.ts +1 -1
  372. package/src/codegen/emit/api-route.ts +184 -0
  373. package/src/codegen/emit/context.ts +32 -0
  374. package/src/codegen/emit/declarations.ts +49 -0
  375. package/src/codegen/emit/format-types.ts +232 -0
  376. package/src/codegen/emit/http-stream-route.ts +162 -0
  377. package/src/codegen/emit/route-shared.ts +102 -0
  378. package/src/codegen/emit/rpc-route.ts +49 -0
  379. package/src/codegen/emit/scope-file.ts +226 -0
  380. package/src/codegen/emit/stream-route.ts +81 -0
  381. package/src/codegen/emit-errors.integration.test.ts +2 -2
  382. package/src/codegen/emit-errors.test.ts +1 -1
  383. package/src/codegen/emit-errors.ts +1 -1
  384. package/src/codegen/emit-index.test.ts +34 -0
  385. package/src/codegen/emit-index.ts +19 -0
  386. package/src/codegen/emit-scope.test.ts +96 -6
  387. package/src/codegen/emit-scope.ts +15 -1003
  388. package/src/codegen/goldens.test.ts +89 -0
  389. package/src/codegen/group-routes.test.ts +1 -1
  390. package/src/codegen/group-routes.ts +1 -1
  391. package/src/codegen/pipeline.test.ts +1 -1
  392. package/src/codegen/pipeline.ts +1 -1
  393. package/src/codegen/resolve-envelope.test.ts +1 -1
  394. package/src/codegen/resolve-envelope.ts +1 -1
  395. package/src/codegen/targets/_shared/error-schemas.test.ts +1 -1
  396. package/src/codegen/targets/_shared/error-schemas.ts +1 -1
  397. package/src/codegen/targets/_shared/route-slots.test.ts +1 -1
  398. package/src/codegen/targets/_shared/route-slots.ts +1 -1
  399. package/src/codegen/targets/_shared/target-run.ts +1 -1
  400. package/src/codegen/targets/kotlin/__fixtures__/users-golden.kt +6 -0
  401. package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +1 -1
  402. package/src/codegen/targets/kotlin/emit-route-kotlin.ts +1 -1
  403. package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +1 -1
  404. package/src/codegen/targets/swift/__fixtures__/users-golden.swift +6 -0
  405. package/src/codegen/targets/swift/access-level.test.ts +1 -1
  406. package/src/codegen/targets/swift/emit-route-swift.test.ts +1 -1
  407. package/src/codegen/targets/swift/emit-route-swift.ts +1 -1
  408. package/src/codegen/targets/swift/emit-scope-swift.test.ts +1 -1
  409. package/src/codegen/targets/ts/shared-models.test.ts +1 -1
  410. package/src/{create-http-stream.test.ts → core/create-http-stream.test.ts} +1 -1
  411. package/src/core/create-http-stream.ts +207 -0
  412. package/src/{create-http.test.ts → core/create-http.test.ts} +15 -4
  413. package/src/core/create-http.ts +126 -0
  414. package/src/{create-stream.test.ts → core/create-stream.test.ts} +28 -31
  415. package/src/core/create-stream.ts +142 -0
  416. package/src/{create.test.ts → core/create.test.ts} +25 -57
  417. package/src/core/create.ts +121 -0
  418. package/src/{stack-utils.test.ts → core/definition-site.test.ts} +14 -3
  419. package/src/{stack-utils.ts → core/definition-site.ts} +20 -23
  420. package/src/{errors.test.ts → core/errors.test.ts} +1 -1
  421. package/src/{errors.ts → core/errors.ts} +30 -28
  422. package/src/core/factory-options.test.ts +112 -0
  423. package/src/core/http-route.ts +73 -0
  424. package/src/core/internal.ts +203 -0
  425. package/src/{migration.test.ts → core/migration.test.ts} +23 -1
  426. package/src/{index.test.ts → core/procedures.test.ts} +13 -11
  427. package/src/core/procedures.ts +75 -0
  428. package/src/core/types.ts +195 -0
  429. package/src/exports.ts +60 -11
  430. package/src/schema/adapter.test.ts +58 -0
  431. package/src/schema/adapter.ts +45 -0
  432. package/src/schema/compile.test.ts +95 -0
  433. package/src/schema/compile.ts +64 -0
  434. package/src/schema/compute-schema.test.ts +222 -41
  435. package/src/schema/compute-schema.ts +145 -71
  436. package/src/schema/json-schema.ts +21 -0
  437. package/src/schema/typebox.test.ts +40 -0
  438. package/src/schema/typebox.ts +27 -0
  439. package/src/server/context.test.ts +22 -0
  440. package/src/server/context.ts +18 -0
  441. package/src/{doc-envelope.test.ts → server/doc-envelope.test.ts} +2 -2
  442. package/src/{doc-envelope.ts → server/doc-envelope.ts} +1 -1
  443. package/src/{implementations/http → server}/doc-registry.test.ts +32 -26
  444. package/src/{implementations/http → server}/doc-registry.ts +11 -7
  445. package/src/server/docs/docs.test.ts +287 -0
  446. package/src/{implementations/http/hono → server}/docs/http-doc.ts +3 -3
  447. package/src/{implementations/http/hono → server}/docs/http-stream-doc.ts +3 -3
  448. package/src/{implementations/http/hono → server}/docs/rpc-doc.ts +3 -3
  449. package/src/{implementations/http/hono → server}/docs/stream-doc.ts +3 -3
  450. package/src/server/errors/dispatch.test.ts +450 -0
  451. package/src/server/errors/dispatch.ts +189 -0
  452. package/src/{implementations/http/error-taxonomy.test.ts → server/errors/taxonomy.test.ts} +45 -39
  453. package/src/{implementations/http/error-taxonomy.ts → server/errors/taxonomy.ts} +8 -17
  454. package/src/server/index.ts +29 -0
  455. package/src/server/no-framework-imports.test.ts +43 -0
  456. package/src/server/paths.test.ts +141 -0
  457. package/src/{implementations/http/hono/path.ts → server/paths.ts} +2 -13
  458. package/src/server/request/params.test.ts +143 -0
  459. package/src/server/request/params.ts +68 -0
  460. package/src/server/request/query.test.ts +70 -0
  461. package/src/server/request/query.ts +24 -0
  462. package/src/server/sse.test.ts +113 -0
  463. package/src/server/sse.ts +117 -0
  464. package/src/{implementations → server}/types.ts +17 -16
  465. package/build/create-http-stream.d.ts +0 -58
  466. package/build/create-http-stream.js +0 -122
  467. package/build/create-http-stream.js.map +0 -1
  468. package/build/create-http-stream.test.js.map +0 -1
  469. package/build/create-http.d.ts +0 -49
  470. package/build/create-http.js +0 -108
  471. package/build/create-http.js.map +0 -1
  472. package/build/create-http.test.js.map +0 -1
  473. package/build/create-stream.d.ts +0 -35
  474. package/build/create-stream.js +0 -123
  475. package/build/create-stream.js.map +0 -1
  476. package/build/create-stream.test.js.map +0 -1
  477. package/build/create.d.ts +0 -28
  478. package/build/create.js +0 -82
  479. package/build/create.js.map +0 -1
  480. package/build/create.test.js.map +0 -1
  481. package/build/doc-envelope.js.map +0 -1
  482. package/build/doc-envelope.test.js.map +0 -1
  483. package/build/errors.js.map +0 -1
  484. package/build/errors.test.js.map +0 -1
  485. package/build/implementations/http/astro/astro-context.js.map +0 -1
  486. package/build/implementations/http/astro/create-handler.js.map +0 -1
  487. package/build/implementations/http/astro/index.js.map +0 -1
  488. package/build/implementations/http/astro/index.test.js.map +0 -1
  489. package/build/implementations/http/astro/rewrite-request.js.map +0 -1
  490. package/build/implementations/http/doc-registry.js.map +0 -1
  491. package/build/implementations/http/doc-registry.test.js.map +0 -1
  492. package/build/implementations/http/error-dispatch.d.ts +0 -76
  493. package/build/implementations/http/error-dispatch.js.map +0 -1
  494. package/build/implementations/http/error-dispatch.test.js +0 -254
  495. package/build/implementations/http/error-dispatch.test.js.map +0 -1
  496. package/build/implementations/http/error-taxonomy.js.map +0 -1
  497. package/build/implementations/http/error-taxonomy.test.js.map +0 -1
  498. package/build/implementations/http/hono/docs/http-doc.js.map +0 -1
  499. package/build/implementations/http/hono/docs/http-stream-doc.js.map +0 -1
  500. package/build/implementations/http/hono/docs/rpc-doc.js.map +0 -1
  501. package/build/implementations/http/hono/docs/stream-doc.js.map +0 -1
  502. package/build/implementations/http/hono/handlers/http-stream.js +0 -123
  503. package/build/implementations/http/hono/handlers/http-stream.js.map +0 -1
  504. package/build/implementations/http/hono/handlers/http-stream.test.js.map +0 -1
  505. package/build/implementations/http/hono/handlers/http.js +0 -110
  506. package/build/implementations/http/hono/handlers/http.js.map +0 -1
  507. package/build/implementations/http/hono/handlers/http.test.js.map +0 -1
  508. package/build/implementations/http/hono/handlers/rpc.js +0 -32
  509. package/build/implementations/http/hono/handlers/rpc.js.map +0 -1
  510. package/build/implementations/http/hono/handlers/rpc.test.js.map +0 -1
  511. package/build/implementations/http/hono/handlers/stream.d.ts +0 -23
  512. package/build/implementations/http/hono/handlers/stream.js +0 -147
  513. package/build/implementations/http/hono/handlers/stream.js.map +0 -1
  514. package/build/implementations/http/hono/handlers/stream.test.js.map +0 -1
  515. package/build/implementations/http/hono/index.js.map +0 -1
  516. package/build/implementations/http/hono/index.test.js.map +0 -1
  517. package/build/implementations/http/hono/path.js.map +0 -1
  518. package/build/implementations/http/hono/path.test.js +0 -83
  519. package/build/implementations/http/hono/path.test.js.map +0 -1
  520. package/build/implementations/http/hono/types.d.ts +0 -51
  521. package/build/implementations/http/hono/types.js.map +0 -1
  522. package/build/implementations/http/on-request-error.test.js.map +0 -1
  523. package/build/implementations/http/route-errors.test.js.map +0 -1
  524. package/build/index.d.ts +0 -175
  525. package/build/index.js +0 -47
  526. package/build/index.js.map +0 -1
  527. package/build/index.test.js.map +0 -1
  528. package/build/migration.test.js.map +0 -1
  529. package/build/schema/extract-json-schema.d.ts +0 -2
  530. package/build/schema/extract-json-schema.js +0 -12
  531. package/build/schema/extract-json-schema.js.map +0 -1
  532. package/build/schema/extract-json-schema.test.js +0 -23
  533. package/build/schema/extract-json-schema.test.js.map +0 -1
  534. package/build/schema/parser.d.ts +0 -36
  535. package/build/schema/parser.js +0 -210
  536. package/build/schema/parser.js.map +0 -1
  537. package/build/schema/parser.test.js +0 -120
  538. package/build/schema/parser.test.js.map +0 -1
  539. package/build/schema/resolve-schema-lib.d.ts +0 -12
  540. package/build/schema/resolve-schema-lib.js +0 -11
  541. package/build/schema/resolve-schema-lib.js.map +0 -1
  542. package/build/schema/resolve-schema-lib.test.js +0 -17
  543. package/build/schema/resolve-schema-lib.test.js.map +0 -1
  544. package/build/schema/types.d.ts +0 -8
  545. package/build/schema/types.js +0 -2
  546. package/build/stack-utils.d.ts +0 -25
  547. package/build/stack-utils.js.map +0 -1
  548. package/build/stack-utils.test.js.map +0 -1
  549. package/build/types.d.ts +0 -142
  550. package/build/types.js +0 -2
  551. package/build/types.js.map +0 -1
  552. package/docs/decisions/2026-06-02-monorepo-split-evaluation.md +0 -80
  553. package/docs/handoffs/ajsc-named-type-collision.md +0 -134
  554. package/docs/handoffs/ajsc-named-type-support.md +0 -181
  555. package/docs/handoffs/shared-models-auto-resolve-response.md +0 -181
  556. package/docs/npm-workspaces-migration-plan.md +0 -611
  557. package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +0 -886
  558. package/docs/superpowers/plans/2026-04-24-kotlin-codegen-target.md +0 -1265
  559. package/docs/superpowers/plans/2026-04-25-ajsc-v7-kotlin-polish.md +0 -1993
  560. package/docs/superpowers/plans/2026-04-29-safe-result-api.md +0 -2293
  561. package/docs/superpowers/plans/2026-05-07-astro-adapter.md +0 -1391
  562. package/docs/superpowers/plans/2026-05-08-create-http.md +0 -3355
  563. package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +0 -3365
  564. package/docs/superpowers/plans/2026-06-05-dx-feedback-round.md +0 -1292
  565. package/docs/superpowers/plans/2026-06-06-shared-models-convention-and-diagnostics.md +0 -659
  566. package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +0 -401
  567. package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +0 -314
  568. package/docs/superpowers/specs/2026-04-25-swift-codegen-design.md +0 -264
  569. package/docs/superpowers/specs/2026-04-29-safe-result-api-design.md +0 -324
  570. package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +0 -252
  571. package/docs/superpowers/specs/2026-05-08-create-http-design.md +0 -409
  572. package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +0 -411
  573. package/docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md +0 -285
  574. package/src/create-http-stream.ts +0 -191
  575. package/src/create-http.ts +0 -210
  576. package/src/create-stream.ts +0 -228
  577. package/src/create.ts +0 -172
  578. package/src/implementations/http/README.md +0 -390
  579. package/src/implementations/http/error-dispatch.test.ts +0 -283
  580. package/src/implementations/http/error-dispatch.ts +0 -176
  581. package/src/implementations/http/hono/handlers/http-stream.ts +0 -152
  582. package/src/implementations/http/hono/handlers/http.ts +0 -145
  583. package/src/implementations/http/hono/handlers/rpc.ts +0 -54
  584. package/src/implementations/http/hono/path.test.ts +0 -96
  585. package/src/index.ts +0 -101
  586. package/src/schema/extract-json-schema.test.ts +0 -25
  587. package/src/schema/extract-json-schema.ts +0 -15
  588. package/src/schema/parser.test.ts +0 -182
  589. package/src/schema/parser.ts +0 -265
  590. package/src/schema/resolve-schema-lib.test.ts +0 -19
  591. package/src/schema/resolve-schema-lib.ts +0 -29
  592. package/src/schema/types.ts +0 -20
  593. package/src/types.ts +0 -133
  594. /package/build/{implementations/http → adapters}/astro/astro-context.d.ts +0 -0
  595. /package/build/{implementations/http → adapters}/astro/astro-context.js +0 -0
  596. /package/build/{implementations/http → adapters}/astro/create-handler.d.ts +0 -0
  597. /package/build/{implementations/http → adapters}/astro/create-handler.js +0 -0
  598. /package/build/{implementations/http → adapters}/astro/index.d.ts +0 -0
  599. /package/build/{implementations/http → adapters}/astro/index.js +0 -0
  600. /package/build/{implementations/http → adapters}/astro/index.test.d.ts +0 -0
  601. /package/build/{implementations/http → adapters}/astro/rewrite-request.d.ts +0 -0
  602. /package/build/{implementations/http → adapters}/astro/rewrite-request.js +0 -0
  603. /package/build/{create-http-stream.test.d.ts → adapters/hono/envelope-parity.test.d.ts} +0 -0
  604. /package/build/{implementations/http → adapters}/hono/handlers/http-stream.test.d.ts +0 -0
  605. /package/build/{implementations/http → adapters}/hono/handlers/http.test.d.ts +0 -0
  606. /package/build/{implementations/http → adapters}/hono/handlers/rpc.test.d.ts +0 -0
  607. /package/build/{implementations/http → adapters}/hono/handlers/stream.test.d.ts +0 -0
  608. /package/build/{implementations/http → adapters}/hono/index.test.d.ts +0 -0
  609. /package/build/{implementations/http → adapters/hono}/on-request-error.test.d.ts +0 -0
  610. /package/build/{implementations/http → adapters/hono}/route-errors.test.d.ts +0 -0
  611. /package/build/{create-http.test.d.ts → client/freeze.test.d.ts} +0 -0
  612. /package/build/{create-stream.test.d.ts → codegen/goldens.test.d.ts} +0 -0
  613. /package/build/{create.test.d.ts → core/create-http-stream.test.d.ts} +0 -0
  614. /package/build/{doc-envelope.test.d.ts → core/create-http.test.d.ts} +0 -0
  615. /package/build/{errors.test.d.ts → core/create-stream.test.d.ts} +0 -0
  616. /package/build/{implementations/http/doc-registry.test.d.ts → core/create.test.d.ts} +0 -0
  617. /package/build/{implementations/http/error-dispatch.test.d.ts → core/definition-site.test.d.ts} +0 -0
  618. /package/build/{implementations/http/error-taxonomy.test.d.ts → core/errors.test.d.ts} +0 -0
  619. /package/build/{errors.test.js → core/errors.test.js} +0 -0
  620. /package/build/{implementations/http/hono/path.test.d.ts → core/factory-options.test.d.ts} +0 -0
  621. /package/build/{migration.test.d.ts → core/migration.test.d.ts} +0 -0
  622. /package/build/{index.test.d.ts → core/procedures.test.d.ts} +0 -0
  623. /package/build/{implementations/http/hono → core}/types.js +0 -0
  624. /package/build/schema/{extract-json-schema.test.d.ts → adapter.test.d.ts} +0 -0
  625. /package/build/schema/{parser.test.d.ts → compile.test.d.ts} +0 -0
  626. /package/build/schema/{resolve-schema-lib.test.d.ts → typebox.test.d.ts} +0 -0
  627. /package/build/{stack-utils.test.d.ts → server/context.test.d.ts} +0 -0
  628. /package/build/{doc-envelope.js → server/doc-envelope.js} +0 -0
  629. /package/build/{doc-envelope.test.js → server/doc-envelope.test.js} +0 -0
  630. /package/build/{implementations → server}/types.js +0 -0
  631. /package/src/{implementations/http → adapters}/astro/README.md +0 -0
  632. /package/src/{implementations/http → adapters}/astro/astro-context.ts +0 -0
  633. /package/src/{implementations/http → adapters}/astro/create-handler.ts +0 -0
  634. /package/src/{implementations/http → adapters}/astro/index.ts +0 -0
  635. /package/src/{implementations/http → adapters}/astro/rewrite-request.ts +0 -0
@@ -1,324 +0,0 @@
1
- # Safe Result API & Normalized Client Errors — Design
2
-
3
- Date: 2026-04-29
4
- Branch: TBD (next major)
5
- Status: design
6
-
7
- ## Goal
8
-
9
- Eliminate the "what type is `err`?" problem in catch blocks of generated client code by:
10
-
11
- 1. **Normalizing** every error that can be raised by the client into a known set of framework error classes (network, timeout, abort, http, parse, usage). Today the client throws a mix of its own classes (`ClientRequestError`, `ClientPathParamError`) and raw platform errors (`TypeError`, `DOMException`); the normalized layer means consumers only ever see framework classes from the throwing path.
12
- 2. Adding a **`.safe()` sibling form** on every RPC and API callable that returns a discriminated `Result<T, E>` instead of throwing. Consumers narrow via the `kind` discriminant; per-route typed errors (declared via `route.errors`) come through under `kind: 'typed'`; framework errors come through under their own kinds (`'http'`, `'network'`, `'timeout'`, `'aborted'`, `'usage'`, `'parse'`, `'unknown'`).
13
- 3. Making the `kind` set **extensible** via TypeScript declaration merging on a `ClientErrorMap` interface, paired with an adapter-provided `classifyError` runtime classifier. This mirrors the existing `RequestMeta` augmentation pattern and lets downstream apps surface their own error categories as first-class members of the `Result` union.
14
-
15
- The throwing form remains canonical; `.safe()` is opt-in. Both forms are first-class, neither is deprecated.
16
-
17
- ## Non-goals
18
-
19
- - **Streams.** `TypedStream` mixes async iteration, a `.result` promise, and mid-stream SSE errors. Bolting `Result` on top is messy and out of scope. Streams keep the throwing form. (Stream callables benefit from the normalization layer for pre-stream errors — same classifier — but no `.safe` sibling is emitted on streams.)
20
- - **Per-client generic error map.** Considered and rejected during brainstorming. Module augmentation is the single supported extension mechanism. If apps with multiple clients needing distinct maps materialize, a per-client generic can be added later (additive, non-breaking).
21
- - **Result-style API wrappers for hooks or adapters.** Hooks still throw or resolve normally. The classifier runs at the `executeCall` boundary only.
22
- - **Auto-retry, circuit breaking, backoff.** Out of scope. The normalized error categories make these cleaner to implement at consumer layer, but the framework does not ship them.
23
- - **Renaming `ClientPathParamError` or `ClientStreamError`.** They keep their names; only `ClientRequestError` is renamed (see below).
24
-
25
- ## Output shape
26
-
27
- ### What the downstream dev writes
28
-
29
- Throwing form (unchanged surface, normalized error classes):
30
-
31
- ```ts
32
- try {
33
- const response = await client.records.DownloadRecord(params, { timeout: 60_000 })
34
- if (response.pdfBase64) downloadBase64AsPdf(...)
35
- } catch (err) {
36
- if (err instanceof ApiErrors.UseCaseError) Alerts.error(err.body.message)
37
- else if (err instanceof ClientHttpError) Alerts.error(`Server ${err.status}`)
38
- else if (err instanceof ClientTimeoutError) Alerts.error('Timed out')
39
- else if (err instanceof ClientNetworkError) Alerts.error('Network error')
40
- else if (err instanceof ClientAbortError) { /* user cancelled */ }
41
- else throw err // programmer/framework bug
42
- }
43
- ```
44
-
45
- Safe form (new):
46
-
47
- ```ts
48
- const r = await client.records.DownloadRecord.safe(params, { timeout: 60_000 })
49
-
50
- if (r.ok) {
51
- if (r.value.pdfBase64) downloadBase64AsPdf(...)
52
- return
53
- }
54
-
55
- switch (r.kind) {
56
- case 'typed':
57
- if (r.error instanceof ApiErrors.UseCaseError) Alerts.error(r.error.body.message)
58
- break
59
- case 'http': Alerts.error(`Server ${r.error.status}`); break
60
- case 'network': Alerts.error('Network error'); break
61
- case 'timeout': Alerts.error('Timed out'); break
62
- case 'aborted': break // user cancelled
63
- case 'rateLimited': Alerts.error(`Retry in ${r.error.retryAfter}s`); break // app-defined
64
- case 'parse':
65
- case 'usage': throw r.error // programmer/framework bug — fail loud
66
- case 'unknown': Alerts.error('Unknown error'); console.error(r.error); break
67
- }
68
- ```
69
-
70
- ### Generated callable signature (RPC and API)
71
-
72
- Today (unchanged):
73
- ```ts
74
- DownloadRecord(params: Records.DownloadRecord.Params, options?: ProcedureCallOptions): Promise<Records.DownloadRecord.Response>
75
- ```
76
-
77
- Added: a `.safe` property on the same function value:
78
- ```ts
79
- DownloadRecord.safe(
80
- params: Records.DownloadRecord.Params,
81
- options?: ProcedureCallOptions
82
- ): Promise<Result<Records.DownloadRecord.Response, Records.DownloadRecord.Errors>>
83
- ```
84
-
85
- The throwing form and `.safe` share the same params, options, descriptor, hooks, classifier, and adapter — only the failure presentation differs.
86
-
87
- ### `Result` type derivation
88
-
89
- Defined in `src/client/types.ts` (and bundled into `_types.ts` in self-contained mode):
90
-
91
- ```ts
92
- export type Result<T, ETyped> =
93
- | { ok: true; value: T }
94
- | { ok: false; kind: 'typed'; error: ETyped }
95
- | FrameworkFailure
96
-
97
- type FrameworkFailure = {
98
- [K in keyof ClientErrorMap]: { ok: false; kind: K; error: ClientErrorMap[K] }
99
- }[keyof ClientErrorMap]
100
-
101
- // Default (augmentable) map — devs add keys via declaration merging.
102
- export interface ClientErrorMap {
103
- http: ClientHttpError
104
- network: ClientNetworkError
105
- timeout: ClientTimeoutError
106
- aborted: ClientAbortError
107
- parse: ClientParseError
108
- usage: ClientPathParamError
109
- unknown: unknown
110
- }
111
- ```
112
-
113
- Augmentation example (downstream app code, written once):
114
-
115
- ```ts
116
- declare module 'ts-procedures/client' {
117
- interface ClientErrorMap {
118
- rateLimited: MyRateLimitError
119
- paymentRequired: MyPaymentError
120
- }
121
- }
122
- ```
123
-
124
- (For self-contained generated clients, the augmentation target is `./generated/_types` instead — same pattern as `RequestMeta` today.)
125
-
126
- After augmentation, `Result`'s union expands automatically; every `.safe()` call narrows the new kinds with no per-call type ceremony and no codegen change.
127
-
128
- **`'typed'` is reserved.** Augmentation cannot register a `kind: 'typed'` because the typed-arm is part of `Result<T, ETyped>` itself, not `ClientErrorMap`. Attempting `interface ClientErrorMap { typed: ... }` produces a confusing TS error about overlapping discriminants — which is the desired behavior (don't do it), but the docs page calls this out so adventurous consumers don't have to discover it the hard way.
129
-
130
- ## Architectural decisions
131
-
132
- ### 1. Normalize platform errors at the `executeCall` boundary
133
-
134
- The classifier wraps the adapter call in `executeCall` (and the equivalent stream pre-error path in `executeStream`). Raw platform errors (`TypeError`, `DOMException`) are caught and translated to framework classes. The classifier composition is:
135
-
136
- 1. **Adapter-provided `classifyError`** runs first if present. Returns `{ kind, error }` or `null` (fall through).
137
- 2. **Default classifier** runs second. Recognizes `TypeError` from fetch → `ClientNetworkError`; `DOMException` with `name: 'AbortError'` + timeout-signal-aborted → `ClientTimeoutError`; `DOMException` with `name: 'AbortError'` + user-signal-aborted → `ClientAbortError`; anything else → `null`.
138
- 3. **Fallthrough** — anything still unclassified is wrapped as `kind: 'unknown'` with the raw error attached.
139
-
140
- The default classifier is exported (`defaultClassifyError`) so adapter authors can compose deliberately:
141
-
142
- ```ts
143
- const adapter = createFetchAdapter({
144
- classifyError: (e) => myClassify(e) ?? defaultClassifyError(e),
145
- })
146
- ```
147
-
148
- **Where each `kind` originates** — the `executeCall` flow has three distinct exit paths to a non-`ok` Result. The classifier only runs on the second.
149
-
150
- | # | Stage | Source of failure | Resulting kind(s) | Notes |
151
- |---|---|---|---|---|
152
- | 1 | **Pre-adapter** | `buildAdapterRequest` synchronously throws (e.g. missing path param) | `'usage'` (`ClientPathParamError`) | Bypasses classifier and `onError` hook entirely. `executeSafeCall` catches it at the outer try and maps to `kind: 'usage'`. The throwing form propagates as today. |
153
- | 2 | **Adapter throws** | `adapter.request()` rejects (network failure, abort, anything custom) | `'network'` / `'timeout'` / `'aborted'` / custom kinds / `'unknown'` | Classifier runs in the try/catch around `adapter.request()`. `onError` hook receives the *normalized* error (post-classification). |
154
- | 3 | **Adapter returns non-2xx** | `response.status < 200 \|\| >= 300` | `'typed'` (registry match) or `'http'` (no match → `ClientHttpError`) | Classifier does *not* run on this path. The registry-or-fallback dispatch in `call.ts` is the sole source of `'typed'` and `'http'` kinds. |
155
-
156
- The `'typed'` arm is **only** reachable via path 3. The framework-failure kinds (`'network'`, `'timeout'`, `'aborted'`, `'unknown'`, custom) are **only** reachable via path 2. The `'usage'` kind is **only** reachable via path 1. There is no overlap; each kind has exactly one source location in the runtime.
157
-
158
- ### 2. Distinguishing timeout from user-abort
159
-
160
- `AbortSignal.any([timeoutSignal, userSignal])` loses provenance — when `aborted` fires, the combined signal alone can't tell us which input triggered it. `executeCall` keeps references to both signals locally and inspects `timeoutSignal?.aborted` first when an `AbortError` lands; if true, classify as timeout, else as user-abort.
161
-
162
- This logic lives in `defaultClassifyError`, but it needs the timeout signal as input. Resolution: classifier signature takes `(raw, ctx)` where `ctx` carries `{ timeoutSignal?, userSignal? }`:
163
-
164
- ```ts
165
- type ErrorClassifier = (
166
- raw: unknown,
167
- ctx: { timeoutSignal?: AbortSignal; userSignal?: AbortSignal }
168
- ) => { kind: string; error: Error } | null
169
- ```
170
-
171
- The classifier's return contract is `error: Error` — every classified output is a real `Error` subclass (the framework class). Non-`Error` throws (e.g. a hook that threw a string) are *not* classified; they fall through to `kind: 'unknown'` with the raw value preserved as-is. `kind: 'unknown'` is the *only* path that produces a non-`Error` `error` field, and it's the only `ClientErrorMap` entry typed as `unknown` rather than a class.
172
-
173
- ### 3. New error class taxonomy
174
-
175
- | Class | Replaces / new | `kind` | Notes |
176
- |---|---|---|---|
177
- | `ClientHttpError` | renames `ClientRequestError` | `'http'` | Same shape (`status`, `headers`, `body`, `procedureName`, `scope`). Old name re-exported as a deprecated alias for one minor cycle? See migration. |
178
- | `ClientNetworkError` | new | `'network'` | Wraps the original `TypeError`; carries the raw cause. |
179
- | `ClientTimeoutError` | new | `'timeout'` | Carries `timeoutMs`. |
180
- | `ClientAbortError` | new | `'aborted'` | Carries the `AbortSignal.reason` if available. |
181
- | `ClientParseError` | new | `'parse'` | Adapter-reported body parse failure (not currently raised; reserved for adapters that want to flag unparseable bodies). |
182
- | `ClientPathParamError` | unchanged | `'usage'` | Existing class, kind added. |
183
-
184
- All extend `Error`. All have `readonly procedureName` and `readonly scope` for telemetry. **Every framework class accepts an optional `cause` constructor argument and assigns it to the standard `Error.cause` property** — explicit contract so adapter authors and telemetry consumers can rely on `error.cause` to recover the underlying platform error (e.g. the original `TypeError` that produced a `ClientNetworkError`). The default classifier always populates `cause` when wrapping a platform error; consumer-provided classifiers SHOULD do the same and the docs page calls this out.
185
-
186
- ### 4. `.safe` is a sibling property on the callable, not a separate namespace
187
-
188
- Each generated callable becomes the throwing function plus a `.safe` property:
189
-
190
- ```ts
191
- // in emit-scope.ts output
192
- function DownloadRecord(params, options) { return client.call({...}, options) }
193
- DownloadRecord.safe = function (params, options) { return client.safeCall({...}, options) }
194
- ```
195
-
196
- In TypeScript this is expressed via an intersection type at codegen time, or by annotating the binding with a generated interface. (Implementation detail for the writing-plans phase.) Discoverability wins: hover on `DownloadRecord` shows `.safe`.
197
-
198
- ### 5. Codegen omits the `'typed'` arm when the route declares no errors
199
-
200
- A naive emission of `Result<Response, never>` for routes without typed errors leaves `{ ok: false; kind: 'typed'; error: never }` in the union. TypeScript does not collapse `never`-payload arms in hover output — the consumer sees an unreachable five-line arm cluttering every IDE tooltip. To prevent this, codegen branches:
201
-
202
- - Route has at least one entry in `route.errors` → `Result<Response, RouteErrors>` (full union, including `'typed'` arm).
203
- - Route has no errors declared → `ResultNoTyped<Response>` (`{ ok: true; value: T } | FrameworkFailure`, no `'typed'` arm).
204
-
205
- Both type aliases live in `src/client/types.ts` (and the bundled `_types.ts` in self-contained mode). The codegen change in `emit-scope.ts` selects which alias to emit per route based on the presence of `route.errors`. Hover output stays clean either way.
206
-
207
- ### 6. `.safe` is RPC and API only
208
-
209
- Streams keep the throwing form. The classifier still normalizes their pre-stream errors (so `executeStream` throws `ClientHttpError` etc. instead of raw `DOMException`), but `TypedStream.result` and async iteration stay as-is.
210
-
211
- ### 7. `ClientErrorMap` augmentation matches `RequestMeta` precedent
212
-
213
- Same module name (`ts-procedures/client` for direct consumers, `./generated/_types` for self-contained), same `interface X { ... }` declaration-merging mechanism, same documentation pattern. Devs who already learned the `RequestMeta` augmentation idiom transfer it directly.
214
-
215
- ### 8. `unknown` is an explicit kind, not a leak
216
-
217
- Anything the classifier can't categorize falls through to `kind: 'unknown'` with `error: unknown`. This is a **deliberate** discriminant — consumers handling all kinds exhaustively get a single fallthrough branch for true edge cases (a hook threw something weird, a custom adapter raised something nobody declared). It is *not* a synonym for "error we forgot to classify" — every category we ship default coverage for is represented as its own kind.
218
-
219
- ### 9. Hooks see normalized errors
220
-
221
- `onError` hook receives the classified framework error in `ctx.error`, not the raw platform exception. This is a behavior change vs. today (today the hook sees whatever the adapter threw — typically raw `TypeError`/`DOMException`). Documented in migration notes.
222
-
223
- The hook ordering is unchanged: `onError` runs after the adapter throws, before `executeCall` re-throws / the `.safe` wrapper catches.
224
-
225
- ### 10. `.safe` invokes `onError` (cross-cutting telemetry)
226
-
227
- `onError` runs on every failure regardless of whether the consumer used the throwing form or the `.safe` form. The hook is for cross-cutting concerns (logging, tracing, metrics) which want to observe all failures uniformly; making it conditional on call style would force telemetry consumers to wire two paths.
228
-
229
- **Known consequence — retries-via-`.safe`.** A consumer using `.safe` to drive a retry loop will get one `onError` invocation per attempt, including attempts that the application considers expected/recoverable. This is by design for v1 (telemetry should see all attempts; the *application* knows what's "expected", not the framework). The FAQ in `docs/client-error-handling.md` documents this and shows the suppression pattern: the consumer attaches a per-call `onError` that no-ops, or uses the `meta` field to flag "this is a retry, ignore" inside their global `onError`. A dedicated config knob is deliberately not added in v1; if the pattern proves heavy enough that consumers consistently reach for the workaround, a `suppressOnErrorForSafe` adapter-level toggle can be added later (additive, non-breaking).
230
-
231
- ### 11. Implementation locus
232
-
233
- - `src/client/errors.ts` — add `ClientNetworkError`, `ClientTimeoutError`, `ClientAbortError`, `ClientParseError`. Rename `ClientRequestError` → `ClientHttpError`.
234
- - `src/client/classify-error.ts` — **new file**. Exports `defaultClassifyError`, `ErrorClassifier` type.
235
- - `src/client/types.ts` — add `ClientErrorMap` interface, `Result<T, E>` type, `FrameworkFailure` derivation. Extend `ClientAdapter` with optional `classifyError`. Extend `ClientInstance` with `safeCall<TResponse, ETyped>(descriptor, options): Promise<Result<TResponse, ETyped>>`.
236
- - `src/client/call.ts` — wrap adapter call in classifier; `executeCall` throws normalized errors. Add `executeSafeCall` that catches `executeCall` and returns `Result`. The generated `.safe` callable closes over `client.safeCall`, which delegates to `executeSafeCall`. Single source of truth for the safe path: `executeSafeCall`.
237
- - `src/client/stream.ts` — wrap adapter stream call in classifier (pre-stream errors only). Mid-stream errors unchanged.
238
- - `src/client/fetch-adapter.ts` — accept `classifyError` in config (composes with default).
239
- - `src/client/index.ts` — export new classes, classifier, `Result`, `ClientErrorMap`.
240
- - `src/codegen/emit-scope.ts` — emit `.safe` sibling on every RPC/API callable. Type signature uses `Result<Response, RouteErrors>`.
241
- - `src/codegen/emit-index.ts` — no changes needed (factory shape unchanged).
242
- - `src/codegen/targets/_shared/` — no changes (Kotlin/Swift unaffected).
243
-
244
- ## Pipeline integration
245
-
246
- No CLI flag changes. The `.safe` form is always emitted for RPC/API routes; consumers who don't use it pay zero runtime cost (the property is a thin closure over `client.safeCall`).
247
-
248
- For self-contained mode (`--self-contained`, the default), `_types.ts` and `_client.ts` bundle the new types and runtime. The augmentation target shifts from `ts-procedures/client` to `./generated/_types` — documented in the codegen docs alongside `RequestMeta`'s analogous note.
249
-
250
- `_errors.ts` is unchanged.
251
-
252
- ## Migration (breaking changes)
253
-
254
- **Major version bump (next major).**
255
-
256
- | Change | Impact | Mitigation |
257
- |---|---|---|
258
- | `ClientRequestError` → `ClientHttpError` | Anyone catching `ClientRequestError` directly | Re-export `ClientRequestError` as a deprecated alias of `ClientHttpError` for one minor cycle. Console-warn on import? (Probably no — pure type re-export is silent. Documentation only.) |
259
- | Raw `DOMException`/`TypeError` no longer reach consumer catch blocks | Anyone catching `DOMException` directly to detect aborts/timeouts | Migration guide shows `instanceof ClientTimeoutError` / `ClientAbortError` pattern. Old pattern keeps working only if the consumer's own code does the check before our throw — which by definition won't happen since we re-throw normalized. |
260
- | `onError` hook sees normalized errors | Anyone inspecting `ctx.error` for `DOMException` / `TypeError` shape | Migration guide. The normalized class wraps the raw cause (`error.cause`), so consumers can still drill in if needed. |
261
- | `ClientAdapter` interface gains optional `classifyError` | None for default fetch adapter consumers; custom adapter authors must consider whether to provide one | Optional field, no required change. |
262
-
263
- No changes to: schema authoring, server-side procedure definitions, `Procedures()` factory, hook order, request descriptors, generated index/factory shape, Kotlin/Swift targets.
264
-
265
- ## Tests
266
-
267
- ### Runtime (`src/client/`)
268
-
269
- 1. **`classify-error.test.ts`** — default classifier: `TypeError` → `ClientNetworkError`; `AbortError` + timeout-signal-aborted → `ClientTimeoutError`; `AbortError` + user-signal-aborted → `ClientAbortError`; both signals fired → timeout wins (deterministic precedence); unknown error → `null` (fallthrough).
270
- 2. **`call.test.ts`** (extend) — `executeCall` throws normalized classes for each platform error category. Adapter-provided classifier runs first; default runs second; unclassified raises original error wrapped as a generic `Error` (NOT swallowed). Hooks see normalized error.
271
- 3. **`safe-call.test.ts`** (new) — every kind in `ClientErrorMap` produces the corresponding `Result` shape. `kind: 'typed'` carries the registry-dispatched typed error. `ok: true` for 2xx with body. Adapter classifier custom kinds round-trip.
272
- 4. **`fetch-adapter.test.ts`** (extend) — `classifyError` config composes with default (custom-then-default order).
273
- 5. **`stream.test.ts`** (extend) — pre-stream errors are classified; mid-stream errors are not affected.
274
-
275
- ### Codegen (`src/codegen/`)
276
-
277
- 6. **`emit-scope.test.ts`** (extend) — RPC and API callables emit a `.safe` property with the correct `Result<Response, RouteErrors>` signature. Streams do not emit `.safe`. Routes without typed errors emit `Result<Response, never>` (the `kind: 'typed'` branch is unreachable but type-valid).
278
- 7. **Golden file integration** — update `users-golden.ts` (or equivalent ts-target fixture) with the new `.safe` siblings.
279
- 8. **TypeScript compilation test** — verify generated output type-checks under `tsc --noEmit`. Existing test, will catch regressions.
280
-
281
- ### Augmentation (manual / docs example)
282
-
283
- 9. **`augment-error-map.test.ts`** (new) — a sample `declare module` block in a `.test-d.ts` file using `tsd` or `expectType` to verify augmented kinds appear in the `Result` union and narrow correctly.
284
-
285
- 10. **Bundle-size budget test** (new) — generate a 100-route fixture, run the codegen, minify the output (`esbuild --minify`), and assert the per-route byte cost added by the `.safe` sibling is within budget. The doc claims "zero runtime cost" but each callable now wraps two functions in a binding object that prevents tree-shaking; rough estimate ~100 bytes per route post-minify. Initial budget: assert the *delta* between this branch and the prior major's output stays below **200 bytes per route** for the 100-route fixture. If the test fails or the budget is wrong, the budget can be revised but the test itself stays as a regression guard.
286
-
287
- ## Documentation
288
-
289
- 1. **`docs/client-error-handling.md`** — new file. Covers:
290
- - The throwing form: framework error class taxonomy + narrowing pattern.
291
- - The `.safe` form: when to use, full example with exhaustive switch.
292
- - Custom error categories: `ClientErrorMap` augmentation + adapter `classifyError`.
293
- - Migration from `ClientRequestError` / `DOMException` catches.
294
- 2. **`CLAUDE.md`** — add a paragraph in the Client section pointing at the new file. Update the "Important Patterns" list to mention the normalized error taxonomy and `.safe` form.
295
- 3. **`agent_config/`** — update the AI-assistant skills:
296
- - `claude-code/skills/ts-procedures/patterns.md` — add a "Handling errors in client code" section showing both forms.
297
- - `claude-code/skills/ts-procedures/anti-patterns.md` — add "Don't catch raw DOMException/TypeError" anti-pattern, point at framework classes.
298
- - `copilot/copilot-instructions.md` and `cursor/cursorrules` — same patterns, condensed.
299
- 4. The TS-target consumer guide currently lives in `CLAUDE.md` and the new `docs/client-error-handling.md` — no separate `docs/codegen-ts.md` file exists today, and creating one is out of scope for this change. The `.safe` form is documented in `docs/client-error-handling.md` (item 1) with a back-reference from the `CLAUDE.md` Client section (item 2).
300
- 5. **CHANGELOG / release notes** — major bump entry covering the rename, the new classes, the `.safe` form, the augmentation pattern.
301
-
302
- ## Open questions
303
-
304
- These are the remaining design decisions to resolve before implementation. Each has a recommended default in parentheses; called out explicitly so the writing-plans phase makes them explicit choices rather than implicit ones.
305
-
306
- 1. **Does the deprecated `ClientRequestError` alias ship?** (Recommended: **yes**, for one minor release after the major bump. Pure type re-export, zero runtime cost.)
307
- 2. **Does `ClientParseError` get raised by the default fetch adapter today?** (Recommended: **no**, the default adapter falls back to text/null on parse failure — current behavior. The class ships so adapter authors who want stricter parsing can use it. Keeps scope tight.)
308
- 3. **What does `executeStream`'s pre-stream error path look like?** (Recommended: same classifier composition as `executeCall`. Mid-stream errors unchanged. No `.safe` on stream callables.)
309
- 4. **Is `ClientErrorMap['unknown']` typed as `unknown` or as `Error`?** (Recommended: **`unknown`**, because by definition nobody knows what raw thing landed there. Forcing `Error` would require wrapping non-`Error` throws and lose information.)
310
-
311
- ## Decision log
312
-
313
- | Decision | Choice | Rationale |
314
- |---|---|---|
315
- | Extension mechanism | Module augmentation on `ClientErrorMap` | Matches `RequestMeta` precedent; zero codegen complexity; zero per-call ceremony. Per-client generic rejected as over-engineering for the multi-client case. |
316
- | `'typed'` discriminant | Reserved; not part of `ClientErrorMap` | Keeps the typed-arm tied to per-route `ETyped` carrier rather than the global map. Augmentation collisions impossible by construction. |
317
- | `.safe` shape | Sibling property on each callable | Most discoverable; hover shows both forms; only form that carries the per-route `Errors` union typed correctly. Parallel namespace and options-toggle rejected. |
318
- | Hover cleanliness for routes without errors | Codegen omits `'typed'` arm via `ResultNoTyped<T>` | TS does not collapse `never`-payload arms in hover; explicit alias keeps tooltips clean. |
319
- | Stream coverage | Out of scope for `.safe` | Stream failure surface is a three-way split (pre-stream, mid-stream, completion); `Result` doesn't fit cleanly. Classifier still normalizes pre-stream errors. |
320
- | Throwing form | Stays canonical, not deprecated | Most consumers prefer try/catch; `.safe` is opt-in for sites that want exhaustive failure handling. Both forms first-class. |
321
- | Default classifier composition | Custom-then-default | Adapter authors can intercept early; default is the floor. `defaultClassifyError` exported so authors can opt back in deliberately. |
322
- | Timeout vs. user-abort distinction | Local refs in `executeCall`, passed to classifier via `ctx` | `AbortSignal.any` loses provenance; refs are the only way to recover it. |
323
- | `onError` invocation by `.safe` | Yes — fires regardless of call style | Telemetry is cross-cutting; conditional firing forces dual wiring. Retry-via-`.safe` consumers suppress at the call-site (per-call `onError` no-op or `meta.isRetry` flag), not via framework toggle. |
324
- | Cause chain | Every framework class accepts `cause` constructor arg, sets `Error.cause` | Adapter authors and telemetry consumers rely on `error.cause` to recover the underlying platform error. Explicit contract, default classifier always populates. |
@@ -1,252 +0,0 @@
1
- # Astro adapter for ts-procedures
2
-
3
- **Date:** 2026-05-07
4
- **Status:** Design — pending implementation
5
- **Subpath export:** `ts-procedures/astro`
6
-
7
- ## Problem
8
-
9
- Downstream developers using Astro want to host ts-procedures handlers inside Astro server endpoints (`src/pages/**/*.ts`). Today they must hand-roll the bridge: take an Astro `APIRoute`, convert its `APIContext` into a `Request`, find a way to dispatch through their procedure factories, and translate the result back. Astro's filesystem-as-router model also resists the existing `register(...).build()` pattern, which produces one app for many routes.
10
-
11
- The goal is a "drop in the entry point" adapter: a single catch-all Astro file delegates every procedure call to one or more already-built ts-procedures apps, with full access to Astro's per-request data (`locals`, `cookies`, `redirect`) inside procedure handlers.
12
-
13
- ## Non-goals
14
-
15
- - Native Astro builders. Mirroring `HonoAPIAppBuilder` as `AstroAPIAppBuilder` would duplicate path-matching, schema validation, error taxonomy, and DocRegistry plumbing. The catch-all pattern covers the request without that cost.
16
- - Per-procedure file scaffolding (one Astro file per procedure). Out of scope for v1.
17
- - DocRegistry / codegen integration. Users keep wiring `DocRegistry` against the same builders they pass to the adapter; the adapter returns no `.docs`.
18
- - `dispatch: 'merge'` strategy. YAGNI — users who want one Hono app can pre-merge with `app.route(...)` themselves.
19
-
20
- ## Approach
21
-
22
- The adapter is a thin Web-fetch delegator. It accepts already-built Hono apps (from any of `HonoAPIAppBuilder`, `HonoRPCAppBuilder`, `HonoStreamAppBuilder`) and exposes Astro `APIRoute` exports the developer re-exports from a catch-all file.
23
-
24
- ```
25
- Browser → /api/users/123
26
-
27
- Astro invokes ALL({ request, locals, cookies, params, redirect, url, ... })
28
-
29
- Adapter:
30
- 1. Strip pathPrefix from request URL (if configured) → `rewritten`
31
- (when no prefix configured, `rewritten` IS `request`)
32
- 2. astroContextMap.set(rewritten, apiContext) — WeakMap stash
33
- 3. For each app in order: response = await app.fetch(rewritten)
34
- — first response with status !== 404 is returned (a 500 from
35
- app A stops dispatch; it is treated as a real answer, not a
36
- miss). Only a literal 404 falls through to the next app.
37
- 4. All apps 404 → new Response(null, { status: 404 })
38
-
39
- Inside any factory-context closure:
40
- getAstroContext(c) → reads c.req.raw → WeakMap lookup → APIContext
41
- ```
42
-
43
- The WeakMap is keyed by the `Request` object that Hono ends up seeing — i.e., the post-rewrite Request when `pathPrefix` is configured, and the original Request when it isn't. Hono exposes that same Request on `c.req.raw`, which is what `getAstroContext` reads. Stashing happens AFTER the rewrite so the key matches what Hono observes. Entries clear when the Request is GC'd — no manual cleanup, no leak.
44
-
45
- ## Public API
46
-
47
- Two exports:
48
-
49
- ```ts
50
- import type { Hono, Context as HonoContext } from 'hono'
51
- import type { APIContext, APIRoute } from 'astro'
52
-
53
- export type AstroAdapterConfig = {
54
- /** One or more built Hono apps. Order matters in 'first-match' dispatch. */
55
- apps: Hono | Hono[]
56
-
57
- /**
58
- * Path prefix matching the Astro catch-all mount point.
59
- * For src/pages/api/[...rest].ts use '/api'. The adapter strips this
60
- * before delegating, so Hono routes do NOT need to repeat the prefix.
61
- *
62
- * Normalization: leading and trailing slashes are optional; '/api',
63
- * 'api', and '/api/' are equivalent.
64
- */
65
- pathPrefix?: string
66
- }
67
-
68
- export type AstroHandlers = {
69
- ALL: APIRoute
70
- GET: APIRoute
71
- POST: APIRoute
72
- PUT: APIRoute
73
- PATCH: APIRoute
74
- DELETE: APIRoute
75
- HEAD: APIRoute
76
- OPTIONS: APIRoute
77
- }
78
-
79
- export function createAstroHandler(config: AstroAdapterConfig): AstroHandlers
80
-
81
- /**
82
- * Read Astro's APIContext from inside a Hono factory-context closure.
83
- * Throws if called outside an Astro request scope (e.g., if the Hono app
84
- * is also being served outside the adapter and the closure ran there).
85
- */
86
- export function getAstroContext(c: HonoContext): APIContext
87
- ```
88
-
89
- `createAstroHandler` returns every method handler so the developer can destructure whichever they want. Most consumers use `ALL`. Per-method exports support cases where Astro's behavior diverges by method (e.g., `HEAD` auto-derived from `GET`).
90
-
91
- ## Developer-facing usage
92
-
93
- ```ts
94
- // src/server/procedures/users.ts
95
- import { Procedures } from 'ts-procedures'
96
- import { Type } from 'typebox'
97
-
98
- export const usersAPI = Procedures<{ db: Db; user: User | null }, APIConfig>()
99
-
100
- usersAPI.Create('GetUser', {
101
- path: '/users/:id',
102
- method: 'get',
103
- schema: {
104
- input: { pathParams: Type.Object({ id: Type.String() }) },
105
- returnType: Type.Object({ id: Type.String(), name: Type.String() }),
106
- },
107
- }, async (ctx, { pathParams }) => {
108
- return ctx.db.users.findOne(pathParams.id)
109
- })
110
- ```
111
-
112
- ```ts
113
- // src/server/api.ts
114
- import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
115
- import { getAstroContext } from 'ts-procedures/astro'
116
- import { usersAPI } from './procedures/users'
117
- import { db } from './db'
118
-
119
- export const apiApp = new HonoAPIAppBuilder()
120
- .register(usersAPI, (c) => {
121
- const astro = getAstroContext(c)
122
- return { db, user: astro.locals.user ?? null }
123
- })
124
- .build()
125
- ```
126
-
127
- ```ts
128
- // src/pages/api/[...rest].ts
129
- import { createAstroHandler } from 'ts-procedures/astro'
130
- import { apiApp } from '../../server/api'
131
-
132
- export const { ALL } = createAstroHandler({
133
- apps: apiApp,
134
- pathPrefix: '/api',
135
- })
136
- ```
137
-
138
- Multi-app variant:
139
-
140
- ```ts
141
- export const { ALL } = createAstroHandler({
142
- apps: [apiApp, rpcApp, streamsApp],
143
- pathPrefix: '/api',
144
- })
145
- ```
146
-
147
- ## Path-prefix rewrite
148
-
149
- ```ts
150
- function stripPrefix(request: Request, prefix: string | undefined): Request | null {
151
- if (!prefix) return request
152
- const url = new URL(request.url)
153
- const norm = '/' + prefix.replace(/^\/|\/$/g, '') // → '/api'
154
- if (norm === '/') return request
155
- if (!url.pathname.startsWith(norm)) return null // 404 fast-path
156
- const rest = url.pathname.slice(norm.length)
157
- url.pathname = rest === '' ? '/' : rest
158
- return new Request(url, request) // copies method/headers/body/signal/duplex
159
- }
160
- ```
161
-
162
- `new Request(url, request)` per the Web spec init pattern preserves method, headers, body stream, abort signal, and `duplex` mode. Body streams pass through; client disconnect aborts continue to fire on `c.req.raw.signal` inside Hono.
163
-
164
- Edge cases:
165
- - `pathPrefix` undefined → no rewrite
166
- - `pathPrefix` normalizes to `'/'` → no rewrite (root mount)
167
- - Request path doesn't start with the normalized prefix → adapter returns 404 without invoking any app
168
- - Exact prefix match (`/api` with request `/api`) → rewrites to `/`
169
-
170
- ## Streams & abort signals
171
-
172
- Hono stream builders return `Response` with a `ReadableStream` body. Astro SSR forwards that body verbatim — no special handling.
173
-
174
- `request.signal` flow on client disconnect:
175
- ```
176
- Browser closes EventSource
177
- → Astro aborts request.signal
178
- → rewriteRequest preserves signal via new Request(url, request)
179
- → Hono's c.req.raw.signal === request.signal
180
- → Stream builder's ctx.signal fires (reason 'client-disconnect')
181
- ```
182
-
183
- Same path the existing Hono stream tests cover; one new integration test wires Astro→Hono-stream end-to-end.
184
-
185
- ## Error handling
186
-
187
- The adapter performs no error handling of its own. Errors flow through whatever the underlying Hono builder is configured with — taxonomy, `onError`, `onRequestError`. The adapter returns whatever Response Hono produced.
188
-
189
- The only Response the adapter constructs itself: a stock `new Response(null, { status: 404 })` returned when (a) the path-prefix rewrite fails (request path outside the mount), or (b) every registered app returned 404.
190
-
191
- If `getAstroContext(c)` is called outside an Astro request (the Hono app is also being served via a non-Astro path and the closure ran there), it throws with a clear message. This propagates into Hono's normal error path and through the configured taxonomy.
192
-
193
- ## File layout
194
-
195
- ```
196
- src/implementations/http/astro/
197
- ├── README.md
198
- ├── index.ts # public exports
199
- ├── create-handler.ts # createAstroHandler, multi-app dispatch
200
- ├── astro-context.ts # WeakMap + getAstroContext
201
- ├── rewrite-request.ts # stripPrefix
202
- └── index.test.ts # integration tests vs real Hono builders
203
- ```
204
-
205
- ## Tests
206
-
207
- Integration-style, real builders, simulated `Request`:
208
-
209
- 1. Single Hono API app — GET and POST roundtrip, request/response bodies intact, headers preserved
210
- 2. Path prefix normalization: `/api`, `api`, `/api/`, exact-match → root, missing prefix → 404 short-circuit, no prefix configured, query string preserved through the rewrite (`/api/users?limit=10` → inner app sees `/users?limit=10`)
211
- 3. `getAstroContext(c)` returns the same `APIContext` object passed to `ALL`
212
- 4. Multi-app first-match: app A returns 404 → app B handles → app B's response returned
213
- 5. Multi-app non-404 short-circuit: app A returns 500 → app B is NOT invoked; app A's 500 is returned
214
- 6. All-404 fallback returns adapter's 404 (status 404, empty body), not Hono's
215
- 7. Stream pass-through: `HonoStreamAppBuilder` + generator yielding 3 events; verify all 3 reach the consumer through the adapter
216
- 8. Abort signal flow: simulate client disconnect by aborting the incoming `Request` mid-stream; assert `ctx.signal.aborted === true` inside the handler with `signal.reason !== 'stream-completed'`
217
- 9. `getAstroContext` outside Astro scope throws a clear error
218
-
219
- ## Package wiring
220
-
221
- ```json
222
- // package.json (additions)
223
- "exports": {
224
- "./astro": {
225
- "types": "./build/implementations/http/astro/index.d.ts",
226
- "import": "./build/implementations/http/astro/index.js"
227
- }
228
- },
229
- "optionalDependencies": {
230
- "ajsc": "...",
231
- "hono": "...",
232
- "astro": "^5.0.0"
233
- },
234
- "devDependencies": {
235
- "...": "...",
236
- "astro": "^5.0.0"
237
- }
238
- ```
239
-
240
- Matching the existing pattern for `hono`: listed as `optionalDependencies` so it isn't pulled into non-Astro deployments, and as `devDependencies` so types resolve at build time. Astro is imported via `import type` only — zero runtime dependency on the published package.
241
-
242
- ## Agent config / docs
243
-
244
- Mirror the pattern used when other builders shipped:
245
- - New `docs/astro-adapter.md` end-to-end walkthrough
246
- - Update `agent_config/claude-code/skills/ts-procedures/api-reference.md` and `patterns.md` to teach downstream Claude/Cursor/Copilot rules the Astro path
247
- - New `agent_config/claude-code/skills/ts-procedures-scaffold/templates/astro-catchall.ts` template
248
- - `src/implementations/http/astro/README.md` with usage and the prefix-semantics table
249
-
250
- ## Open questions
251
-
252
- None at design time. Ready for implementation planning.