ts-procedures 8.6.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (627) hide show
  1. package/README.md +166 -101
  2. package/agent_config/claude-code/.claude-plugin/plugin.json +1 -1
  3. package/agent_config/claude-code/agents/ts-procedures-architect.md +11 -10
  4. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +25 -12
  5. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +10 -12
  6. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +141 -45
  7. package/agent_config/claude-code/skills/ts-procedures/checklist.md +7 -6
  8. package/agent_config/claude-code/skills/ts-procedures/patterns.md +45 -6
  9. package/agent_config/claude-code/skills/ts-procedures/templates/client.md +1 -1
  10. package/agent_config/claude-code/skills/ts-procedures/templates/hono.md +1 -1
  11. package/agent_config/copilot/copilot-instructions.md +50 -33
  12. package/agent_config/cursor/cursorrules +50 -33
  13. package/build/adapters/astro/astro-context.js.map +1 -0
  14. package/build/adapters/astro/create-handler.js.map +1 -0
  15. package/build/adapters/astro/index.js.map +1 -0
  16. package/build/{implementations/http → adapters}/astro/index.test.js +1 -1
  17. package/build/adapters/astro/index.test.js.map +1 -0
  18. package/build/adapters/astro/rewrite-request.js.map +1 -0
  19. package/build/adapters/hono/envelope-parity.test.js +98 -0
  20. package/build/adapters/hono/envelope-parity.test.js.map +1 -0
  21. package/build/{implementations/http → adapters}/hono/handlers/http-stream.d.ts +1 -1
  22. package/build/adapters/hono/handlers/http-stream.js +55 -0
  23. package/build/adapters/hono/handlers/http-stream.js.map +1 -0
  24. package/build/{implementations/http → adapters}/hono/handlers/http-stream.test.js +1 -1
  25. package/build/adapters/hono/handlers/http-stream.test.js.map +1 -0
  26. package/build/{implementations/http → adapters}/hono/handlers/http.d.ts +1 -1
  27. package/build/adapters/hono/handlers/http.js +50 -0
  28. package/build/adapters/hono/handlers/http.js.map +1 -0
  29. package/build/{implementations/http → adapters}/hono/handlers/http.test.js +1 -1
  30. package/build/adapters/hono/handlers/http.test.js.map +1 -0
  31. package/build/{implementations/http → adapters}/hono/handlers/rpc.d.ts +2 -2
  32. package/build/adapters/hono/handlers/rpc.js +23 -0
  33. package/build/adapters/hono/handlers/rpc.js.map +1 -0
  34. package/build/{implementations/http → adapters}/hono/handlers/rpc.test.js +1 -1
  35. package/build/adapters/hono/handlers/rpc.test.js.map +1 -0
  36. package/build/adapters/hono/handlers/stream.d.ts +12 -0
  37. package/build/adapters/hono/handlers/stream.js +89 -0
  38. package/build/adapters/hono/handlers/stream.js.map +1 -0
  39. package/build/{implementations/http → adapters}/hono/handlers/stream.test.js +3 -2
  40. package/build/adapters/hono/handlers/stream.test.js.map +1 -0
  41. package/build/{implementations/http → adapters}/hono/index.d.ts +24 -12
  42. package/build/{implementations/http → adapters}/hono/index.js +19 -8
  43. package/build/adapters/hono/index.js.map +1 -0
  44. package/build/{implementations/http → adapters}/hono/index.test.js +2 -4
  45. package/build/adapters/hono/index.test.js.map +1 -0
  46. package/build/{implementations/http → adapters/hono}/on-request-error.test.js +2 -2
  47. package/build/adapters/hono/on-request-error.test.js.map +1 -0
  48. package/build/adapters/hono/request.d.ts +7 -0
  49. package/build/adapters/hono/request.js +22 -0
  50. package/build/adapters/hono/request.js.map +1 -0
  51. package/build/{implementations/http → adapters/hono}/route-errors.test.js +4 -4
  52. package/build/adapters/hono/route-errors.test.js.map +1 -0
  53. package/build/adapters/hono/types.d.ts +55 -0
  54. package/build/adapters/hono/types.js +19 -0
  55. package/build/adapters/hono/types.js.map +1 -0
  56. package/build/client/freeze.test.js +39 -0
  57. package/build/client/freeze.test.js.map +1 -0
  58. package/build/client/typed-error-dispatch.test.js +2 -2
  59. package/build/client/typed-error-dispatch.test.js.map +1 -1
  60. package/build/codegen/__fixtures__/make-envelope.d.ts +1 -1
  61. package/build/codegen/bin/cli.d.ts +5 -0
  62. package/build/codegen/bin/cli.js +139 -182
  63. package/build/codegen/bin/cli.js.map +1 -1
  64. package/build/codegen/bin/cli.test.js +12 -2
  65. package/build/codegen/bin/cli.test.js.map +1 -1
  66. package/build/codegen/bin/flag-specs.d.ts +9 -0
  67. package/build/codegen/bin/flag-specs.js +33 -31
  68. package/build/codegen/bin/flag-specs.js.map +1 -1
  69. package/build/codegen/bin/flag-specs.test.js +14 -1
  70. package/build/codegen/bin/flag-specs.test.js.map +1 -1
  71. package/build/codegen/collect-models.d.ts +1 -1
  72. package/build/codegen/emit/api-route.d.ts +8 -0
  73. package/build/codegen/emit/api-route.js +156 -0
  74. package/build/codegen/emit/api-route.js.map +1 -0
  75. package/build/codegen/emit/context.d.ts +30 -0
  76. package/build/codegen/emit/context.js +2 -0
  77. package/build/codegen/emit/context.js.map +1 -0
  78. package/build/codegen/emit/declarations.d.ts +24 -0
  79. package/build/codegen/emit/declarations.js +48 -0
  80. package/build/codegen/emit/declarations.js.map +1 -0
  81. package/build/codegen/emit/format-types.d.ts +61 -0
  82. package/build/codegen/emit/format-types.js +188 -0
  83. package/build/codegen/emit/format-types.js.map +1 -0
  84. package/build/codegen/emit/http-stream-route.d.ts +7 -0
  85. package/build/codegen/emit/http-stream-route.js +138 -0
  86. package/build/codegen/emit/http-stream-route.js.map +1 -0
  87. package/build/codegen/emit/route-shared.d.ts +35 -0
  88. package/build/codegen/emit/route-shared.js +88 -0
  89. package/build/codegen/emit/route-shared.js.map +1 -0
  90. package/build/codegen/emit/rpc-route.d.ts +7 -0
  91. package/build/codegen/emit/rpc-route.js +37 -0
  92. package/build/codegen/emit/rpc-route.js.map +1 -0
  93. package/build/codegen/emit/scope-file.d.ts +39 -0
  94. package/build/codegen/emit/scope-file.js +166 -0
  95. package/build/codegen/emit/scope-file.js.map +1 -0
  96. package/build/codegen/emit/stream-route.d.ts +7 -0
  97. package/build/codegen/emit/stream-route.js +62 -0
  98. package/build/codegen/emit/stream-route.js.map +1 -0
  99. package/build/codegen/emit-errors.d.ts +1 -1
  100. package/build/codegen/emit-errors.integration.test.js +1 -1
  101. package/build/codegen/emit-errors.integration.test.js.map +1 -1
  102. package/build/codegen/emit-scope.d.ts +13 -30
  103. package/build/codegen/emit-scope.js +15 -844
  104. package/build/codegen/emit-scope.js.map +1 -1
  105. package/build/codegen/goldens.test.js +69 -0
  106. package/build/codegen/goldens.test.js.map +1 -0
  107. package/build/codegen/group-routes.d.ts +1 -1
  108. package/build/codegen/pipeline.d.ts +1 -1
  109. package/build/codegen/resolve-envelope.d.ts +1 -1
  110. package/build/codegen/targets/_shared/error-schemas.d.ts +1 -1
  111. package/build/codegen/targets/_shared/route-slots.d.ts +1 -1
  112. package/build/codegen/targets/_shared/target-run.d.ts +1 -1
  113. package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +1 -1
  114. package/build/codegen/targets/swift/emit-route-swift.d.ts +1 -1
  115. package/build/core/create-http-stream.d.ts +50 -0
  116. package/build/core/create-http-stream.js +108 -0
  117. package/build/core/create-http-stream.js.map +1 -0
  118. package/build/{create-http-stream.test.js → core/create-http-stream.test.js} +1 -1
  119. package/build/core/create-http-stream.test.js.map +1 -0
  120. package/build/core/create-http.d.ts +51 -0
  121. package/build/core/create-http.js +65 -0
  122. package/build/core/create-http.js.map +1 -0
  123. package/build/{create-http.test.js → core/create-http.test.js} +13 -4
  124. package/build/core/create-http.test.js.map +1 -0
  125. package/build/core/create-stream.d.ts +26 -0
  126. package/build/core/create-stream.js +80 -0
  127. package/build/core/create-stream.js.map +1 -0
  128. package/build/{create-stream.test.js → core/create-stream.test.js} +23 -28
  129. package/build/core/create-stream.test.js.map +1 -0
  130. package/build/core/create.d.ts +22 -0
  131. package/build/core/create.js +71 -0
  132. package/build/core/create.js.map +1 -0
  133. package/build/{create.test.js → core/create.test.js} +25 -46
  134. package/build/core/create.test.js.map +1 -0
  135. package/build/core/definition-site.d.ts +24 -0
  136. package/build/{stack-utils.js → core/definition-site.js} +20 -20
  137. package/build/core/definition-site.js.map +1 -0
  138. package/build/{stack-utils.test.js → core/definition-site.test.js} +12 -3
  139. package/build/core/definition-site.test.js.map +1 -0
  140. package/build/{errors.d.ts → core/errors.d.ts} +19 -8
  141. package/build/{errors.js → core/errors.js} +21 -26
  142. package/build/core/errors.js.map +1 -0
  143. package/build/core/errors.test.js.map +1 -0
  144. package/build/core/factory-options.test.js +82 -0
  145. package/build/core/factory-options.test.js.map +1 -0
  146. package/build/core/http-route.d.ts +13 -0
  147. package/build/core/http-route.js +54 -0
  148. package/build/core/http-route.js.map +1 -0
  149. package/build/core/internal.d.ts +72 -0
  150. package/build/core/internal.js +128 -0
  151. package/build/core/internal.js.map +1 -0
  152. package/build/{migration.test.js → core/migration.test.js} +17 -1
  153. package/build/core/migration.test.js.map +1 -0
  154. package/build/core/procedures.d.ts +143 -0
  155. package/build/core/procedures.js +64 -0
  156. package/build/core/procedures.js.map +1 -0
  157. package/build/{index.test.js → core/procedures.test.js} +14 -11
  158. package/build/core/procedures.test.js.map +1 -0
  159. package/build/core/types.d.ts +182 -0
  160. package/build/{schema → core}/types.js.map +1 -1
  161. package/build/exports.d.ts +31 -11
  162. package/build/exports.js +23 -8
  163. package/build/exports.js.map +1 -1
  164. package/build/schema/adapter.d.ts +35 -0
  165. package/build/schema/adapter.js +13 -0
  166. package/build/schema/adapter.js.map +1 -0
  167. package/build/schema/adapter.test.js +53 -0
  168. package/build/schema/adapter.test.js.map +1 -0
  169. package/build/schema/compile.d.ts +37 -0
  170. package/build/schema/compile.js +38 -0
  171. package/build/schema/compile.js.map +1 -0
  172. package/build/schema/compile.test.js +78 -0
  173. package/build/schema/compile.test.js.map +1 -0
  174. package/build/schema/compute-schema.d.ts +47 -37
  175. package/build/schema/compute-schema.js +86 -29
  176. package/build/schema/compute-schema.js.map +1 -1
  177. package/build/schema/compute-schema.test.js +158 -40
  178. package/build/schema/compute-schema.test.js.map +1 -1
  179. package/build/schema/json-schema.d.ts +17 -0
  180. package/build/schema/json-schema.js +2 -0
  181. package/build/schema/json-schema.js.map +1 -0
  182. package/build/schema/typebox.d.ts +11 -0
  183. package/build/schema/typebox.js +24 -0
  184. package/build/schema/typebox.js.map +1 -0
  185. package/build/schema/typebox.test.js +34 -0
  186. package/build/schema/typebox.test.js.map +1 -0
  187. package/build/server/context.d.ts +8 -0
  188. package/build/server/context.js +7 -0
  189. package/build/server/context.js.map +1 -0
  190. package/build/server/context.test.js +16 -0
  191. package/build/server/context.test.js.map +1 -0
  192. package/build/{doc-envelope.d.ts → server/doc-envelope.d.ts} +1 -1
  193. package/build/server/doc-envelope.js.map +1 -0
  194. package/build/server/doc-envelope.test.d.ts +1 -0
  195. package/build/server/doc-envelope.test.js.map +1 -0
  196. package/build/{implementations/http → server}/doc-registry.d.ts +7 -2
  197. package/build/{implementations/http → server}/doc-registry.js +9 -5
  198. package/build/server/doc-registry.js.map +1 -0
  199. package/build/server/doc-registry.test.d.ts +1 -0
  200. package/build/{implementations/http → server}/doc-registry.test.js +27 -24
  201. package/build/server/doc-registry.test.js.map +1 -0
  202. package/build/server/docs/docs.test.d.ts +1 -0
  203. package/build/server/docs/docs.test.js +237 -0
  204. package/build/server/docs/docs.test.js.map +1 -0
  205. package/build/{implementations/http/hono → server}/docs/http-doc.d.ts +2 -2
  206. package/build/{implementations/http/hono → server}/docs/http-doc.js +1 -1
  207. package/build/server/docs/http-doc.js.map +1 -0
  208. package/build/{implementations/http/hono → server}/docs/http-stream-doc.d.ts +2 -2
  209. package/build/{implementations/http/hono → server}/docs/http-stream-doc.js +1 -1
  210. package/build/server/docs/http-stream-doc.js.map +1 -0
  211. package/build/{implementations/http/hono → server}/docs/rpc-doc.d.ts +2 -2
  212. package/build/{implementations/http/hono → server}/docs/rpc-doc.js +1 -1
  213. package/build/server/docs/rpc-doc.js.map +1 -0
  214. package/build/{implementations/http/hono → server}/docs/stream-doc.d.ts +2 -2
  215. package/build/{implementations/http/hono → server}/docs/stream-doc.js +1 -1
  216. package/build/server/docs/stream-doc.js.map +1 -0
  217. package/build/server/errors/dispatch.d.ts +96 -0
  218. package/build/{implementations/http/error-dispatch.js → server/errors/dispatch.js} +20 -10
  219. package/build/server/errors/dispatch.js.map +1 -0
  220. package/build/server/errors/dispatch.test.d.ts +1 -0
  221. package/build/server/errors/dispatch.test.js +418 -0
  222. package/build/server/errors/dispatch.test.js.map +1 -0
  223. package/build/{implementations/http/error-taxonomy.d.ts → server/errors/taxonomy.d.ts} +8 -17
  224. package/build/{implementations/http/error-taxonomy.js → server/errors/taxonomy.js} +6 -15
  225. package/build/server/errors/taxonomy.js.map +1 -0
  226. package/build/server/errors/taxonomy.test.d.ts +1 -0
  227. package/build/{implementations/http/error-taxonomy.test.js → server/errors/taxonomy.test.js} +45 -39
  228. package/build/server/errors/taxonomy.test.js.map +1 -0
  229. package/build/server/index.d.ts +29 -0
  230. package/build/server/index.js +27 -0
  231. package/build/server/index.js.map +1 -0
  232. package/build/server/no-framework-imports.test.d.ts +1 -0
  233. package/build/server/no-framework-imports.test.js +40 -0
  234. package/build/server/no-framework-imports.test.js.map +1 -0
  235. package/build/{implementations/http/hono/path.d.ts → server/paths.d.ts} +2 -3
  236. package/build/{implementations/http/hono/path.js → server/paths.js} +1 -1
  237. package/build/server/paths.js.map +1 -0
  238. package/build/server/paths.test.d.ts +1 -0
  239. package/build/server/paths.test.js +111 -0
  240. package/build/server/paths.test.js.map +1 -0
  241. package/build/server/request/params.d.ts +29 -0
  242. package/build/server/request/params.js +43 -0
  243. package/build/server/request/params.js.map +1 -0
  244. package/build/server/request/params.test.d.ts +1 -0
  245. package/build/server/request/params.test.js +91 -0
  246. package/build/server/request/params.test.js.map +1 -0
  247. package/build/server/request/query.d.ts +9 -0
  248. package/build/server/request/query.js +22 -0
  249. package/build/server/request/query.js.map +1 -0
  250. package/build/server/request/query.test.d.ts +1 -0
  251. package/build/server/request/query.test.js +60 -0
  252. package/build/server/request/query.test.js.map +1 -0
  253. package/build/server/sse.d.ts +70 -0
  254. package/build/server/sse.js +94 -0
  255. package/build/server/sse.js.map +1 -0
  256. package/build/server/sse.test.d.ts +1 -0
  257. package/build/server/sse.test.js +98 -0
  258. package/build/server/sse.test.js.map +1 -0
  259. package/build/{implementations → server}/types.d.ts +17 -15
  260. package/build/{implementations → server}/types.js.map +1 -1
  261. package/docs/astro-adapter.md +8 -9
  262. package/docs/client-and-codegen.md +4 -4
  263. package/docs/client-error-handling.md +5 -5
  264. package/docs/codegen-kotlin.md +2 -3
  265. package/docs/codegen-swift.md +1 -2
  266. package/docs/core.md +135 -54
  267. package/docs/http-integrations.md +58 -6
  268. package/docs/migration-v8-to-v9.md +192 -0
  269. package/docs/plans/2026-06-09-v9-rewrite.md +130 -0
  270. package/docs/specs/2026-06-09-v9-rewrite-design.md +221 -0
  271. package/docs/streaming.md +12 -0
  272. package/package.json +23 -47
  273. package/src/{implementations/http → adapters}/astro/index.test.ts +2 -2
  274. package/src/adapters/hono/__fixtures__/parity-envelope.json +389 -0
  275. package/src/adapters/hono/envelope-parity.test.ts +126 -0
  276. package/src/{implementations/http → adapters}/hono/handlers/http-stream.test.ts +1 -1
  277. package/src/adapters/hono/handlers/http-stream.ts +73 -0
  278. package/src/{implementations/http → adapters}/hono/handlers/http.test.ts +1 -1
  279. package/src/adapters/hono/handlers/http.ts +70 -0
  280. package/src/{implementations/http → adapters}/hono/handlers/rpc.test.ts +2 -2
  281. package/src/adapters/hono/handlers/rpc.ts +39 -0
  282. package/src/{implementations/http → adapters}/hono/handlers/stream.test.ts +4 -3
  283. package/src/{implementations/http → adapters}/hono/handlers/stream.ts +19 -92
  284. package/src/{implementations/http → adapters}/hono/index.test.ts +14 -16
  285. package/src/{implementations/http → adapters}/hono/index.ts +35 -30
  286. package/src/{implementations/http → adapters/hono}/on-request-error.test.ts +3 -3
  287. package/src/adapters/hono/request.ts +28 -0
  288. package/src/{implementations/http → adapters/hono}/route-errors.test.ts +5 -5
  289. package/src/{implementations/http → adapters}/hono/types.ts +43 -20
  290. package/src/client/freeze.test.ts +41 -0
  291. package/src/client/typed-error-dispatch.test.ts +3 -3
  292. package/src/codegen/__fixtures__/make-envelope.ts +1 -1
  293. package/src/codegen/__fixtures__/models-envelope.json +310 -0
  294. package/src/codegen/__goldens__/MANIFEST.json +85 -0
  295. package/src/codegen/__goldens__/kotlin-default--models/Billing.kt +112 -0
  296. package/src/codegen/__goldens__/kotlin-default--models/BillingReports.kt +26 -0
  297. package/src/codegen/__goldens__/kotlin-default--models/Orders.kt +88 -0
  298. package/src/codegen/__goldens__/kotlin-default--users/Users.kt +189 -0
  299. package/src/codegen/__goldens__/swift-default--models/Billing.swift +97 -0
  300. package/src/codegen/__goldens__/swift-default--models/BillingReports.swift +20 -0
  301. package/src/codegen/__goldens__/swift-default--models/Orders.swift +81 -0
  302. package/src/codegen/__goldens__/swift-default--users/Users.swift +204 -0
  303. package/src/codegen/__goldens__/ts-default--models/_client.ts +1319 -0
  304. package/src/codegen/__goldens__/ts-default--models/_errors.ts +90 -0
  305. package/src/codegen/__goldens__/ts-default--models/_models.ts +10 -0
  306. package/src/codegen/__goldens__/ts-default--models/_types.ts +502 -0
  307. package/src/codegen/__goldens__/ts-default--models/billing-reports.ts +29 -0
  308. package/src/codegen/__goldens__/ts-default--models/billing.ts +67 -0
  309. package/src/codegen/__goldens__/ts-default--models/index.ts +48 -0
  310. package/src/codegen/__goldens__/ts-default--models/orders.ts +80 -0
  311. package/src/codegen/__goldens__/ts-default--users/_client.ts +1319 -0
  312. package/src/codegen/__goldens__/ts-default--users/_errors.ts +90 -0
  313. package/src/codegen/__goldens__/ts-default--users/_types.ts +502 -0
  314. package/src/codegen/__goldens__/ts-default--users/index.ts +38 -0
  315. package/src/codegen/__goldens__/ts-default--users/users.ts +169 -0
  316. package/src/codegen/__goldens__/ts-external-runtime--models/_errors.ts +90 -0
  317. package/src/codegen/__goldens__/ts-external-runtime--models/_models.ts +10 -0
  318. package/src/codegen/__goldens__/ts-external-runtime--models/billing-reports.ts +29 -0
  319. package/src/codegen/__goldens__/ts-external-runtime--models/billing.ts +67 -0
  320. package/src/codegen/__goldens__/ts-external-runtime--models/index.ts +48 -0
  321. package/src/codegen/__goldens__/ts-external-runtime--models/orders.ts +80 -0
  322. package/src/codegen/__goldens__/ts-external-runtime--users/_errors.ts +90 -0
  323. package/src/codegen/__goldens__/ts-external-runtime--users/index.ts +38 -0
  324. package/src/codegen/__goldens__/ts-external-runtime--users/users.ts +169 -0
  325. package/src/codegen/__goldens__/ts-flat--models/_client.ts +1319 -0
  326. package/src/codegen/__goldens__/ts-flat--models/_errors.ts +87 -0
  327. package/src/codegen/__goldens__/ts-flat--models/_models.ts +10 -0
  328. package/src/codegen/__goldens__/ts-flat--models/_types.ts +502 -0
  329. package/src/codegen/__goldens__/ts-flat--models/billing-reports.ts +28 -0
  330. package/src/codegen/__goldens__/ts-flat--models/billing.ts +51 -0
  331. package/src/codegen/__goldens__/ts-flat--models/index.ts +42 -0
  332. package/src/codegen/__goldens__/ts-flat--models/orders.ts +73 -0
  333. package/src/codegen/__goldens__/ts-flat--users/_client.ts +1319 -0
  334. package/src/codegen/__goldens__/ts-flat--users/_errors.ts +87 -0
  335. package/src/codegen/__goldens__/ts-flat--users/_types.ts +502 -0
  336. package/src/codegen/__goldens__/ts-flat--users/index.ts +34 -0
  337. package/src/codegen/__goldens__/ts-flat--users/users.ts +126 -0
  338. package/src/codegen/__goldens__/ts-no-share-models--models/_client.ts +1319 -0
  339. package/src/codegen/__goldens__/ts-no-share-models--models/_errors.ts +90 -0
  340. package/src/codegen/__goldens__/ts-no-share-models--models/_types.ts +502 -0
  341. package/src/codegen/__goldens__/ts-no-share-models--models/billing-reports.ts +29 -0
  342. package/src/codegen/__goldens__/ts-no-share-models--models/billing.ts +111 -0
  343. package/src/codegen/__goldens__/ts-no-share-models--models/index.ts +48 -0
  344. package/src/codegen/__goldens__/ts-no-share-models--models/orders.ts +112 -0
  345. package/src/codegen/__goldens__/ts-no-share-models--users/_client.ts +1319 -0
  346. package/src/codegen/__goldens__/ts-no-share-models--users/_errors.ts +90 -0
  347. package/src/codegen/__goldens__/ts-no-share-models--users/_types.ts +502 -0
  348. package/src/codegen/__goldens__/ts-no-share-models--users/index.ts +38 -0
  349. package/src/codegen/__goldens__/ts-no-share-models--users/users.ts +169 -0
  350. package/src/codegen/__goldens__/ts-shared-models-module--models/_client.ts +1319 -0
  351. package/src/codegen/__goldens__/ts-shared-models-module--models/_errors.ts +90 -0
  352. package/src/codegen/__goldens__/ts-shared-models-module--models/_models.ts +7 -0
  353. package/src/codegen/__goldens__/ts-shared-models-module--models/_types.ts +502 -0
  354. package/src/codegen/__goldens__/ts-shared-models-module--models/billing-reports.ts +29 -0
  355. package/src/codegen/__goldens__/ts-shared-models-module--models/billing.ts +67 -0
  356. package/src/codegen/__goldens__/ts-shared-models-module--models/index.ts +48 -0
  357. package/src/codegen/__goldens__/ts-shared-models-module--models/orders.ts +80 -0
  358. package/src/codegen/bin/cli.test.ts +13 -2
  359. package/src/codegen/bin/cli.ts +181 -144
  360. package/src/codegen/bin/flag-specs.test.ts +16 -1
  361. package/src/codegen/bin/flag-specs.ts +43 -31
  362. package/src/codegen/bundle-size.test.ts +1 -1
  363. package/src/codegen/collect-models.ts +1 -1
  364. package/src/codegen/e2e.test.ts +1 -1
  365. package/src/codegen/emit/api-route.ts +184 -0
  366. package/src/codegen/emit/context.ts +32 -0
  367. package/src/codegen/emit/declarations.ts +49 -0
  368. package/src/codegen/emit/format-types.ts +232 -0
  369. package/src/codegen/emit/http-stream-route.ts +162 -0
  370. package/src/codegen/emit/route-shared.ts +102 -0
  371. package/src/codegen/emit/rpc-route.ts +49 -0
  372. package/src/codegen/emit/scope-file.ts +226 -0
  373. package/src/codegen/emit/stream-route.ts +81 -0
  374. package/src/codegen/emit-errors.integration.test.ts +2 -2
  375. package/src/codegen/emit-errors.test.ts +1 -1
  376. package/src/codegen/emit-errors.ts +1 -1
  377. package/src/codegen/emit-scope.test.ts +2 -2
  378. package/src/codegen/emit-scope.ts +15 -1048
  379. package/src/codegen/goldens.test.ts +89 -0
  380. package/src/codegen/group-routes.test.ts +1 -1
  381. package/src/codegen/group-routes.ts +1 -1
  382. package/src/codegen/pipeline.test.ts +1 -1
  383. package/src/codegen/pipeline.ts +1 -1
  384. package/src/codegen/resolve-envelope.test.ts +1 -1
  385. package/src/codegen/resolve-envelope.ts +1 -1
  386. package/src/codegen/targets/_shared/error-schemas.test.ts +1 -1
  387. package/src/codegen/targets/_shared/error-schemas.ts +1 -1
  388. package/src/codegen/targets/_shared/route-slots.test.ts +1 -1
  389. package/src/codegen/targets/_shared/route-slots.ts +1 -1
  390. package/src/codegen/targets/_shared/target-run.ts +1 -1
  391. package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +1 -1
  392. package/src/codegen/targets/kotlin/emit-route-kotlin.ts +1 -1
  393. package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +1 -1
  394. package/src/codegen/targets/swift/access-level.test.ts +1 -1
  395. package/src/codegen/targets/swift/emit-route-swift.test.ts +1 -1
  396. package/src/codegen/targets/swift/emit-route-swift.ts +1 -1
  397. package/src/codegen/targets/swift/emit-scope-swift.test.ts +1 -1
  398. package/src/codegen/targets/ts/shared-models.test.ts +1 -1
  399. package/src/{create-http-stream.test.ts → core/create-http-stream.test.ts} +1 -1
  400. package/src/core/create-http-stream.ts +207 -0
  401. package/src/{create-http.test.ts → core/create-http.test.ts} +15 -4
  402. package/src/core/create-http.ts +126 -0
  403. package/src/{create-stream.test.ts → core/create-stream.test.ts} +28 -31
  404. package/src/core/create-stream.ts +142 -0
  405. package/src/{create.test.ts → core/create.test.ts} +25 -57
  406. package/src/core/create.ts +121 -0
  407. package/src/{stack-utils.test.ts → core/definition-site.test.ts} +14 -3
  408. package/src/{stack-utils.ts → core/definition-site.ts} +20 -23
  409. package/src/{errors.test.ts → core/errors.test.ts} +1 -1
  410. package/src/{errors.ts → core/errors.ts} +30 -28
  411. package/src/core/factory-options.test.ts +112 -0
  412. package/src/core/http-route.ts +73 -0
  413. package/src/core/internal.ts +203 -0
  414. package/src/{migration.test.ts → core/migration.test.ts} +23 -1
  415. package/src/{index.test.ts → core/procedures.test.ts} +13 -11
  416. package/src/core/procedures.ts +75 -0
  417. package/src/core/types.ts +195 -0
  418. package/src/exports.ts +60 -11
  419. package/src/schema/adapter.test.ts +58 -0
  420. package/src/schema/adapter.ts +45 -0
  421. package/src/schema/compile.test.ts +95 -0
  422. package/src/schema/compile.ts +64 -0
  423. package/src/schema/compute-schema.test.ts +222 -41
  424. package/src/schema/compute-schema.ts +145 -71
  425. package/src/schema/json-schema.ts +21 -0
  426. package/src/schema/typebox.test.ts +40 -0
  427. package/src/schema/typebox.ts +27 -0
  428. package/src/server/context.test.ts +22 -0
  429. package/src/server/context.ts +18 -0
  430. package/src/{doc-envelope.test.ts → server/doc-envelope.test.ts} +2 -2
  431. package/src/{doc-envelope.ts → server/doc-envelope.ts} +1 -1
  432. package/src/{implementations/http → server}/doc-registry.test.ts +32 -26
  433. package/src/{implementations/http → server}/doc-registry.ts +11 -7
  434. package/src/server/docs/docs.test.ts +287 -0
  435. package/src/{implementations/http/hono → server}/docs/http-doc.ts +3 -3
  436. package/src/{implementations/http/hono → server}/docs/http-stream-doc.ts +3 -3
  437. package/src/{implementations/http/hono → server}/docs/rpc-doc.ts +3 -3
  438. package/src/{implementations/http/hono → server}/docs/stream-doc.ts +3 -3
  439. package/src/server/errors/dispatch.test.ts +450 -0
  440. package/src/server/errors/dispatch.ts +189 -0
  441. package/src/{implementations/http/error-taxonomy.test.ts → server/errors/taxonomy.test.ts} +45 -39
  442. package/src/{implementations/http/error-taxonomy.ts → server/errors/taxonomy.ts} +8 -17
  443. package/src/server/index.ts +29 -0
  444. package/src/server/no-framework-imports.test.ts +43 -0
  445. package/src/server/paths.test.ts +141 -0
  446. package/src/{implementations/http/hono/path.ts → server/paths.ts} +2 -13
  447. package/src/server/request/params.test.ts +143 -0
  448. package/src/server/request/params.ts +68 -0
  449. package/src/server/request/query.test.ts +70 -0
  450. package/src/server/request/query.ts +24 -0
  451. package/src/server/sse.test.ts +113 -0
  452. package/src/server/sse.ts +117 -0
  453. package/src/{implementations → server}/types.ts +17 -16
  454. package/build/create-http-stream.d.ts +0 -58
  455. package/build/create-http-stream.js +0 -122
  456. package/build/create-http-stream.js.map +0 -1
  457. package/build/create-http-stream.test.js.map +0 -1
  458. package/build/create-http.d.ts +0 -49
  459. package/build/create-http.js +0 -108
  460. package/build/create-http.js.map +0 -1
  461. package/build/create-http.test.js.map +0 -1
  462. package/build/create-stream.d.ts +0 -35
  463. package/build/create-stream.js +0 -123
  464. package/build/create-stream.js.map +0 -1
  465. package/build/create-stream.test.js.map +0 -1
  466. package/build/create.d.ts +0 -28
  467. package/build/create.js +0 -82
  468. package/build/create.js.map +0 -1
  469. package/build/create.test.js.map +0 -1
  470. package/build/doc-envelope.js.map +0 -1
  471. package/build/doc-envelope.test.js.map +0 -1
  472. package/build/errors.js.map +0 -1
  473. package/build/errors.test.js.map +0 -1
  474. package/build/implementations/http/astro/astro-context.js.map +0 -1
  475. package/build/implementations/http/astro/create-handler.js.map +0 -1
  476. package/build/implementations/http/astro/index.js.map +0 -1
  477. package/build/implementations/http/astro/index.test.js.map +0 -1
  478. package/build/implementations/http/astro/rewrite-request.js.map +0 -1
  479. package/build/implementations/http/doc-registry.js.map +0 -1
  480. package/build/implementations/http/doc-registry.test.js.map +0 -1
  481. package/build/implementations/http/error-dispatch.d.ts +0 -76
  482. package/build/implementations/http/error-dispatch.js.map +0 -1
  483. package/build/implementations/http/error-dispatch.test.js +0 -254
  484. package/build/implementations/http/error-dispatch.test.js.map +0 -1
  485. package/build/implementations/http/error-taxonomy.js.map +0 -1
  486. package/build/implementations/http/error-taxonomy.test.js.map +0 -1
  487. package/build/implementations/http/hono/docs/http-doc.js.map +0 -1
  488. package/build/implementations/http/hono/docs/http-stream-doc.js.map +0 -1
  489. package/build/implementations/http/hono/docs/rpc-doc.js.map +0 -1
  490. package/build/implementations/http/hono/docs/stream-doc.js.map +0 -1
  491. package/build/implementations/http/hono/handlers/http-stream.js +0 -123
  492. package/build/implementations/http/hono/handlers/http-stream.js.map +0 -1
  493. package/build/implementations/http/hono/handlers/http-stream.test.js.map +0 -1
  494. package/build/implementations/http/hono/handlers/http.js +0 -110
  495. package/build/implementations/http/hono/handlers/http.js.map +0 -1
  496. package/build/implementations/http/hono/handlers/http.test.js.map +0 -1
  497. package/build/implementations/http/hono/handlers/rpc.js +0 -32
  498. package/build/implementations/http/hono/handlers/rpc.js.map +0 -1
  499. package/build/implementations/http/hono/handlers/rpc.test.js.map +0 -1
  500. package/build/implementations/http/hono/handlers/stream.d.ts +0 -23
  501. package/build/implementations/http/hono/handlers/stream.js +0 -147
  502. package/build/implementations/http/hono/handlers/stream.js.map +0 -1
  503. package/build/implementations/http/hono/handlers/stream.test.js.map +0 -1
  504. package/build/implementations/http/hono/index.js.map +0 -1
  505. package/build/implementations/http/hono/index.test.js.map +0 -1
  506. package/build/implementations/http/hono/path.js.map +0 -1
  507. package/build/implementations/http/hono/path.test.js +0 -83
  508. package/build/implementations/http/hono/path.test.js.map +0 -1
  509. package/build/implementations/http/hono/types.d.ts +0 -51
  510. package/build/implementations/http/hono/types.js.map +0 -1
  511. package/build/implementations/http/on-request-error.test.js.map +0 -1
  512. package/build/implementations/http/route-errors.test.js.map +0 -1
  513. package/build/index.d.ts +0 -175
  514. package/build/index.js +0 -47
  515. package/build/index.js.map +0 -1
  516. package/build/index.test.js.map +0 -1
  517. package/build/migration.test.js.map +0 -1
  518. package/build/schema/extract-json-schema.d.ts +0 -2
  519. package/build/schema/extract-json-schema.js +0 -12
  520. package/build/schema/extract-json-schema.js.map +0 -1
  521. package/build/schema/extract-json-schema.test.js +0 -23
  522. package/build/schema/extract-json-schema.test.js.map +0 -1
  523. package/build/schema/parser.d.ts +0 -36
  524. package/build/schema/parser.js +0 -210
  525. package/build/schema/parser.js.map +0 -1
  526. package/build/schema/parser.test.js +0 -120
  527. package/build/schema/parser.test.js.map +0 -1
  528. package/build/schema/resolve-schema-lib.d.ts +0 -12
  529. package/build/schema/resolve-schema-lib.js +0 -11
  530. package/build/schema/resolve-schema-lib.js.map +0 -1
  531. package/build/schema/resolve-schema-lib.test.js +0 -17
  532. package/build/schema/resolve-schema-lib.test.js.map +0 -1
  533. package/build/schema/types.d.ts +0 -8
  534. package/build/schema/types.js +0 -2
  535. package/build/stack-utils.d.ts +0 -25
  536. package/build/stack-utils.js.map +0 -1
  537. package/build/stack-utils.test.js.map +0 -1
  538. package/build/types.d.ts +0 -142
  539. package/build/types.js +0 -2
  540. package/build/types.js.map +0 -1
  541. package/docs/decisions/2026-06-02-monorepo-split-evaluation.md +0 -80
  542. package/docs/handoffs/2026-06-08-dx-round2-declines.md +0 -45
  543. package/docs/handoffs/ajsc-named-type-collision.md +0 -134
  544. package/docs/handoffs/ajsc-named-type-support.md +0 -181
  545. package/docs/handoffs/shared-models-auto-resolve-response.md +0 -181
  546. package/docs/npm-workspaces-migration-plan.md +0 -611
  547. package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +0 -886
  548. package/docs/superpowers/plans/2026-04-24-kotlin-codegen-target.md +0 -1265
  549. package/docs/superpowers/plans/2026-04-25-ajsc-v7-kotlin-polish.md +0 -1993
  550. package/docs/superpowers/plans/2026-04-29-safe-result-api.md +0 -2293
  551. package/docs/superpowers/plans/2026-05-07-astro-adapter.md +0 -1391
  552. package/docs/superpowers/plans/2026-05-08-create-http.md +0 -3355
  553. package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +0 -3365
  554. package/docs/superpowers/plans/2026-06-05-dx-feedback-round.md +0 -1292
  555. package/docs/superpowers/plans/2026-06-06-shared-models-convention-and-diagnostics.md +0 -659
  556. package/docs/superpowers/plans/2026-06-08-codegen-dx-surfacing.md +0 -428
  557. package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +0 -401
  558. package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +0 -314
  559. package/docs/superpowers/specs/2026-04-25-swift-codegen-design.md +0 -264
  560. package/docs/superpowers/specs/2026-04-29-safe-result-api-design.md +0 -324
  561. package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +0 -252
  562. package/docs/superpowers/specs/2026-05-08-create-http-design.md +0 -409
  563. package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +0 -411
  564. package/docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md +0 -285
  565. package/docs/superpowers/specs/2026-06-08-dx-feedback-round-2-design.md +0 -376
  566. package/src/create-http-stream.ts +0 -191
  567. package/src/create-http.ts +0 -210
  568. package/src/create-stream.ts +0 -228
  569. package/src/create.ts +0 -172
  570. package/src/implementations/http/README.md +0 -390
  571. package/src/implementations/http/error-dispatch.test.ts +0 -283
  572. package/src/implementations/http/error-dispatch.ts +0 -176
  573. package/src/implementations/http/hono/handlers/http-stream.ts +0 -152
  574. package/src/implementations/http/hono/handlers/http.ts +0 -145
  575. package/src/implementations/http/hono/handlers/rpc.ts +0 -54
  576. package/src/implementations/http/hono/path.test.ts +0 -96
  577. package/src/index.ts +0 -101
  578. package/src/schema/extract-json-schema.test.ts +0 -25
  579. package/src/schema/extract-json-schema.ts +0 -15
  580. package/src/schema/parser.test.ts +0 -182
  581. package/src/schema/parser.ts +0 -265
  582. package/src/schema/resolve-schema-lib.test.ts +0 -19
  583. package/src/schema/resolve-schema-lib.ts +0 -29
  584. package/src/schema/types.ts +0 -20
  585. package/src/types.ts +0 -133
  586. /package/build/{implementations/http → adapters}/astro/astro-context.d.ts +0 -0
  587. /package/build/{implementations/http → adapters}/astro/astro-context.js +0 -0
  588. /package/build/{implementations/http → adapters}/astro/create-handler.d.ts +0 -0
  589. /package/build/{implementations/http → adapters}/astro/create-handler.js +0 -0
  590. /package/build/{implementations/http → adapters}/astro/index.d.ts +0 -0
  591. /package/build/{implementations/http → adapters}/astro/index.js +0 -0
  592. /package/build/{implementations/http → adapters}/astro/index.test.d.ts +0 -0
  593. /package/build/{implementations/http → adapters}/astro/rewrite-request.d.ts +0 -0
  594. /package/build/{implementations/http → adapters}/astro/rewrite-request.js +0 -0
  595. /package/build/{create-http-stream.test.d.ts → adapters/hono/envelope-parity.test.d.ts} +0 -0
  596. /package/build/{implementations/http → adapters}/hono/handlers/http-stream.test.d.ts +0 -0
  597. /package/build/{implementations/http → adapters}/hono/handlers/http.test.d.ts +0 -0
  598. /package/build/{implementations/http → adapters}/hono/handlers/rpc.test.d.ts +0 -0
  599. /package/build/{implementations/http → adapters}/hono/handlers/stream.test.d.ts +0 -0
  600. /package/build/{implementations/http → adapters}/hono/index.test.d.ts +0 -0
  601. /package/build/{implementations/http → adapters/hono}/on-request-error.test.d.ts +0 -0
  602. /package/build/{implementations/http → adapters/hono}/route-errors.test.d.ts +0 -0
  603. /package/build/{create-http.test.d.ts → client/freeze.test.d.ts} +0 -0
  604. /package/build/{create-stream.test.d.ts → codegen/goldens.test.d.ts} +0 -0
  605. /package/build/{create.test.d.ts → core/create-http-stream.test.d.ts} +0 -0
  606. /package/build/{doc-envelope.test.d.ts → core/create-http.test.d.ts} +0 -0
  607. /package/build/{errors.test.d.ts → core/create-stream.test.d.ts} +0 -0
  608. /package/build/{implementations/http/doc-registry.test.d.ts → core/create.test.d.ts} +0 -0
  609. /package/build/{implementations/http/error-dispatch.test.d.ts → core/definition-site.test.d.ts} +0 -0
  610. /package/build/{implementations/http/error-taxonomy.test.d.ts → core/errors.test.d.ts} +0 -0
  611. /package/build/{errors.test.js → core/errors.test.js} +0 -0
  612. /package/build/{implementations/http/hono/path.test.d.ts → core/factory-options.test.d.ts} +0 -0
  613. /package/build/{migration.test.d.ts → core/migration.test.d.ts} +0 -0
  614. /package/build/{index.test.d.ts → core/procedures.test.d.ts} +0 -0
  615. /package/build/{implementations/http/hono → core}/types.js +0 -0
  616. /package/build/schema/{extract-json-schema.test.d.ts → adapter.test.d.ts} +0 -0
  617. /package/build/schema/{parser.test.d.ts → compile.test.d.ts} +0 -0
  618. /package/build/schema/{resolve-schema-lib.test.d.ts → typebox.test.d.ts} +0 -0
  619. /package/build/{stack-utils.test.d.ts → server/context.test.d.ts} +0 -0
  620. /package/build/{doc-envelope.js → server/doc-envelope.js} +0 -0
  621. /package/build/{doc-envelope.test.js → server/doc-envelope.test.js} +0 -0
  622. /package/build/{implementations → server}/types.js +0 -0
  623. /package/src/{implementations/http → adapters}/astro/README.md +0 -0
  624. /package/src/{implementations/http → adapters}/astro/astro-context.ts +0 -0
  625. /package/src/{implementations/http → adapters}/astro/create-handler.ts +0 -0
  626. /package/src/{implementations/http → adapters}/astro/index.ts +0 -0
  627. /package/src/{implementations/http → adapters}/astro/rewrite-request.ts +0 -0
