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.
- package/README.md +2 -0
- package/agent_config/bin/setup.mjs +2 -2
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +3 -1
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +38 -1
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +216 -4
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +60 -2
- package/agent_config/claude-code/skills/ts-procedures-kotlin/SKILL.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures-swift/SKILL.md +119 -0
- package/agent_config/copilot/copilot-instructions.md +4 -0
- package/agent_config/cursor/cursorrules +4 -0
- package/agent_config/lib/install-claude.mjs +1 -1
- package/build/client/augment-error-map.test-d.d.ts +10 -0
- package/build/client/augment-error-map.test-d.js +14 -0
- package/build/client/augment-error-map.test-d.js.map +1 -0
- package/build/client/call.d.ts +14 -2
- package/build/client/call.js +96 -9
- package/build/client/call.js.map +1 -1
- package/build/client/call.test.js +50 -1
- package/build/client/call.test.js.map +1 -1
- package/build/client/classify-error.d.ts +11 -0
- package/build/client/classify-error.js +49 -0
- package/build/client/classify-error.js.map +1 -0
- package/build/client/classify-error.test.d.ts +1 -0
- package/build/client/classify-error.test.js +55 -0
- package/build/client/classify-error.test.js.map +1 -0
- package/build/client/error-dispatch.d.ts +1 -1
- package/build/client/error-dispatch.js +1 -1
- package/build/client/errors.d.ts +55 -4
- package/build/client/errors.js +54 -7
- package/build/client/errors.js.map +1 -1
- package/build/client/errors.test.js +89 -4
- package/build/client/errors.test.js.map +1 -1
- package/build/client/fetch-adapter.d.ts +2 -1
- package/build/client/fetch-adapter.js +2 -1
- package/build/client/fetch-adapter.js.map +1 -1
- package/build/client/fetch-adapter.test.js +12 -0
- package/build/client/fetch-adapter.test.js.map +1 -1
- package/build/client/index.d.ts +5 -3
- package/build/client/index.js +15 -3
- package/build/client/index.js.map +1 -1
- package/build/client/resolve-options.d.ts +32 -1
- package/build/client/resolve-options.js +32 -16
- package/build/client/resolve-options.js.map +1 -1
- package/build/client/resolve-options.test.js +67 -6
- package/build/client/resolve-options.test.js.map +1 -1
- package/build/client/result-type.test-d.d.ts +1 -0
- package/build/client/result-type.test-d.js +28 -0
- package/build/client/result-type.test-d.js.map +1 -0
- package/build/client/safe-call.test.d.ts +1 -0
- package/build/client/safe-call.test.js +137 -0
- package/build/client/safe-call.test.js.map +1 -0
- package/build/client/stream.d.ts +1 -1
- package/build/client/stream.js +22 -8
- package/build/client/stream.js.map +1 -1
- package/build/client/stream.test.js +11 -1
- package/build/client/stream.test.js.map +1 -1
- package/build/client/types.d.ts +96 -3
- package/build/codegen/bin/cli.d.ts +17 -3
- package/build/codegen/bin/cli.js +79 -3
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/bundle-size.test.d.ts +1 -0
- package/build/codegen/bundle-size.test.js +68 -0
- package/build/codegen/bundle-size.test.js.map +1 -0
- package/build/codegen/e2e.test.js +103 -1
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-client-runtime.js +7 -0
- package/build/codegen/emit-client-runtime.js.map +1 -1
- package/build/codegen/emit-client-runtime.test.js +6 -2
- package/build/codegen/emit-client-runtime.test.js.map +1 -1
- package/build/codegen/emit-client-types.d.ts +7 -2
- package/build/codegen/emit-client-types.js +29 -8
- package/build/codegen/emit-client-types.js.map +1 -1
- package/build/codegen/emit-client-types.test.js +20 -8
- package/build/codegen/emit-client-types.test.js.map +1 -1
- package/build/codegen/emit-errors.d.ts +1 -1
- package/build/codegen/emit-errors.js +1 -1
- package/build/codegen/emit-index.js +1 -1
- package/build/codegen/emit-index.js.map +1 -1
- package/build/codegen/emit-scope.js +94 -26
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +297 -2
- package/build/codegen/emit-scope.test.js.map +1 -1
- package/build/codegen/index.d.ts +18 -1
- package/build/codegen/index.js +3 -0
- package/build/codegen/index.js.map +1 -1
- package/build/codegen/pipeline.d.ts +16 -5
- package/build/codegen/pipeline.js +44 -143
- package/build/codegen/pipeline.js.map +1 -1
- package/build/codegen/targets/_shared/error-schemas.d.ts +10 -0
- package/build/codegen/targets/_shared/error-schemas.js +17 -0
- package/build/codegen/targets/_shared/error-schemas.js.map +1 -0
- package/build/codegen/targets/_shared/error-schemas.test.d.ts +1 -0
- package/build/codegen/targets/_shared/error-schemas.test.js +38 -0
- package/build/codegen/targets/_shared/error-schemas.test.js.map +1 -0
- package/build/codegen/targets/_shared/indent.d.ts +6 -0
- package/build/codegen/targets/_shared/indent.js +13 -0
- package/build/codegen/targets/_shared/indent.js.map +1 -0
- package/build/codegen/targets/_shared/indent.test.d.ts +1 -0
- package/build/codegen/targets/_shared/indent.test.js +21 -0
- package/build/codegen/targets/_shared/indent.test.js.map +1 -0
- package/build/codegen/targets/_shared/pascal-case.d.ts +6 -0
- package/build/codegen/targets/_shared/pascal-case.js +13 -0
- package/build/codegen/targets/_shared/pascal-case.js.map +1 -0
- package/build/codegen/targets/_shared/pascal-case.test.d.ts +1 -0
- package/build/codegen/targets/_shared/pascal-case.test.js +25 -0
- package/build/codegen/targets/_shared/pascal-case.test.js.map +1 -0
- package/build/codegen/targets/_shared/path-utils.d.ts +12 -0
- package/build/codegen/targets/_shared/path-utils.js +20 -0
- package/build/codegen/targets/_shared/path-utils.js.map +1 -0
- package/build/codegen/targets/_shared/path-utils.test.d.ts +1 -0
- package/build/codegen/targets/_shared/path-utils.test.js +42 -0
- package/build/codegen/targets/_shared/path-utils.test.js.map +1 -0
- package/build/codegen/targets/_shared/pick-defined.d.ts +11 -0
- package/build/codegen/targets/_shared/pick-defined.js +21 -0
- package/build/codegen/targets/_shared/pick-defined.js.map +1 -0
- package/build/codegen/targets/_shared/pick-defined.test.d.ts +1 -0
- package/build/codegen/targets/_shared/pick-defined.test.js +25 -0
- package/build/codegen/targets/_shared/pick-defined.test.js.map +1 -0
- package/build/codegen/targets/_shared/route-slots.d.ts +17 -0
- package/build/codegen/targets/_shared/route-slots.js +17 -0
- package/build/codegen/targets/_shared/route-slots.js.map +1 -0
- package/build/codegen/targets/_shared/route-slots.test.d.ts +1 -0
- package/build/codegen/targets/_shared/route-slots.test.js +43 -0
- package/build/codegen/targets/_shared/route-slots.test.js.map +1 -0
- package/build/codegen/targets/_shared/target-run.d.ts +27 -0
- package/build/codegen/targets/_shared/target-run.js +2 -0
- package/build/codegen/targets/_shared/target-run.js.map +1 -0
- package/build/codegen/targets/_shared/write-files.d.ts +24 -0
- package/build/codegen/targets/_shared/write-files.js +35 -0
- package/build/codegen/targets/_shared/write-files.js.map +1 -0
- package/build/codegen/targets/_shared/write-files.test.d.ts +1 -0
- package/build/codegen/targets/_shared/write-files.test.js +79 -0
- package/build/codegen/targets/_shared/write-files.test.js.map +1 -0
- package/build/codegen/targets/kotlin/e2e-compile.test.js +1 -1
- package/build/codegen/targets/kotlin/e2e-compile.test.js.map +1 -1
- package/build/codegen/targets/kotlin/emit-route-kotlin.js +5 -22
- package/build/codegen/targets/kotlin/emit-route-kotlin.js.map +1 -1
- package/build/codegen/targets/kotlin/emit-scope-kotlin.js +4 -8
- package/build/codegen/targets/kotlin/emit-scope-kotlin.js.map +1 -1
- package/build/codegen/targets/kotlin/format-kotlin.d.ts +0 -12
- package/build/codegen/targets/kotlin/format-kotlin.js +0 -27
- package/build/codegen/targets/kotlin/format-kotlin.js.map +1 -1
- package/build/codegen/targets/kotlin/format-kotlin.test.js +1 -34
- package/build/codegen/targets/kotlin/format-kotlin.test.js.map +1 -1
- package/build/codegen/targets/kotlin/integration.test.js +1 -1
- package/build/codegen/targets/kotlin/integration.test.js.map +1 -1
- package/build/codegen/targets/kotlin/run.d.ts +11 -0
- package/build/codegen/targets/kotlin/run.js +51 -0
- package/build/codegen/targets/kotlin/run.js.map +1 -0
- package/build/codegen/targets/swift/access-level.test.d.ts +1 -0
- package/build/codegen/targets/swift/access-level.test.js +98 -0
- package/build/codegen/targets/swift/access-level.test.js.map +1 -0
- package/build/codegen/targets/swift/ajsc-adapter.d.ts +27 -0
- package/build/codegen/targets/swift/ajsc-adapter.js +38 -0
- package/build/codegen/targets/swift/ajsc-adapter.js.map +1 -0
- package/build/codegen/targets/swift/ajsc-adapter.test.d.ts +1 -0
- package/build/codegen/targets/swift/ajsc-adapter.test.js +37 -0
- package/build/codegen/targets/swift/ajsc-adapter.test.js.map +1 -0
- package/build/codegen/targets/swift/e2e-compile.test.d.ts +1 -0
- package/build/codegen/targets/swift/e2e-compile.test.js +57 -0
- package/build/codegen/targets/swift/e2e-compile.test.js.map +1 -0
- package/build/codegen/targets/swift/emit-route-swift.d.ts +15 -0
- package/build/codegen/targets/swift/emit-route-swift.js +64 -0
- package/build/codegen/targets/swift/emit-route-swift.js.map +1 -0
- package/build/codegen/targets/swift/emit-route-swift.test.d.ts +1 -0
- package/build/codegen/targets/swift/emit-route-swift.test.js +258 -0
- package/build/codegen/targets/swift/emit-route-swift.test.js.map +1 -0
- package/build/codegen/targets/swift/emit-scope-swift.d.ts +13 -0
- package/build/codegen/targets/swift/emit-scope-swift.js +36 -0
- package/build/codegen/targets/swift/emit-scope-swift.js.map +1 -0
- package/build/codegen/targets/swift/emit-scope-swift.test.d.ts +1 -0
- package/build/codegen/targets/swift/emit-scope-swift.test.js +136 -0
- package/build/codegen/targets/swift/emit-scope-swift.test.js.map +1 -0
- package/build/codegen/targets/swift/format-swift.d.ts +2 -0
- package/build/codegen/targets/swift/format-swift.js +10 -0
- package/build/codegen/targets/swift/format-swift.js.map +1 -0
- package/build/codegen/targets/swift/format-swift.test.d.ts +1 -0
- package/build/codegen/targets/swift/format-swift.test.js +14 -0
- package/build/codegen/targets/swift/format-swift.test.js.map +1 -0
- package/build/codegen/targets/swift/integration.test.d.ts +1 -0
- package/build/codegen/targets/swift/integration.test.js +53 -0
- package/build/codegen/targets/swift/integration.test.js.map +1 -0
- package/build/codegen/targets/swift/run.d.ts +11 -0
- package/build/codegen/targets/swift/run.js +47 -0
- package/build/codegen/targets/swift/run.js.map +1 -0
- package/build/codegen/targets/ts/run.d.ts +4 -0
- package/build/codegen/targets/ts/run.js +86 -0
- package/build/codegen/targets/ts/run.js.map +1 -0
- package/docs/client-and-codegen.md +77 -7
- package/docs/client-error-handling.md +357 -0
- package/docs/codegen-kotlin.md +1 -0
- package/docs/codegen-swift.md +314 -0
- package/docs/superpowers/plans/2026-04-29-safe-result-api.md +2293 -0
- package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +1 -1
- package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +1 -1
- package/docs/superpowers/specs/2026-04-25-swift-codegen-design.md +264 -0
- package/docs/superpowers/specs/2026-04-29-safe-result-api-design.md +324 -0
- package/package.json +2 -2
- package/src/client/augment-error-map.test-d.ts +22 -0
- package/src/client/call.test.ts +65 -1
- package/src/client/call.ts +111 -9
- package/src/client/classify-error.test.ts +65 -0
- package/src/client/classify-error.ts +59 -0
- package/src/client/error-dispatch.ts +1 -1
- package/src/client/errors.test.ts +108 -4
- package/src/client/errors.ts +70 -7
- package/src/client/fetch-adapter.test.ts +15 -0
- package/src/client/fetch-adapter.ts +5 -2
- package/src/client/index.ts +39 -3
- package/src/client/resolve-options.test.ts +83 -5
- package/src/client/resolve-options.ts +61 -16
- package/src/client/result-type.test-d.ts +51 -0
- package/src/client/safe-call.test.ts +157 -0
- package/src/client/stream.test.ts +13 -1
- package/src/client/stream.ts +25 -8
- package/src/client/types.ts +112 -3
- package/src/codegen/bin/cli.ts +91 -7
- package/src/codegen/bundle-size.test.ts +74 -0
- package/src/codegen/e2e.test.ts +108 -1
- package/src/codegen/emit-client-runtime.test.ts +7 -2
- package/src/codegen/emit-client-runtime.ts +7 -0
- package/src/codegen/emit-client-types.test.ts +22 -7
- package/src/codegen/emit-client-types.ts +35 -10
- package/src/codegen/emit-errors.ts +1 -1
- package/src/codegen/emit-index.ts +1 -1
- package/src/codegen/emit-scope.test.ts +324 -2
- package/src/codegen/emit-scope.ts +98 -36
- package/src/codegen/index.ts +24 -1
- package/src/codegen/pipeline.ts +52 -174
- package/src/codegen/targets/_shared/error-schemas.test.ts +42 -0
- package/src/codegen/targets/_shared/error-schemas.ts +17 -0
- package/src/codegen/targets/_shared/indent.test.ts +25 -0
- package/src/codegen/targets/_shared/indent.ts +12 -0
- package/src/codegen/targets/_shared/pascal-case.test.ts +30 -0
- package/src/codegen/targets/_shared/pascal-case.ts +12 -0
- package/src/codegen/targets/_shared/path-utils.test.ts +51 -0
- package/src/codegen/targets/_shared/path-utils.ts +21 -0
- package/src/codegen/targets/_shared/pick-defined.test.ts +48 -0
- package/src/codegen/targets/_shared/pick-defined.ts +23 -0
- package/src/codegen/targets/_shared/route-slots.test.ts +55 -0
- package/src/codegen/targets/_shared/route-slots.ts +32 -0
- package/src/codegen/targets/_shared/target-run.ts +28 -0
- package/src/codegen/targets/_shared/write-files.test.ts +110 -0
- package/src/codegen/targets/_shared/write-files.ts +53 -0
- package/src/codegen/targets/kotlin/e2e-compile.test.ts +1 -1
- package/src/codegen/targets/kotlin/emit-route-kotlin.ts +5 -25
- package/src/codegen/targets/kotlin/emit-scope-kotlin.ts +4 -9
- package/src/codegen/targets/kotlin/format-kotlin.test.ts +0 -44
- package/src/codegen/targets/kotlin/format-kotlin.ts +0 -32
- package/src/codegen/targets/kotlin/integration.test.ts +1 -1
- package/src/codegen/targets/kotlin/run.ts +78 -0
- package/src/codegen/targets/swift/__fixtures__/users-golden.swift +123 -0
- package/src/codegen/targets/swift/access-level.test.ts +108 -0
- package/src/codegen/targets/swift/ajsc-adapter.test.ts +47 -0
- package/src/codegen/targets/swift/ajsc-adapter.ts +67 -0
- package/src/codegen/targets/swift/e2e-compile.test.ts +66 -0
- package/src/codegen/targets/swift/emit-route-swift.test.ts +300 -0
- package/src/codegen/targets/swift/emit-route-swift.ts +90 -0
- package/src/codegen/targets/swift/emit-scope-swift.test.ts +164 -0
- package/src/codegen/targets/swift/emit-scope-swift.ts +59 -0
- package/src/codegen/targets/swift/format-swift.test.ts +23 -0
- package/src/codegen/targets/swift/format-swift.ts +9 -0
- package/src/codegen/targets/swift/integration.test.ts +80 -0
- package/src/codegen/targets/swift/run.ts +74 -0
- package/src/codegen/targets/ts/run.ts +117 -0
- /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
|
-
| `
|
|
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 `
|
|
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 `
|
|
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 {
|
|
1216
|
-
|
|
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
|
|
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 |
|
|
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
|
|
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 —
|
|
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) {
|