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,327 +0,0 @@
1
- import express from 'express'
2
- import { kebabCase } from 'es-toolkit/string'
3
- import { Procedures, TProcedureRegistration } from '../../../index.js'
4
- import {
5
- ExtractConfig,
6
- ExtractContext,
7
- ProceduresFactory,
8
- RPCConfig,
9
- RPCHttpRouteDoc,
10
- } from '../../types.js'
11
- import {
12
- ErrorTaxonomy,
13
- ErrorTaxonomyEntry,
14
- UnknownErrorConfig,
15
- defineErrorTaxonomy,
16
- resolveErrorResponse,
17
- } from '../error-taxonomy.js'
18
- import { castArray } from 'es-toolkit/compat'
19
- import { ExpressFactoryItem } from './types.js'
20
-
21
- export type { RPCConfig, RPCHttpRouteDoc }
22
- export { defineErrorTaxonomy }
23
- export type { ErrorTaxonomy, ErrorTaxonomyEntry, UnknownErrorConfig }
24
-
25
- export type ExpressRPCAppBuilderConfig = {
26
- /**
27
- * An existing Express application instance to use.
28
- * When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
29
- * If not provided, a new instance will be created.
30
- */
31
- app?: express.Express
32
- /** Optional path prefix for all RPC routes. */
33
- pathPrefix?: string
34
- onRequestStart?: (req: express.Request) => void
35
- onRequestEnd?: (req: express.Request, res: express.Response) => void
36
- onSuccess?: (
37
- procedure: TProcedureRegistration,
38
- req: express.Request,
39
- res: express.Response
40
- ) => void
41
- /**
42
- * Declarative error-to-response mapping (one of the two peer error modes).
43
- * See hono-api for the full taxonomy contract. The `raw` field passed to
44
- * taxonomy callbacks is `{ req, res }`.
45
- */
46
- errors?: ErrorTaxonomy
47
- /** Fallback serializer for errors not matched by the taxonomy. */
48
- unknownError?: UnknownErrorConfig
49
- /**
50
- * Imperative error callback — the other peer error mode. Receives every
51
- * error directly and writes the response via `res`. Use this when you want
52
- * full control, or alongside `errors` for the tail of errors the taxonomy
53
- * doesn't cover.
54
- */
55
- onError?: (
56
- procedure: TProcedureRegistration,
57
- req: express.Request,
58
- res: express.Response,
59
- error: Error
60
- ) => void
61
- /**
62
- * Cross-cutting observer — fires for every caught error, BEFORE dispatch.
63
- * Awaited. Cannot write to `res` (observer only — check `res.headersSent`
64
- * if you must touch it). Thrown errors inside the observer are swallowed
65
- * and logged.
66
- */
67
- onRequestError?: (ctx: OnRequestErrorContext) => void | Promise<void>
68
- }
69
-
70
- /**
71
- * Context passed to the `onRequestError` observer. `raw` is `{ req, res }`
72
- * for the in-flight request.
73
- */
74
- export type OnRequestErrorContext = {
75
- err: unknown
76
- procedure: TProcedureRegistration
77
- raw: { req: express.Request; res: express.Response }
78
- }
79
-
80
- /**
81
- * Builder class for creating an Express application with RPC routes.
82
- *
83
- * Usage:
84
- * const PublicRPC = Procedures<PublicRPCContext, RPCConfig>()
85
- * const ProtectedRPC = Procedures<ProtectedRPCContext, RPCConfig>()
86
- *
87
- * const rpcApp = new ExpressRPCAppBuilder()
88
- * .register(PublicRPC, (req): Promise<PublicRPCContext> => { /* context resolution logic * / })
89
- * .register(ProtectedRPC, (req): Promise<ProtectedRPCContext> => { /* context resolution logic * / })
90
- * .build();
91
- *
92
- * const app = rpcApp.app; // Express application
93
- * const docs = rpcApp.docs; // RPC route documentation
94
- */
95
- export class ExpressRPCAppBuilder {
96
- /**
97
- * Constructor for ExpressRPCAppBuilder.
98
- *
99
- * @param config
100
- */
101
- constructor(readonly config?: ExpressRPCAppBuilderConfig) {
102
- if (config?.app) {
103
- this._app = config.app
104
- } else {
105
- // Default middleware if no app is provided
106
- this._app.use(express.json())
107
- }
108
-
109
- if (config?.onRequestStart) {
110
- this._app.use((req, res, next) => {
111
- config.onRequestStart!(req)
112
- next()
113
- })
114
- }
115
-
116
- if (config?.onRequestEnd) {
117
- this._app.use((req, res, next) => {
118
- res.on('finish', () => {
119
- config.onRequestEnd!(req, res)
120
- })
121
- next()
122
- })
123
- }
124
- }
125
-
126
- /**
127
- * Generates the RPC route path based on the RPC configuration.
128
- * `RPCConfig.scope` can be a string or an array of strings to form nested paths.
129
- *
130
- * Example
131
- * name: 'GetUser'
132
- * scope: ['users', 'profile']
133
- * version: 1
134
- * path: /users/profile/get-user/1
135
- * @param config
136
- */
137
- static makeRPCHttpRoutePath({
138
- name,
139
- config,
140
- prefix,
141
- }: {
142
- name: string
143
- prefix?: string
144
- config: RPCConfig
145
- }) {
146
- const normalizedPrefix = prefix ? (prefix.startsWith('/') ? prefix : `/${prefix}`) : ''
147
-
148
- return `${normalizedPrefix}/${castArray(config.scope).map(kebabCase).join('/')}/${kebabCase(name)}/${String(config.version).trim()}`
149
- }
150
-
151
- /**
152
- * Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
153
- * @param config - The RPC configuration
154
- */
155
- makeRPCHttpRoutePath(name: string, config: RPCConfig): string {
156
- return ExpressRPCAppBuilder.makeRPCHttpRoutePath({
157
- name,
158
- config,
159
- prefix: this.config?.pathPrefix,
160
- })
161
- }
162
-
163
- private factories: ExpressFactoryItem<any>[] = []
164
-
165
- private _app: express.Express = express()
166
- private _docs: (RPCHttpRouteDoc & object)[] = []
167
-
168
- get app(): express.Express {
169
- return this._app
170
- }
171
-
172
- get docs(): RPCHttpRouteDoc[] {
173
- return this._docs
174
- }
175
-
176
- /**
177
- * Registers a procedure factory with its context.
178
- * @param factory - The procedure factory created by Procedures<Context, RPCConfig>()
179
- * @param factoryContext - The context for procedure handlers. Can be a direct value,
180
- * a sync function (req) => Context, or an async function (req) => Promise<Context>
181
- * @param extendProcedureDoc - A custom function to extend the generated RPC route documentation for each procedure.
182
- */
183
- register<TFactory extends ProceduresFactory>(
184
- factory: TFactory,
185
- factoryContext:
186
- | ExtractContext<TFactory>
187
- | ((req: express.Request) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>),
188
- extendProcedureDoc?: (params: {
189
- /* RPC App builder base http route doc */
190
- base: RPCHttpRouteDoc
191
- /* Procedure registration */
192
- procedure: TProcedureRegistration<any, ExtractConfig<TFactory>>
193
- }) => Record<string, any>
194
- ): this {
195
- this.factories.push({ factory, factoryContext, extendProcedureDoc } as ExpressFactoryItem<any>)
196
- return this
197
- }
198
-
199
- /**
200
- * Builds and returns the Express application with registered RPC routes.
201
- * @return express.Application
202
- */
203
- build(): express.Application {
204
- this.factories.forEach(({ factory, factoryContext, extendProcedureDoc }) => {
205
- factory.getProcedures().map((procedure: TProcedureRegistration<any, RPCConfig>) => {
206
- const route = this.buildRpcHttpRouteDoc(procedure, extendProcedureDoc)
207
-
208
- this._docs.push(route)
209
-
210
- this._app[route.method](route.path, async (req, res) => {
211
- try {
212
- const context =
213
- typeof factoryContext === 'function'
214
- ? await factoryContext(req)
215
- : (factoryContext as ExtractContext<typeof factory>)
216
-
217
- let ac: AbortController | undefined
218
- const ctxWithSignal = Object.defineProperty({ ...context }, 'signal', {
219
- get() {
220
- if (!ac) {
221
- ac = new AbortController()
222
- req.on('close', () => { if (!res.writableFinished) ac!.abort() })
223
- }
224
- return ac.signal
225
- },
226
- enumerable: true,
227
- })
228
-
229
- res.json(await procedure.handler(ctxWithSignal, req.body))
230
- if (this.config?.onSuccess) {
231
- this.config.onSuccess(procedure, req, res)
232
- }
233
- // if status not set, set to 200
234
- if (!res.status) {
235
- res.status(200)
236
- }
237
- } catch (error) {
238
- if (this.config?.onRequestError) {
239
- try {
240
- await this.config.onRequestError({ err: error, procedure, raw: { req, res } })
241
- } catch (observerErr) {
242
- console.error('[ts-procedures express-rpc] onRequestError threw — swallowed:', observerErr)
243
- }
244
- }
245
- if (this.config?.errors || this.config?.unknownError) {
246
- const resolved = resolveErrorResponse({
247
- err: error,
248
- userTaxonomy: this.config.errors,
249
- unknownError: this.config.unknownError,
250
- procedure,
251
- raw: { req, res },
252
- })
253
- if (resolved) {
254
- await resolved.runOnCatch()
255
- if (!res.headersSent) {
256
- res.status(resolved.statusCode).json(resolved.body)
257
- }
258
- return
259
- }
260
- }
261
- if (this.config?.onError) {
262
- this.config.onError(procedure, req, res, error as Error)
263
- return
264
- }
265
- // Hard default — `res.status` is always truthy (it's a method),
266
- // so the previous `if (!res.status)` guard never ran. Set status
267
- // and body together unconditionally, respecting an already-sent
268
- // response.
269
- if (!res.headersSent) {
270
- res.status(500).json({ error: (error as Error).message })
271
- }
272
- }
273
- })
274
- })
275
- })
276
-
277
- return this._app
278
- }
279
-
280
- /**
281
- * Generates the RPC HTTP route for the given procedure.
282
- * @param procedure
283
- */
284
- private buildRpcHttpRouteDoc(
285
- procedure: TProcedureRegistration<any, RPCConfig>,
286
- extendProcedureDoc: ExpressFactoryItem['extendProcedureDoc']
287
- ): RPCHttpRouteDoc {
288
- const { config } = procedure
289
- const path = ExpressRPCAppBuilder.makeRPCHttpRoutePath({
290
- name: procedure.name,
291
- config,
292
- prefix: this.config?.pathPrefix,
293
- })
294
- const method = 'post' as const // RPCs use POST method
295
- const jsonSchema: { body?: Record<string, unknown>; response?: Record<string, unknown> } = {}
296
-
297
- if (config.schema?.params) {
298
- jsonSchema.body = config.schema.params
299
- }
300
- if (config.schema?.returnType) {
301
- jsonSchema.response = config.schema.returnType
302
- }
303
-
304
- const base: RPCHttpRouteDoc = {
305
- kind: 'rpc' as const,
306
- name: procedure.name,
307
- version: config.version,
308
- scope: config.scope,
309
- path,
310
- method,
311
- jsonSchema,
312
- }
313
- if (config.errors && config.errors.length > 0) {
314
- base.errors = [...config.errors]
315
- }
316
- let extendedDoc: object = {}
317
-
318
- if (extendProcedureDoc) {
319
- extendedDoc = extendProcedureDoc({ base, procedure })
320
- }
321
-
322
- return {
323
- ...extendedDoc,
324
- ...base,
325
- }
326
- }
327
- }
@@ -1,16 +0,0 @@
1
- import { ExtractConfig, ExtractContext, RPCConfig, RPCHttpRouteDoc } from '../../types.js'
2
- import { Procedures, TProcedureRegistration } from '../../../index.js'
3
- import express from 'express'
4
-
5
- export type ExpressFactoryItem<TFactory = ReturnType<typeof Procedures<any, RPCConfig>>> = {
6
- factory: TFactory
7
- factoryContext:
8
- | ExtractContext<TFactory>
9
- | ((req: express.Request) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>)
10
- extendProcedureDoc?: (params: {
11
- /* RPC App builder base http route doc */
12
- base: RPCHttpRouteDoc
13
- /* Procedure registration */
14
- procedure: TProcedureRegistration<any, ExtractConfig<TFactory>>
15
- }) => Record<string, any>
16
- }
@@ -1,284 +0,0 @@
1
- # Hono API (REST-style) Integration
2
-
3
- REST-style HTTP integration for Hono — routes are dispatched by HTTP method with per-channel input validation (`schema.input.pathParams`, `query`, `body`, `headers`). Works with Bun, Deno, Cloudflare Workers, and Node.js.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install ts-procedures hono
9
- ```
10
-
11
- ## Quick Start
12
-
13
- ```typescript
14
- import { Procedures } from 'ts-procedures'
15
- import { HonoAPIAppBuilder, defineErrorTaxonomy } from 'ts-procedures/hono-api'
16
- import type { APIConfig } from 'ts-procedures/hono-api'
17
- import { Type } from 'typebox'
18
-
19
- // ─── Procedures ───────────────────────────────────────────
20
-
21
- const API = Procedures<{ userId: string }, APIConfig>()
22
-
23
- API.Create('GetUser', {
24
- path: '/users/:id',
25
- method: 'get',
26
- schema: {
27
- input: {
28
- pathParams: Type.Object({ id: Type.String() }),
29
- },
30
- returnType: Type.Object({ id: Type.String(), name: Type.String() }),
31
- },
32
- }, async (ctx, { pathParams }) => {
33
- return { id: pathParams.id, name: 'John Doe' }
34
- })
35
-
36
- API.Create('CreateUser', {
37
- path: '/users',
38
- method: 'post', // → 201 by default
39
- schema: {
40
- input: { body: Type.Object({ name: Type.String(), email: Type.String() }) },
41
- returnType: Type.Object({ id: Type.String() }),
42
- },
43
- }, async (ctx, { body }) => {
44
- return { id: await createUser(body) }
45
- })
46
-
47
- // ─── Build ────────────────────────────────────────────────
48
-
49
- const app = new HonoAPIAppBuilder({ pathPrefix: '/api' })
50
- .register(API, (c) => ({ userId: c.req.header('x-user-id') || 'anonymous' }))
51
- .build()
52
-
53
- // Routes:
54
- // GET /api/users/:id → 200
55
- // POST /api/users → 201
56
- ```
57
-
58
- ## Configuration
59
-
60
- ```typescript
61
- export type HonoAPIAppBuilderConfig = {
62
- app?: Hono // Reuse an existing Hono instance
63
- pathPrefix?: string // Prepend to every route
64
- queryParser?: QueryParser // Custom query-string parser
65
- onRequestStart?: (c: Context) => void
66
- onRequestEnd?: (c: Context) => void
67
- onSuccess?: (procedure, c: Context) => void
68
- errors?: ErrorTaxonomy // Declarative error-to-response mapping
69
- unknownError?: UnknownErrorConfig // Fallback for unmatched errors
70
- onError?: (procedure, c, error) => Response // Imperative error callback (peer of `errors` above)
71
- onRequestError?: (ctx) => void | Promise<void> // Cross-cutting observer for logging/tracing
72
- }
73
- ```
74
-
75
- | Option | Description |
76
- |---|---|
77
- | `app` | Existing Hono instance to register routes on. If omitted, a new `Hono()` is created. |
78
- | `pathPrefix` | Prefix applied to every route (e.g. `/api/v1`). Leading slash is optional. |
79
- | `queryParser` | Override the default native `URLSearchParams` parser (see *Query Parsing* below). |
80
- | `onRequestStart` / `onRequestEnd` | Global lifecycle hooks — wrap every registered route. |
81
- | `onSuccess` | Called after a handler returns successfully, before the response is sent. |
82
- | `errors` / `unknownError` | Declarative error handling — see *Error Handling* below. |
83
- | `onError` | Imperative error callback — first-class peer of the declarative taxonomy. |
84
- | `onRequestError` | Cross-cutting observer — fires for every caught error before dispatch. Awaited; can't mutate the response. For logging / tracing / metrics. |
85
-
86
- ## schema.input — Multi-Channel Structured Input
87
-
88
- REST endpoints carry input via several transport channels. `schema.input` lets you type and validate each independently:
89
-
90
- ```typescript
91
- API.Create('UpdatePost', {
92
- path: '/posts/:id',
93
- method: 'put',
94
- schema: {
95
- input: {
96
- pathParams: Type.Object({ id: Type.String() }),
97
- query: Type.Object({ draft: Type.Optional(Type.Boolean()) }),
98
- body: Type.Object({ title: Type.String(), body: Type.String() }),
99
- headers: Type.Object({ 'if-match': Type.String() }),
100
- },
101
- returnType: Type.Object({ id: Type.String(), version: Type.Number() }),
102
- },
103
- }, async (ctx, { pathParams, query, body, headers }) => {
104
- // All four channels typed independently.
105
- // AJV validates each channel; `removeAdditional` strips undeclared keys from headers.
106
- })
107
- ```
108
-
109
- Supported channels: `pathParams`, `query`, `body`, `headers`. `schema.input` is mutually exclusive with `schema.params` — defining both throws `ProcedureRegistrationError` at registration time.
110
-
111
- `HonoAPIAppBuilder` performs build-time consistency checks: `:id` in the path template must match a `pathParams.id` entry in the schema, or the builder throws at `.build()`.
112
-
113
- ### APIInput helper
114
-
115
- ```typescript
116
- import type { APIInput } from 'ts-procedures/hono-api'
117
-
118
- const schema = {
119
- input: {
120
- pathParams: Type.Object({ id: Type.String() }),
121
- qurey: Type.Object({ /* ... */ }), // ← TS error: 'qurey' not in APIInput
122
- } satisfies APIInput,
123
- }
124
- ```
125
-
126
- `APIInput` constrains channel names so typos become compile errors.
127
-
128
- ## Default Success Status
129
-
130
- | Method | Default status |
131
- |---|---|
132
- | `post` | 201 |
133
- | `delete` | 204 |
134
- | `get`, `put`, `patch`, `head` | 200 |
135
-
136
- Override via `successStatus` on the per-route config:
137
-
138
- ```typescript
139
- API.Create('RemoveUser', {
140
- path: '/users/:id',
141
- method: 'delete',
142
- successStatus: 200, // Override the default 204
143
- schema: { input: { pathParams: Type.Object({ id: Type.String() }) } },
144
- }, async (ctx, { pathParams }) => { /* ... */ })
145
- ```
146
-
147
- ## Query Parsing
148
-
149
- Default: native `URLSearchParams`. Handles flat keys (`?page=2`) and repeated keys (`?tag=a&tag=b → { tag: ['a', 'b'] }`). It does **not** parse bracket objects, bracket arrays, dot paths, or comma-split arrays.
150
-
151
- Opt in to richer parsing via `qs`:
152
-
153
- ```typescript
154
- import qs from 'qs'
155
-
156
- new HonoAPIAppBuilder({
157
- queryParser: (raw) => qs.parse(raw) as Record<string, unknown>,
158
- })
159
- ```
160
-
161
- ## Context Resolution
162
-
163
- The context resolver receives Hono's `Context` object:
164
-
165
- ```typescript
166
- builder.register(API, (c) => ({
167
- userId: c.req.header('x-user-id') || 'anonymous',
168
- requestId: c.req.header('x-request-id') ?? crypto.randomUUID(),
169
- }))
170
-
171
- // Async context resolution — authenticate per request
172
- builder.register(API, async (c) => {
173
- const token = c.req.header('authorization')?.replace('Bearer ', '')
174
- const user = await verifyToken(token)
175
- return { userId: user.id, roles: user.roles }
176
- })
177
- ```
178
-
179
- ## Abort Signal
180
-
181
- `HonoAPIAppBuilder` injects `c.req.raw.signal` as `ctx.signal` in every handler so downstream async calls (fetch, DB queries) can cancel when the client disconnects.
182
-
183
- ```typescript
184
- API.Create('StreamingQuery', { /* ... */ }, async (ctx) => {
185
- const result = await db.query(sql, { signal: ctx.signal })
186
- return result
187
- })
188
- ```
189
-
190
- ## Error Handling
191
-
192
- Declare error classes via `defineErrorTaxonomy` and pass them to the builder's `errors` option. Handlers `throw` their classes; the builder auto-serializes to the configured status + body.
193
-
194
- ```typescript
195
- import { defineErrorTaxonomy } from 'ts-procedures/hono-api'
196
-
197
- const appErrors = defineErrorTaxonomy({
198
- NotFoundError: { class: NotFoundError, statusCode: 404 },
199
- })
200
-
201
- new HonoAPIAppBuilder({
202
- errors: appErrors,
203
- unknownError: { toResponse: () => ({ error: 'Internal server error' }) },
204
- })
205
- ```
206
-
207
- Full contract (both peer error modes, `onRequestError` observer, per-route narrowing via `APIConfig<keyof typeof appErrors & string>`): see **[docs/http-integrations.md § Error Handling](../../../../docs/http-integrations.md#error-handling)** — the canonical explanation shared across all four HTTP builders.
208
-
209
- ## Extending Procedure Documentation
210
-
211
- Like the other builders, `register()` accepts an optional third argument that extends each route's generated doc object:
212
-
213
- ```typescript
214
- builder.register(API, ctxResolver, ({ base, procedure }) => ({
215
- summary: procedure.config.description,
216
- tags: [base.scope ?? 'default'],
217
- deprecated: procedure.config.description?.toLowerCase().includes('deprecated') ?? false,
218
- }))
219
- ```
220
-
221
- The extended doc is spread onto the `APIHttpRouteDoc` before `base`, so base fields (`kind`, `name`, `method`, `path`, `fullPath`, `jsonSchema`, `errors`) always win.
222
-
223
- ## Using an Existing Hono App
224
-
225
- ```typescript
226
- const app = new Hono()
227
- app.use('*', cors())
228
- app.get('/health', (c) => c.json({ ok: true }))
229
-
230
- new HonoAPIAppBuilder({ app, pathPrefix: '/api' })
231
- .register(API, contextResolver)
232
- .build()
233
-
234
- // API routes added alongside your custom routes.
235
- ```
236
-
237
- ## Route Documentation
238
-
239
- Each registered procedure generates an `APIHttpRouteDoc` accessible via `builder.docs`:
240
-
241
- ```typescript
242
- interface APIHttpRouteDoc {
243
- kind: 'api'
244
- name: string
245
- scope?: string
246
- path: string
247
- fullPath: string // path with pathPrefix applied
248
- method: HttpMethod
249
- successStatus?: number
250
- jsonSchema: {
251
- pathParams?: Record<string, unknown>
252
- query?: Record<string, unknown>
253
- body?: Record<string, unknown>
254
- headers?: Record<string, unknown>
255
- response?: Record<string, unknown>
256
- }
257
- errors?: string[] // Taxonomy keys this route may emit
258
- }
259
- ```
260
-
261
- Feed these into `DocRegistry` to compose a single `/docs` endpoint from multiple builders — see [docs/http-integrations.md § DocRegistry](../../../../docs/http-integrations.md#docregistry--composing-docs-from-multiple-builders).
262
-
263
- ## Runtime Compatibility
264
-
265
- Runs wherever Hono runs: Bun, Deno, Cloudflare Workers, Node.js 18+. Uses standard Fetch API (`c.req.raw.signal`, `Request`, `Response`) — no Node-specific APIs.
266
-
267
- ## TypeScript Types
268
-
269
- ```typescript
270
- import type {
271
- APIConfig,
272
- APIHttpRouteDoc,
273
- APIInput,
274
- HttpMethod,
275
- HonoAPIAppBuilderConfig,
276
- QueryParser,
277
- ErrorTaxonomy,
278
- ErrorTaxonomyEntry,
279
- UnknownErrorConfig,
280
- OnRequestErrorContext,
281
- } from 'ts-procedures/hono-api'
282
-
283
- import { HonoAPIAppBuilder, defineErrorTaxonomy } from 'ts-procedures/hono-api'
284
- ```