ts-procedures 7.3.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/README.md +65 -3
  2. package/agent_config/claude-code/agents/ts-procedures-architect.md +6 -8
  3. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +30 -33
  4. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +104 -53
  5. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +205 -232
  6. package/agent_config/claude-code/skills/ts-procedures/patterns.md +80 -153
  7. package/agent_config/claude-code/skills/ts-procedures-review/SKILL.md +1 -1
  8. package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +4 -5
  9. package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +4 -7
  10. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono.md +223 -0
  11. package/agent_config/copilot/copilot-instructions.md +34 -48
  12. package/agent_config/cursor/cursorrules +34 -48
  13. package/build/client/call.js +4 -1
  14. package/build/client/call.js.map +1 -1
  15. package/build/client/call.test.js +23 -0
  16. package/build/client/call.test.js.map +1 -1
  17. package/build/client/fetch-adapter.js +3 -1
  18. package/build/client/fetch-adapter.js.map +1 -1
  19. package/build/client/fetch-adapter.test.js +11 -1
  20. package/build/client/fetch-adapter.test.js.map +1 -1
  21. package/build/client/index.test.js +7 -7
  22. package/build/client/index.test.js.map +1 -1
  23. package/build/client/request-builder.d.ts +1 -1
  24. package/build/client/request-builder.js +2 -2
  25. package/build/client/request-builder.js.map +1 -1
  26. package/build/client/stream.js +13 -2
  27. package/build/client/stream.js.map +1 -1
  28. package/build/client/stream.test.js +32 -7
  29. package/build/client/stream.test.js.map +1 -1
  30. package/build/client/typed-error-dispatch.test.js +8 -92
  31. package/build/client/typed-error-dispatch.test.js.map +1 -1
  32. package/build/client/types.d.ts +21 -3
  33. package/build/codegen/bin/cli.js +0 -0
  34. package/build/codegen/e2e.test.js +87 -23
  35. package/build/codegen/e2e.test.js.map +1 -1
  36. package/build/codegen/emit-errors.integration.test.js +1 -1
  37. package/build/codegen/emit-errors.integration.test.js.map +1 -1
  38. package/build/codegen/emit-scope.js +308 -47
  39. package/build/codegen/emit-scope.js.map +1 -1
  40. package/build/codegen/emit-scope.test.js +363 -110
  41. package/build/codegen/emit-scope.test.js.map +1 -1
  42. package/build/codegen/pipeline.test.js +7 -7
  43. package/build/codegen/pipeline.test.js.map +1 -1
  44. package/build/codegen/resolve-envelope.js +1 -1
  45. package/build/codegen/resolve-envelope.js.map +1 -1
  46. package/build/codegen/resolve-envelope.test.js +5 -5
  47. package/build/codegen/resolve-envelope.test.js.map +1 -1
  48. package/build/codegen/targets/_shared/route-slots.d.ts +8 -3
  49. package/build/codegen/targets/_shared/route-slots.js +49 -8
  50. package/build/codegen/targets/_shared/route-slots.js.map +1 -1
  51. package/build/codegen/targets/_shared/route-slots.test.js +99 -26
  52. package/build/codegen/targets/_shared/route-slots.test.js.map +1 -1
  53. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +88 -17
  54. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -1
  55. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +9 -6
  56. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -1
  57. package/build/codegen/targets/kotlin/integration.test.js +6 -0
  58. package/build/codegen/targets/kotlin/integration.test.js.map +1 -1
  59. package/build/codegen/targets/swift/access-level.test.js +8 -11
  60. package/build/codegen/targets/swift/access-level.test.js.map +1 -1
  61. package/build/codegen/targets/swift/emit-route-swift.test.js +91 -20
  62. package/build/codegen/targets/swift/emit-route-swift.test.js.map +1 -1
  63. package/build/codegen/targets/swift/emit-scope-swift.test.js +12 -9
  64. package/build/codegen/targets/swift/emit-scope-swift.test.js.map +1 -1
  65. package/build/codegen/targets/swift/integration.test.js +6 -0
  66. package/build/codegen/targets/swift/integration.test.js.map +1 -1
  67. package/build/create-http-stream.d.ts +58 -0
  68. package/build/create-http-stream.js +122 -0
  69. package/build/create-http-stream.js.map +1 -0
  70. package/build/create-http-stream.test.js +88 -0
  71. package/build/create-http-stream.test.js.map +1 -0
  72. package/build/create-http.d.ts +49 -0
  73. package/build/create-http.js +108 -0
  74. package/build/create-http.js.map +1 -0
  75. package/build/create-http.test.js +137 -0
  76. package/build/create-http.test.js.map +1 -0
  77. package/build/create-stream.d.ts +35 -0
  78. package/build/create-stream.js +123 -0
  79. package/build/create-stream.js.map +1 -0
  80. package/build/create-stream.test.js +428 -0
  81. package/build/create-stream.test.js.map +1 -0
  82. package/build/create.d.ts +28 -0
  83. package/build/create.js +82 -0
  84. package/build/create.js.map +1 -0
  85. package/build/create.test.js +483 -0
  86. package/build/create.test.js.map +1 -0
  87. package/build/exports.d.ts +2 -0
  88. package/build/implementations/http/astro/index.test.js +20 -12
  89. package/build/implementations/http/astro/index.test.js.map +1 -1
  90. package/build/implementations/http/doc-registry.js +1 -1
  91. package/build/implementations/http/doc-registry.js.map +1 -1
  92. package/build/implementations/http/doc-registry.test.js +36 -5
  93. package/build/implementations/http/doc-registry.test.js.map +1 -1
  94. package/build/implementations/http/error-dispatch.d.ts +76 -0
  95. package/build/implementations/http/error-dispatch.js +77 -0
  96. package/build/implementations/http/error-dispatch.js.map +1 -0
  97. package/build/implementations/http/error-dispatch.test.js +254 -0
  98. package/build/implementations/http/error-dispatch.test.js.map +1 -0
  99. package/build/implementations/http/error-taxonomy.d.ts +5 -5
  100. package/build/implementations/http/hono/docs/http-doc.d.ts +6 -0
  101. package/build/implementations/http/hono/docs/http-doc.js +42 -0
  102. package/build/implementations/http/hono/docs/http-doc.js.map +1 -0
  103. package/build/implementations/http/hono/docs/http-stream-doc.d.ts +6 -0
  104. package/build/implementations/http/hono/docs/http-stream-doc.js +40 -0
  105. package/build/implementations/http/hono/docs/http-stream-doc.js.map +1 -0
  106. package/build/implementations/http/hono/docs/rpc-doc.d.ts +6 -0
  107. package/build/implementations/http/hono/docs/rpc-doc.js +24 -0
  108. package/build/implementations/http/hono/docs/rpc-doc.js.map +1 -0
  109. package/build/implementations/http/hono/docs/stream-doc.d.ts +6 -0
  110. package/build/implementations/http/hono/docs/stream-doc.js +42 -0
  111. package/build/implementations/http/hono/docs/stream-doc.js.map +1 -0
  112. package/build/implementations/http/hono/handlers/http-stream.d.ts +10 -0
  113. package/build/implementations/http/hono/handlers/http-stream.js +123 -0
  114. package/build/implementations/http/hono/handlers/http-stream.js.map +1 -0
  115. package/build/implementations/http/hono/handlers/http-stream.test.js +128 -0
  116. package/build/implementations/http/hono/handlers/http-stream.test.js.map +1 -0
  117. package/build/implementations/http/hono/handlers/http.d.ts +10 -0
  118. package/build/implementations/http/hono/handlers/http.js +115 -0
  119. package/build/implementations/http/hono/handlers/http.js.map +1 -0
  120. package/build/implementations/http/hono/handlers/http.test.js +118 -0
  121. package/build/implementations/http/hono/handlers/http.test.js.map +1 -0
  122. package/build/implementations/http/hono/handlers/rpc.d.ts +11 -0
  123. package/build/implementations/http/hono/handlers/rpc.js +32 -0
  124. package/build/implementations/http/hono/handlers/rpc.js.map +1 -0
  125. package/build/implementations/http/hono/handlers/rpc.test.js +73 -0
  126. package/build/implementations/http/hono/handlers/rpc.test.js.map +1 -0
  127. package/build/implementations/http/hono/handlers/stream.d.ts +23 -0
  128. package/build/implementations/http/hono/handlers/stream.js +147 -0
  129. package/build/implementations/http/hono/handlers/stream.js.map +1 -0
  130. package/build/implementations/http/hono/handlers/stream.test.d.ts +1 -0
  131. package/build/implementations/http/hono/handlers/stream.test.js +177 -0
  132. package/build/implementations/http/hono/handlers/stream.test.js.map +1 -0
  133. package/build/implementations/http/hono/index.d.ts +57 -0
  134. package/build/implementations/http/hono/index.js +149 -0
  135. package/build/implementations/http/hono/index.js.map +1 -0
  136. package/build/implementations/http/hono/index.test.d.ts +1 -0
  137. package/build/implementations/http/hono/index.test.js +274 -0
  138. package/build/implementations/http/hono/index.test.js.map +1 -0
  139. package/build/implementations/http/hono/path.d.ts +17 -0
  140. package/build/implementations/http/hono/path.js +39 -0
  141. package/build/implementations/http/hono/path.js.map +1 -0
  142. package/build/implementations/http/hono/path.test.d.ts +1 -0
  143. package/build/implementations/http/hono/path.test.js +83 -0
  144. package/build/implementations/http/hono/path.test.js.map +1 -0
  145. package/build/implementations/http/hono/types.d.ts +51 -0
  146. package/build/implementations/http/hono/types.js.map +1 -0
  147. package/build/implementations/http/on-request-error.test.js +6 -96
  148. package/build/implementations/http/on-request-error.test.js.map +1 -1
  149. package/build/implementations/http/route-errors.test.js +11 -59
  150. package/build/implementations/http/route-errors.test.js.map +1 -1
  151. package/build/implementations/types.d.ts +43 -9
  152. package/build/index.d.ts +124 -124
  153. package/build/index.js +10 -221
  154. package/build/index.js.map +1 -1
  155. package/build/index.test.js +20 -919
  156. package/build/index.test.js.map +1 -1
  157. package/build/migration.test.d.ts +1 -0
  158. package/build/migration.test.js +34 -0
  159. package/build/migration.test.js.map +1 -0
  160. package/build/schema/compute-schema.d.ts +11 -3
  161. package/build/schema/compute-schema.js +13 -7
  162. package/build/schema/compute-schema.js.map +1 -1
  163. package/build/schema/parser.d.ts +11 -3
  164. package/build/schema/parser.js +49 -9
  165. package/build/schema/parser.js.map +1 -1
  166. package/build/stack-utils.js +8 -0
  167. package/build/stack-utils.js.map +1 -1
  168. package/build/types.d.ts +142 -0
  169. package/build/types.js.map +1 -0
  170. package/docs/astro-adapter.md +5 -5
  171. package/docs/core.md +15 -17
  172. package/docs/http-integrations.md +83 -170
  173. package/docs/streaming.md +3 -60
  174. package/docs/superpowers/plans/2026-05-07-astro-adapter.md +2 -7
  175. package/docs/superpowers/plans/2026-05-08-create-http.md +3355 -0
  176. package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +3365 -0
  177. package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +1 -3
  178. package/docs/superpowers/specs/2026-05-08-create-http-design.md +409 -0
  179. package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +411 -0
  180. package/package.json +4 -22
  181. package/src/client/call.test.ts +26 -0
  182. package/src/client/call.ts +4 -1
  183. package/src/client/fetch-adapter.test.ts +14 -1
  184. package/src/client/fetch-adapter.ts +3 -1
  185. package/src/client/index.test.ts +7 -7
  186. package/src/client/request-builder.ts +2 -2
  187. package/src/client/stream.test.ts +39 -7
  188. package/src/client/stream.ts +16 -2
  189. package/src/client/typed-error-dispatch.test.ts +7 -97
  190. package/src/client/types.ts +21 -3
  191. package/src/codegen/__fixtures__/users-envelope.json +119 -38
  192. package/src/codegen/e2e.test.ts +98 -24
  193. package/src/codegen/emit-errors.integration.test.ts +1 -1
  194. package/src/codegen/emit-scope.test.ts +395 -110
  195. package/src/codegen/emit-scope.ts +350 -55
  196. package/src/codegen/pipeline.test.ts +7 -7
  197. package/src/codegen/resolve-envelope.test.ts +5 -5
  198. package/src/codegen/resolve-envelope.ts +1 -1
  199. package/src/codegen/targets/_shared/route-slots.test.ts +109 -26
  200. package/src/codegen/targets/_shared/route-slots.ts +48 -11
  201. package/src/codegen/targets/kotlin/__fixtures__/users-golden.kt +73 -0
  202. package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +100 -17
  203. package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +9 -6
  204. package/src/codegen/targets/kotlin/integration.test.ts +19 -0
  205. package/src/codegen/targets/swift/__fixtures__/users-golden.swift +79 -0
  206. package/src/codegen/targets/swift/access-level.test.ts +8 -11
  207. package/src/codegen/targets/swift/emit-route-swift.test.ts +103 -20
  208. package/src/codegen/targets/swift/emit-scope-swift.test.ts +12 -9
  209. package/src/codegen/targets/swift/integration.test.ts +17 -0
  210. package/src/create-http-stream.test.ts +97 -0
  211. package/src/create-http-stream.ts +191 -0
  212. package/src/create-http.test.ts +163 -0
  213. package/src/create-http.ts +211 -0
  214. package/src/create-stream.test.ts +565 -0
  215. package/src/create-stream.ts +228 -0
  216. package/src/create.test.ts +658 -0
  217. package/src/create.ts +172 -0
  218. package/src/exports.ts +2 -0
  219. package/src/implementations/http/README.md +135 -95
  220. package/src/implementations/http/astro/README.md +4 -5
  221. package/src/implementations/http/astro/index.test.ts +25 -18
  222. package/src/implementations/http/doc-registry.test.ts +42 -5
  223. package/src/implementations/http/doc-registry.ts +1 -1
  224. package/src/implementations/http/error-dispatch.test.ts +283 -0
  225. package/src/implementations/http/error-dispatch.ts +176 -0
  226. package/src/implementations/http/error-taxonomy.ts +5 -5
  227. package/src/implementations/http/hono/docs/http-doc.ts +43 -0
  228. package/src/implementations/http/hono/docs/http-stream-doc.ts +44 -0
  229. package/src/implementations/http/hono/docs/rpc-doc.ts +34 -0
  230. package/src/implementations/http/hono/docs/stream-doc.ts +53 -0
  231. package/src/implementations/http/hono/handlers/http-stream.test.ts +150 -0
  232. package/src/implementations/http/hono/handlers/http-stream.ts +152 -0
  233. package/src/implementations/http/hono/handlers/http.test.ts +130 -0
  234. package/src/implementations/http/hono/handlers/http.ts +147 -0
  235. package/src/implementations/http/hono/handlers/rpc.test.ts +81 -0
  236. package/src/implementations/http/hono/handlers/rpc.ts +54 -0
  237. package/src/implementations/http/hono/handlers/stream.test.ts +198 -0
  238. package/src/implementations/http/hono/handlers/stream.ts +208 -0
  239. package/src/implementations/http/hono/index.test.ts +329 -0
  240. package/src/implementations/http/hono/index.ts +204 -0
  241. package/src/implementations/http/hono/path.test.ts +96 -0
  242. package/src/implementations/http/hono/path.ts +59 -0
  243. package/src/implementations/http/hono/types.ts +93 -0
  244. package/src/implementations/http/on-request-error.test.ts +10 -116
  245. package/src/implementations/http/route-errors.test.ts +11 -77
  246. package/src/implementations/types.ts +44 -9
  247. package/src/index.test.ts +22 -1249
  248. package/src/index.ts +49 -485
  249. package/src/migration.test.ts +48 -0
  250. package/src/schema/compute-schema.ts +26 -12
  251. package/src/schema/parser.ts +62 -12
  252. package/src/stack-utils.ts +8 -0
  253. package/src/types.ts +133 -0
  254. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/express-rpc.md +0 -137
  255. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-api.md +0 -173
  256. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-rpc.md +0 -142
  257. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-stream.md +0 -147
  258. package/build/implementations/http/express-rpc/error-taxonomy.test.js +0 -83
  259. package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +0 -1
  260. package/build/implementations/http/express-rpc/index.d.ts +0 -125
  261. package/build/implementations/http/express-rpc/index.js +0 -216
  262. package/build/implementations/http/express-rpc/index.js.map +0 -1
  263. package/build/implementations/http/express-rpc/index.test.js +0 -684
  264. package/build/implementations/http/express-rpc/index.test.js.map +0 -1
  265. package/build/implementations/http/express-rpc/types.d.ts +0 -11
  266. package/build/implementations/http/express-rpc/types.js.map +0 -1
  267. package/build/implementations/http/hono-api/error-taxonomy.test.js +0 -137
  268. package/build/implementations/http/hono-api/error-taxonomy.test.js.map +0 -1
  269. package/build/implementations/http/hono-api/index.d.ts +0 -151
  270. package/build/implementations/http/hono-api/index.js +0 -344
  271. package/build/implementations/http/hono-api/index.js.map +0 -1
  272. package/build/implementations/http/hono-api/index.test.js +0 -992
  273. package/build/implementations/http/hono-api/index.test.js.map +0 -1
  274. package/build/implementations/http/hono-api/types.d.ts +0 -13
  275. package/build/implementations/http/hono-api/types.js.map +0 -1
  276. package/build/implementations/http/hono-rpc/error-taxonomy.test.js +0 -64
  277. package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +0 -1
  278. package/build/implementations/http/hono-rpc/index.d.ts +0 -130
  279. package/build/implementations/http/hono-rpc/index.js +0 -209
  280. package/build/implementations/http/hono-rpc/index.js.map +0 -1
  281. package/build/implementations/http/hono-rpc/index.test.js +0 -828
  282. package/build/implementations/http/hono-rpc/index.test.js.map +0 -1
  283. package/build/implementations/http/hono-rpc/types.d.ts +0 -11
  284. package/build/implementations/http/hono-rpc/types.js +0 -2
  285. package/build/implementations/http/hono-rpc/types.js.map +0 -1
  286. package/build/implementations/http/hono-stream/error-taxonomy.test.js +0 -159
  287. package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +0 -1
  288. package/build/implementations/http/hono-stream/index.d.ts +0 -171
  289. package/build/implementations/http/hono-stream/index.js +0 -415
  290. package/build/implementations/http/hono-stream/index.js.map +0 -1
  291. package/build/implementations/http/hono-stream/index.test.js +0 -1383
  292. package/build/implementations/http/hono-stream/index.test.js.map +0 -1
  293. package/build/implementations/http/hono-stream/types.d.ts +0 -15
  294. package/build/implementations/http/hono-stream/types.js +0 -2
  295. package/build/implementations/http/hono-stream/types.js.map +0 -1
  296. package/src/implementations/http/express-rpc/README.md +0 -280
  297. package/src/implementations/http/express-rpc/error-taxonomy.test.ts +0 -103
  298. package/src/implementations/http/express-rpc/index.test.ts +0 -957
  299. package/src/implementations/http/express-rpc/index.ts +0 -327
  300. package/src/implementations/http/express-rpc/types.ts +0 -16
  301. package/src/implementations/http/hono-api/README.md +0 -284
  302. package/src/implementations/http/hono-api/error-taxonomy.test.ts +0 -179
  303. package/src/implementations/http/hono-api/index.test.ts +0 -1341
  304. package/src/implementations/http/hono-api/index.ts +0 -519
  305. package/src/implementations/http/hono-api/types.ts +0 -16
  306. package/src/implementations/http/hono-rpc/README.md +0 -357
  307. package/src/implementations/http/hono-rpc/error-taxonomy.test.ts +0 -82
  308. package/src/implementations/http/hono-rpc/index.test.ts +0 -1107
  309. package/src/implementations/http/hono-rpc/index.ts +0 -320
  310. package/src/implementations/http/hono-rpc/types.ts +0 -16
  311. package/src/implementations/http/hono-stream/README.md +0 -559
  312. package/src/implementations/http/hono-stream/error-taxonomy.test.ts +0 -178
  313. package/src/implementations/http/hono-stream/index.test.ts +0 -1804
  314. package/src/implementations/http/hono-stream/index.ts +0 -622
  315. package/src/implementations/http/hono-stream/types.ts +0 -20
  316. /package/build/{implementations/http/express-rpc/error-taxonomy.test.d.ts → create-http-stream.test.d.ts} +0 -0
  317. /package/build/{implementations/http/express-rpc/index.test.d.ts → create-http.test.d.ts} +0 -0
  318. /package/build/{implementations/http/hono-api/error-taxonomy.test.d.ts → create-stream.test.d.ts} +0 -0
  319. /package/build/{implementations/http/hono-api/index.test.d.ts → create.test.d.ts} +0 -0
  320. /package/build/implementations/http/{hono-rpc/error-taxonomy.test.d.ts → error-dispatch.test.d.ts} +0 -0
  321. /package/build/implementations/http/{hono-rpc/index.test.d.ts → hono/handlers/http-stream.test.d.ts} +0 -0
  322. /package/build/implementations/http/{hono-stream/error-taxonomy.test.d.ts → hono/handlers/http.test.d.ts} +0 -0
  323. /package/build/implementations/http/{hono-stream/index.test.d.ts → hono/handlers/rpc.test.d.ts} +0 -0
  324. /package/build/implementations/http/{express-rpc → hono}/types.js +0 -0
  325. /package/build/{implementations/http/hono-api/types.js → types.js} +0 -0
