ts-procedures 6.0.2 → 6.2.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 (199) hide show
  1. package/agent_config/bin/setup.mjs +2 -2
  2. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +2 -0
  3. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +2 -0
  4. package/agent_config/claude-code/skills/ts-procedures-kotlin/SKILL.md +106 -0
  5. package/agent_config/claude-code/skills/ts-procedures-swift/SKILL.md +119 -0
  6. package/agent_config/copilot/copilot-instructions.md +3 -0
  7. package/agent_config/cursor/cursorrules +3 -0
  8. package/agent_config/lib/install-claude.mjs +1 -1
  9. package/build/codegen/bin/cli.d.ts +39 -0
  10. package/build/codegen/bin/cli.js +164 -0
  11. package/build/codegen/bin/cli.js.map +1 -1
  12. package/build/codegen/bin/cli.test.js +180 -1
  13. package/build/codegen/bin/cli.test.js.map +1 -1
  14. package/build/codegen/index.d.ts +36 -0
  15. package/build/codegen/index.js +8 -0
  16. package/build/codegen/index.js.map +1 -1
  17. package/build/codegen/pipeline.d.ts +22 -4
  18. package/build/codegen/pipeline.js +44 -86
  19. package/build/codegen/pipeline.js.map +1 -1
  20. package/build/codegen/pipeline.test.js +162 -0
  21. package/build/codegen/pipeline.test.js.map +1 -1
  22. package/build/codegen/targets/_shared/error-schemas.d.ts +10 -0
  23. package/build/codegen/targets/_shared/error-schemas.js +17 -0
  24. package/build/codegen/targets/_shared/error-schemas.js.map +1 -0
  25. package/build/codegen/targets/_shared/error-schemas.test.d.ts +1 -0
  26. package/build/codegen/targets/_shared/error-schemas.test.js +38 -0
  27. package/build/codegen/targets/_shared/error-schemas.test.js.map +1 -0
  28. package/build/codegen/targets/_shared/indent.d.ts +6 -0
  29. package/build/codegen/targets/_shared/indent.js +13 -0
  30. package/build/codegen/targets/_shared/indent.js.map +1 -0
  31. package/build/codegen/targets/_shared/indent.test.d.ts +1 -0
  32. package/build/codegen/targets/_shared/indent.test.js +21 -0
  33. package/build/codegen/targets/_shared/indent.test.js.map +1 -0
  34. package/build/codegen/targets/_shared/pascal-case.d.ts +6 -0
  35. package/build/codegen/targets/_shared/pascal-case.js +13 -0
  36. package/build/codegen/targets/_shared/pascal-case.js.map +1 -0
  37. package/build/codegen/targets/_shared/pascal-case.test.d.ts +1 -0
  38. package/build/codegen/targets/_shared/pascal-case.test.js +25 -0
  39. package/build/codegen/targets/_shared/pascal-case.test.js.map +1 -0
  40. package/build/codegen/targets/_shared/path-utils.d.ts +12 -0
  41. package/build/codegen/targets/_shared/path-utils.js +20 -0
  42. package/build/codegen/targets/_shared/path-utils.js.map +1 -0
  43. package/build/codegen/targets/_shared/path-utils.test.d.ts +1 -0
  44. package/build/codegen/targets/_shared/path-utils.test.js +42 -0
  45. package/build/codegen/targets/_shared/path-utils.test.js.map +1 -0
  46. package/build/codegen/targets/_shared/pick-defined.d.ts +11 -0
  47. package/build/codegen/targets/_shared/pick-defined.js +21 -0
  48. package/build/codegen/targets/_shared/pick-defined.js.map +1 -0
  49. package/build/codegen/targets/_shared/pick-defined.test.d.ts +1 -0
  50. package/build/codegen/targets/_shared/pick-defined.test.js +25 -0
  51. package/build/codegen/targets/_shared/pick-defined.test.js.map +1 -0
  52. package/build/codegen/targets/_shared/route-slots.d.ts +17 -0
  53. package/build/codegen/targets/_shared/route-slots.js +17 -0
  54. package/build/codegen/targets/_shared/route-slots.js.map +1 -0
  55. package/build/codegen/targets/_shared/route-slots.test.d.ts +1 -0
  56. package/build/codegen/targets/_shared/route-slots.test.js +43 -0
  57. package/build/codegen/targets/_shared/route-slots.test.js.map +1 -0
  58. package/build/codegen/targets/_shared/target-run.d.ts +27 -0
  59. package/build/codegen/targets/_shared/target-run.js +2 -0
  60. package/build/codegen/targets/_shared/target-run.js.map +1 -0
  61. package/build/codegen/targets/_shared/write-files.d.ts +24 -0
  62. package/build/codegen/targets/_shared/write-files.js +35 -0
  63. package/build/codegen/targets/_shared/write-files.js.map +1 -0
  64. package/build/codegen/targets/_shared/write-files.test.d.ts +1 -0
  65. package/build/codegen/targets/_shared/write-files.test.js +79 -0
  66. package/build/codegen/targets/_shared/write-files.test.js.map +1 -0
  67. package/build/codegen/targets/kotlin/ajsc-adapter.d.ts +6 -4
  68. package/build/codegen/targets/kotlin/ajsc-adapter.js +12 -7
  69. package/build/codegen/targets/kotlin/ajsc-adapter.js.map +1 -1
  70. package/build/codegen/targets/kotlin/ajsc-adapter.test.js +20 -2
  71. package/build/codegen/targets/kotlin/ajsc-adapter.test.js.map +1 -1
  72. package/build/codegen/targets/kotlin/e2e-compile.test.js +41 -9
  73. package/build/codegen/targets/kotlin/e2e-compile.test.js.map +1 -1
  74. package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +6 -2
  75. package/build/codegen/targets/kotlin/emit-route-kotlin.js +18 -28
  76. package/build/codegen/targets/kotlin/emit-route-kotlin.js.map +1 -1
  77. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +120 -1
  78. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -1
  79. package/build/codegen/targets/kotlin/emit-scope-kotlin.d.ts +4 -1
  80. package/build/codegen/targets/kotlin/emit-scope-kotlin.js +12 -11
  81. package/build/codegen/targets/kotlin/emit-scope-kotlin.js.map +1 -1
  82. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +39 -0
  83. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -1
  84. package/build/codegen/targets/kotlin/format-kotlin.d.ts +0 -1
  85. package/build/codegen/targets/kotlin/format-kotlin.js +0 -7
  86. package/build/codegen/targets/kotlin/format-kotlin.js.map +1 -1
  87. package/build/codegen/targets/kotlin/format-kotlin.test.js +1 -8
  88. package/build/codegen/targets/kotlin/format-kotlin.test.js.map +1 -1
  89. package/build/codegen/targets/kotlin/integration.test.js +27 -10
  90. package/build/codegen/targets/kotlin/integration.test.js.map +1 -1
  91. package/build/codegen/targets/kotlin/probe-unsupported-unions.test.d.ts +1 -0
  92. package/build/codegen/targets/kotlin/probe-unsupported-unions.test.js +50 -0
  93. package/build/codegen/targets/kotlin/probe-unsupported-unions.test.js.map +1 -0
  94. package/build/codegen/targets/kotlin/run.d.ts +11 -0
  95. package/build/codegen/targets/kotlin/run.js +51 -0
  96. package/build/codegen/targets/kotlin/run.js.map +1 -0
  97. package/build/codegen/targets/swift/access-level.test.d.ts +1 -0
  98. package/build/codegen/targets/swift/access-level.test.js +98 -0
  99. package/build/codegen/targets/swift/access-level.test.js.map +1 -0
  100. package/build/codegen/targets/swift/ajsc-adapter.d.ts +27 -0
  101. package/build/codegen/targets/swift/ajsc-adapter.js +38 -0
  102. package/build/codegen/targets/swift/ajsc-adapter.js.map +1 -0
  103. package/build/codegen/targets/swift/ajsc-adapter.test.d.ts +1 -0
  104. package/build/codegen/targets/swift/ajsc-adapter.test.js +37 -0
  105. package/build/codegen/targets/swift/ajsc-adapter.test.js.map +1 -0
  106. package/build/codegen/targets/swift/e2e-compile.test.d.ts +1 -0
  107. package/build/codegen/targets/swift/e2e-compile.test.js +57 -0
  108. package/build/codegen/targets/swift/e2e-compile.test.js.map +1 -0
  109. package/build/codegen/targets/swift/emit-route-swift.d.ts +15 -0
  110. package/build/codegen/targets/swift/emit-route-swift.js +64 -0
  111. package/build/codegen/targets/swift/emit-route-swift.js.map +1 -0
  112. package/build/codegen/targets/swift/emit-route-swift.test.d.ts +1 -0
  113. package/build/codegen/targets/swift/emit-route-swift.test.js +258 -0
  114. package/build/codegen/targets/swift/emit-route-swift.test.js.map +1 -0
  115. package/build/codegen/targets/swift/emit-scope-swift.d.ts +13 -0
  116. package/build/codegen/targets/swift/emit-scope-swift.js +36 -0
  117. package/build/codegen/targets/swift/emit-scope-swift.js.map +1 -0
  118. package/build/codegen/targets/swift/emit-scope-swift.test.d.ts +1 -0
  119. package/build/codegen/targets/swift/emit-scope-swift.test.js +136 -0
  120. package/build/codegen/targets/swift/emit-scope-swift.test.js.map +1 -0
  121. package/build/codegen/targets/swift/format-swift.d.ts +2 -0
  122. package/build/codegen/targets/swift/format-swift.js +10 -0
  123. package/build/codegen/targets/swift/format-swift.js.map +1 -0
  124. package/build/codegen/targets/swift/format-swift.test.d.ts +1 -0
  125. package/build/codegen/targets/swift/format-swift.test.js +14 -0
  126. package/build/codegen/targets/swift/format-swift.test.js.map +1 -0
  127. package/build/codegen/targets/swift/integration.test.d.ts +1 -0
  128. package/build/codegen/targets/swift/integration.test.js +53 -0
  129. package/build/codegen/targets/swift/integration.test.js.map +1 -0
  130. package/build/codegen/targets/swift/run.d.ts +11 -0
  131. package/build/codegen/targets/swift/run.js +47 -0
  132. package/build/codegen/targets/swift/run.js.map +1 -0
  133. package/build/codegen/targets/ts/run.d.ts +4 -0
  134. package/build/codegen/targets/ts/run.js +86 -0
  135. package/build/codegen/targets/ts/run.js.map +1 -0
  136. package/build/codegen/test-helpers/golden.d.ts +15 -0
  137. package/build/codegen/test-helpers/golden.js +30 -0
  138. package/build/codegen/test-helpers/golden.js.map +1 -0
  139. package/build/codegen/test-helpers/golden.test.d.ts +1 -0
  140. package/build/codegen/test-helpers/golden.test.js +76 -0
  141. package/build/codegen/test-helpers/golden.test.js.map +1 -0
  142. package/docs/codegen-kotlin.md +176 -0
  143. package/docs/codegen-swift.md +314 -0
  144. package/docs/superpowers/plans/2026-04-25-ajsc-v7-kotlin-polish.md +1993 -0
  145. package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +1 -1
  146. package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +314 -0
  147. package/docs/superpowers/specs/2026-04-25-swift-codegen-design.md +264 -0
  148. package/package.json +2 -2
  149. package/src/codegen/__fixtures__/users-envelope.json +144 -0
  150. package/src/codegen/bin/cli.test.ts +200 -1
  151. package/src/codegen/bin/cli.ts +187 -0
  152. package/src/codegen/index.ts +50 -0
  153. package/src/codegen/pipeline.test.ts +175 -0
  154. package/src/codegen/pipeline.ts +58 -101
  155. package/src/codegen/targets/_shared/error-schemas.test.ts +42 -0
  156. package/src/codegen/targets/_shared/error-schemas.ts +17 -0
  157. package/src/codegen/targets/_shared/indent.test.ts +25 -0
  158. package/src/codegen/targets/_shared/indent.ts +12 -0
  159. package/src/codegen/targets/_shared/pascal-case.test.ts +30 -0
  160. package/src/codegen/targets/_shared/pascal-case.ts +12 -0
  161. package/src/codegen/targets/_shared/path-utils.test.ts +51 -0
  162. package/src/codegen/targets/_shared/path-utils.ts +21 -0
  163. package/src/codegen/targets/_shared/pick-defined.test.ts +48 -0
  164. package/src/codegen/targets/_shared/pick-defined.ts +23 -0
  165. package/src/codegen/targets/_shared/route-slots.test.ts +55 -0
  166. package/src/codegen/targets/_shared/route-slots.ts +32 -0
  167. package/src/codegen/targets/_shared/target-run.ts +28 -0
  168. package/src/codegen/targets/_shared/write-files.test.ts +110 -0
  169. package/src/codegen/targets/_shared/write-files.ts +53 -0
  170. package/src/codegen/targets/kotlin/__fixtures__/users-golden.kt +121 -0
  171. package/src/codegen/targets/kotlin/__snapshots__/probe-unsupported-unions.test.ts.snap +27 -0
  172. package/src/codegen/targets/kotlin/ajsc-adapter.test.ts +47 -0
  173. package/src/codegen/targets/kotlin/ajsc-adapter.ts +66 -0
  174. package/src/codegen/targets/kotlin/e2e-compile.test.ts +86 -0
  175. package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +239 -0
  176. package/src/codegen/targets/kotlin/emit-route-kotlin.ts +89 -0
  177. package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +112 -0
  178. package/src/codegen/targets/kotlin/emit-scope-kotlin.ts +60 -0
  179. package/src/codegen/targets/kotlin/format-kotlin.test.ts +26 -0
  180. package/src/codegen/targets/kotlin/format-kotlin.ts +13 -0
  181. package/src/codegen/targets/kotlin/integration.test.ts +77 -0
  182. package/src/codegen/targets/kotlin/probe-unsupported-unions.test.ts +64 -0
  183. package/src/codegen/targets/kotlin/run.ts +78 -0
  184. package/src/codegen/targets/swift/__fixtures__/users-golden.swift +123 -0
  185. package/src/codegen/targets/swift/access-level.test.ts +108 -0
  186. package/src/codegen/targets/swift/ajsc-adapter.test.ts +47 -0
  187. package/src/codegen/targets/swift/ajsc-adapter.ts +67 -0
  188. package/src/codegen/targets/swift/e2e-compile.test.ts +66 -0
  189. package/src/codegen/targets/swift/emit-route-swift.test.ts +300 -0
  190. package/src/codegen/targets/swift/emit-route-swift.ts +90 -0
  191. package/src/codegen/targets/swift/emit-scope-swift.test.ts +164 -0
  192. package/src/codegen/targets/swift/emit-scope-swift.ts +59 -0
  193. package/src/codegen/targets/swift/format-swift.test.ts +23 -0
  194. package/src/codegen/targets/swift/format-swift.ts +9 -0
  195. package/src/codegen/targets/swift/integration.test.ts +80 -0
  196. package/src/codegen/targets/swift/run.ts +74 -0
  197. package/src/codegen/targets/ts/run.ts +117 -0
  198. package/src/codegen/test-helpers/golden.test.ts +80 -0
  199. package/src/codegen/test-helpers/golden.ts +34 -0
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it, afterEach } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { assertGoldenOrUpdate } from './golden.js';
6
+ let tmpDir;
7
+ afterEach(() => {
8
+ if (tmpDir != null) {
9
+ rmSync(tmpDir, { recursive: true, force: true });
10
+ tmpDir = undefined;
11
+ }
12
+ });
13
+ function makeTmp() {
14
+ tmpDir = mkdtempSync(join(tmpdir(), 'tsp-golden-'));
15
+ return tmpDir;
16
+ }
17
+ describe('assertGoldenOrUpdate', () => {
18
+ it('asserts byte-equality against golden when produced matches (with hash splice)', async () => {
19
+ const dir = makeTmp();
20
+ const goldenPath = join(dir, 'expected.txt');
21
+ writeFileSync(goldenPath, 'hello\n// Source hash: <PLACEHOLDER>\nworld\n', 'utf-8');
22
+ const produced = 'hello\n// Source hash: abc123def456\nworld\n';
23
+ await expect(assertGoldenOrUpdate(produced, goldenPath)).resolves.toBeUndefined();
24
+ });
25
+ it('throws when produced does not match golden', async () => {
26
+ const dir = makeTmp();
27
+ const goldenPath = join(dir, 'expected.txt');
28
+ writeFileSync(goldenPath, 'expected content\n', 'utf-8');
29
+ const produced = 'different content\n';
30
+ await expect(assertGoldenOrUpdate(produced, goldenPath)).rejects.toThrow();
31
+ });
32
+ it('writes a portable golden when UPDATE_GOLDENS=1', async () => {
33
+ const original = process.env.UPDATE_GOLDENS;
34
+ process.env.UPDATE_GOLDENS = '1';
35
+ try {
36
+ const dir = makeTmp();
37
+ const goldenPath = join(dir, 'expected.txt');
38
+ const produced = 'hello\n// Source hash: deadbeef1234\nworld\n';
39
+ await assertGoldenOrUpdate(produced, goldenPath);
40
+ const written = readFileSync(goldenPath, 'utf-8');
41
+ expect(written).toBe('hello\n// Source hash: <PLACEHOLDER>\nworld\n');
42
+ }
43
+ finally {
44
+ if (original === undefined)
45
+ delete process.env.UPDATE_GOLDENS;
46
+ else
47
+ process.env.UPDATE_GOLDENS = original;
48
+ }
49
+ });
50
+ it('handles produced output without a source-hash line', async () => {
51
+ const dir = makeTmp();
52
+ const goldenPath = join(dir, 'expected.txt');
53
+ writeFileSync(goldenPath, 'plain content\n', 'utf-8');
54
+ // No source hash; splice is a no-op on both sides.
55
+ await expect(assertGoldenOrUpdate('plain content\n', goldenPath)).resolves.toBeUndefined();
56
+ });
57
+ it('regenerate mode handles produced output without a source-hash line', async () => {
58
+ const original = process.env.UPDATE_GOLDENS;
59
+ process.env.UPDATE_GOLDENS = '1';
60
+ try {
61
+ const dir = makeTmp();
62
+ const goldenPath = join(dir, 'expected.txt');
63
+ const produced = 'no hash line here\n';
64
+ await assertGoldenOrUpdate(produced, goldenPath);
65
+ const written = readFileSync(goldenPath, 'utf-8');
66
+ expect(written).toBe('no hash line here\n');
67
+ }
68
+ finally {
69
+ if (original === undefined)
70
+ delete process.env.UPDATE_GOLDENS;
71
+ else
72
+ process.env.UPDATE_GOLDENS = original;
73
+ }
74
+ });
75
+ });
76
+ //# sourceMappingURL=golden.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"golden.test.js","sourceRoot":"","sources":["../../../src/codegen/test-helpers/golden.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ # Kotlin Codegen Setup Guide
2
+
3
+ Generated by `ts-procedures-codegen --target kotlin`. One `.kt` file per scope; types are nested under route objects (`Users.GetUser.Response`, `Users.GetUser.Body.Address`).
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ npx ts-procedures-codegen \
9
+ --target kotlin \
10
+ --kotlin-package com.example.api \
11
+ --url https://api.example.com/_ts-procedures.json \
12
+ --out ./src/main/kotlin/com/example/api
13
+ ```
14
+
15
+ Each scope produces one file (e.g. `Users.kt`). Access generated types as `Users.GetUser.Response`, `Users.GetUser.Body.GuestBody`, `Users.GetUser.Errors.NotFound`.
16
+
17
+ ## Gradle setup
18
+
19
+ The default `--kotlin-serializer kotlinx` mode emits `@Serializable` data classes. Add the kotlinx-serialization plugin and runtime:
20
+
21
+ ```kotlin
22
+ // build.gradle.kts
23
+ plugins {
24
+ kotlin("jvm") version "<your-kotlin-version>"
25
+ kotlin("plugin.serialization") version "<your-kotlin-version>"
26
+ }
27
+
28
+ dependencies {
29
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:<version>")
30
+ }
31
+ ```
32
+
33
+ (`kotlinx-serialization-core` is a transitive dependency of `-json`; you don't need to declare it explicitly.)
34
+
35
+ **JVM only.** Kotlin Multiplatform is not yet supported. If you need KMP-portable types, fall back to `--kotlin-serializer none` or post-process the emitted code.
36
+
37
+ ## Contextual serializers
38
+
39
+ `format: date-time`, `format: uuid`, `format: uri`, `format: date`, `format: time` map to JVM stdlib types (`java.time.Instant`, `java.util.UUID`, etc.) annotated with `@Contextual`. Kotlinx-serialization does not know how to serialize these natively — register contextual serializers in your `Json` configuration:
40
+
41
+ ```kotlin
42
+ import kotlinx.serialization.json.Json
43
+ import kotlinx.serialization.modules.SerializersModule
44
+ import kotlinx.serialization.modules.contextual
45
+
46
+ val json = Json {
47
+ serializersModule = SerializersModule {
48
+ contextual(java.time.Instant::class, InstantSerializer)
49
+ contextual(java.util.UUID::class, UUIDSerializer)
50
+ contextual(java.net.URI::class, URISerializer)
51
+ // ...etc for any format you use
52
+ }
53
+ }
54
+ ```
55
+
56
+ The serializers themselves are your responsibility (we don't ship them — the choice between ISO-8601 strings, epoch milliseconds, etc. is application-specific). Public Gist-quality implementations are widely available; we recommend ISO-8601 to match the server.
57
+
58
+ ## Discriminated unions
59
+
60
+ A schema with a `oneOf` whose variants share a const-valued discriminator (e.g. `kind: "guest" | "registered"`) emits as a sealed interface:
61
+
62
+ ```kotlin
63
+ @Serializable
64
+ @JsonClassDiscriminator("kind")
65
+ sealed interface Body {
66
+ @Serializable
67
+ @SerialName("guest")
68
+ data class GuestBody(val displayName: String) : Body
69
+
70
+ @Serializable
71
+ @SerialName("registered")
72
+ data class RegisteredBody(val email: String, val name: String) : Body
73
+ }
74
+ ```
75
+
76
+ The discriminator field (`kind`) is **erased** from each variant under `--kotlin-serializer kotlinx` — `@SerialName` carries it on the wire. With `--kotlin-serializer none` the discriminator field is retained.
77
+
78
+ `@JsonClassDiscriminator` is read automatically by the kotlinx `Json` instance — no extra config needed.
79
+
80
+ ## JSON-key sanitization
81
+
82
+ Kebab-case and snake-case JSON keys become camelCase property names with `@SerialName` auto-emitted so the wire format stays correct:
83
+
84
+ ```kotlin
85
+ @Serializable
86
+ data class Response(
87
+ @SerialName("created-at")
88
+ @Contextual
89
+ val createdAt: java.time.Instant,
90
+ )
91
+ ```
92
+
93
+ Reserved Kotlin words get a trailing underscore (`class` → `class_`).
94
+
95
+ ## Switching off kotlinx (Moshi, Gson, hand-written)
96
+
97
+ `--kotlin-serializer none` emits plain `data class` types with no `@Serializable` annotation. You're then responsible for adapter setup:
98
+
99
+ - **Reflection-based libraries** (Gson) work without further changes.
100
+ - **Codegen-based** (Moshi with codegen) need their own annotation layer added.
101
+ - Sealed interfaces still emit; the discriminator field is **retained** in variants under `none` mode.
102
+
103
+ ## Error types
104
+
105
+ Each route that declares errors gets a nested `Errors` object containing one `@Serializable data class` per error name:
106
+
107
+ ```kotlin
108
+ object Users {
109
+ object GetUser {
110
+ // ... method, path, types ...
111
+ object Errors {
112
+ @Serializable
113
+ data class NotFound(
114
+ val name: String = "NotFound",
115
+ val message: String,
116
+ )
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ Access generated error types as `Users.GetUser.Errors.NotFound`.
123
+
124
+ **No runtime dispatch.** Unlike the TypeScript target, the Kotlin target ships **types only** — there is no error registry, no `instanceof`-style lookup, no `dispatchTypedError`. Mobile consumers catch HTTP failures themselves and inspect `body.name` (which is a regular `String` field, not a type-system discriminator) to decide which error data class to deserialize against:
125
+
126
+ ```kotlin
127
+ suspend fun loadUser(id: String): Result<Users.GetUser.Response> {
128
+ val response = httpClient.request(method = Users.GetUser.method, path = "...")
129
+ return when {
130
+ response.status == 200 -> Result.success(json.decodeFromString(response.body))
131
+ response.status == 404 -> {
132
+ val err = json.decodeFromString<Users.GetUser.Errors.NotFound>(response.body)
133
+ Result.failure(NotFoundException(err.message))
134
+ }
135
+ else -> Result.failure(IOException("HTTP ${response.status}"))
136
+ }
137
+ }
138
+ ```
139
+
140
+ Choosing the dispatch strategy (status-code based, body.name based, sealed-class hierarchy of your own, etc.) is intentionally left to consumers.
141
+
142
+ ## Untagged unions
143
+
144
+ ajsc v7.2's Kotlin emitter silently produces an empty `@Serializable data class` for any untagged `oneOf` schema, regardless of whether `--unsupported-unions fallback` is set. There is no "throws on default" mode for Kotlin (that's Swift behavior). The `--unsupported-unions` flag is currently a no-op for the Kotlin target.
145
+
146
+ For example, the schema `oneOf: [{ type: 'string' }, { type: 'integer' }]` with `rootTypeName: 'Mixed'` produces:
147
+
148
+ ```kotlin
149
+ @Serializable
150
+ data class Mixed()
151
+ ```
152
+
153
+ Imports: `kotlinx.serialization.Serializable`.
154
+
155
+ **Practical implication.** This empty data class won't round-trip your data — calling `Json.decodeFromString<Mixed>("\"hello\"")` will fail because `Mixed` has no payload. **Untagged unions in your schema cannot be consumed via the generated Kotlin types.** Workarounds:
156
+
157
+ 1. **Add a discriminator** to your server-side schema so it becomes a tagged union (sealed interface emission — see "Discriminated unions" section above).
158
+ 2. **Hand-write a `KSerializer<T>` `companion object`** in a sibling file that resolves the union at deserialization time.
159
+ 3. **Strip the union field at the codegen boundary** — pre-process the envelope to replace untagged `oneOf` with a single permissive type (e.g. `JsonElement`).
160
+
161
+ We track this as an ajsc upstream limitation; if it's resolved in a future ajsc release, this section will be updated.
162
+
163
+ ## Documented limitations
164
+
165
+ The following ajsc behaviors are intentional and documented; they are **not bugs**:
166
+
167
+ - `additionalProperties: { type: T }` is silently dropped with a `/** Note: schema permits additional keys of type T — not modeled. */` KDoc note. If your contract uses extra keys, add a sibling `Map<String, T>` field by hand or write a custom `KSerializer`.
168
+ - Tuples with 4 or more positional types throw (`Pair`/`Triple` only). Refactor to a struct schema upstream.
169
+ - Schema-level `examples` and `not` / `patternProperties` are not modeled (the latter throw with a path-bearing error).
170
+
171
+ ## Reference
172
+
173
+ - Spec: [`docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md`](./superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md)
174
+ - For iOS / macOS / Apple-platform consumers, see [`docs/codegen-swift.md`](./codegen-swift.md) — same types-only design with Swift-specific flags and `Codable` setup notes.
175
+ - ajsc README: `node_modules/ajsc/README.md` (or [npmjs.com/package/ajsc](https://www.npmjs.com/package/ajsc))
176
+ - ts-procedures-codegen CLI flags: see `CLAUDE.md` (search for "Kotlin target") and the spec linked above. (`--help` is not currently implemented; pass invalid/missing args to see error messages with usage hints.)
@@ -0,0 +1,314 @@
1
+ # Swift Codegen Setup Guide
2
+
3
+ Generated by `ts-procedures-codegen --target swift`. One `.swift` file per scope; types are nested under route enum namespaces (`Users.GetUser.Response`, `Users.GetUser.Response.Address`, `Users.GetUser.Errors.NotFound`).
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ npx ts-procedures-codegen \
9
+ --target swift \
10
+ --url https://api.example.com/_ts-procedures.json \
11
+ --out ./Sources/MyApp/Generated
12
+ ```
13
+
14
+ Each scope produces one file (e.g. `Users.swift`). Access generated types as `Users.GetUser.Response`, `Users.GetUser.PathParams`, `Users.GetUser.Errors.NotFound`.
15
+
16
+ Unlike the Kotlin target, **no package/module flag is required.** Swift modules are defined by Xcode/SPM targets, not by file-level declarations.
17
+
18
+ ## Swift Package Manager / Xcode integration
19
+
20
+ The generated files are plain `.swift` source — drop them into any target's source directory and they compile as-is. There is no required configuration.
21
+
22
+ ### Swift Package Manager
23
+
24
+ Point a target's `path:` (or `sources:`) at the directory you generated into:
25
+
26
+ ```swift
27
+ // Package.swift
28
+ let package = Package(
29
+ name: "MyApp",
30
+ products: [.library(name: "MyApp", targets: ["MyApp"])],
31
+ targets: [
32
+ .target(
33
+ name: "MyApp",
34
+ path: "Sources/MyApp"
35
+ // Generated files live under Sources/MyApp/Generated/ and
36
+ // are picked up automatically by SPM's recursive source globbing.
37
+ ),
38
+ ]
39
+ )
40
+ ```
41
+
42
+ If you keep the generated dir as its own target, declare it as a dependency of any target that needs to call the API:
43
+
44
+ ```swift
45
+ .target(name: "MyAppAPI", path: "Sources/MyApp/Generated"),
46
+ .target(name: "MyApp", dependencies: ["MyAppAPI"]),
47
+ ```
48
+
49
+ ### Xcode (project-based)
50
+
51
+ In Xcode: **File → Add Files to "<TargetName>"…**, select the generated directory, and ensure the target membership checkbox is set. Re-running codegen overwrites the files in place; Xcode picks up the changes on next build.
52
+
53
+ ## JSONDecoder configuration
54
+
55
+ **This is required.** Schemas with `format: date-time` are emitted as `Foundation.Date`. Decoding fails (`DecodingError.typeMismatch`) unless you tell `JSONDecoder` how to parse the wire format:
56
+
57
+ ```swift
58
+ let decoder = JSONDecoder()
59
+ decoder.dateDecodingStrategy = .iso8601
60
+ ```
61
+
62
+ For symmetric encoding (e.g. when sending request bodies):
63
+
64
+ ```swift
65
+ let encoder = JSONEncoder()
66
+ encoder.dateEncodingStrategy = .iso8601
67
+ ```
68
+
69
+ The server emits ISO-8601 strings by default; `.iso8601` is the matching strategy on the Swift side. If your server emits epoch-millis or another format, swap to `.millisecondsSince1970` / `.formatted(_:)` / `.custom(_:)` accordingly.
70
+
71
+ `format: uuid` and `format: uri` map to `Foundation.UUID` / `Foundation.URL`, both of which are `Codable` natively and need no extra configuration.
72
+
73
+ ## Sample output
74
+
75
+ Given a `users` scope with a single `GetUser` route declaring path params, a response with a nested `Address`, and a `NotFound` error:
76
+
77
+ ```swift
78
+ // Source hash: 9a1b3c…
79
+ // Generated by ts-procedures-codegen — do not edit.
80
+ import Foundation
81
+
82
+ public enum Users {
83
+ public enum GetUser {
84
+ public static let method = "GET"
85
+ public static let pathTemplate = "/users/{id}"
86
+ public static func path(_ p: PathParams) -> String { return "/users/\(p.id)" }
87
+
88
+ public struct PathParams: Codable {
89
+ public let id: String
90
+ }
91
+
92
+ public struct Response: Codable {
93
+ public let id: String
94
+ public let name: String
95
+ /// ISO-8601 — set JSONDecoder.dateDecodingStrategy = .iso8601
96
+ public let createdAt: Date
97
+ public let address: Address
98
+
99
+ enum CodingKeys: String, CodingKey {
100
+ case id, name
101
+ case createdAt = "created-at"
102
+ case address
103
+ }
104
+
105
+ public struct Address: Codable {
106
+ public let street: String
107
+ public let city: String
108
+ }
109
+ }
110
+
111
+ public enum Errors {
112
+ public struct NotFound: Codable {
113
+ public let name: String
114
+ public let message: String
115
+ }
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ Routes without path params get `public static let path = "/users"` (a constant, not a function).
122
+
123
+ ## Discriminated unions
124
+
125
+ A `oneOf` whose variants share a const-valued discriminator (e.g. `kind: "guest" | "registered"`) emits as a Swift `enum` with associated values. Because Swift's standard `Codable` has no built-in tagged-union support, ajsc emits a hand-rolled `init(from:)` / `encode(to:)` that dispatches on the discriminator field:
126
+
127
+ ```swift
128
+ public enum Body: Codable {
129
+ case guest(Guest)
130
+ case registered(Registered)
131
+
132
+ public struct Guest: Codable {
133
+ public let displayName: String
134
+ }
135
+
136
+ public struct Registered: Codable {
137
+ public let email: String
138
+ public let name: String
139
+ }
140
+
141
+ private enum DiscriminatorKeys: String, CodingKey { case kind }
142
+
143
+ public init(from decoder: Decoder) throws {
144
+ let c = try decoder.container(keyedBy: DiscriminatorKeys.self)
145
+ let kind = try c.decode(String.self, forKey: .kind)
146
+ switch kind {
147
+ case "guest": self = .guest(try Guest(from: decoder))
148
+ case "registered": self = .registered(try Registered(from: decoder))
149
+ default: throw DecodingError.dataCorruptedError(
150
+ forKey: .kind, in: c,
151
+ debugDescription: "Unknown discriminator: \(kind)")
152
+ }
153
+ }
154
+
155
+ public func encode(to encoder: Encoder) throws {
156
+ switch self {
157
+ case .guest(let v): try v.encode(to: encoder)
158
+ case .registered(let v): try v.encode(to: encoder)
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ **Nothing for the consumer to configure.** The discriminator handling is baked into the generated code — `JSONDecoder().decode(Body.self, from: data)` works directly.
165
+
166
+ The discriminator field (`kind`) is **retained** on each variant struct (unlike the Kotlin target, which erases it via `@SerialName`). This is a Codable necessity: the variant struct's `init(from:)` is invoked with the same decoder that just read the discriminator, so the field has to be present in the variant's shape too.
167
+
168
+ ## JSON-key sanitization
169
+
170
+ Kebab-case and snake-case JSON keys become camelCase Swift property names; ajsc auto-emits a nested `enum CodingKeys: String, CodingKey` to map between them:
171
+
172
+ ```swift
173
+ public struct Response: Codable {
174
+ public let createdAt: Date
175
+ public let userName: String
176
+
177
+ enum CodingKeys: String, CodingKey {
178
+ case createdAt = "created-at"
179
+ case userName = "user_name"
180
+ }
181
+ }
182
+ ```
183
+
184
+ Reserved Swift keywords get backtick-escaped: a JSON key `class` becomes `` `class` `` on the Swift side (still valid as a property name; the surrounding code references it as `value.`class`).
185
+
186
+ ## Switching off Codable (`--swift-serializer none`)
187
+
188
+ `--swift-serializer none` emits plain structs **without `Codable` conformance and without `CodingKeys`**:
189
+
190
+ ```swift
191
+ public struct Response {
192
+ public let id: String
193
+ public let createdAt: Date
194
+ }
195
+ ```
196
+
197
+ Use cases:
198
+
199
+ - You're using **SwiftyJSON**, **Argo**, or another non-Codable serialization library.
200
+ - You want to hand-roll `Codable` conformance in extensions (e.g. to centralize `dateDecodingStrategy` per type, or to handle a Codable-incompatible shape).
201
+ - You're integrating with an Objective-C bridge that doesn't speak `Codable`.
202
+
203
+ With `none`, the consumer is fully responsible for serialization. CodingKeys are NOT emitted — kebab/snake-case keys aren't sanitized at all on the wire side, so you'll need to handle that mapping yourself.
204
+
205
+ ## `--swift-access-level public | internal`
206
+
207
+ Defaults to `public`. Pass `--swift-access-level internal` when the generated types are consumed only within a single module (e.g. an app target that talks to one backend) and you don't want them appearing in the module's public ABI.
208
+
209
+ ```bash
210
+ npx ts-procedures-codegen --target swift --swift-access-level internal --url ... --out ...
211
+ ```
212
+
213
+ The flag threads through to ajsc's `accessLevel` option and applies uniformly to every emitted type, the namespace enums, and the static `method` / `path` / `pathTemplate` declarations.
214
+
215
+ ## Error types
216
+
217
+ Each route that declares errors gets a nested `Errors` enum (caseless namespace) containing one `Codable` struct per error name:
218
+
219
+ ```swift
220
+ public enum Users {
221
+ public enum GetUser {
222
+ // ... method, path, types ...
223
+ public enum Errors {
224
+ public struct NotFound: Codable {
225
+ public let name: String
226
+ public let message: String
227
+ }
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ Access generated error types as `Users.GetUser.Errors.NotFound`.
234
+
235
+ **No runtime dispatch.** Like the Kotlin target, the Swift target ships **types only** — there is no error registry, no `instanceof`-style lookup, no `dispatchTypedError`. Consumers catch HTTP failures themselves and dispatch on status code or `body.name`:
236
+
237
+ ```swift
238
+ func loadUser(id: String) async throws -> Users.GetUser.Response {
239
+ var req = URLRequest(url: URL(string: "https://api.example.com" + Users.GetUser.path(.init(id: id)))!)
240
+ req.httpMethod = Users.GetUser.method
241
+
242
+ let (data, response) = try await URLSession.shared.data(for: req)
243
+ guard let http = response as? HTTPURLResponse else {
244
+ throw URLError(.badServerResponse)
245
+ }
246
+
247
+ let decoder = JSONDecoder()
248
+ decoder.dateDecodingStrategy = .iso8601
249
+
250
+ switch http.statusCode {
251
+ case 200:
252
+ return try decoder.decode(Users.GetUser.Response.self, from: data)
253
+ case 404:
254
+ throw try decoder.decode(Users.GetUser.Errors.NotFound.self, from: data)
255
+ default:
256
+ throw URLError(.badServerResponse)
257
+ }
258
+ }
259
+ ```
260
+
261
+ For the error structs to also conform to `Error`, declare a single-line empty extension in your own code (kept out of generated files so codegen overwrites are safe):
262
+
263
+ ```swift
264
+ extension Users.GetUser.Errors.NotFound: Error {}
265
+ ```
266
+
267
+ Choosing the dispatch strategy (status-code, `body.name`, your own `enum APIError: Error { case ... }` wrapper, etc.) is intentionally left to consumers.
268
+
269
+ ## Untagged unions
270
+
271
+ **Unlike the Kotlin target, `--unsupported-unions fallback` actually works on Swift.** ajsc emits a self-contained `AnyCodable` helper struct directly inside the generated file (no external dependency, no separate runtime to install) to model schemas that use untagged `anyOf` / `oneOf` with no shared discriminator.
272
+
273
+ Default: `--unsupported-unions throw` — codegen raises an error with the schema path of the offending union, so you can fix it at the source.
274
+
275
+ ```bash
276
+ # Opt in to the fallback. Generated code becomes:
277
+ npx ts-procedures-codegen --target swift --unsupported-unions fallback --url ... --out ...
278
+ ```
279
+
280
+ ```swift
281
+ public struct AnyCodable: Codable {
282
+ public let value: Any
283
+ // ... init(from:), encode(to:) handle Bool/Int/Double/String/Array/Dictionary/null
284
+ }
285
+
286
+ public struct MixedField: Codable {
287
+ public let value: AnyCodable // was: oneOf: [{ type: 'string' }, { type: 'integer' }]
288
+ }
289
+ ```
290
+
291
+ You lose static typing on the union'd value — consumers introspect at runtime. **Prefer adding a discriminator** to the server-side schema if at all possible; the `fallback` mode is an escape hatch for third-party / locked schemas you can't change.
292
+
293
+ ## Documented limitations
294
+
295
+ The following ajsc behaviors are intentional and documented; they are **not bugs**:
296
+
297
+ - **`format: date` and `format: time` map to `String`.** Swift's Foundation has no native date-only or time-only type (`Date` is a point-in-time, not a calendar date). Parse manually with `DateFormatter` if you need a typed value.
298
+ - **`type: integer` maps to `Int64`** (not `Int`). Reason: 32-bit Apple platforms (older watchOS, some embedded targets) have 32-bit `Int`. `Int64` guarantees range parity with the JSON Schema integer type.
299
+ - **`type: number` maps to `Double`.** For monetary or other precision-sensitive values, decode into `Double` and convert to `Decimal` at the boundary:
300
+ ```swift
301
+ let amount = Decimal(response.totalPrice)
302
+ ```
303
+ This is a one-time conversion at the parse boundary; subsequent arithmetic on `Decimal` is precision-safe.
304
+ - **`additionalProperties: { type: T }` is silently dropped** with a `/// Note: schema permits additional keys of type T — not modeled.` doc-comment. Add a sibling `[String: T]` field by hand or write a custom `init(from:)` if your contract uses extra keys.
305
+ - **Heterogeneous tuples throw under Codable.** Swift tuples are not `Codable`. Schemas with positional-tuple `items: [...]` arrays throw at codegen time. Refactor to a struct schema upstream.
306
+ - **`not` and `patternProperties` keywords throw at codegen time** with a path-bearing error message. These don't have idiomatic Swift mappings; the schema needs simplification at the source.
307
+ - **Schema-level `examples` are not modeled.** They're documentation-only on the server side; consumers don't see them.
308
+
309
+ ## Reference
310
+
311
+ - Spec: [`docs/superpowers/specs/2026-04-25-swift-codegen-design.md`](./superpowers/specs/2026-04-25-swift-codegen-design.md)
312
+ - For Kotlin / Android consumers, see [`docs/codegen-kotlin.md`](./codegen-kotlin.md) — same types-only design with Kotlin-specific flags and `kotlinx.serialization` setup notes.
313
+ - ajsc README: `node_modules/ajsc/README.md` (or [npmjs.com/package/ajsc](https://www.npmjs.com/package/ajsc))
314
+ - ts-procedures-codegen CLI flags: see `CLAUDE.md` (search for "Swift target") and the spec linked above. (`--help` is not currently implemented; pass invalid/missing args to see error messages with usage hints.)