@@ -1,80 +0,0 @@
1
- # Decision: keep ts-procedures as a single package (monorepo split declined)
2
-
3
- - **Status:** Accepted — 2026-06-02
4
- - **Supersedes:** the draft [`docs/npm-workspaces-migration-plan.md`](../npm-workspaces-migration-plan.md) (retained for the revisit scenario below)
5
-
6
- ## Context
7
-
8
- A plan was drafted to split `ts-procedures` into a 7-package npm-workspaces
9
- monorepo under the `@ts-procedures/*` scope (core, client, http, codegen, hono,
10
- astro, setup), published fresh at `v1.0.0`. The stated motivation was **consumer
11
- install surface**: "a codegen-CLI user shouldn't pull the Hono server runtime; an
12
- Astro user shouldn't pull `ajsc`."
13
-
14
- The overriding project goal is **a codebase that is VERY easy to maintain with
15
- great DX — no magic, no incidental complexity.** Before committing to the split,
16
- we verified the motivation line-by-line against the source.
17
-
18
- ## Decision
19
-
20
- **Do not split.** Keep `ts-procedures` as a single package and capture the real,
21
- residual benefit cheaply. The split's cost (7 packages, Changesets, an import
22
- codemod, an OIDC release pipeline, an org claim, a hard import cutover, and
23
- cross-package devDependency test edges) is a large, permanent increase in
24
- *maintainer* surface that directly conflicts with the project goal — and it
25
- solves a problem that is mostly already solved.
26
-
27
- ## Rationale (verified evidence)
28
-
29
- | Claim | Evidence |
30
- |---|---|
31
- | The install-surface problem is ~80% already solved | `hono`, `astro`, `ajsc` are `optionalDependencies` (`package.json`) → never installed on a normal `npm install ts-procedures`. |
32
- | `ajsc` is never loaded unless used | Consumed via dynamic `await import('ajsc')` (`src/codegen/emit-types.ts`). |
33
- | Codegen does not pull the server runtime | Codegen's only edge into core is `import type` (erased at compile); zero value imports of `hono`/`astro`. |
34
- | The pain is not real (yet) | No doc, commit, or `CLAUDE.md` line records install surface / unwanted deps as a felt problem. |
35
- | The split would not even shrink the base floor | `core` must keep `suretype`+`typebox` (then) as regular deps, so every consumer pulls them regardless of the split. |
36
- | The one concrete, cheaply-fixable gap | No `sideEffects: false` field → bundlers couldn't tree-shake unused subpaths. Fixed (see below). |
37
-
38
- A split is uniquely justified **only** by benefits the single package cannot
39
- deliver: independent per-concern **versioning/publishing cadence**, or
40
- `@ts-procedures` **org branding/discoverability**. Neither was the actual driver,
41
- so the split was declined.
42
-
43
- ## Consequences — what we did instead (the cheap wins)
44
-
45
- Implemented on the single package, 2026-06-02:
46
-
47
- 1. **`"sideEffects": false`** in `package.json` — enables bundler tree-shaking of
48
- any subpath a consumer doesn't import. Verified safe: no side-effect imports or
49
- global mutation in production code.
50
- 2. **`import type` everywhere it belongs** — converted the schema layer
51
- (`resolve-schema-lib.ts`, `schema/types.ts`) to type-only imports. Result:
52
- `typebox` is no longer a runtime value import anywhere in shipped code (lib
53
- detection is duck-typed); only `suretype`'s `extractSingleJsonSchema` remains a
54
- runtime value import, in one branch.
55
- 3. **`verbatimModuleSyntax: true`** (`tsconfig.json`) — makes value-vs-type import
56
- intent explicit and catches the whole bug class (this is the flag that would
57
- have caught the draft plan's own `import { Procedures }` value-vs-type slip).
58
- 4. **`@typescript-eslint/consistent-type-imports`** rule (`error`) — enforces the
59
- above going forward; the one-time autofix split mixed imports and surfaced 3
60
- genuinely dead imports, which were removed.
61
- 5. **Optional-integrations table** in the README — documents the optional-peer
62
- story (install `hono` for `ts-procedures/hono`, `astro` for the adapter, `ajsc`
63
- for Kotlin/Swift codegen).
64
-
65
- All changes verified: `npm run lint` (0 errors), `npm run build` (clean),
66
- `npm test` (983 passing, 0 type errors).
67
-
68
- ## When to revisit
69
-
70
- Revive the split — and the retained
71
- [`npm-workspaces-migration-plan.md`](../npm-workspaces-migration-plan.md) (with
72
- its corrections: codegen→core is a **normal** dependency because the public API
73
- leaks `DocEnvelope`; expose the wire contract via a named `core/contract` seam;
74
- move spanning tests to a private integration-tests package) — **only if** one of
75
- these becomes a real, stated requirement:
76
-
77
- - per-concern packages need to ship on **independent version cadences**, or
78
- - the `@ts-procedures` **scoped-org identity** is wanted for branding/discovery.
79
-
80
- Until then, "install surface" alone does not justify the maintenance cost.
@@ -1,45 +0,0 @@
1
- # DX Round 2 — Findings #13 and #11: schema-authoring remedies
2
-
3
- Thanks for the careful round-2 report — both #13 and #11 are real friction, and we want to give you the clean fix for each. Both turn out to be schema-authoring choices that live on your side of the contract, so the durable remedy is upstream of codegen. Codegen faithfully reflects the schema it's handed; by the time it runs, each `$id`-bearing model has already been collected as an independent, flattened type (deduped by `$id` and emitted standalone), so codegen has no view of the relationships *between* your models and can't normalize or re-relate them after the fact. Here's the recommended path for each.
4
-
5
- ## #13 — Optional field spells "empty" two ways across Create vs Update
6
-
7
- **The clean fix (do this):** make the Create/Update pair agree on a single "empty" spelling for the optional field, then one form→body mapping serves both branches.
8
-
9
- The two bodies currently disagree at the source:
10
-
11
- - Create authors the field as `Type.Optional(Type.String())` → `string | undefined`
12
- - Update authors it as `Type.Optional(Type.Union([Type.String(), Type.Null()]))` → `string | null` (plus `undefined`)
13
-
14
- That divergence is in the authored contract, so the generated types reflect it exactly — codegen widening or normalizing one to match the other would mean overriding your schema and silently emitting a type that no longer matches what the server validates and accepts. We don't want generated client types to disagree with the wire contract.
15
-
16
- Pick one convention and apply it to both procedures:
17
-
18
- - Both `string | undefined` — `Type.Optional(Type.String())` on each; "omit to leave unchanged."
19
- - Both `string | null` — `Type.Optional(Type.Union([Type.String(), Type.Null()]))` on each; `null` is the explicit "clear it" signal.
20
-
21
- Once the pair is symmetric, a single form→body mapper covers both create and update, and the per-call laundering disappears.
22
-
23
- ## #11 — `UpdateXBody` should be assignable to `Partial<Entity>` without an `as` cast
24
-
25
- **The clean fix (do this):** derive the update body from a shared entity schema so the subset relationship is expressed in the *schema*, where it can survive into the generated types.
26
-
27
- This one codegen genuinely cannot synthesize. By the time codegen sees `UpdateXBody`, it's a flattened, independent `$id` model — the "this is a subset of `Entity`" relationship was never encoded in the JSON Schema we received, so there's nothing in the input from which to reconstruct the `Partial`/subset linkage. We'd be inventing a relationship the contract didn't state.
28
-
29
- Express the relationship at authoring time so it carries through:
30
-
31
- ```ts
32
- // shared entity schema — single source of truth
33
- const Entity = Type.Object({ /* … all fields … */ }, { $id: 'Entity' })
34
-
35
- // update body is a structural subset, authored as such
36
- const UpdateXBody = Type.Partial(Type.Pick(Entity, ['name', 'email', /* … */ ]))
37
- ```
38
-
39
- Authored this way, `UpdateXBody` is structurally a partial subset of `Entity`, and the generated update-body type is assignable to `Partial<Entity>` without a cast.
40
-
41
- If you'd rather not restructure the schemas right now, the documented `as Partial<Entity>` bridge remains a supported, intentional escape hatch — it's a one-line annotation at the call site and doesn't compromise anything else.
42
-
43
- ---
44
-
45
- Happy to pair on either change if it's useful, or to review the schema diff once you've made the Create/Update pair symmetric.
@@ -1,134 +0,0 @@
1
- # Handoff: x-named-type reference should win over a same-named structural extraction
2
-
3
- > **STATUS: OPEN REQUEST — not yet shipped in ajsc.** ts-procedures currently DETECTS this collision and throws a route-qualified error, rejecting an otherwise-valid schema. We'd retire that detect-and-error once ajsc disambiguates per the proposal below.
4
-
5
- **To:** ajsc maintainer
6
- **From:** ts-procedures codegen (consumer of ajsc as the JSON-Schema → TS/Kotlin/Swift emitter)
7
- **Date:** 2026-06-06
8
- **ajsc version inspected:** 7.3.0
9
- **Priority:** medium — removes a DX wart in ts-procedures (a valid schema is rejected); no current miscompilation, because we detect-and-error rather than emit wrong code
10
-
11
- ---
12
-
13
- ## TL;DR
14
-
15
- `x-named-type` (shipped in 7.3.0, thank you) lets ts-procedures reference a shared model `Message` instead of inlining it. But when a route schema *also* has a property whose ajsc-derived extraction name equals that model name, ajsc **silently merges** the two: the external reference resolves to the unrelated structural sub-type. Your README already documents this caveat and the detection signal (`extractedTypeNames` ∩ `referencedNamedTypes`).
16
-
17
- The ask: make an `x-named-type` reference **take precedence** over a would-be structural extraction of the same name — rename the *extraction*, keep the *reference* verbatim. The reference is an explicit author intent ("this is the external `Message`"); the extraction name is an ajsc-derived convenience. Intent should win.
18
-
19
- Additive and opt-in in effect (only changes output for schemas that currently collide, which today produce silently-wrong code). Nothing changes when no collision exists.
20
-
21
- ---
22
-
23
- ## Why this happens
24
-
25
- ts-procedures hoists every `$id`-bearing subschema into a shared `_models.ts` and rewrites each route schema's occurrence into `{ "x-named-type": "<Name>" }` so routes *reference* the model rather than inline it (the integration contract from the prior `x-named-type` handoff, now live).
26
-
27
- Separately, with `inlineTypes: false`, ajsc extracts inline nested objects into sub-types **named after the property** (PascalCased). So a route schema can independently produce an extracted sub-type whose name happens to equal a model's title.
28
-
29
- When those two names coincide, ajsc's `$ref`/extraction merge collapses them. Because the merge happens **inside ajsc, before codegen sees any output**, a post-hoc rename downstream can't fix it — renaming the emitted text would rewrite the model reference too, yielding a silently-wrong type. The merge is lossy at the source.
30
-
31
- ## What ajsc 7.3.0 does today (the blocker)
32
-
33
- A route property named `message` (an inline object) PascalCases to an extracted sub-type `Message`. A sibling property `latest` references the external model via `{ "x-named-type": "Message" }`. The reference and the extraction share the name `Message`, and ajsc merges them — `latest` resolves to the structural `{ unread }`, **not** the external `Message`.
34
-
35
- ```jsonc
36
- // input route schema (inlineTypes: false)
37
- { "type": "object", "properties": {
38
- "latest": { "x-named-type": "Message" }, // external ref → shared _models.ts Message
39
- "message": { "type": "object", "title": "Message", // inline → ajsc extracts a sub-type "Message"
40
- "properties": { "unread": { "type": "boolean" } } } } }
41
- ```
42
-
43
- ```ts
44
- // ajsc 7.3.0 output — the extracted structural type wins; the external reference is lost
45
- export type Message = { unread?: boolean }; // structural extraction (from `message`)
46
- export type Root = { latest?: Message; message?: Message };
47
- // ^^^^^^^ WRONG — `latest` now points at { unread } instead of the
48
- // real shared Message model. The x-named-type intent was silently merged away.
49
- // converter.referencedNamedTypes === ['Message'] AND converter.extractedTypeNames === ['Message', …]
50
- // → both lists contain 'Message': the documented collision signal.
51
- ```
52
-
53
- ```ts
54
- // desired output — the reference wins, the extraction is renamed
55
- export type MessageRef = { unread?: boolean }; // structural extraction, disambiguated
56
- export type Root = { latest?: Message; message?: MessageRef };
57
- // ^^^^^^^ correct — external Message preserved (imported from _models)
58
- // ^^^^^^^^^^ structural sub-type disambiguated
59
- // converter.extractedTypeNames === ['MessageRef', …] // the rename is reported back
60
- // converter.referencedNamedTypes === ['Message'] // reference untouched
61
- ```
62
-
63
- The reference is what the author explicitly asked for; the extraction name is incidental. The reference should be the one preserved verbatim.
64
-
65
- ---
66
-
67
- ## Requested feature
68
-
69
- When a name referenced via `x-named-type` would collide with a name ajsc is about to assign to an *extracted* structural sub-type, **the reference wins**: keep the referenced name verbatim and disambiguate the extraction. Pick whichever of the following best fits ajsc's internals — listed in our order of preference:
70
-
71
- ### Option (a) — x-named-type references win (recommended)
72
-
73
- When a referenced name collides with a would-be extraction, **rename the extraction** (append a suffix — `Ref`, `Inner`, or a numeric tail like ajsc already uses elsewhere) and keep the reference verbatim. Surface the *final* extraction name in `extractedTypeNames` so the caller sees the rename. The referenced name in `referencedNamedTypes` is unchanged.
74
-
75
- This is the most "just works" outcome: the schema author gets a correct external reference and a (renamed) structural type, with zero new API surface. It mirrors ajsc's existing sub-type-naming disambiguation, just seeded by the set of referenced names.
76
-
77
- ### Option (b) — a `reservedExtractionNames` converter option
78
-
79
- Add a converter option — `reservedExtractionNames?: Set<string>` (or `reservedNames`) — so the caller can hand ajsc the set of model names up front and guarantee extractions avoid them (renaming on conflict). This puts the caller in control and is explicit, at the cost of one new option. ts-procedures would pass the full set of `_models.ts` names. Functionally equivalent to (a) for our case; (a) is preferable because it needs no caller wiring and protects every consumer by default.
80
-
81
- ### Option (c) — explicit no-op acknowledgement
82
-
83
- If you prefer to keep disambiguation as the caller's responsibility, a short note in the README confirming that — and that the documented `extractedTypeNames` ∩ `referencedNamedTypes` signal is the intended detection mechanism — lets us keep our detect-and-error in good conscience. Least preferred: it leaves a valid schema rejectable downstream.
84
-
85
- **Recommendation: (a).** It removes the failure mode for every consumer with no new API and matches the principle that an explicit reference outranks a derived extraction name.
86
-
87
- ---
88
-
89
- ## Current downstream interim (the DX wart)
90
-
91
- ts-procedures DETECTS the collision and throws rather than emit wrong code. The check (`assertNoModelNameCollision` in `src/codegen/emit-scope.ts`) is the **set intersection** of the converter's two reported lists — ajsc's documented signal:
92
-
93
- ```
94
- collision = referencedNamedTypes ∩ extractedTypeNames
95
- ```
96
-
97
- A non-empty intersection means a referenced model name also appears as an ajsc-extracted declaration — i.e. the silent merge happened. We throw a clear, route-qualified error telling the author to rename the colliding property or change the model's `$id`/`title`. This is correct but unfortunate: the schema is *valid*, and the author is forced to rename something to satisfy a code-generator limitation. We'd retire this the moment ajsc disambiguates.
98
-
99
- ---
100
-
101
- ## How ts-procedures will consume it (integration contract)
102
-
103
- Once ajsc disambiguates (option (a) or (b)):
104
-
105
- 1. We **drop the detect-and-error** in `emit-scope.ts` — or downgrade it to a defensive `assert` (a non-empty intersection should then be impossible, so a remaining one would indicate an ajsc regression rather than a user error).
106
- 2. The colliding schema **just works**: `latest: Message` resolves to the shared `_models.ts` model; the structural sibling emits under its disambiguated name (e.g. `MessageRef`), referenced correctly from the route type.
107
- 3. We continue to read `referencedNamedTypes` for imports (unchanged) and `extractedTypeNames` for the route's local type set — which, under option (a), now carries the renamed extraction automatically.
108
-
109
- The contract we depend on: **a referenced `x-named-type` name is never overwritten by an extraction — the extraction yields**, and the final extraction name is reported in `extractedTypeNames`.
110
-
111
- ---
112
-
113
- ## Tests worth adding
114
-
115
- 1. `x-named-type: "Message"` sibling to an inline object property `message` (PascalCases to `Message`) ⇒ reference resolves to external `Message`; extraction renamed (e.g. `MessageRef`); `extractedTypeNames` carries the renamed name; `referencedNamedTypes === ['Message']`; the two lists no longer intersect.
116
- 2. Same model referenced at N sites alongside one colliding extraction ⇒ all references stay verbatim; only the extraction is renamed.
117
- 3. Multiple distinct collisions in one schema ⇒ each extraction independently disambiguated; references all preserved.
118
- 4. No collision (referenced name ≠ any extraction name) ⇒ byte-identical to current 7.3.0 output (regression guard).
119
- 5. Option (b) only: `reservedExtractionNames` containing a name that *would not* otherwise collide ⇒ extraction proactively avoids it; references unaffected.
120
-
121
- ---
122
-
123
- ## Versioning
124
-
125
- Additive in effect — it only changes output for schemas that **currently miscompile** (a silent merge). For every non-colliding schema the output is byte-identical. A **minor** bump (e.g. 7.4.0). ts-procedures will gate the removal of its detect-and-error on the ajsc version that introduces the precedence rule, keeping the guard as a fallback for one release if you'd prefer a soft cutover.
126
-
127
- ---
128
-
129
- ## Contact / context
130
-
131
- - `src/codegen/emit-scope.ts` — `assertNoModelNameCollision` (the detect-and-error we want to retire)
132
- - `src/codegen/emit-types.ts` — `referencedNamedTypes` already surfaced (`ExtractedTypeOutput.referencedNamedTypes`, `jsonSchemaToTypeBodyWithRefs`); `extractedTypeNames` not yet surfaced, will be wired when (a)/(b) lands
133
- - `src/codegen/collect-models.ts`, `emit-models.ts` — `$id` model collection / `_models.ts` emission (unaffected)
134
- - `docs/handoffs/ajsc-named-type-support.md` — the prior (shipped) `x-named-type` handoff this builds on
@@ -1,181 +0,0 @@
1
- # Handoff: named-type / verbatim-type support in ajsc
2
-
3
- > **STATUS: Shipped in ajsc 7.3.0 and adopted in ts-procedures (this cutover).** The placeholder-token workaround described below has been deleted; `substituteModelRefs` now emits `{ 'x-named-type': '<Name>' }` and the codegen reads the converter's `referencedNamedTypes`. The rest of this doc is retained for historical context.
4
-
5
- **To:** ajsc maintainer
6
- **From:** ts-procedures codegen (consumer of ajsc as the JSON-Schema → TS/Kotlin/Swift emitter)
7
- **Date:** 2026-06-05
8
- **ajsc version inspected:** 7.2.0
9
- **Priority:** medium — unblocks removing a workaround in ts-procedures, no current breakage
10
-
11
- ---
12
-
13
- ## TL;DR
14
-
15
- ts-procedures needs a way to tell ajsc: *"this subschema is an already-defined named type called `Message` — emit a reference to it (`Message`), don't convert/inline it, and tell me you referenced it so I can add an import."*
16
-
17
- ajsc 7.2.0 has no such mechanism: a `$ref` to a `$defs` entry is **inlined and re-extracted under the property name** (with no dedup), and there is no verbatim-type escape hatch. We've worked around this downstream with a placeholder-token hack (described below) that we'd like to delete once ajsc supports this directly.
18
-
19
- The ask is small and **additive** (a new opt-in schema keyword + one new field on the emit result). Nothing changes when the keyword is absent.
20
-
21
- ---
22
-
23
- ## Why we need it
24
-
25
- ts-procedures generates a typed API client from a server's schema. The same domain entity (e.g. `Message`) appears in many route schemas. Today ajsc inlines a fresh structural literal at every site, so `Message` is emitted ~4–6 times under different names in one file, disconnected from each other and from the author's `Message` type. We want **one** `Message` type that every route references.
26
-
27
- We already know the identity: each such schema carries a stable `$id` (e.g. `urn:msg`) and a `title` (`Message`). So at codegen time we hoist every `$id`-bearing schema into a shared `_models.ts` (emitted via `ajsc.emitTypescript(messageSchema, { rootTypeName: 'Message' })` — which works great), and we want each *route* schema to **reference** `Message` rather than inline it.
28
-
29
- ## What ajsc 7.2.0 does today (the blocker)
30
-
31
- Based on inspecting the installed `dist/`:
32
-
33
- 1. **`$ref` is inlined, not referenced.** The IR layer (`ir/JSONSchemaConverter`) resolves `$ref` against `$defs`/`definitions` by inlining, and rejects non-local refs (`"Only local references are supported"`). With `inlineTypes: false`, the inlined object is then re-extracted **named after the property** — so a schema with `author`, `lastReply`, and `all` all `$ref`-ing the same `Message` emits three duplicate types `Author`, `LastReply`, `All`, with **no deduplication** and no way to make them one `Message`.
34
-
35
- ```jsonc
36
- // input
37
- { "type": "object", "properties": {
38
- "author": { "$ref": "#/$defs/Message" },
39
- "lastReply": { "$ref": "#/$defs/Message" } },
40
- "$defs": { "Message": { "type": "object", "title": "Message",
41
- "properties": { "id": { "type": "string" } } } } }
42
- ```
43
- ```ts
44
- // ajsc 7.2.0 output (inlineTypes:false) — duplicated, property-named, no dedup
45
- export type Author = { id?: string };
46
- export type LastReply = { id?: string };
47
- export type Root = { author?: Author; lastReply?: LastReply };
48
- ```
49
-
50
- 2. **No verbatim-type escape hatch.** Grepping `dist/` for `tsType | x-ts | verbatim | existingType | customType | rawType` returns nothing. We tried `{ tsType: 'Message' }`, `{ "x-tsType": 'Message' }`, `{ $ref: 'Message' }` (bare), `{ type:'object', tsType:'Message' }` — all are ignored (emit `any` / `{ [key:string]: unknown }`) or throw.
51
-
52
- 3. **`EmitResult.imports` is empty for TypeScript.** The function-form `emitTypescript(schema, opts)` returns `{ code, rootTypeName, extractedTypeNames, imports }`, but `imports` is documented (README ~line 110) as Kotlin/Swift-only — there's no channel to surface "this TS output references external type `Message`."
53
-
54
- ### Our current workaround (what we want to delete)
55
-
56
- Because ajsc can't reference a named type, ts-procedures smuggles a sentinel through ajsc and scrapes it back out:
57
-
58
- 1. Before calling ajsc, replace each `$id`-bearing subschema with `{ const: '__MODELREF__Message__' }`. ajsc emits that verbatim as a string-literal type (`author?: "__MODELREF__Message__"`), and — usefully — never extracts it as a sub-type, and survives inside `Array<…>`.
59
- 2. After ajsc, a global regex `/["']__MODELREF__([A-Za-z_$][\w$]*)__["']/g` rewrites the tokens to bare names (`author?: Message`) and collects the referenced names so we can emit `import type { Message } from './_models'`.
60
-
61
- It works and is well-tested, but it encodes a *type identity* as schema *data*, runs it through a code generator, and recovers it from generated *text* with a regex. We'd much rather express the intent directly to ajsc.
62
-
63
- ---
64
-
65
- ## Requested feature
66
-
67
- A new **opt-in schema keyword** that marks a subschema as an already-defined named type. When present, ajsc:
68
-
69
- 1. emits a **reference** to that name (does not convert/inline the subschema, does not extract a sub-type), and
70
- 2. **reports** the referenced name on the emit result so the caller can wire an import.
71
-
72
- ### Keyword name & shape
73
-
74
- ajsc is multi-target (TS/Kotlin/Swift), so prefer a target-agnostic keyword over a TS-specific one. Suggested:
75
-
76
- ```jsonc
77
- { "x-named-type": "Message" }
78
- ```
79
-
80
- `x-`-prefixed so it's unambiguously an annotation and ignored by standard JSON-Schema validators. (If you'd rather not use `x-`, `namedType` or `typeRef` are fine — your call on convention.)
81
-
82
- - **Value = string:** the identifier emitted verbatim in **every** target (`Message` in TS, Kotlin, and Swift). This matches our usage — we use the same PascalCase name across targets.
83
- - **Optional future extension:** allow an object for per-language names, e.g. `{ "x-named-type": { "ts": "Message", "kotlin": "Message", "swift": "Message" } }`. Not needed by us now; mentioning so the string form can widen later without a breaking change.
84
-
85
- ### Semantics
86
-
87
- - The keyword **short-circuits conversion** of that subschema node: emit the bare identifier as the type, in all the positions ajsc already handles a leaf type (direct property, `Array<…>` items, union member, map value, etc.).
88
- - With `inlineTypes: false`, the named type is **not** added to `extractedTypeNames` (it's external — defined elsewhere).
89
- - The node is **not** recursed into (its `properties`/`items` are irrelevant once it's a named reference). A `type`/`properties` sitting alongside `x-named-type` should be ignored (or, if you prefer strictness, validated to match — but ignore is simpler and matches our need; we strip the body anyway).
90
- - **Absent keyword ⇒ zero behaviour change.** Fully backward compatible.
91
-
92
- ### Reporting referenced names
93
-
94
- Add referenced external names to the emit result so the caller can build imports. Two acceptable shapes:
95
-
96
- - **Preferred:** a new field `referencedNamedTypes: string[]` on `EmitResult` (deduped, sorted or insertion-order — either is fine; we sort downstream).
97
- - **Or:** populate the existing (currently-empty-for-TS) `imports` field with the bare names.
98
-
99
- A new dedicated field is cleaner than overloading `imports` (which means module paths for Kotlin/Swift).
100
-
101
- ### Worked example
102
-
103
- ```jsonc
104
- // input
105
- { "type": "object", "properties": {
106
- "author": { "x-named-type": "Message" },
107
- "replies": { "type": "array", "items": { "x-named-type": "Message" } },
108
- "id": { "type": "string" } } }
109
- ```
110
-
111
- ```ts
112
- // desired TS output (inlineTypes:false)
113
- export type Root = { author?: Message; replies?: Array<Message>; id?: string };
114
- // emitResult.referencedNamedTypes === ['Message'] // Root is NOT polluted with a Message decl
115
- ```
116
-
117
- ```kotlin
118
- // desired Kotlin output
119
- data class Root(val author: Message? = null, val replies: List<Message>? = null, val id: String? = null)
120
- // referencedNamedTypes === ['Message']
121
- ```
122
-
123
- ```swift
124
- // desired Swift output
125
- struct Root: Codable { let author: Message?; let replies: [Message]?; let id: String? }
126
- // referencedNamedTypes === ['Message']
127
- ```
128
-
129
- In every case ajsc does **not** define `Message` — the caller (ts-procedures) defines it once elsewhere and adds the import/reference.
130
-
131
- ---
132
-
133
- ## Where this likely lives in ajsc
134
-
135
- From the `dist/` surface (you know the real source layout):
136
-
137
- - **IR conversion** (`ir/JSONSchemaConverter` or equivalent): detect `x-named-type` on a node early and lower it to a new IR node kind — e.g. `NamedTypeRef(name)` — instead of converting the object. This is the one place that needs to intercept before the existing `$ref`-inline / object-extract logic runs.
138
- - **Each language emitter** (`typescript/`, `kotlin/`, `swift/`): render `NamedTypeRef(name)` as the bare identifier wherever a leaf type is rendered, and **collect** the name into the result.
139
- - **`EmitResult` type** (`index.d.ts`): add `referencedNamedTypes?: string[]` (or document `imports` carrying them for TS).
140
- - **`BaseConverterOpts`:** no new option needed — the keyword lives in the schema, not the options. (You *could* add a `validateNamedTypes` strictness flag later; not required.)
141
-
142
- ---
143
-
144
- ## Tests worth adding (mirror our spike findings)
145
-
146
- 1. `x-named-type` on a direct property ⇒ `prop?: Message`, `referencedNamedTypes` includes `Message`, no `Message` extracted.
147
- 2. Inside `array.items` ⇒ `Array<Message>` (TS) / `List<Message>` (Kotlin) / `[Message]` (Swift).
148
- 3. Same name referenced by N properties ⇒ deduped to a single `Message` reference, `referencedNamedTypes === ['Message']` (the dedup we can't get today).
149
- 4. As a whole root schema (`{ "x-named-type": "Message" }` at top level) ⇒ output is just `Message` (our route-response-is-exactly-a-model case).
150
- 5. Both `inlineTypes: true` and `inlineTypes: false` behave identically for the referenced name (it's a leaf either way).
151
- 6. Absent keyword ⇒ byte-identical to current output (regression guard).
152
- 7. Coexists with genuine sibling extraction: a schema with one `x-named-type` property and one ordinary nested object still extracts the ordinary object normally under `inlineTypes:false`.
153
-
154
- ---
155
-
156
- ## How ts-procedures will consume it (integration contract)
157
-
158
- So you can sanity-check the design against the real caller:
159
-
160
- 1. We already collect every `$id`-bearing subschema into a registry `{ $id, name, schema }` and emit each one standalone via `emitTypescript(schema, { rootTypeName: name })` into `_models.ts`. **No change needed there.**
161
- 2. For each *route* schema, instead of our placeholder-token substitution, we'll walk the schema and replace each `$id`-bearing subschema with `{ "x-named-type": <name> }` before calling ajsc.
162
- 3. We read `emitResult.referencedNamedTypes` to emit `import type { Message, … } from './_models'` per scope file.
163
- 4. We delete `model-refs.ts` (the placeholder/regex module) entirely and the two wrapper functions in `emit-scope.ts`.
164
-
165
- The contract we depend on: **(a)** a referenced `x-named-type` is never inlined and never extracted, and **(b)** its name is reported back. Those two guarantees are the whole feature.
166
-
167
- ---
168
-
169
- ## Versioning
170
-
171
- Additive and opt-in ⇒ a **minor** bump (e.g. 7.3.0). No migration for existing consumers. ts-procedures will gate on the ajsc version that introduces `referencedNamedTypes` and keep the placeholder-token path as a fallback for one release if you'd like a soft cutover, or hard-switch if you prefer.
172
-
173
- ---
174
-
175
- ## Contact / context
176
-
177
- The placeholder-token workaround, the design rationale, and the empirical ajsc probes live in the ts-procedures repo:
178
- - `src/codegen/model-refs.ts` — the current substitution we want to remove
179
- - `src/codegen/emit-models.ts`, `collect-models.ts` — the model collection/emission (unaffected)
180
- - `docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md` — design (see finding #3)
181
- - commit `483185e` — the spike that established ajsc's inlining behaviour and chose the workaround
@@ -1,181 +0,0 @@
1
- # Response: shared-models auto-resolve (re: `--shared-types-from` request)
2
-
3
- > **STATUS: SHIPPED (v8.5.0).** Three targeted improvements shipped in place of source-file scanning. See below for what landed and why the requested mechanism was declined.
4
-
5
- **To:** downstream developer
6
- **From:** ts-procedures codegen maintainers
7
- **Date:** 2026-06-06
8
- **Reference:** feedback on `sharedTypesImport` DX — pain points 1–3
9
-
10
- ---
11
-
12
- ## TL;DR
13
-
14
- The goal — "define it once, no second table, no silent drift" — was right. We shipped three targeted fixes that deliver those ergonomics from the DocEnvelope side rather than by scanning TypeScript source files, which would introduce a worse class of silent failure. The per-`$id` `sharedTypesImport` map is unchanged and remains available as a precision escape hatch.
15
-
16
- ---
17
-
18
- ## What shipped
19
-
20
- ### 1. `sharedModelsModule` — one line replaces the entire map
21
-
22
- A single module path tells codegen: *"re-export every `$id` model from this module under its derived name."*
23
-
24
- ```jsonc
25
- // ts-procedures-codegen.config.json
26
- { "sharedModelsModule": "@app/schemas" }
27
- ```
28
-
29
- ```bash
30
- # CLI
31
- npx ts-procedures-codegen --url http://localhost:3000/doc --out ./generated --shared-models-module @app/schemas
32
- ```
33
-
34
- ```ts
35
- // generateClient() API
36
- await generateClient({ url, outDir, sharedModelsModule: '@app/schemas' })
37
- ```
38
-
39
- Convention: the derived name is the model's `$id`/`title` (PascalCase), which is the same identifier your schema file already exports. If the names align — they usually do — the entire `sharedTypesImport` map collapses to this single value.
40
-
41
- Precedence: explicit `sharedTypesImport[$id]` entry → `sharedModelsModule` convention → generate locally. The map remains available to override individual entries (rename, different package, multi-consumer split).
42
-
43
- ### 2. Summary log (CLI)
44
-
45
- Every CLI run that finds `$id`-bearing models now prints:
46
-
47
- ```
48
- [ts-procedures-codegen] Shared models: 15 total — 15 re-exported, 0 generated locally.
49
- ```
50
-
51
- This makes silent generated-twin drift visible without requiring any additional flags or contract tests. (Calling `generateClient` programmatically is silent by default — pass `logger: console.log` to opt in — so the summary never leaks into your own build-script output.)
52
-
53
- ### 3. `--strict-shared-models` — hard-fail on local twins
54
-
55
- Fails the run and lists every `$id` that would be generated as a local structural twin instead of re-exported:
56
-
57
- ```bash
58
- npx ts-procedures-codegen ... --strict-shared-models
59
- ```
60
-
61
- ```jsonc
62
- { "strictSharedModels": true }
63
- ```
64
-
65
- ```ts
66
- await generateClient({ url, outDir, sharedModelsModule: '@app/schemas', strictSharedModels: true })
67
- ```
68
-
69
- This is a **CI flag, not a development default** — run it in your pipeline and codegen becomes the drift gate. It replaces a hand-written `toEqualTypeOf` contract test.
70
-
71
- ---
72
-
73
- ## How this maps to your three pain points
74
-
75
- ### Pain 1: Two places to keep in lockstep (`$id` and the map entry)
76
-
77
- `sharedModelsModule` derives the import from the model's own `$id`/`title` — you define the convention once and it applies to every model. There is no second table to maintain.
78
-
79
- ### Pain 2: Silent fallback — a generated structural twin that typechecks but isn't the real type
80
-
81
- Two layers now make this visible:
82
-
83
- - The CLI summary log surfaces the "generated locally" count on every codegen run.
84
- - `--strict-shared-models` promotes that to a hard failure, listing the specific `$id`s. Add it to your CI pipeline and the generated-twin state becomes impossible to ship.
85
-
86
- This replaces your hand-written `toEqualTypeOf` contract test with a single flag — the codegen itself owns the invariant.
87
-
88
- ### Pain 3: The map is redundant — `module` is "where the schema lives" and `name` is usually the `$id`
89
-
90
- `sharedModelsModule` makes that redundancy disappear. `name` is derived from the model's `$id`/`title`; `module` is supplied once for all models. The 15-entry map becomes one line.
91
-
92
- ---
93
-
94
- ## Why `--shared-types-from <dir|glob>` was declined
95
-
96
- The goal was right. The mechanism is what we changed.
97
-
98
- ts-procedures codegen has one explicit input contract: a **DocEnvelope** obtained via `--url`, `--file`, or a passed object. The envelope is a self-contained JSON document — it contains schemas, routes, and error shapes. Codegen never reads the consumer's TypeScript source tree. That decoupling is what makes codegen work against a live remote server, a serialized offline file, or a published package with no local source.
99
-
100
- Source-file scanning would break that contract in four concrete ways:
101
-
102
- **(a) Silent desync in `--url` / offline `--file` cases.** The live server's envelope and the local source tree can disagree at any point in time — a schema field renamed in the server but not yet deployed, an `$id` changed in source but not rebuilt. Source scanning would produce a "shared" reference that names a type the server actually never returns. The result typechecks and the single-source-of-truth claim is restored, but the types are silently WRONG — a worse outcome than today's duplicate-but-correct structural twin.
103
-
104
- **(b) Fails for published-package consumers.** If `@app/schemas` lives in `node_modules`, there is no TypeScript source tree to scan — only compiled `.d.ts` files (if any) or nothing. The scan would silently skip models that are genuinely shared from a published package and generate local twins for them anyway, with no signal.
105
-
106
- **(c) Requires a TS parser, glob runner, and export-resolution heuristics in codegen.** This is a substantial dependency and surface area. It introduces failure modes (barrel re-exports, `export * from`, conditional exports, `.d.ts` vs. `.ts`) whose edge cases are hard to exhaustively enumerate and maintain. The DocEnvelope contract specifically exists to avoid this category of tooling dependency.
107
-
108
- **(d) Does not solve module-path correctness.** A file path discovered by glob scanning (`src/schemas/message.ts`) is not a valid TypeScript import specifier for a consumer who imports `@app/schemas`. Path-to-module-alias resolution requires TypeScript's full compiler configuration. Without it, any path the scan produces must be corrected by hand — which brings back a second table.
109
-
110
- The `sharedModelsModule` convention solves the same "define once, no second table" problem. The module specifier (`@app/schemas`) is typed once; names are derived from the model's own `$id`/`title`. It works in all deployment topologies: live server, offline file, published package.
111
-
112
- ---
113
-
114
- ## Escape hatch: `sharedTypesImport` unchanged
115
-
116
- The per-`$id` map is unchanged and remains available for cases where `sharedModelsModule` is not enough:
117
-
118
- - A model whose TypeScript name differs from its `$id` (rename case).
119
- - Different models imported from different packages (`@app/schemas` and `@app/internal-schemas`).
120
- - Multi-consumer split where a subset of models come from a shared package and others are generated locally.
121
-
122
- ```jsonc
123
- {
124
- "sharedModelsModule": "@app/schemas",
125
- "sharedTypesImport": {
126
- "urn:internal-msg": { "module": "@app/internal-schemas", "name": "InternalMessage" }
127
- }
128
- }
129
- ```
130
-
131
- `sharedTypesImport` entries take precedence over `sharedModelsModule`, so you can cover the general case with the convention and override individual outliers explicitly.
132
-
133
- ---
134
-
135
- ## Migration example
136
-
137
- **Before (v8.3 and earlier) — 15-entry hand-maintained map + contract test:**
138
-
139
- ```jsonc
140
- // ts-procedures-codegen.config.json
141
- {
142
- "shareModels": true,
143
- "sharedTypesImport": {
144
- "urn:message": { "module": "@app/schemas", "name": "Message" },
145
- "urn:user": { "module": "@app/schemas", "name": "User" },
146
- "urn:thread": { "module": "@app/schemas", "name": "Thread" },
147
- "urn:attachment": { "module": "@app/schemas", "name": "Attachment" },
148
- "urn:reaction": { "module": "@app/schemas", "name": "Reaction" },
149
- "urn:channel": { "module": "@app/schemas", "name": "Channel" },
150
- "urn:workspace": { "module": "@app/schemas", "name": "Workspace" },
151
- "urn:notification": { "module": "@app/schemas", "name": "Notification" },
152
- "urn:role": { "module": "@app/schemas", "name": "Role" },
153
- "urn:permission": { "module": "@app/schemas", "name": "Permission" },
154
- "urn:bot": { "module": "@app/schemas", "name": "Bot" },
155
- "urn:webhook": { "module": "@app/schemas", "name": "Webhook" },
156
- "urn:app": { "module": "@app/schemas", "name": "App" },
157
- "urn:token": { "module": "@app/schemas", "name": "Token" },
158
- "urn:audit-log": { "module": "@app/schemas", "name": "AuditLog" }
159
- }
160
- }
161
- ```
162
-
163
- ```ts
164
- // contract test to catch silent twins
165
- expect(generatedMessage).toEqualTypeOf<Message>()
166
- expect(generatedUser).toEqualTypeOf<User>()
167
- // ... × 15
168
- ```
169
-
170
- **After (v8.5.0) — one config line + one CI flag:**
171
-
172
- ```jsonc
173
- // ts-procedures-codegen.config.json
174
- {
175
- "shareModels": true,
176
- "sharedModelsModule": "@app/schemas",
177
- "strictSharedModels": true
178
- }
179
- ```
180
-
181
- No map. No contract tests. `--strict-shared-models` (or `"strictSharedModels": true`) in CI is the drift gate: codegen fails and names the offender if any `$id` model would silently generate as a local twin.