@@ -1,519 +0,0 @@
1
- import { Hono, Context } from 'hono'
2
- import { TProcedureRegistration } from '../../../index.js'
3
- import {
4
- ExtractConfig,
5
- ExtractContext,
6
- ProceduresFactory,
7
- APIConfig,
8
- APIHttpRouteDoc,
9
- APIInput,
10
- HttpMethod,
11
- } from '../../types.js'
12
- import {
13
- ErrorTaxonomy,
14
- ErrorTaxonomyEntry,
15
- UnknownErrorConfig,
16
- defineErrorTaxonomy,
17
- resolveErrorResponse,
18
- } from '../error-taxonomy.js'
19
- import { HonoAPIFactoryItem } from './types.js'
20
-
21
- export type { APIConfig, APIHttpRouteDoc, APIInput, HttpMethod }
22
- export { defineErrorTaxonomy }
23
- export type { ErrorTaxonomy, ErrorTaxonomyEntry, UnknownErrorConfig }
24
-
25
- // ================
26
- // Query string parsing
27
- // ================
28
-
29
- export type QueryParser = (queryString: string) => Record<string, unknown>
30
-
31
- /** Default query parser using native URLSearchParams. */
32
- function parseQueryNative(queryString: string): Record<string, unknown> {
33
- const searchParams = new URLSearchParams(queryString)
34
- const result: Record<string, unknown> = {}
35
- for (const key of new Set(searchParams.keys())) {
36
- const values = searchParams.getAll(key)
37
- result[key] = values.length > 1 ? values : values[0]
38
- }
39
- return result
40
- }
41
-
42
- /** Extract path parameter names from a route pattern (e.g., '/users/:id' → ['id']) */
43
- function extractPathParamNames(path: string): string[] {
44
- const matches = path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)
45
- return matches ? matches.map((m) => m.slice(1)) : []
46
- }
47
-
48
- /** Default success status codes by HTTP method */
49
- function defaultSuccessStatus(method: HttpMethod): number {
50
- switch (method) {
51
- case 'post':
52
- return 201
53
- case 'delete':
54
- return 204
55
- default:
56
- return 200
57
- }
58
- }
59
-
60
- /** HTTP methods that typically carry a request body */
61
- const BODY_METHODS: HttpMethod[] = ['post', 'put', 'patch']
62
-
63
- /** Extracts the raw query string from a URL and runs it through the resolved parser. */
64
- function extractQuery(url: string, queryParser: QueryParser): Record<string, unknown> {
65
- const questionMark = url.indexOf('?')
66
- if (questionMark === -1) return {}
67
- const rawQuery = url.slice(questionMark + 1)
68
- if (!rawQuery) return {}
69
- return queryParser(rawQuery)
70
- }
71
-
72
- // ================
73
- // Builder
74
- // ================
75
-
76
- export type HonoAPIAppBuilderConfig = {
77
- /**
78
- * An existing Hono application instance to use.
79
- * If not provided, a new instance will be created.
80
- */
81
- app?: Hono
82
- /** Optional path prefix for all API routes. */
83
- pathPrefix?: string
84
- /**
85
- * Custom query string parser. Receives the raw query string (without '?').
86
- *
87
- * Default: native `URLSearchParams`. The default handles:
88
- * - flat keys: `?page=2&limit=10` → `{ page: '2', limit: '10' }`
89
- * - repeated keys: `?tag=a&tag=b` → `{ tag: ['a', 'b'] }`
90
- *
91
- * The default does NOT parse any of these — the bracket/dot syntax is kept
92
- * as part of the literal key name, not interpreted:
93
- * - bracket objects: `?user[name]=John` → `{ 'user[name]': 'John' }`
94
- * - bracket arrays: `?tags[]=a&tags[]=b` → `{ 'tags[]': ['a', 'b'] }`
95
- * - dot paths: `?user.name=John` → `{ 'user.name': 'John' }`
96
- * - comma-split arrays: `?tags=a,b,c` → `{ tags: 'a,b,c' }`
97
- *
98
- * For any of the above, install `qs` and opt in explicitly:
99
- * `queryParser: (raw) => qs.parse(raw) as Record<string, unknown>`
100
- */
101
- queryParser?: QueryParser
102
- onRequestStart?: (c: Context) => void
103
- onRequestEnd?: (c: Context) => void
104
- onSuccess?: (procedure: TProcedureRegistration, c: Context) => void
105
- /**
106
- * Declarative error-to-response mapping (one of the two peer error modes).
107
- * When a thrown error matches an entry (by `class` or `match`), the builder
108
- * serializes it automatically. User entries are checked before the framework
109
- * defaults (`ProcedureValidationError`, `ProcedureYieldValidationError`,
110
- * `ProcedureError`).
111
- */
112
- errors?: ErrorTaxonomy
113
- /**
114
- * Fallback serializer for errors not matched by the taxonomy. Used together
115
- * with `errors` for apps that want declarative dispatch plus a well-defined
116
- * shape for unexpected errors.
117
- */
118
- unknownError?: UnknownErrorConfig
119
- /**
120
- * Imperative error callback — the other peer error mode. Receives every
121
- * error directly and returns the HTTP response. Use this when you want full
122
- * control over the response shape, or alongside `errors` for the tail of
123
- * errors the taxonomy doesn't cover.
124
- */
125
- onError?: (
126
- procedure: TProcedureRegistration,
127
- c: Context,
128
- error: Error
129
- ) => Response | Promise<Response>
130
- /**
131
- * Cross-cutting observer — fires for every caught error, BEFORE dispatch to
132
- * the taxonomy or `onError`. Awaited. Cannot mutate the response. Intended
133
- * for logging, tracing, and metrics (Sentry, Datadog, OpenTelemetry). Any
134
- * error thrown inside the observer is swallowed and logged so the primary
135
- * dispatch flow is never disrupted.
136
- */
137
- onRequestError?: (ctx: OnRequestErrorContext) => void | Promise<void>
138
- }
139
-
140
- /**
141
- * Context passed to the `onRequestError` observer. `raw` is the Hono
142
- * `Context` for the in-flight request.
143
- */
144
- export type OnRequestErrorContext = {
145
- err: unknown
146
- procedure: TProcedureRegistration
147
- raw: Context
148
- }
149
-
150
- /**
151
- * Builder class for creating a Hono application with REST-style API routes.
152
- *
153
- * Uses `schema.input` for per-channel type safety:
154
- * - `input.pathParams` → validated path parameters
155
- * - `input.query` → validated query string parameters
156
- * - `input.body` → validated request body
157
- * - `input.headers` → validated request headers
158
- *
159
- * Usage:
160
- * const API = Procedures<MyContext, APIConfig>()
161
- *
162
- * API.Create('GetUser', {
163
- * path: '/users/:id',
164
- * method: 'get',
165
- * schema: {
166
- * input: {
167
- * pathParams: Type.Object({ id: Type.String() }),
168
- * query: Type.Object({ include: Type.Optional(Type.String()) }),
169
- * },
170
- * returnType: Type.Object({ id: Type.String(), name: Type.String() }),
171
- * }
172
- * }, async (ctx, { pathParams, query }) => {
173
- * return { id: pathParams.id, name: 'John' }
174
- * })
175
- *
176
- * const apiApp = new HonoAPIAppBuilder()
177
- * .register(API, (c) => ({ ... }))
178
- * .build()
179
- */
180
- export class HonoAPIAppBuilder {
181
- constructor(readonly config?: HonoAPIAppBuilderConfig) {
182
- if (config?.app) {
183
- this._app = config.app
184
- }
185
-
186
- if (config?.onRequestStart) {
187
- this._app.use('*', async (c, next) => {
188
- config.onRequestStart!(c)
189
- await next()
190
- })
191
- }
192
-
193
- if (config?.onRequestEnd) {
194
- this._app.use('*', async (c, next) => {
195
- await next()
196
- config.onRequestEnd!(c)
197
- })
198
- }
199
- }
200
-
201
- private factories: HonoAPIFactoryItem<any>[] = []
202
-
203
- private _app: Hono = new Hono()
204
- private _docs: (APIHttpRouteDoc & object)[] = []
205
-
206
- get app(): Hono {
207
- return this._app
208
- }
209
-
210
- get docs(): APIHttpRouteDoc[] {
211
- return this._docs
212
- }
213
-
214
- /**
215
- * Registers a procedure factory with its context.
216
- * @param factory - The procedure factory created by Procedures<Context, APIConfig>()
217
- * @param factoryContext - Context for handlers. Direct value, sync function, or async function.
218
- * @param extendProcedureDoc - Custom function to extend the generated route documentation.
219
- */
220
- register<TFactory extends ProceduresFactory>(
221
- factory: TFactory,
222
- factoryContext:
223
- | ExtractContext<TFactory>
224
- | ((c: Context) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>),
225
- extendProcedureDoc?: (params: {
226
- base: APIHttpRouteDoc
227
- procedure: TProcedureRegistration<any, ExtractConfig<TFactory>>
228
- }) => Record<string, any>
229
- ): this {
230
- this.factories.push({ factory, factoryContext, extendProcedureDoc } as HonoAPIFactoryItem<any>)
231
- return this
232
- }
233
-
234
- /**
235
- * Resolves the full path for a route, combining pathPrefix and the procedure's path.
236
- */
237
- private resolveFullPath(procedurePath: string): string {
238
- const prefix = this.config?.pathPrefix
239
- const normalizedPrefix = prefix ? (prefix.startsWith('/') ? prefix : `/${prefix}`) : ''
240
- const normalizedPath = procedurePath.startsWith('/') ? procedurePath : `/${procedurePath}`
241
- return `${normalizedPrefix}${normalizedPath}`
242
- }
243
-
244
- /**
245
- * Builds and returns the Hono application with registered API routes.
246
- */
247
- build(): Hono {
248
- const queryParser = this.config?.queryParser ?? parseQueryNative
249
-
250
- this.factories.forEach(({ factory, factoryContext, extendProcedureDoc }) => {
251
- factory.getProcedures().map((procedure: TProcedureRegistration<any, APIConfig>) => {
252
- const { config } = procedure
253
- const fullPath = this.resolveFullPath(config.path)
254
- const inputSchema = procedure.config.schema?.input
255
-
256
- // Validate consistency: path params in path template must match schema.input.pathParams
257
- this.validatePathParamConsistency(procedure.name, config.path, inputSchema)
258
-
259
- const route = this.buildApiHttpRouteDoc(procedure, fullPath, extendProcedureDoc)
260
- this._docs.push(route)
261
-
262
- const method = config.method as HttpMethod
263
- const handler = this.createRouteHandler(procedure, factoryContext, config, inputSchema, queryParser)
264
-
265
- this._app.on(method.toUpperCase(), fullPath, handler)
266
- })
267
- })
268
-
269
- return this._app
270
- }
271
-
272
- /**
273
- * Validates that path parameter names in the path template match the schema.input.pathParams declaration.
274
- */
275
- private validatePathParamConsistency(
276
- procedureName: string,
277
- path: string,
278
- inputSchema?: Record<string, unknown>
279
- ): void {
280
- // Only validate when schema.input is used; schema.params (flat mode) skips this check
281
- if (!inputSchema) return
282
-
283
- const pathParamNames = extractPathParamNames(path)
284
-
285
- if (pathParamNames.length > 0 && !inputSchema.pathParams) {
286
- throw new Error(
287
- `Path "${path}" has path parameters [${pathParamNames.join(', ')}] but schema.input.pathParams is not defined for procedure "${procedureName}". ` +
288
- `Define schema.input.pathParams to validate path parameters.`
289
- )
290
- }
291
-
292
- if (inputSchema.pathParams && pathParamNames.length === 0) {
293
- throw new Error(
294
- `schema.input.pathParams is defined for procedure "${procedureName}" but path "${path}" has no path parameters. ` +
295
- `Remove schema.input.pathParams or add path parameters to the path.`
296
- )
297
- }
298
-
299
- // Deep validation: verify schema property names match the :param names in the path
300
- if (inputSchema.pathParams && pathParamNames.length > 0) {
301
- const pathParamsJsonSchema = inputSchema.pathParams as Record<string, unknown>
302
- const schemaProperties = pathParamsJsonSchema.properties as Record<string, unknown> | undefined
303
-
304
- if (schemaProperties) {
305
- const schemaKeys = Object.keys(schemaProperties)
306
- const missingInSchema = pathParamNames.filter((p) => !schemaKeys.includes(p))
307
- const extraInSchema = schemaKeys.filter((k) => !pathParamNames.includes(k))
308
-
309
- if (missingInSchema.length > 0 || extraInSchema.length > 0) {
310
- const parts: string[] = []
311
- if (missingInSchema.length > 0) {
312
- parts.push(`path has [${missingInSchema.join(', ')}] but they are missing from schema`)
313
- }
314
- if (extraInSchema.length > 0) {
315
- parts.push(`schema has [${extraInSchema.join(', ')}] but they are not in the path`)
316
- }
317
- throw new Error(
318
- `Path param mismatch for procedure "${procedureName}": ${parts.join('; ')}. ` +
319
- `Path "${path}" expects [${pathParamNames.join(', ')}], schema defines [${schemaKeys.join(', ')}].`
320
- )
321
- }
322
- }
323
- }
324
- }
325
-
326
- /**
327
- * Creates the async route handler for a procedure.
328
- */
329
- private createRouteHandler(
330
- procedure: TProcedureRegistration<any, APIConfig>,
331
- factoryContext: HonoAPIFactoryItem['factoryContext'],
332
- config: APIConfig,
333
- inputSchema: Record<string, unknown> | undefined,
334
- queryParser: QueryParser
335
- ) {
336
- const successStatus = config.successStatus ?? defaultSuccessStatus(config.method)
337
-
338
- return async (c: Context) => {
339
- try {
340
- const context =
341
- typeof factoryContext === 'function'
342
- ? await factoryContext(c)
343
- : (factoryContext as any)
344
-
345
- let params: unknown
346
-
347
- if (inputSchema) {
348
- // schema.input mode: build structured params from HTTP sources
349
- params = await this.extractInputParams(c, config.method, inputSchema, queryParser)
350
- } else if (procedure.config.schema?.params) {
351
- // schema.params mode: extract from body or query based on method
352
- if (BODY_METHODS.includes(config.method)) {
353
- params = await c.req.json().catch(() => ({}))
354
- } else {
355
- params = extractQuery(c.req.url, queryParser)
356
- }
357
- } else {
358
- params = await c.req.json().catch(() => undefined)
359
- }
360
-
361
- const result = await procedure.handler({ ...context, signal: c.req.raw.signal }, params)
362
-
363
- if (this.config?.onSuccess) {
364
- this.config.onSuccess(procedure, c)
365
- }
366
-
367
- // 204 No Content returns no body
368
- if (successStatus === 204) {
369
- return c.body(null, 204)
370
- }
371
-
372
- return c.json(result, successStatus as any)
373
- } catch (error) {
374
- // Observer fires first — cross-cutting, cannot alter dispatch or the
375
- // response. Swallow any throw from the observer so instrumentation
376
- // bugs never break the primary error-response flow.
377
- if (this.config?.onRequestError) {
378
- try {
379
- await this.config.onRequestError({ err: error, procedure, raw: c })
380
- } catch (observerErr) {
381
- console.error('[ts-procedures hono-api] onRequestError threw — swallowed:', observerErr)
382
- }
383
- }
384
-
385
- // Dispatch: taxonomy → onError → hard default. The two modes are
386
- // peers; apps configure whichever fits (or both, in which case the
387
- // taxonomy handles what it covers and `onError` handles the tail).
388
- if (this.config?.errors || this.config?.unknownError) {
389
- const resolved = resolveErrorResponse({
390
- err: error,
391
- userTaxonomy: this.config.errors,
392
- unknownError: this.config.unknownError,
393
- procedure,
394
- raw: c,
395
- })
396
- if (resolved) {
397
- await resolved.runOnCatch()
398
- return c.json(resolved.body, resolved.statusCode as never)
399
- }
400
- }
401
- if (this.config?.onError) {
402
- return this.config.onError(procedure, c, error as Error)
403
- }
404
- return c.json({ error: (error as Error).message }, 500)
405
- }
406
- }
407
- }
408
-
409
- /**
410
- * Extracts and assembles structured input params from HTTP request sources.
411
- * Each channel (pathParams, query, body, headers) is extracted from its HTTP source.
412
- * The core validates each channel independently via schema.input validators.
413
- */
414
- private async extractInputParams(
415
- c: Context,
416
- method: HttpMethod,
417
- inputSchema: Record<string, unknown>,
418
- queryParser: QueryParser
419
- ): Promise<Record<string, unknown>> {
420
- const params: Record<string, unknown> = {}
421
- const channelNames = Object.keys(inputSchema)
422
-
423
- for (const channel of channelNames) {
424
- switch (channel) {
425
- case 'pathParams':
426
- params.pathParams = c.req.param()
427
- break
428
-
429
- case 'query':
430
- params.query = extractQuery(c.req.url, queryParser)
431
- break
432
-
433
- case 'body':
434
- if (BODY_METHODS.includes(method)) {
435
- params.body = await c.req.json().catch(() => ({}))
436
- }
437
- break
438
-
439
- case 'headers': {
440
- // Pass all request headers; AJV's removeAdditional strips non-declared keys
441
- const headersObj: Record<string, string> = {}
442
- c.req.raw.headers.forEach((value, key) => {
443
- headersObj[key] = value
444
- })
445
- params.headers = headersObj
446
- break
447
- }
448
-
449
- default:
450
- // Unknown channel — pass through empty. Core validation will catch mismatches.
451
- params[channel] = undefined
452
- break
453
- }
454
- }
455
-
456
- return params
457
- }
458
-
459
- /**
460
- * Generates the API HTTP route documentation for the given procedure.
461
- */
462
- private buildApiHttpRouteDoc(
463
- procedure: TProcedureRegistration<any, APIConfig>,
464
- fullPath: string,
465
- extendProcedureDoc?: HonoAPIFactoryItem['extendProcedureDoc']
466
- ): APIHttpRouteDoc {
467
- const { config } = procedure
468
- const inputSchema = config.schema?.input
469
- const jsonSchema: APIHttpRouteDoc['jsonSchema'] = {}
470
-
471
- // Populate per-channel JSON schemas from schema.input
472
- if (inputSchema) {
473
- if (inputSchema.pathParams) jsonSchema.pathParams = inputSchema.pathParams as Record<string, unknown>
474
- if (inputSchema.query) jsonSchema.query = inputSchema.query as Record<string, unknown>
475
- if (inputSchema.body) jsonSchema.body = inputSchema.body as Record<string, unknown>
476
- if (inputSchema.headers) jsonSchema.headers = inputSchema.headers as Record<string, unknown>
477
- } else if (config.schema?.params) {
478
- // Fallback: schema.params treated as body/query depending on method
479
- if (BODY_METHODS.includes(config.method)) {
480
- jsonSchema.body = config.schema.params
481
- } else {
482
- jsonSchema.query = config.schema.params
483
- }
484
- }
485
-
486
- if (config.schema?.returnType) {
487
- jsonSchema.response = config.schema.returnType
488
- }
489
-
490
- const base: APIHttpRouteDoc = {
491
- kind: 'api',
492
- name: procedure.name,
493
- scope: config.scope,
494
- path: config.path,
495
- method: config.method,
496
- fullPath,
497
- jsonSchema,
498
- }
499
-
500
- if (config.successStatus) {
501
- base.successStatus = config.successStatus
502
- }
503
-
504
- if (config.errors && config.errors.length > 0) {
505
- base.errors = [...config.errors]
506
- }
507
-
508
- let extendedDoc: object = {}
509
-
510
- if (extendProcedureDoc) {
511
- extendedDoc = extendProcedureDoc({ base, procedure })
512
- }
513
-
514
- return {
515
- ...extendedDoc,
516
- ...base,
517
- }
518
- }
519
- }
@@ -1,16 +0,0 @@
1
- import { ExtractConfig, ExtractContext, APIConfig, APIHttpRouteDoc } from '../../types.js'
2
- import { Procedures, TProcedureRegistration } from '../../../index.js'
3
- import { Context } from 'hono'
4
-
5
- export type HonoAPIFactoryItem<TFactory = ReturnType<typeof Procedures<any, APIConfig>>> = {
6
- factory: TFactory
7
- factoryContext:
8
- | ExtractContext<TFactory>
9
- | ((c: Context) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>)
10
- extendProcedureDoc?: (params: {
11
- /** API App builder base http route doc */
12
- base: APIHttpRouteDoc
13
- /** Procedure registration */
14
- procedure: TProcedureRegistration<any, ExtractConfig<TFactory>>
15
- }) => Record<string, any>
16
- }