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
@@ -0,0 +1,658 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { describe, expect, it, test } from 'vitest'
3
+ import { Procedures } from './index.js'
4
+ import { v } from 'suretype'
5
+ import { Type } from 'typebox'
6
+ import { ProcedureError, ProcedureValidationError } from './errors.js'
7
+
8
+ describe('Procedures', () => {
9
+ test('Procedures generic context & extended config', () => {
10
+ interface CustomContext {
11
+ authToken: string
12
+ }
13
+
14
+ interface ExtendedConfig {
15
+ customProp: string
16
+ optionalProp?: number
17
+ }
18
+
19
+ const { Create } = Procedures<CustomContext, ExtendedConfig>()
20
+
21
+ const { info } = Create(
22
+ 'TestProcedure',
23
+ {
24
+ // should not throw type errors
25
+ customProp: 'customProp',
26
+ },
27
+ async (ctx) => {
28
+ // should not throw type errors
29
+ return ctx.authToken
30
+ }
31
+ )
32
+
33
+ expect(info.customProp).toEqual('customProp')
34
+ expect(info.optionalProp).toEqual(undefined)
35
+ })
36
+
37
+ test('Create Single Procedures', () => {
38
+ const { procedure: procedure1, info: info1 } = Procedures().Create('test1', {}, async () => {
39
+ return '1'
40
+ })
41
+ const { procedure: procedure2, info: info2 } = Procedures().Create('test2', {}, async () => {
42
+ return '2'
43
+ })
44
+
45
+ expect(procedure1).toBeDefined()
46
+ expect(info1).toBeDefined()
47
+ expect(procedure2).toBeDefined()
48
+ expect(info2).toBeDefined()
49
+ })
50
+
51
+ test('Procedures - Create call', () =>
52
+ new Promise<void>((done) => {
53
+ let mockHttpCall: any
54
+
55
+ const { Create } = Procedures({
56
+ onCreate: ({ handler }) => {
57
+ mockHttpCall = handler
58
+ },
59
+ })
60
+
61
+ Create(
62
+ 'Handler',
63
+ {
64
+ schema: {
65
+ params: v.object({ name: v.string() }),
66
+ returnType: v.string(),
67
+ },
68
+ },
69
+ async (ctx, params) => {
70
+ expect(params).toEqual({ name: 'name' })
71
+ done()
72
+ return 'name'
73
+ }
74
+ )
75
+
76
+ mockHttpCall({}, { name: 'name' })
77
+ }))
78
+
79
+ test('Procedures - Create call w/ Typebox', () =>
80
+ new Promise<void>((done) => {
81
+ let mockHttpCall: any
82
+
83
+ const { Create } = Procedures({
84
+ onCreate: ({ handler, config, name }) => {
85
+ mockHttpCall = handler
86
+ },
87
+ })
88
+
89
+ Create(
90
+ 'Handler',
91
+ {
92
+ schema: {
93
+ params: Type.Object({ name: Type.Optional(Type.String()) }),
94
+ returnType: Type.String(),
95
+ },
96
+ },
97
+ async (ctx, params) => {
98
+ expect(params).toEqual({ name: 'name' })
99
+ done()
100
+ return 'name'
101
+ }
102
+ )
103
+
104
+ mockHttpCall({}, { name: 'name' })
105
+ }))
106
+
107
+ test('Procedures - Create returns a handler to call/test the Procedure registration', async () => {
108
+ let ProcedureRegisteredCbHandler: any
109
+
110
+ const { Create } = Procedures({
111
+ onCreate: (Procedure) => {
112
+ ProcedureRegisteredCbHandler = Procedure.handler
113
+ },
114
+ })
115
+
116
+ const { NamedExportHandler, procedure, info } = Create(
117
+ 'NamedExportHandler',
118
+ {
119
+ description: 'Handler description',
120
+ schema: {
121
+ params: v.object({ number: v.number() }),
122
+ },
123
+ },
124
+ async (ctx, params) => {
125
+ return params.number
126
+ }
127
+ )
128
+
129
+ expect(NamedExportHandler).toBeDefined()
130
+ expect(procedure).toBeDefined()
131
+ expect(ProcedureRegisteredCbHandler).toEqual(NamedExportHandler)
132
+ expect(ProcedureRegisteredCbHandler).toEqual(procedure)
133
+
134
+ const result = NamedExportHandler({}, { number: 1 })
135
+
136
+ expect(result).toBeDefined()
137
+ expect(result).toBeInstanceOf(Promise)
138
+ await expect(result).resolves.toEqual(1)
139
+
140
+ expect(info).toBeDefined()
141
+ expect(info).toBeInstanceOf(Object)
142
+ expect(info.schema).toHaveProperty('params')
143
+ expect(info.schema.params).toEqual({
144
+ type: 'object',
145
+ properties: { number: { type: 'number' } },
146
+ })
147
+ expect(info).toHaveProperty('description')
148
+ expect(info.description).toEqual('Handler description')
149
+ })
150
+
151
+ test('Procedures - Create params validation w/ no params provided', () =>
152
+ new Promise<void>((done) => {
153
+ let mockHttpCall: any
154
+
155
+ const { Create } = Procedures({
156
+ onCreate: ({ handler, config, name }) => {
157
+ mockHttpCall = (callParams: any) => {
158
+ const validation = (config as any).validation
159
+ if (validation?.params) {
160
+ const { errors } = validation.params(callParams)
161
+
162
+ if (errors && 'message' in errors[0]) {
163
+ expect(errors[0].message).toEqual('must be object')
164
+ done()
165
+ return
166
+ }
167
+ }
168
+
169
+ handler(callParams, {})
170
+ }
171
+ },
172
+ })
173
+
174
+ Create(
175
+ 'test',
176
+ {
177
+ schema: {
178
+ params: v.object({}),
179
+ },
180
+ },
181
+ async () => {
182
+ done()
183
+ }
184
+ )
185
+
186
+ mockHttpCall()
187
+ }))
188
+
189
+ test('Procedures - Create params validation w/ missing params', async () =>
190
+ new Promise<void>((done) => {
191
+ let mockHttpCall: any
192
+
193
+ const { Create } = Procedures({
194
+ onCreate: async ({ handler, config, name }) => {
195
+ mockHttpCall = async (callParams: any) => {
196
+ const validation = (config as any).validation
197
+ if (validation?.params) {
198
+ const { errors } = validation.params(callParams)
199
+ expect(errors).toBeDefined()
200
+ expect(errors?.length).toEqual(2)
201
+ }
202
+
203
+ try {
204
+ await handler(callParams, {})
205
+ } catch (e: any) {
206
+ expect(e).instanceof(ProcedureValidationError)
207
+ expect(e.errors.length).toEqual(2)
208
+ done()
209
+ }
210
+ }
211
+ },
212
+ })
213
+
214
+ Create(
215
+ 'test',
216
+ {
217
+ schema: {
218
+ params: v.object({
219
+ name: v.string().required(),
220
+ id: v.number().required(),
221
+ email: v.string(),
222
+ }),
223
+ },
224
+ },
225
+ async () => {
226
+ return
227
+ }
228
+ )
229
+
230
+ mockHttpCall({})
231
+ }))
232
+
233
+ test('Procedures - Create call provides ctx to handler', () =>
234
+ new Promise<void>((done) => {
235
+ let mockHttpCall: any
236
+
237
+ const { Create } = Procedures<{
238
+ testCtx: string
239
+ }>({
240
+ onCreate: ({ handler }) => {
241
+ mockHttpCall = () => handler({ testCtx: 'testCtx' })
242
+ },
243
+ })
244
+
245
+ Create('test', {}, async (ctx, params) => {
246
+ expect(ctx.testCtx).toEqual('testCtx')
247
+ done()
248
+ })
249
+
250
+ mockHttpCall()
251
+ }))
252
+
253
+ test('Procedure handler can throw local ctx error and is caught', async () => {
254
+ const { Create } = Procedures()
255
+
256
+ const { TestProcedureHandlerError } = Create('TestProcedureHandlerError', {}, async (ctx) => {
257
+ throw ctx.error('Local context error')
258
+ })
259
+
260
+ try {
261
+ await TestProcedureHandlerError({}, {})
262
+ } catch (e: any) {
263
+ expect(e).toBeInstanceOf(ProcedureError)
264
+
265
+ expect(e.message).toEqual('Local context error')
266
+ expect(e.procedureName).toEqual('TestProcedureHandlerError')
267
+ }
268
+ })
269
+
270
+ test('Procedures - context() throws', async () => {
271
+ interface CustomContext {
272
+ authToken: string
273
+ }
274
+
275
+ const { Create } = Procedures<CustomContext>()
276
+
277
+ function validateAuthToken(token: string) {
278
+ return token === 'valid-token'
279
+ }
280
+
281
+ const { CheckIsAuthenticated } = Create(
282
+ 'CheckIsAuthenticated',
283
+ {
284
+ schema: {
285
+ returnType: v.string(),
286
+ },
287
+ },
288
+ async (ctx) => {
289
+ if (!validateAuthToken(ctx.authToken)) {
290
+ throw ctx.error('Invalid auth token')
291
+ }
292
+
293
+ return 'User authentication is valid'
294
+ }
295
+ )
296
+
297
+ await expect(CheckIsAuthenticated({ authToken: 'valid-token' }, {})).resolves.toEqual(
298
+ 'User authentication is valid'
299
+ )
300
+ await expect(CheckIsAuthenticated({ authToken: 'not-valid-token' }, {})).rejects.toThrowError(
301
+ ProcedureError
302
+ )
303
+ })
304
+
305
+ test('Procedures - wrapped errors preserve cause', async () => {
306
+ const { Create } = Procedures()
307
+ const originalError = new Error('Database connection failed')
308
+ ;(originalError as any).code = 'ECONNREFUSED'
309
+
310
+ const { TestCause } = Create('TestCause', {}, async () => {
311
+ throw originalError
312
+ })
313
+
314
+ try {
315
+ await TestCause({}, {})
316
+ } catch (e: any) {
317
+ expect(e).toBeInstanceOf(ProcedureError)
318
+ expect(e.cause).toBe(originalError)
319
+ expect(e.cause.code).toBe('ECONNREFUSED')
320
+ }
321
+ })
322
+
323
+ test('Procedures - ctx.error still works after optimization', async () => {
324
+ const { Create } = Procedures()
325
+
326
+ const { ErrorTest } = Create('ErrorTest', {}, async (ctx) => {
327
+ throw ctx.error('Custom error message', { code: 'ERR_001' })
328
+ })
329
+
330
+ try {
331
+ await ErrorTest({}, {})
332
+ } catch (e: any) {
333
+ expect(e).toBeInstanceOf(ProcedureError)
334
+ expect(e.message).toBe('Custom error message')
335
+ expect(e.procedureName).toBe('ErrorTest')
336
+ expect(e.meta).toEqual({ code: 'ERR_001' })
337
+ }
338
+ })
339
+
340
+ test('Create passes through external signal from context', async () => {
341
+ const { Create } = Procedures<{ signal: AbortSignal }>()
342
+ const externalAc = new AbortController()
343
+ let capturedSignal: AbortSignal | null = null
344
+
345
+ const { WithSignal } = Create('WithSignal', {}, async (ctx) => {
346
+ capturedSignal = ctx.signal!
347
+ return 'done'
348
+ })
349
+
350
+ await WithSignal({ signal: externalAc.signal }, {})
351
+ expect(capturedSignal).toBe(externalAc.signal)
352
+ expect(capturedSignal!.aborted).toBe(false)
353
+ })
354
+
355
+ test('Create external signal reflects abort from caller', async () => {
356
+ const { Create } = Procedures<{ signal: AbortSignal }>()
357
+ const externalAc = new AbortController()
358
+ let capturedSignal: AbortSignal | null = null
359
+
360
+ const { AbortedSignal } = Create('AbortedSignal', {}, async (ctx) => {
361
+ capturedSignal = ctx.signal!
362
+ expect(ctx.signal!.aborted).toBe(true)
363
+ return 'done'
364
+ })
365
+
366
+ externalAc.abort()
367
+ await AbortedSignal({ signal: externalAc.signal }, {})
368
+ expect(capturedSignal!.aborted).toBe(true)
369
+ })
370
+
371
+ test('Create external signal cancels in-flight async work', async () => {
372
+ const { Create } = Procedures<{ signal: AbortSignal }>()
373
+ const externalAc = new AbortController()
374
+ let wasAbortedDuringWork = false
375
+ const ready = Promise.withResolvers<void>()
376
+
377
+ const { LongWork } = Create('LongWork', {}, async (ctx) => {
378
+ ready.resolve()
379
+ await new Promise<void>((resolve) => {
380
+ ctx.signal!.addEventListener('abort', () => {
381
+ wasAbortedDuringWork = true
382
+ resolve()
383
+ })
384
+ })
385
+ return 'done'
386
+ })
387
+
388
+ const p = LongWork({ signal: externalAc.signal }, {})
389
+ await ready.promise
390
+ externalAc.abort()
391
+ await p
392
+ expect(wasAbortedDuringWork).toBe(true)
393
+ })
394
+ })
395
+
396
+ describe('Procedures - Definition Location in Errors', () => {
397
+ test('ProcedureValidationError includes definition location', async () => {
398
+ const { Create } = Procedures()
399
+
400
+ const { TestValidation } = Create(
401
+ 'TestValidation',
402
+ {
403
+ schema: {
404
+ params: v.object({ name: v.string().required() }),
405
+ },
406
+ },
407
+ async (ctx, params) => {
408
+ return params.name
409
+ }
410
+ )
411
+
412
+ try {
413
+ // @ts-expect-error - intentionally passing invalid params
414
+ await TestValidation({}, {}) // Missing required 'name' param
415
+ } catch (e: any) {
416
+ expect(e).toBeInstanceOf(ProcedureValidationError)
417
+ expect(e.definedAt).toBeDefined()
418
+ expect(e.definedAt.file).toContain('create.test.ts')
419
+ expect(e.definedAt.line).toBeGreaterThan(0)
420
+ expect(e.definedAt.column).toBeGreaterThan(0)
421
+ expect(e.stack).toContain('--- Procedure "TestValidation" defined at ---')
422
+ }
423
+ })
424
+
425
+ test('ctx.error() includes definition location', async () => {
426
+ const { Create } = Procedures()
427
+
428
+ const { TestCtxError } = Create('TestCtxError', {}, async (ctx) => {
429
+ throw ctx.error('Custom error')
430
+ })
431
+
432
+ try {
433
+ await TestCtxError({}, {})
434
+ } catch (e: any) {
435
+ expect(e).toBeInstanceOf(ProcedureError)
436
+ expect(e.definedAt).toBeDefined()
437
+ expect(e.definedAt.file).toContain('create.test.ts')
438
+ expect(e.getDefinitionLocation()).toBeDefined()
439
+ expect(e.stack).toContain('--- Procedure "TestCtxError" defined at ---')
440
+ }
441
+ })
442
+
443
+ test('wrapped errors include definition location', async () => {
444
+ const { Create } = Procedures()
445
+
446
+ const { TestWrappedError } = Create('TestWrappedError', {}, async () => {
447
+ throw new Error('Original error')
448
+ })
449
+
450
+ try {
451
+ await TestWrappedError({}, {})
452
+ } catch (e: any) {
453
+ expect(e).toBeInstanceOf(ProcedureError)
454
+ expect(e.definedAt).toBeDefined()
455
+ expect(e.definedAt.file).toContain('create.test.ts')
456
+ expect(e.message).toContain('Error in handler for TestWrappedError')
457
+ expect(e.cause).toBeInstanceOf(Error)
458
+ expect(e.cause.message).toBe('Original error')
459
+ }
460
+ })
461
+
462
+ test('getDefinitionLocation returns formatted string', async () => {
463
+ const { Create } = Procedures()
464
+
465
+ const { TestGetLocation } = Create(
466
+ 'TestGetLocation',
467
+ {
468
+ schema: {
469
+ params: v.object({ id: v.number().required() }),
470
+ },
471
+ },
472
+ async (ctx, params) => {
473
+ return params.id
474
+ }
475
+ )
476
+
477
+ try {
478
+ // @ts-expect-error - intentionally passing invalid params
479
+ await TestGetLocation({}, {}) // Missing required 'id' param
480
+ } catch (e: any) {
481
+ const location = e.getDefinitionLocation()
482
+ expect(location).toBeDefined()
483
+ expect(location).toMatch(/create\.test\.ts:\d+:\d+/)
484
+ }
485
+ })
486
+
487
+ test('error stack shows procedure definition location at the end', async () => {
488
+ const { Create } = Procedures()
489
+
490
+ const { TestStackFormat } = Create(
491
+ 'TestStackFormat',
492
+ {
493
+ schema: {
494
+ params: v.object({ value: v.string().required() }),
495
+ },
496
+ },
497
+ async (ctx, params) => {
498
+ return params.value
499
+ }
500
+ )
501
+
502
+ try {
503
+ // @ts-expect-error - intentionally passing invalid params
504
+ await TestStackFormat({}, {})
505
+ } catch (e: any) {
506
+ // Verify it's a validation error
507
+ expect(e.name).toBe('ProcedureValidationError')
508
+ expect(e).toBeInstanceOf(ProcedureValidationError)
509
+ // Stack should contain the error message and definition location
510
+ expect(e.stack).toContain('Validation error for TestStackFormat')
511
+ expect(e.stack).toContain('--- Procedure "TestStackFormat" defined at ---')
512
+ // The definition section should be at the end of the stack
513
+ const stackLines = e.stack.split('\n')
514
+ const definitionIndex = stackLines.findIndex((line: string) =>
515
+ line.includes('--- Procedure "TestStackFormat" defined at ---')
516
+ )
517
+ expect(definitionIndex).toBeGreaterThan(0)
518
+ }
519
+ })
520
+ })
521
+
522
+ describe('isPrevalidated context property', () => {
523
+ test('Create skips validation when ctx.isPrevalidated is true', async () => {
524
+ const { Create } = Procedures()
525
+
526
+ const { SkipValidation } = Create(
527
+ 'SkipValidation',
528
+ {
529
+ schema: {
530
+ params: v.object({ name: v.string().required() }),
531
+ },
532
+ },
533
+ async (ctx, params) => {
534
+ return params
535
+ }
536
+ )
537
+
538
+ // Without isPrevalidated, missing required param would throw
539
+ // With isPrevalidated: true, validation is skipped
540
+ const result = await SkipValidation({ isPrevalidated: true }, {} as any)
541
+ expect(result).toEqual({})
542
+ })
543
+
544
+ test('Create validates when ctx.isPrevalidated is false', async () => {
545
+ const { Create } = Procedures()
546
+
547
+ const { ValidateParams } = Create(
548
+ 'ValidateParams',
549
+ {
550
+ schema: {
551
+ params: v.object({ name: v.string().required() }),
552
+ },
553
+ },
554
+ async (ctx, params) => {
555
+ return params
556
+ }
557
+ )
558
+
559
+ // With isPrevalidated: false, validation should still run
560
+ try {
561
+ await ValidateParams({ isPrevalidated: false }, {} as any)
562
+ expect.fail('Should have thrown validation error')
563
+ } catch (e: any) {
564
+ expect(e).toBeInstanceOf(ProcedureValidationError)
565
+ }
566
+ })
567
+
568
+ test('Create validates when ctx.isPrevalidated is undefined', async () => {
569
+ const { Create } = Procedures()
570
+
571
+ const { ValidateUndefined } = Create(
572
+ 'ValidateUndefined',
573
+ {
574
+ schema: {
575
+ params: v.object({ id: v.number().required() }),
576
+ },
577
+ },
578
+ async (ctx, params) => {
579
+ return params
580
+ }
581
+ )
582
+
583
+ // Without isPrevalidated property, validation should run
584
+ try {
585
+ await ValidateUndefined({}, {} as any)
586
+ expect.fail('Should have thrown validation error')
587
+ } catch (e: any) {
588
+ expect(e).toBeInstanceOf(ProcedureValidationError)
589
+ }
590
+ })
591
+
592
+ test('isPrevalidated is not exposed in handler context type', async () => {
593
+ const { Create } = Procedures()
594
+
595
+ Create('CheckCtxType', {}, async (ctx) => {
596
+ // @ts-expect-error - isPrevalidated should not be on handler context type
597
+ void ctx.isPrevalidated
598
+ return 'done'
599
+ })
600
+ })
601
+ })
602
+
603
+ describe('builder.config.noRuntimeValidation', () => {
604
+ test('Create skips params validation when noRuntimeValidation is true', async () => {
605
+ const { Create } = Procedures({ config: { noRuntimeValidation: true } })
606
+
607
+ const { SkipParams } = Create(
608
+ 'SkipParams',
609
+ {
610
+ schema: {
611
+ params: Type.Object({ name: Type.String() }),
612
+ },
613
+ },
614
+ async (_ctx, params) => params
615
+ )
616
+
617
+ // Missing required `name` would normally throw ProcedureValidationError
618
+ const result = await SkipParams({}, {} as any)
619
+ expect(result).toEqual({})
620
+ })
621
+
622
+ // migrated to schema.req in Phase 3+
623
+ test.todo('Create skips input channel validation when noRuntimeValidation is true')
624
+
625
+ test('Create still validates when noRuntimeValidation is omitted', async () => {
626
+ const { Create } = Procedures({ config: {} })
627
+
628
+ const { ValidateDefault } = Create(
629
+ 'ValidateDefault',
630
+ {
631
+ schema: {
632
+ params: Type.Object({ name: Type.String() }),
633
+ },
634
+ },
635
+ async (_ctx, params) => params
636
+ )
637
+
638
+ await expect(ValidateDefault({}, {} as any)).rejects.toBeInstanceOf(ProcedureValidationError)
639
+ })
640
+
641
+ test('Create validates when builder is omitted entirely', async () => {
642
+ const { Create } = Procedures()
643
+
644
+ const { ValidateNoBuilder } = Create(
645
+ 'ValidateNoBuilder',
646
+ {
647
+ schema: {
648
+ params: Type.Object({ name: Type.String() }),
649
+ },
650
+ },
651
+ async (_ctx, params) => params
652
+ )
653
+
654
+ await expect(ValidateNoBuilder({}, {} as any)).rejects.toBeInstanceOf(
655
+ ProcedureValidationError
656
+ )
657
+ })
658
+ })