ts-procedures 6.1.0 → 7.0.0-beta.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 (267) hide show
  1. package/README.md +2 -0
  2. package/agent_config/bin/setup.mjs +2 -2
  3. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +3 -1
  4. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +38 -1
  5. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +216 -4
  6. package/agent_config/claude-code/skills/ts-procedures/patterns.md +60 -2
  7. package/agent_config/claude-code/skills/ts-procedures-kotlin/SKILL.md +1 -1
  8. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +1 -1
  9. package/agent_config/claude-code/skills/ts-procedures-swift/SKILL.md +119 -0
  10. package/agent_config/copilot/copilot-instructions.md +4 -0
  11. package/agent_config/cursor/cursorrules +4 -0
  12. package/agent_config/lib/install-claude.mjs +1 -1
  13. package/build/client/augment-error-map.test-d.d.ts +10 -0
  14. package/build/client/augment-error-map.test-d.js +14 -0
  15. package/build/client/augment-error-map.test-d.js.map +1 -0
  16. package/build/client/call.d.ts +14 -2
  17. package/build/client/call.js +96 -9
  18. package/build/client/call.js.map +1 -1
  19. package/build/client/call.test.js +50 -1
  20. package/build/client/call.test.js.map +1 -1
  21. package/build/client/classify-error.d.ts +11 -0
  22. package/build/client/classify-error.js +49 -0
  23. package/build/client/classify-error.js.map +1 -0
  24. package/build/client/classify-error.test.d.ts +1 -0
  25. package/build/client/classify-error.test.js +55 -0
  26. package/build/client/classify-error.test.js.map +1 -0
  27. package/build/client/error-dispatch.d.ts +1 -1
  28. package/build/client/error-dispatch.js +1 -1
  29. package/build/client/errors.d.ts +55 -4
  30. package/build/client/errors.js +54 -7
  31. package/build/client/errors.js.map +1 -1
  32. package/build/client/errors.test.js +89 -4
  33. package/build/client/errors.test.js.map +1 -1
  34. package/build/client/fetch-adapter.d.ts +2 -1
  35. package/build/client/fetch-adapter.js +2 -1
  36. package/build/client/fetch-adapter.js.map +1 -1
  37. package/build/client/fetch-adapter.test.js +12 -0
  38. package/build/client/fetch-adapter.test.js.map +1 -1
  39. package/build/client/index.d.ts +5 -3
  40. package/build/client/index.js +15 -3
  41. package/build/client/index.js.map +1 -1
  42. package/build/client/resolve-options.d.ts +32 -1
  43. package/build/client/resolve-options.js +32 -16
  44. package/build/client/resolve-options.js.map +1 -1
  45. package/build/client/resolve-options.test.js +67 -6
  46. package/build/client/resolve-options.test.js.map +1 -1
  47. package/build/client/result-type.test-d.d.ts +1 -0
  48. package/build/client/result-type.test-d.js +28 -0
  49. package/build/client/result-type.test-d.js.map +1 -0
  50. package/build/client/safe-call.test.d.ts +1 -0
  51. package/build/client/safe-call.test.js +137 -0
  52. package/build/client/safe-call.test.js.map +1 -0
  53. package/build/client/stream.d.ts +1 -1
  54. package/build/client/stream.js +22 -8
  55. package/build/client/stream.js.map +1 -1
  56. package/build/client/stream.test.js +11 -1
  57. package/build/client/stream.test.js.map +1 -1
  58. package/build/client/types.d.ts +96 -3
  59. package/build/codegen/bin/cli.d.ts +17 -3
  60. package/build/codegen/bin/cli.js +79 -3
  61. package/build/codegen/bin/cli.js.map +1 -1
  62. package/build/codegen/bundle-size.test.d.ts +1 -0
  63. package/build/codegen/bundle-size.test.js +68 -0
  64. package/build/codegen/bundle-size.test.js.map +1 -0
  65. package/build/codegen/e2e.test.js +103 -1
  66. package/build/codegen/e2e.test.js.map +1 -1
  67. package/build/codegen/emit-client-runtime.js +7 -0
  68. package/build/codegen/emit-client-runtime.js.map +1 -1
  69. package/build/codegen/emit-client-runtime.test.js +6 -2
  70. package/build/codegen/emit-client-runtime.test.js.map +1 -1
  71. package/build/codegen/emit-client-types.d.ts +7 -2
  72. package/build/codegen/emit-client-types.js +29 -8
  73. package/build/codegen/emit-client-types.js.map +1 -1
  74. package/build/codegen/emit-client-types.test.js +20 -8
  75. package/build/codegen/emit-client-types.test.js.map +1 -1
  76. package/build/codegen/emit-errors.d.ts +1 -1
  77. package/build/codegen/emit-errors.js +1 -1
  78. package/build/codegen/emit-index.js +1 -1
  79. package/build/codegen/emit-index.js.map +1 -1
  80. package/build/codegen/emit-scope.js +94 -26
  81. package/build/codegen/emit-scope.js.map +1 -1
  82. package/build/codegen/emit-scope.test.js +297 -2
  83. package/build/codegen/emit-scope.test.js.map +1 -1
  84. package/build/codegen/index.d.ts +18 -1
  85. package/build/codegen/index.js +3 -0
  86. package/build/codegen/index.js.map +1 -1
  87. package/build/codegen/pipeline.d.ts +16 -5
  88. package/build/codegen/pipeline.js +44 -143
  89. package/build/codegen/pipeline.js.map +1 -1
  90. package/build/codegen/targets/_shared/error-schemas.d.ts +10 -0
  91. package/build/codegen/targets/_shared/error-schemas.js +17 -0
  92. package/build/codegen/targets/_shared/error-schemas.js.map +1 -0
  93. package/build/codegen/targets/_shared/error-schemas.test.d.ts +1 -0
  94. package/build/codegen/targets/_shared/error-schemas.test.js +38 -0
  95. package/build/codegen/targets/_shared/error-schemas.test.js.map +1 -0
  96. package/build/codegen/targets/_shared/indent.d.ts +6 -0
  97. package/build/codegen/targets/_shared/indent.js +13 -0
  98. package/build/codegen/targets/_shared/indent.js.map +1 -0
  99. package/build/codegen/targets/_shared/indent.test.d.ts +1 -0
  100. package/build/codegen/targets/_shared/indent.test.js +21 -0
  101. package/build/codegen/targets/_shared/indent.test.js.map +1 -0
  102. package/build/codegen/targets/_shared/pascal-case.d.ts +6 -0
  103. package/build/codegen/targets/_shared/pascal-case.js +13 -0
  104. package/build/codegen/targets/_shared/pascal-case.js.map +1 -0
  105. package/build/codegen/targets/_shared/pascal-case.test.d.ts +1 -0
  106. package/build/codegen/targets/_shared/pascal-case.test.js +25 -0
  107. package/build/codegen/targets/_shared/pascal-case.test.js.map +1 -0
  108. package/build/codegen/targets/_shared/path-utils.d.ts +12 -0
  109. package/build/codegen/targets/_shared/path-utils.js +20 -0
  110. package/build/codegen/targets/_shared/path-utils.js.map +1 -0
  111. package/build/codegen/targets/_shared/path-utils.test.d.ts +1 -0
  112. package/build/codegen/targets/_shared/path-utils.test.js +42 -0
  113. package/build/codegen/targets/_shared/path-utils.test.js.map +1 -0
  114. package/build/codegen/targets/_shared/pick-defined.d.ts +11 -0
  115. package/build/codegen/targets/_shared/pick-defined.js +21 -0
  116. package/build/codegen/targets/_shared/pick-defined.js.map +1 -0
  117. package/build/codegen/targets/_shared/pick-defined.test.d.ts +1 -0
  118. package/build/codegen/targets/_shared/pick-defined.test.js +25 -0
  119. package/build/codegen/targets/_shared/pick-defined.test.js.map +1 -0
  120. package/build/codegen/targets/_shared/route-slots.d.ts +17 -0
  121. package/build/codegen/targets/_shared/route-slots.js +17 -0
  122. package/build/codegen/targets/_shared/route-slots.js.map +1 -0
  123. package/build/codegen/targets/_shared/route-slots.test.d.ts +1 -0
  124. package/build/codegen/targets/_shared/route-slots.test.js +43 -0
  125. package/build/codegen/targets/_shared/route-slots.test.js.map +1 -0
  126. package/build/codegen/targets/_shared/target-run.d.ts +27 -0
  127. package/build/codegen/targets/_shared/target-run.js +2 -0
  128. package/build/codegen/targets/_shared/target-run.js.map +1 -0
  129. package/build/codegen/targets/_shared/write-files.d.ts +24 -0
  130. package/build/codegen/targets/_shared/write-files.js +35 -0
  131. package/build/codegen/targets/_shared/write-files.js.map +1 -0
  132. package/build/codegen/targets/_shared/write-files.test.d.ts +1 -0
  133. package/build/codegen/targets/_shared/write-files.test.js +79 -0
  134. package/build/codegen/targets/_shared/write-files.test.js.map +1 -0
  135. package/build/codegen/targets/kotlin/e2e-compile.test.js +1 -1
  136. package/build/codegen/targets/kotlin/e2e-compile.test.js.map +1 -1
  137. package/build/codegen/targets/kotlin/emit-route-kotlin.js +5 -22
  138. package/build/codegen/targets/kotlin/emit-route-kotlin.js.map +1 -1
  139. package/build/codegen/targets/kotlin/emit-scope-kotlin.js +4 -8
  140. package/build/codegen/targets/kotlin/emit-scope-kotlin.js.map +1 -1
  141. package/build/codegen/targets/kotlin/format-kotlin.d.ts +0 -12
  142. package/build/codegen/targets/kotlin/format-kotlin.js +0 -27
  143. package/build/codegen/targets/kotlin/format-kotlin.js.map +1 -1
  144. package/build/codegen/targets/kotlin/format-kotlin.test.js +1 -34
  145. package/build/codegen/targets/kotlin/format-kotlin.test.js.map +1 -1
  146. package/build/codegen/targets/kotlin/integration.test.js +1 -1
  147. package/build/codegen/targets/kotlin/integration.test.js.map +1 -1
  148. package/build/codegen/targets/kotlin/run.d.ts +11 -0
  149. package/build/codegen/targets/kotlin/run.js +51 -0
  150. package/build/codegen/targets/kotlin/run.js.map +1 -0
  151. package/build/codegen/targets/swift/access-level.test.d.ts +1 -0
  152. package/build/codegen/targets/swift/access-level.test.js +98 -0
  153. package/build/codegen/targets/swift/access-level.test.js.map +1 -0
  154. package/build/codegen/targets/swift/ajsc-adapter.d.ts +27 -0
  155. package/build/codegen/targets/swift/ajsc-adapter.js +38 -0
  156. package/build/codegen/targets/swift/ajsc-adapter.js.map +1 -0
  157. package/build/codegen/targets/swift/ajsc-adapter.test.d.ts +1 -0
  158. package/build/codegen/targets/swift/ajsc-adapter.test.js +37 -0
  159. package/build/codegen/targets/swift/ajsc-adapter.test.js.map +1 -0
  160. package/build/codegen/targets/swift/e2e-compile.test.d.ts +1 -0
  161. package/build/codegen/targets/swift/e2e-compile.test.js +57 -0
  162. package/build/codegen/targets/swift/e2e-compile.test.js.map +1 -0
  163. package/build/codegen/targets/swift/emit-route-swift.d.ts +15 -0
  164. package/build/codegen/targets/swift/emit-route-swift.js +64 -0
  165. package/build/codegen/targets/swift/emit-route-swift.js.map +1 -0
  166. package/build/codegen/targets/swift/emit-route-swift.test.d.ts +1 -0
  167. package/build/codegen/targets/swift/emit-route-swift.test.js +258 -0
  168. package/build/codegen/targets/swift/emit-route-swift.test.js.map +1 -0
  169. package/build/codegen/targets/swift/emit-scope-swift.d.ts +13 -0
  170. package/build/codegen/targets/swift/emit-scope-swift.js +36 -0
  171. package/build/codegen/targets/swift/emit-scope-swift.js.map +1 -0
  172. package/build/codegen/targets/swift/emit-scope-swift.test.d.ts +1 -0
  173. package/build/codegen/targets/swift/emit-scope-swift.test.js +136 -0
  174. package/build/codegen/targets/swift/emit-scope-swift.test.js.map +1 -0
  175. package/build/codegen/targets/swift/format-swift.d.ts +2 -0
  176. package/build/codegen/targets/swift/format-swift.js +10 -0
  177. package/build/codegen/targets/swift/format-swift.js.map +1 -0
  178. package/build/codegen/targets/swift/format-swift.test.d.ts +1 -0
  179. package/build/codegen/targets/swift/format-swift.test.js +14 -0
  180. package/build/codegen/targets/swift/format-swift.test.js.map +1 -0
  181. package/build/codegen/targets/swift/integration.test.d.ts +1 -0
  182. package/build/codegen/targets/swift/integration.test.js +53 -0
  183. package/build/codegen/targets/swift/integration.test.js.map +1 -0
  184. package/build/codegen/targets/swift/run.d.ts +11 -0
  185. package/build/codegen/targets/swift/run.js +47 -0
  186. package/build/codegen/targets/swift/run.js.map +1 -0
  187. package/build/codegen/targets/ts/run.d.ts +4 -0
  188. package/build/codegen/targets/ts/run.js +86 -0
  189. package/build/codegen/targets/ts/run.js.map +1 -0
  190. package/docs/client-and-codegen.md +77 -7
  191. package/docs/client-error-handling.md +357 -0
  192. package/docs/codegen-kotlin.md +1 -0
  193. package/docs/codegen-swift.md +314 -0
  194. package/docs/superpowers/plans/2026-04-29-safe-result-api.md +2293 -0
  195. package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +1 -1
  196. package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +1 -1
  197. package/docs/superpowers/specs/2026-04-25-swift-codegen-design.md +264 -0
  198. package/docs/superpowers/specs/2026-04-29-safe-result-api-design.md +324 -0
  199. package/package.json +2 -2
  200. package/src/client/augment-error-map.test-d.ts +22 -0
  201. package/src/client/call.test.ts +65 -1
  202. package/src/client/call.ts +111 -9
  203. package/src/client/classify-error.test.ts +65 -0
  204. package/src/client/classify-error.ts +59 -0
  205. package/src/client/error-dispatch.ts +1 -1
  206. package/src/client/errors.test.ts +108 -4
  207. package/src/client/errors.ts +70 -7
  208. package/src/client/fetch-adapter.test.ts +15 -0
  209. package/src/client/fetch-adapter.ts +5 -2
  210. package/src/client/index.ts +39 -3
  211. package/src/client/resolve-options.test.ts +83 -5
  212. package/src/client/resolve-options.ts +61 -16
  213. package/src/client/result-type.test-d.ts +51 -0
  214. package/src/client/safe-call.test.ts +157 -0
  215. package/src/client/stream.test.ts +13 -1
  216. package/src/client/stream.ts +25 -8
  217. package/src/client/types.ts +112 -3
  218. package/src/codegen/bin/cli.ts +91 -7
  219. package/src/codegen/bundle-size.test.ts +74 -0
  220. package/src/codegen/e2e.test.ts +108 -1
  221. package/src/codegen/emit-client-runtime.test.ts +7 -2
  222. package/src/codegen/emit-client-runtime.ts +7 -0
  223. package/src/codegen/emit-client-types.test.ts +22 -7
  224. package/src/codegen/emit-client-types.ts +35 -10
  225. package/src/codegen/emit-errors.ts +1 -1
  226. package/src/codegen/emit-index.ts +1 -1
  227. package/src/codegen/emit-scope.test.ts +324 -2
  228. package/src/codegen/emit-scope.ts +98 -36
  229. package/src/codegen/index.ts +24 -1
  230. package/src/codegen/pipeline.ts +52 -174
  231. package/src/codegen/targets/_shared/error-schemas.test.ts +42 -0
  232. package/src/codegen/targets/_shared/error-schemas.ts +17 -0
  233. package/src/codegen/targets/_shared/indent.test.ts +25 -0
  234. package/src/codegen/targets/_shared/indent.ts +12 -0
  235. package/src/codegen/targets/_shared/pascal-case.test.ts +30 -0
  236. package/src/codegen/targets/_shared/pascal-case.ts +12 -0
  237. package/src/codegen/targets/_shared/path-utils.test.ts +51 -0
  238. package/src/codegen/targets/_shared/path-utils.ts +21 -0
  239. package/src/codegen/targets/_shared/pick-defined.test.ts +48 -0
  240. package/src/codegen/targets/_shared/pick-defined.ts +23 -0
  241. package/src/codegen/targets/_shared/route-slots.test.ts +55 -0
  242. package/src/codegen/targets/_shared/route-slots.ts +32 -0
  243. package/src/codegen/targets/_shared/target-run.ts +28 -0
  244. package/src/codegen/targets/_shared/write-files.test.ts +110 -0
  245. package/src/codegen/targets/_shared/write-files.ts +53 -0
  246. package/src/codegen/targets/kotlin/e2e-compile.test.ts +1 -1
  247. package/src/codegen/targets/kotlin/emit-route-kotlin.ts +5 -25
  248. package/src/codegen/targets/kotlin/emit-scope-kotlin.ts +4 -9
  249. package/src/codegen/targets/kotlin/format-kotlin.test.ts +0 -44
  250. package/src/codegen/targets/kotlin/format-kotlin.ts +0 -32
  251. package/src/codegen/targets/kotlin/integration.test.ts +1 -1
  252. package/src/codegen/targets/kotlin/run.ts +78 -0
  253. package/src/codegen/targets/swift/__fixtures__/users-golden.swift +123 -0
  254. package/src/codegen/targets/swift/access-level.test.ts +108 -0
  255. package/src/codegen/targets/swift/ajsc-adapter.test.ts +47 -0
  256. package/src/codegen/targets/swift/ajsc-adapter.ts +67 -0
  257. package/src/codegen/targets/swift/e2e-compile.test.ts +66 -0
  258. package/src/codegen/targets/swift/emit-route-swift.test.ts +300 -0
  259. package/src/codegen/targets/swift/emit-route-swift.ts +90 -0
  260. package/src/codegen/targets/swift/emit-scope-swift.test.ts +164 -0
  261. package/src/codegen/targets/swift/emit-scope-swift.ts +59 -0
  262. package/src/codegen/targets/swift/format-swift.test.ts +23 -0
  263. package/src/codegen/targets/swift/format-swift.ts +9 -0
  264. package/src/codegen/targets/swift/integration.test.ts +80 -0
  265. package/src/codegen/targets/swift/run.ts +74 -0
  266. package/src/codegen/targets/ts/run.ts +117 -0
  267. /package/src/codegen/{targets/kotlin/__fixtures__ → __fixtures__}/users-envelope.json +0 -0
package/README.md CHANGED
@@ -52,6 +52,8 @@ const user2 = await procedure({}, { userId: '456' })
52
52
 
53
53
  - **[Typed Error Handling](docs/http-integrations.md#error-handling)** — Declarative `defineErrorTaxonomy` maps thrown error classes to HTTP responses across every builder; generated clients throw typed class instances you can catch with `instanceof`.
54
54
 
55
+ - **[Client Error Handling](docs/client-error-handling.md)** — Normalized framework error classes (`ClientHttpError`, `ClientNetworkError`, `ClientTimeoutError`, `ClientAbortError`, `ClientParseError`), `.safe()` Result API for exhaustive narrowing without try/catch, and augmentable `ClientErrorMap` for custom error categories.
56
+
55
57
  - **[AI Agent Setup](docs/ai-agent-setup.md)** — Built-in configuration for Claude Code, Cursor, and GitHub Copilot. Auto-updates on `npm install`.
56
58
 
57
59
  Full documentation is available on [GitHub](https://github.com/thermsio/ts-procedures).
@@ -92,7 +92,7 @@ function setupClaude({ dryRun, check } = {}) {
92
92
  // Mirror install-claude.mjs: copy every file under each source skill dir, plus the agent.
93
93
  const claudeFiles = [];
94
94
  const skillsSrc = join(AGENT_CONFIG_DIR, 'claude-code', 'skills');
95
- for (const skill of ['ts-procedures', 'ts-procedures-review', 'ts-procedures-scaffold']) {
95
+ for (const skill of ['ts-procedures', 'ts-procedures-review', 'ts-procedures-scaffold', 'ts-procedures-kotlin', 'ts-procedures-swift']) {
96
96
  const walk = (dir, prefix) => {
97
97
  for (const entry of readdirSync(dir)) {
98
98
  const full = join(dir, entry);
@@ -128,7 +128,7 @@ function setupClaude({ dryRun, check } = {}) {
128
128
  console.log(` ${f}`);
129
129
  }
130
130
  console.log('');
131
- console.log(' Skills: ts-procedures, ts-procedures-scaffold, ts-procedures-review');
131
+ console.log(' Skills: ts-procedures, ts-procedures-scaffold, ts-procedures-review, ts-procedures-kotlin, ts-procedures-swift');
132
132
  console.log(' Agent: ts-procedures-architect (architecture planning)\n');
133
133
  return false;
134
134
  }
@@ -16,6 +16,7 @@ Load the right reference for your task:
16
16
  - **Reviewing or debugging existing code?** Read [anti-patterns.md](anti-patterns.md) — 20 common mistakes with before/after fixes and severity ratings
17
17
  - **Need exact API signatures or type definitions?** Read [api-reference.md](api-reference.md) — complete API documentation with type signatures for every export
18
18
  - **Generating a Kotlin client for Android/JVM consumers?** Use the separate `ts-procedures-kotlin` skill — it covers `--target kotlin` end-to-end and won't load unless the user mentions Kotlin/Android.
19
+ - **Generating a Swift client for iOS/macOS/Apple-platform consumers?** Use the separate `ts-procedures-swift` skill — it covers `--target swift` end-to-end and won't load unless the user mentions Swift/iOS/Apple platforms.
19
20
 
20
21
  ## Core Flow
21
22
 
@@ -174,7 +175,8 @@ The npm package ships user-facing documentation with narrative explanations and
174
175
  | `docs/streaming.md` | Streaming procedures, AbortSignal, SSE patterns |
175
176
  | `docs/http-integrations.md` | Express RPC, Hono RPC/Stream/API builders, **error taxonomy (canonical)**, DocRegistry (unified constructor, `.documentError()`) |
176
177
  | `docs/client-and-codegen.md` | Client code generation, `createApiClient`/`createClient`, typed error dispatch, per-route `Errors` unions, per-call options, client-level defaults, typed RequestMeta augmentation, CLI options |
177
- | `CHANGELOG.md` | Release notes see `[6.0.0]` for the current peer error-handling model (taxonomy + `onError` + `onRequestError`), per-route errors, and client runtime error classes. |
178
+ | `docs/client-error-handling.md` | **Canonical guide** for 7.0+ client error surface: normalized error classes, `.safe()` Result API, `ClientErrorMap` augmentation, custom `ErrorClassifier`, migration from `ClientRequestError`. |
179
+ | `CHANGELOG.md` | Release notes — see `[7.0.0]` for the safe-result API, new error classes, and `ClientRequestError` → `ClientHttpError` rename. See `[6.0.0]` for the peer error-handling model (taxonomy + `onError` + `onRequestError`). |
178
180
 
179
181
  ## Workflow
180
182
 
@@ -612,7 +612,7 @@ Create('GetUser', {
612
612
 
613
613
  ## 20. Hand-writing `onError` instanceof ladders
614
614
 
615
- **Problem:** Writing manual `instanceof` ladders inside `onError` to map each error class to a status + body. Every builder gets its own copy; generated clients see opaque `ClientRequestError` objects instead of typed instances; response shapes drift between routes.
615
+ **Problem:** Writing manual `instanceof` ladders inside `onError` to map each error class to a status + body. Every builder gets its own copy; generated clients see opaque `ClientHttpError` objects instead of typed instances; response shapes drift between routes.
616
616
 
617
617
  ```typescript
618
618
  // BAD — duplicated across builders, invisible to generated clients
@@ -659,6 +659,42 @@ The same `errors` / `unknownError` shape plugs into every builder (`HonoAPIAppBu
659
659
 
660
660
  ---
661
661
 
662
+ ## 21. Catching raw `DOMException` or `TypeError` from generated callables
663
+
664
+ **Problem:** Checking `instanceof DOMException` or `instanceof TypeError` in `catch` blocks after calling a generated RPC/API callable. The framework normalizes these platform errors into typed framework classes at the `executeCall` boundary — after 7.0.0, raw platform errors will not reach your `catch` block from a generated callable. The original platform error is preserved on `error.cause` if you need it.
665
+
666
+ ```typescript
667
+ // BAD — platform error classes won't reach here from a generated callable
668
+ catch (e) {
669
+ if (e instanceof DOMException && e.name === 'AbortError') {
670
+ // Was this a timeout or a user cancel? Hard to tell.
671
+ }
672
+ if (e instanceof TypeError) {
673
+ // Network failure? Something else?
674
+ }
675
+ }
676
+ ```
677
+
678
+ **Fix:** Catch the framework error classes instead.
679
+
680
+ ```typescript
681
+ // GOOD
682
+ import { ClientTimeoutError, ClientAbortError, ClientNetworkError } from 'ts-procedures/client'
683
+
684
+ catch (e) {
685
+ if (e instanceof ClientTimeoutError) Alerts.error('Timed out')
686
+ else if (e instanceof ClientAbortError) { /* user cancelled — silent */ }
687
+ else if (e instanceof ClientNetworkError) Alerts.error('Network error')
688
+ else throw e // unknown — let it propagate
689
+ }
690
+ ```
691
+
692
+ Or use the `.safe()` sibling for exhaustive Result-based narrowing (see `patterns.md` — "Handling errors in client code").
693
+
694
+ **Why:** The framework wraps `AbortError` (from timeout or signal) into `ClientTimeoutError` / `ClientAbortError` and `TypeError`/fetch network failures into `ClientNetworkError`. This gives you distinct, meaningful types and removes ambiguity. Streams keep the throwing form — `.safe()` is only available on RPC and API callables.
695
+
696
+ ---
697
+
662
698
  ## Summary Table
663
699
 
664
700
  | # | Anti-Pattern | Risk | Severity |
@@ -683,3 +719,4 @@ The same `errors` / `unknownError` shape plugs into every builder (`HonoAPIAppBu
683
719
  | 18 | Both schema.params and schema.input | ProcedureRegistrationError at startup | CRITICAL |
684
720
  | 19 | Mismatched path param names | Build-time error or confusing validation failures | CRITICAL |
685
721
  | 20 | Hand-writing onError instanceof ladders | Drifting response shapes, untyped client errors | WARNING |
722
+ | 21 | Catching raw DOMException/TypeError from generated callables | Framework normalizes these; raw platform errors won't reach catch blocks after 7.0.0 | WARNING |
@@ -354,7 +354,7 @@ Returns a typed error instance when:
354
354
  - `body` is an object with a string `name`, AND
355
355
  - `registry[body.name].fromResponse(body, meta)` returns an `Error` subclass.
356
356
 
357
- Otherwise returns `null`; callers fall back to `ClientRequestError`.
357
+ Otherwise returns `null`; callers fall back to `ClientHttpError`.
358
358
 
359
359
  ### CreateClientConfig.errorRegistry
360
360
 
@@ -369,6 +369,189 @@ Threaded into both `executeCall` and `executeStream`. The generated `create${Ser
369
369
 
370
370
  ---
371
371
 
372
+ ## Client Error Classes (7.0+)
373
+
374
+ All framework errors are normalized at the `executeCall` / `executeStream` boundary — raw `TypeError` and `DOMException` from the platform never reach consumer catch blocks.
375
+
376
+ ### ClientHttpError
377
+
378
+ ```typescript
379
+ class ClientHttpError extends Error {
380
+ readonly status: number
381
+ readonly cause?: unknown
382
+ }
383
+ ```
384
+
385
+ Thrown for non-2xx HTTP responses whose body does not match any registry entry (or when no registry is configured). Renamed from `ClientRequestError` in 7.0.0; the old name is re-exported as a deprecated alias for one minor cycle.
386
+
387
+ ### ClientNetworkError
388
+
389
+ ```typescript
390
+ class ClientNetworkError extends Error {
391
+ readonly cause?: unknown
392
+ }
393
+ ```
394
+
395
+ Thrown when the network request itself fails (e.g., DNS failure, connection refused — a `TypeError` from `fetch`). The original error is available on `cause`.
396
+
397
+ ### ClientTimeoutError
398
+
399
+ ```typescript
400
+ class ClientTimeoutError extends Error {
401
+ readonly cause?: unknown
402
+ }
403
+ ```
404
+
405
+ Thrown when the request is aborted by a timeout (`AbortSignal.timeout`). The original `DOMException` is available on `cause`.
406
+
407
+ ### ClientAbortError
408
+
409
+ ```typescript
410
+ class ClientAbortError extends Error {
411
+ readonly cause?: unknown
412
+ }
413
+ ```
414
+
415
+ Thrown when the request is aborted by a caller-supplied `AbortSignal`. The original `DOMException` is available on `cause`.
416
+
417
+ ### ClientParseError
418
+
419
+ ```typescript
420
+ class ClientParseError extends Error {
421
+ readonly cause?: unknown
422
+ }
423
+ ```
424
+
425
+ Thrown when the response body cannot be parsed as JSON (e.g., the server returned HTML for a non-2xx status). The parse error is available on `cause`.
426
+
427
+ ### ClientPathParamError
428
+
429
+ ```typescript
430
+ class ClientPathParamError extends Error {}
431
+ ```
432
+
433
+ Thrown at call time when a required path parameter is missing in the options. This is a programming error (the generated callable could not interpolate the URL path).
434
+
435
+ ### ClientStreamError
436
+
437
+ ```typescript
438
+ class ClientStreamError extends Error {
439
+ readonly cause?: unknown
440
+ }
441
+ ```
442
+
443
+ Thrown for stream-level transport errors (SSE connection failures, unexpected stream termination).
444
+
445
+ ---
446
+
447
+ ## Safe-Result API (7.0+)
448
+
449
+ ### Result\<T, ETyped\>
450
+
451
+ ```typescript
452
+ type Result<T, ETyped = never> =
453
+ | { ok: true; value: T }
454
+ | FrameworkFailure<ETyped>
455
+ ```
456
+
457
+ Returned by every generated `.safe()` callable. When `ok` is `true`, `value` is the typed response. When `ok` is `false`, the failure is discriminated by `kind`.
458
+
459
+ ### ResultNoTyped\<T\>
460
+
461
+ ```typescript
462
+ type ResultNoTyped<T> = Result<T, never>
463
+ ```
464
+
465
+ Convenience alias when no typed error registry is wired (all failures are framework-level).
466
+
467
+ ### FrameworkFailure\<ETyped\>
468
+
469
+ ```typescript
470
+ type FrameworkFailure<ETyped = never> =
471
+ | { ok: false; kind: 'typed'; error: ETyped }
472
+ | { ok: false; kind: 'http'; error: ClientHttpError }
473
+ | { ok: false; kind: 'network'; error: ClientNetworkError }
474
+ | { ok: false; kind: 'timeout'; error: ClientTimeoutError }
475
+ | { ok: false; kind: 'aborted'; error: ClientAbortError }
476
+ | { ok: false; kind: 'parse'; error: ClientParseError }
477
+ | { ok: false; kind: 'usage'; error: ClientPathParamError }
478
+ | { ok: false; kind: 'unknown'; error: unknown }
479
+ // + any augmented kinds from ClientErrorMap
480
+ ```
481
+
482
+ The failure branch of `Result`. Discriminate on `kind` to narrow `error`.
483
+
484
+ ### ClientErrorMap
485
+
486
+ ```typescript
487
+ interface ClientErrorMap {
488
+ // empty — designed for declaration merging
489
+ }
490
+ ```
491
+
492
+ Augment this interface to add custom `kind` entries to `FrameworkFailure`. Example:
493
+
494
+ ```typescript
495
+ declare module 'ts-procedures/client' {
496
+ interface ClientErrorMap {
497
+ rateLimit: RateLimitError
498
+ }
499
+ }
500
+ ```
501
+
502
+ After augmentation, `r.kind === 'rateLimit'` is a valid branch in a `switch` over `FrameworkFailure`.
503
+
504
+ ### ClientInstance.safeCall\<TResponse, ETyped\>(descriptor, options)
505
+
506
+ ```typescript
507
+ interface ClientInstance {
508
+ safeCall<TResponse, ETyped = never>(
509
+ descriptor: CallDescriptor,
510
+ options?: ProcedureCallOptions
511
+ ): Promise<Result<TResponse, ETyped>>
512
+ }
513
+ ```
514
+
515
+ The underlying method that all generated `.safe()` callables delegate to. When `ETyped` is provided, it is the type of the `typed` failure branch (the registry-dispatched error class). Streams do not expose a `.safe()` sibling — `safeCall` applies to RPC and API calls only.
516
+
517
+ ---
518
+
519
+ ## Error Classifier API (7.0+)
520
+
521
+ ### defaultClassifyError
522
+
523
+ ```typescript
524
+ function defaultClassifyError(err: unknown, ctx: ClassifyErrorContext): ClassifiedError | null
525
+ ```
526
+
527
+ The built-in classifier that maps raw platform errors to framework classes. Returns a `ClassifiedError` (with `kind` + `error`) when the error is recognized, or `null` to fall through to `unknown`. Pass a custom `ErrorClassifier` to `createFetchAdapter` / `ClientAdapter` to override or extend.
528
+
529
+ ### ErrorClassifier
530
+
531
+ ```typescript
532
+ type ErrorClassifier = (err: unknown, ctx: ClassifyErrorContext) => ClassifiedError | null
533
+ ```
534
+
535
+ ### ClassifyErrorContext
536
+
537
+ ```typescript
538
+ interface ClassifyErrorContext {
539
+ request: AdapterRequest
540
+ signal?: AbortSignal
541
+ }
542
+ ```
543
+
544
+ ### ClassifiedError
545
+
546
+ ```typescript
547
+ interface ClassifiedError {
548
+ kind: keyof ClientErrorMap | 'network' | 'timeout' | 'aborted' | 'parse' | 'unknown'
549
+ error: Error
550
+ }
551
+ ```
552
+
553
+ ---
554
+
372
555
  ## Schema Utilities
373
556
 
374
557
  ### extractJsonSchema(libSchema)
@@ -986,12 +1169,14 @@ Creates a `ClientAdapter` using the global `fetch` API.
986
1169
  ```typescript
987
1170
  function createFetchAdapter(options?: {
988
1171
  fetch?: typeof globalThis.fetch
1172
+ classifyError?: ErrorClassifier
989
1173
  }): ClientAdapter
990
1174
  ```
991
1175
 
992
1176
  ### Parameters
993
1177
 
994
1178
  - `options.fetch` — Optional custom fetch implementation. Defaults to `globalThis.fetch`.
1179
+ - `options.classifyError` — Optional classifier override. When provided, replaces `defaultClassifyError` for mapping raw platform errors to framework classes. Use this to add custom `ClientErrorMap` categories or to adjust the default mapping.
995
1180
 
996
1181
  ### Return Value
997
1182
 
@@ -1013,7 +1198,7 @@ const adapter = createFetchAdapter({ fetch: customFetch })
1013
1198
 
1014
1199
  ## generateClient(options)
1015
1200
 
1016
- > For **Kotlin** client codegen (Android/JVM, types-only output), see the dedicated `ts-procedures-kotlin` skill. The reference below covers TypeScript codegen only.
1201
+ > For **Kotlin** client codegen (Android/JVM, types-only output), see the dedicated `ts-procedures-kotlin` skill. For **Swift** client codegen (iOS/macOS/Apple platforms, types-only output), see the dedicated `ts-procedures-swift` skill. The reference below covers TypeScript codegen only.
1017
1202
 
1018
1203
  Build-time CLI and programmatic API for generating typed client files from a `DocEnvelope`.
1019
1204
 
@@ -1212,8 +1397,35 @@ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
1212
1397
  import type { APIConfig, APIHttpRouteDoc, APIInput, HttpMethod } from 'ts-procedures/hono-api'
1213
1398
 
1214
1399
  // Client
1215
- import { createClient, createFetchAdapter } from 'ts-procedures/client'
1216
- import type { ClientAdapter, ClientHooks, ClientInstance, TypedStream } from 'ts-procedures/client'
1400
+ import {
1401
+ createClient,
1402
+ createFetchAdapter,
1403
+ dispatchTypedError,
1404
+ defaultClassifyError,
1405
+ // Error classes (7.0+)
1406
+ ClientHttpError, // renamed from ClientRequestError; deprecated alias retained one cycle
1407
+ ClientNetworkError,
1408
+ ClientTimeoutError,
1409
+ ClientAbortError,
1410
+ ClientParseError,
1411
+ ClientPathParamError,
1412
+ ClientStreamError,
1413
+ } from 'ts-procedures/client'
1414
+ import type {
1415
+ ClientAdapter,
1416
+ ClientHooks,
1417
+ ClientInstance,
1418
+ TypedStream,
1419
+ // Safe-result types (7.0+)
1420
+ Result,
1421
+ ResultNoTyped,
1422
+ FrameworkFailure,
1423
+ ClientErrorMap,
1424
+ // Classifier types (7.0+)
1425
+ ErrorClassifier,
1426
+ ClassifyErrorContext,
1427
+ ClassifiedError,
1428
+ } from 'ts-procedures/client'
1217
1429
 
1218
1430
  // Code generation
1219
1431
  import { generateClient } from 'ts-procedures/codegen'
@@ -242,7 +242,7 @@ try {
242
242
  } else if (err instanceof ApiErrors.ApiProcedureError) {
243
243
  // Catch-all for any generated service error.
244
244
  } else {
245
- // Transport error (network, non-JSON body) — still a ClientRequestError.
245
+ // Transport error (network, non-JSON body) — still a ClientHttpError.
246
246
  }
247
247
  }
248
248
  ```
@@ -263,13 +263,71 @@ export namespace Users {
263
263
  catch (err: unknown) {
264
264
  // Cast is manual today (TS has no typed throws); the union documents
265
265
  // which errors this specific route can throw.
266
- const e = err as Users.GetUser.Errors | ClientRequestError
266
+ const e = err as Users.GetUser.Errors | ClientHttpError
267
267
  if (e instanceof ApiErrors.UseCaseError) { /* ... */ }
268
268
  }
269
269
  ```
270
270
 
271
271
  ---
272
272
 
273
+ ## Handling errors in client code
274
+
275
+ The framework normalizes platform errors (`TypeError`, `DOMException`) into framework error classes at the `executeCall` boundary. Consumers see typed framework classes, not raw platform errors.
276
+
277
+ ### Throwing form (canonical)
278
+
279
+ ```typescript
280
+ import { ClientHttpError, ClientTimeoutError, ClientNetworkError, ClientAbortError } from 'ts-procedures/client'
281
+ import { createApiClient, ApiErrors, createFetchAdapter } from './generated'
282
+
283
+ const api = createApiClient({ adapter: createFetchAdapter(), basePath: 'https://api.example.com' })
284
+
285
+ try {
286
+ const response = await api.records.DownloadRecord(params, { timeout: 60_000 })
287
+ if (response.pdfBase64) downloadBase64AsPdf(response.pdfBase64)
288
+ } catch (err) {
289
+ if (err instanceof ApiErrors.UseCaseError) {
290
+ Alerts.error(err.body.message)
291
+ } else if (err instanceof ClientHttpError) {
292
+ Alerts.error(`Server returned ${err.status}`)
293
+ } else if (err instanceof ClientTimeoutError) {
294
+ Alerts.error('Request timed out')
295
+ } else if (err instanceof ClientNetworkError) {
296
+ Alerts.error('Network error')
297
+ } else if (err instanceof ClientAbortError) {
298
+ // user cancelled — silent
299
+ } else {
300
+ throw err // programmer/framework bug
301
+ }
302
+ }
303
+ ```
304
+
305
+ ### Safe form (Result-returning, exhaustive narrowing)
306
+
307
+ Every RPC and API callable has a `.safe()` sibling that returns `Result<T, ETyped>` instead of throwing:
308
+
309
+ ```typescript
310
+ const r = await api.records.DownloadRecord.safe(params, { timeout: 60_000 })
311
+ if (r.ok) {
312
+ if (r.value.pdfBase64) downloadBase64AsPdf(r.value.pdfBase64)
313
+ return
314
+ }
315
+ switch (r.kind) {
316
+ case 'typed': /* registry-dispatched typed error, e.g. ApiErrors.UseCaseError */; break
317
+ case 'http': Alerts.error(`Server ${r.error.status}`); break
318
+ case 'network': Alerts.error('Network error'); break
319
+ case 'timeout': Alerts.error('Timed out'); break
320
+ case 'aborted': break // user cancelled
321
+ case 'parse':
322
+ case 'usage': throw r.error // programmer/framework bug
323
+ case 'unknown': console.error(r.error); break
324
+ }
325
+ ```
326
+
327
+ Streams keep the throwing form (no `.safe`). Custom error categories can be added via `ClientErrorMap` interface augmentation — see `docs/client-error-handling.md`.
328
+
329
+ ---
330
+
273
331
  ## Stream Procedures
274
332
 
275
333
  ```typescript
@@ -14,7 +14,7 @@ You are assisting a developer who needs to generate Kotlin types from a `ts-proc
14
14
  - The user wants to share API types between a `ts-procedures` server and a Kotlin/JVM consumer.
15
15
  - The user is debugging Kotlin codegen output, Gradle setup, or contextual serializer registration.
16
16
 
17
- If the user is generating a **TypeScript** client, redirect them to the main `ts-procedures` skill instead.
17
+ If the user is generating a **TypeScript** client, redirect them to the main `ts-procedures` skill. For **Swift / iOS / macOS / Apple-platform** consumers, redirect to `ts-procedures-swift`.
18
18
 
19
19
  ## Quickstart
20
20
 
@@ -50,7 +50,7 @@ export const {{name}}Client = createApiClient({
50
50
  // } catch (err) {
51
51
  // if (err instanceof ApiErrors.UseCaseError) { /* err.body typed; err.status, procedureName, scope */ }
52
52
  // else if (err instanceof ApiErrors.ApiProcedureError) { /* catch-all for service errors */ }
53
- // else { /* transport error — ClientRequestError */ }
53
+ // else { /* transport error — ClientHttpError */ }
54
54
  // }
55
55
 
56
56
  // --- RPC call example ---
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: ts-procedures-swift
3
+ description: "Swift client codegen for ts-procedures — generate types-only Swift source from a ts-procedures DocEnvelope for iOS/macOS/Apple-platform consumers. Use when the user mentions Swift, iOS, macOS, Apple platforms, Codable, or asks how to generate non-TypeScript types from a ts-procedures server."
4
+ user-invocable: false
5
+ ---
6
+
7
+ # ts-procedures — Swift Client Codegen
8
+
9
+ You are assisting a developer who needs to generate Swift types from a `ts-procedures` server's `DocEnvelope`. The Swift target is **types-only** — no runtime, no adapter, no error registry. iOS / macOS / Apple-platform consumers own the HTTP layer.
10
+
11
+ ## When this skill applies
12
+
13
+ - The user mentions Swift, iOS, macOS, watchOS, tvOS, visionOS, Apple platforms, `Codable`, `URLSession`, or `--target swift`.
14
+ - The user wants to share API types between a `ts-procedures` server and a Swift consumer.
15
+ - The user is debugging Swift codegen output, SPM/Xcode integration, `JSONDecoder` configuration, or `Codable` conformance issues.
16
+
17
+ If the user is generating a **TypeScript** client, redirect them to the main `ts-procedures` skill. For **Kotlin/Android** consumers, redirect to `ts-procedures-kotlin`.
18
+
19
+ ## Quickstart
20
+
21
+ ```bash
22
+ npx ts-procedures-codegen \
23
+ --target swift \
24
+ --url https://api.example.com/_ts-procedures.json \
25
+ --out ./Sources/MyApp/Generated
26
+ ```
27
+
28
+ One `.swift` file per scope. Types accessed as `Users.GetUser.Response`, `Users.GetUser.PathParams`, `Users.GetUser.Response.Address` (nested structs via `inlineTypes: true`), `Users.GetUser.Errors.NotFound`.
29
+
30
+ **Note:** unlike the Kotlin target, no `--swift-package` flag exists or is required. Swift modules are defined by Xcode/SPM targets.
31
+
32
+ ## CLI flags (Swift-specific)
33
+
34
+ | Flag | Default | Purpose |
35
+ |---|---|---|
36
+ | `--target swift` | `ts` | Switch to the Swift codegen path |
37
+ | `--swift-serializer <codable\|none>` | `codable` | `codable` emits `: Codable` + `CodingKeys`; `none` emits plain structs (consumer handles serialization) |
38
+ | `--swift-access-level <public\|internal>` | `public` | Threads through to ajsc's `accessLevel`; use `internal` when generated types shouldn't appear in module ABI |
39
+ | `--unsupported-unions <throw\|fallback>` | `throw` | **Functional for Swift** (unlike Kotlin where it's a no-op) — `fallback` emits a self-contained `AnyCodable` helper for untagged `anyOf`/`oneOf` |
40
+
41
+ `--array-item-naming`, `--depluralize`, `--uncountable-words` also apply to the Swift target.
42
+
43
+ ## Output shape (what consumers see)
44
+
45
+ ```swift
46
+ import Foundation
47
+
48
+ public enum Users {
49
+ public enum GetUser {
50
+ public static let method = "GET"
51
+ public static let pathTemplate = "/users/{id}"
52
+ public static func path(_ p: PathParams) -> String { return "/users/\(p.id)" }
53
+
54
+ public struct PathParams: Codable {
55
+ public let id: String
56
+ }
57
+
58
+ public struct Response: Codable {
59
+ public let id: String
60
+ public let createdAt: Date
61
+ public let address: Address
62
+
63
+ enum CodingKeys: String, CodingKey {
64
+ case id
65
+ case createdAt = "created-at"
66
+ case address
67
+ }
68
+
69
+ public struct Address: Codable {
70
+ public let street: String
71
+ public let city: String
72
+ }
73
+ }
74
+
75
+ public enum Errors {
76
+ public struct NotFound: Codable {
77
+ public let name: String
78
+ public let message: String
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ For routes without path params, `path` is a `static let`, not a function. Namespaces are caseless `enum`s (the standard Swift idiom — uninstantiable, zero runtime cost).
86
+
87
+ ## Consumer-side setup the dev MUST do
88
+
89
+ The generated code requires these on the Apple-platform side. **Don't let the user assume the codegen handles them.**
90
+
91
+ 1. **Drop generated files into your SPM target or Xcode source group.** Any target containing the generated dir picks them up — no special build config. SPM globs sources recursively; for Xcode, use **File → Add Files to "<TargetName>"…** and ensure target membership is checked.
92
+
93
+ 2. **Configure `JSONDecoder.dateDecodingStrategy = .iso8601`.** **Required** if the schema uses any `format: date-time` field (which become `Foundation.Date`). Without this, decoding fails with `DecodingError.typeMismatch`. Symmetric: set `JSONEncoder.dateEncodingStrategy = .iso8601` for request bodies.
94
+
95
+ 3. **HTTP transport is the consumer's choice.** `URLSession` (with `async/await`) is the default recommendation — it's built-in and works on all Apple platforms with no dependencies. Alamofire / other libraries are fine but never required.
96
+
97
+ 4. **No runtime dispatch.** Error types are emitted as nested structs (`Users.GetUser.Errors.NotFound`), but there's no registry, no `instanceof`-style lookup, no `dispatchTypedError`. Consumers catch HTTP failures themselves and dispatch on status code or `body.name` (a regular `String` field, not a type-system discriminator) to decide which error struct to decode against. To make error structs throwable, declare a one-line `extension X.Errors.NotFound: Error {}` in user code (keep it out of the generated file so re-runs don't clobber it). This is by design; don't suggest implementing a registry.
98
+
99
+ The full setup guide lives at `docs/codegen-swift.md` in the `ts-procedures` repo.
100
+
101
+ ## Documented limitations to flag during reviews
102
+
103
+ - **`format: date` and `format: time` map to `String`.** Foundation has no native date-only or time-only type. Parse with `DateFormatter` if a typed value is needed.
104
+ - **`type: integer` maps to `Int64`** (not `Int`). 32-bit Apple platforms exist; `Int64` guarantees range parity.
105
+ - **`type: number` maps to `Double`.** For monetary values, convert to `Decimal` at the parse boundary.
106
+ - **Heterogeneous tuples throw under `Codable`.** Swift tuples aren't `Codable`. Schemas with positional-tuple `items: [...]` arrays throw at codegen time. Refactor to a struct schema upstream.
107
+ - **`additionalProperties: { type: T }` is silently dropped** with a doc-comment. Add a sibling `[String: T]` field by hand if your contract uses extra keys.
108
+ - **`not` and `patternProperties` throw at codegen time.** Simplify the schema upstream.
109
+ - **Untagged `oneOf` throws by default.** Add a discriminator, or pass `--unsupported-unions fallback` to emit `AnyCodable`-typed values (loses static typing).
110
+
111
+ ## Anti-patterns
112
+
113
+ - **Suggesting the Swift target ships a networking layer or HTTP adapter.** It does not — consumers own `URLSession`/etc. entirely.
114
+ - **Recommending Alamofire as required.** `URLSession` + `async/await` is fine and ships with the OS. Alamofire is a personal-preference choice, not a dependency of the generated code.
115
+ - **Conflating `--swift-serializer none` with "no setup needed".** `none` removes `Codable` conformance and `CodingKeys` — the consumer then needs their own serialization plan (hand-rolled, SwiftyJSON, etc.). It's strictly *more* setup, not less.
116
+ - **Treating `--unsupported-unions fallback` as a no-op for Swift.** Unlike Kotlin (where it's silently dropped), the Swift target actually emits an `AnyCodable` helper that round-trips correctly. Use it when needed.
117
+ - **Suggesting backwards compatibility with Swift 4 / pre-async-await.** Assume Swift 5.5+ (the async/await era). Sample dispatch code in docs uses `async throws`.
118
+ - **Suggesting the codegen emits `: Error` conformance on error structs.** It doesn't (`Codable` only) — consumers add a one-line extension. Don't ask for this to be added to the codegen; it would force users into a particular error model.
119
+ - **Mixing `--target swift` flags into a TypeScript-target invocation.** The flags are silently ignored; no harm, but the user is probably running the wrong target.
@@ -280,6 +280,8 @@ try {
280
280
 
281
281
  Per-route error unions: routes with `errors: [...]` get an `Errors` type in their namespace (e.g. `Users.GetUser.Errors = ApiErrors.UseCaseError | ApiErrors.AuthError`).
282
282
 
283
+ - Generated client error handling: catch the framework classes (`ClientHttpError`, `ClientNetworkError`, `ClientTimeoutError`, `ClientAbortError`), not raw `DOMException`/`TypeError` — the framework normalizes platform errors at the `executeCall` boundary, so raw platform errors no longer reach `catch` blocks after 7.0.0 (original is on `error.cause`). Use the `.safe()` sibling on RPC/API callables for an exhaustive `Result<T, E>` switch (`ok`, `typed`, `http`, `network`, `timeout`, `aborted`, `parse`, `usage`, `unknown`). Streams keep the throwing form — no `.safe()`.
284
+
283
285
  ## Lifecycle Hook Order
284
286
 
285
287
  ### Standard RPC
@@ -327,6 +329,7 @@ onRequestStart → factoryContext() → validation → onStreamStart → handler
327
329
  9. **Never assume extra params fields survive** — `removeAdditional: true` strips them
328
330
  10. **Never manually parse types AJV coerces** — `coerceTypes: true` handles it
329
331
  11. **Never define both schema.params and schema.input** — mutually exclusive, throws ProcedureRegistrationError
332
+ 12. **Never catch raw DOMException/TypeError from generated callables** — catch `ClientTimeoutError`, `ClientAbortError`, `ClientNetworkError` instead; or use `.safe()` for Result-based narrowing
330
333
 
331
334
  ## Testing
332
335
 
@@ -387,6 +390,7 @@ npx ts-procedures-codegen --url http://localhost:3000/docs --out ./src/generated
387
390
  ```
388
391
 
389
392
  For Kotlin codegen (Android/JVM consumers), see `docs/codegen-kotlin.md` in the ts-procedures repo. The Kotlin target is types-only; consumer apps own HTTP/error handling.
393
+ For Swift codegen (iOS/macOS/Apple-platform consumers), see `docs/codegen-swift.md` in the ts-procedures repo. The Swift target is types-only; consumer apps own HTTP/error handling.
390
394
 
391
395
  Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings(client)` factory AND a `create${ServiceName}Client(config)` convenience factory that pre-wires the error registry (defaults to `createApiBindings` / `createApiClient`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.UseCaseError`, etc. The errors file (`_errors.ts`) emits runtime error classes extending a shared `${ServiceName}ProcedureError` base, each with `static fromResponse(body, meta)`, plus `${ServiceName}ErrorRegistry` (runtime dispatch map) and `${ServiceName}ProcedureErrorUnion` (type union). Defaults: `ApiErrors`, `ApiProcedureError`, `ApiErrorRegistry`, `ApiProcedureErrorUnion`.
392
396
  By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`); this also disables the outer service namespace in `index.ts` and skips importing `_errors` from there.
@@ -280,6 +280,8 @@ try {
280
280
 
281
281
  Per-route error unions: routes with `errors: [...]` get an `Errors` type in their namespace (e.g. `Users.GetUser.Errors = ApiErrors.UseCaseError | ApiErrors.AuthError`).
282
282
 
283
+ - Generated client error handling: catch the framework classes (`ClientHttpError`, `ClientNetworkError`, `ClientTimeoutError`, `ClientAbortError`), not raw `DOMException`/`TypeError` — the framework normalizes platform errors at the `executeCall` boundary, so raw platform errors no longer reach `catch` blocks after 7.0.0 (original is on `error.cause`). Use the `.safe()` sibling on RPC/API callables for an exhaustive `Result<T, E>` switch (`ok`, `typed`, `http`, `network`, `timeout`, `aborted`, `parse`, `usage`, `unknown`). Streams keep the throwing form — no `.safe()`.
284
+
283
285
  ## Lifecycle Hook Order
284
286
 
285
287
  ### Standard RPC
@@ -327,6 +329,7 @@ onRequestStart → factoryContext() → validation → onStreamStart → handler
327
329
  9. **Never assume extra params fields survive** — `removeAdditional: true` strips them
328
330
  10. **Never manually parse types AJV coerces** — `coerceTypes: true` handles it
329
331
  11. **Never define both schema.params and schema.input** — mutually exclusive, throws ProcedureRegistrationError
332
+ 12. **Never catch raw DOMException/TypeError from generated callables** — catch `ClientTimeoutError`, `ClientAbortError`, `ClientNetworkError` instead; or use `.safe()` for Result-based narrowing
330
333
 
331
334
  ## Testing
332
335
 
@@ -387,6 +390,7 @@ npx ts-procedures-codegen --url http://localhost:3000/docs --out ./src/generated
387
390
  ```
388
391
 
389
392
  For Kotlin codegen (Android/JVM consumers), see `docs/codegen-kotlin.md` in the ts-procedures repo. The Kotlin target is types-only; consumer apps own HTTP/error handling.
393
+ For Swift codegen (iOS/macOS/Apple-platform consumers), see `docs/codegen-swift.md` in the ts-procedures repo. The Swift target is types-only; consumer apps own HTTP/error handling.
390
394
 
391
395
  Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings(client)` factory AND a `create${ServiceName}Client(config)` convenience factory that pre-wires the error registry (defaults to `createApiBindings` / `createApiClient`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.UseCaseError`, etc. The errors file (`_errors.ts`) emits runtime error classes extending a shared `${ServiceName}ProcedureError` base, each with `static fromResponse(body, meta)`, plus `${ServiceName}ErrorRegistry` (runtime dispatch map) and `${ServiceName}ProcedureErrorUnion` (type union). Defaults: `ApiErrors`, `ApiProcedureError`, `ApiErrorRegistry`, `ApiProcedureErrorUnion`.
392
396
  By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`); this also disables the outer service namespace in `index.ts` and skips importing `_errors` from there.
@@ -6,7 +6,7 @@ const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = dirname(__filename);
7
7
  const SOURCE_DIR = join(__dirname, '..', 'claude-code');
8
8
 
9
- const SKILL_NAMES = ['ts-procedures', 'ts-procedures-review', 'ts-procedures-scaffold'];
9
+ const SKILL_NAMES = ['ts-procedures', 'ts-procedures-review', 'ts-procedures-scaffold', 'ts-procedures-kotlin', 'ts-procedures-swift'];
10
10
  const AGENT_FILES = ['ts-procedures-architect.md'];
11
11
 
12
12
  function listFilesRecursive(dir) {
@@ -0,0 +1,10 @@
1
+ declare class RateLimitError extends Error {
2
+ retryAfter: number;
3
+ constructor(retryAfter: number);
4
+ }
5
+ declare module './types.js' {
6
+ interface ClientErrorMap {
7
+ rateLimited: RateLimitError;
8
+ }
9
+ }
10
+ export {};