ts-procedures 6.1.0 → 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.
- package/agent_config/bin/setup.mjs +2 -2
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +1 -0
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures-kotlin/SKILL.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures-swift/SKILL.md +119 -0
- package/agent_config/copilot/copilot-instructions.md +1 -0
- package/agent_config/cursor/cursorrules +1 -0
- package/agent_config/lib/install-claude.mjs +1 -1
- 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/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/codegen-kotlin.md +1 -0
- package/docs/codegen-swift.md +314 -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/package.json +2 -2
- package/src/codegen/bin/cli.ts +91 -7
- 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
|
@@ -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.)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ajsc v7.2 Kotlin Codegen Polish
|
|
2
2
|
|
|
3
|
-
**Status:**
|
|
3
|
+
**Status:** Shipped
|
|
4
4
|
**Date:** 2026-04-25
|
|
5
5
|
**Author:** Cory Robinson
|
|
6
6
|
**Supersedes parts of:** [`2026-04-24-kotlin-swift-codegen-design.md`](./2026-04-24-kotlin-swift-codegen-design.md) — Kotlin sections only. Swift sections of the prior design remain in force and unscheduled.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Swift Codegen Target — Design & Implementation Plan
|
|
2
|
+
|
|
3
|
+
Date: 2026-04-25
|
|
4
|
+
Branch: codegen-kotlin-swift
|
|
5
|
+
Status: shipped
|
|
6
|
+
|
|
7
|
+
## Outcome
|
|
8
|
+
|
|
9
|
+
Implemented as designed: `--target swift`, `--swift-serializer`, `--swift-access-level`, full integration test against `users-golden.swift`, plus a `swiftc -parse` e2e gated by `swiftc` availability. One post-implementation deviation worth noting: the originally-spec'd files in `src/codegen/targets/swift/` were later supplemented by an extracted `src/codegen/targets/_shared/` directory of language-agnostic utilities (`path-utils`, `pick-defined`, `indent`, `pascal-case`, `route-slots`, `error-schemas`, `write-files`, `target-run`). The Swift run module (`src/codegen/targets/swift/run.ts`) and the Kotlin run module both consume `_shared/` so the dispatcher in `src/codegen/pipeline.ts` stays thin (~94 lines).
|
|
10
|
+
|
|
11
|
+
## Goal
|
|
12
|
+
|
|
13
|
+
Add a `--target swift` codegen target to `ts-procedures-codegen` that emits idiomatic, types-only Swift source from a `DocEnvelope`. Design mirrors the existing Kotlin target so iOS / Apple-platform consumers get parity DX with Android consumers — both targets are types-only, no runtime, no error registry, no HTTP adapter.
|
|
14
|
+
|
|
15
|
+
## Non-goals
|
|
16
|
+
|
|
17
|
+
- HTTP client, networking, async/await wrappers (consumers own this).
|
|
18
|
+
- SSE / streams (skipped, same as Kotlin).
|
|
19
|
+
- Hooks, per-call options, error dispatch logic.
|
|
20
|
+
- Swift Package Manager scaffolding (consumers create `Package.swift` themselves).
|
|
21
|
+
- Module declarations (Swift modules are defined by Xcode/SPM targets, not per-file like Kotlin packages).
|
|
22
|
+
- ObjC interop, `@objc` annotations.
|
|
23
|
+
|
|
24
|
+
## Output shape
|
|
25
|
+
|
|
26
|
+
One `.swift` file per scope. Idiomatic Swift namespacing via **caseless enums** (the standard Swift idiom for namespaces — uninstantiable, zero runtime cost):
|
|
27
|
+
|
|
28
|
+
```swift
|
|
29
|
+
// Source hash: <md5>
|
|
30
|
+
// Generated by ts-procedures-codegen — do not edit.
|
|
31
|
+
import Foundation
|
|
32
|
+
|
|
33
|
+
public enum Users {
|
|
34
|
+
public enum GetUser {
|
|
35
|
+
public static let method = "GET"
|
|
36
|
+
public static let pathTemplate = "/users/{id}"
|
|
37
|
+
public static func path(_ p: PathParams) -> String { return "/users/\(p.id)" }
|
|
38
|
+
|
|
39
|
+
public struct PathParams: Codable {
|
|
40
|
+
public let id: String
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public struct Response: Codable {
|
|
44
|
+
public let id: String
|
|
45
|
+
public let name: String
|
|
46
|
+
/// ISO-8601 — set JSONDecoder.dateDecodingStrategy = .iso8601
|
|
47
|
+
public let createdAt: Date
|
|
48
|
+
public let address: Address
|
|
49
|
+
|
|
50
|
+
enum CodingKeys: String, CodingKey {
|
|
51
|
+
case id, name
|
|
52
|
+
case createdAt = "created-at"
|
|
53
|
+
case address
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public struct Address: Codable {
|
|
57
|
+
public let street: String
|
|
58
|
+
public let city: String
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public enum Errors {
|
|
63
|
+
public struct NotFound: Codable {
|
|
64
|
+
public let name: String
|
|
65
|
+
public let message: String
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Routes without path params get `public static let path = "/users"` (constant, not function).
|
|
73
|
+
|
|
74
|
+
## CLI surface
|
|
75
|
+
|
|
76
|
+
New flag: `--target swift` (extends current `'ts' | 'kotlin'` union to `'ts' | 'kotlin' | 'swift'`).
|
|
77
|
+
|
|
78
|
+
Swift-specific flags:
|
|
79
|
+
|
|
80
|
+
| Flag | Default | Notes |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `--swift-serializer <codable\|none>` | `codable` | Emits `: Codable`/`CodingKeys`. `none` for plain structs (consumer handles serialization). |
|
|
83
|
+
| `--swift-access-level <public\|internal>` | `public` | Threads through to ajsc `accessLevel`. |
|
|
84
|
+
|
|
85
|
+
Reused (apply to all targets): `--unsupported-unions`, `--array-item-naming`, `--depluralize`, `--uncountable-words`.
|
|
86
|
+
|
|
87
|
+
**Important:** Unlike Kotlin, `--unsupported-unions fallback` actually works on Swift — ajsc emits a self-contained `AnyCodable` helper struct. Do NOT add the kotlin-style "no-op warning" for Swift.
|
|
88
|
+
|
|
89
|
+
**No `--swift-package` flag.** Swift has no file-level package/module declaration; modules are defined by SPM/Xcode targets. This is a deliberate DX win — one fewer required arg vs Kotlin.
|
|
90
|
+
|
|
91
|
+
## Config file shape
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"target": "swift",
|
|
96
|
+
"swift": {
|
|
97
|
+
"serializer": "codable",
|
|
98
|
+
"accessLevel": "public"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## File layout
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
src/codegen/targets/swift/
|
|
107
|
+
├── ajsc-adapter.ts # SwiftEmitter interface + production wrapper around ajsc.emitSwift
|
|
108
|
+
├── ajsc-adapter.test.ts
|
|
109
|
+
├── format-swift.ts # header, imports, indent, pickDefined helpers
|
|
110
|
+
├── format-swift.test.ts
|
|
111
|
+
├── emit-route-swift.ts # per-route emitter (PathParams, Query, Body, Response, Errors)
|
|
112
|
+
├── emit-route-swift.test.ts
|
|
113
|
+
├── emit-scope-swift.ts # per-scope emitter (wraps routes in nested enum namespace)
|
|
114
|
+
├── emit-scope-swift.test.ts
|
|
115
|
+
├── integration.test.ts # golden-file end-to-end with stub emitter
|
|
116
|
+
├── e2e-compile.test.ts # SKIPPED by default — runs swiftc to validate generated output
|
|
117
|
+
└── __fixtures__/
|
|
118
|
+
├── users-envelope.json # copy of kotlin fixture (same input)
|
|
119
|
+
└── users-golden.swift
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Architectural decisions
|
|
123
|
+
|
|
124
|
+
### 1. Namespacing via caseless enums
|
|
125
|
+
|
|
126
|
+
Swift idiom: `public enum Users {}` for namespace. Better than `struct Users {}` (no init, no instantiation possible, zero runtime cost). Better than nested `class` (reference type, unnecessary).
|
|
127
|
+
|
|
128
|
+
### 2. Static members for path/method
|
|
129
|
+
|
|
130
|
+
Inside an enum namespace, all members must be `static`. So:
|
|
131
|
+
```swift
|
|
132
|
+
public static let method = "GET"
|
|
133
|
+
public static let pathTemplate = "/users/{id}"
|
|
134
|
+
public static func path(_ p: PathParams) -> String { return "/users/\(p.id)" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
(Kotlin uses `const val` and top-level `fun`; Swift uses `public static let` and `public static func`.)
|
|
138
|
+
|
|
139
|
+
### 3. ajsc options threaded
|
|
140
|
+
|
|
141
|
+
Per route slot we call `emitSwift(slotSchema, { rootTypeName, inlineTypes: true, ...passthrough })`. Passthrough: `serializer`, `accessLevel`, `unsupportedUnions`, `arrayItemNaming`, `depluralize`, `uncountableWords`.
|
|
142
|
+
|
|
143
|
+
`inlineTypes: true` is critical — same as Kotlin. Without it, nested object types extract to siblings and clutter the namespace.
|
|
144
|
+
|
|
145
|
+
### 4. Imports merge per file
|
|
146
|
+
|
|
147
|
+
ajsc returns imports per emit call (typically `["Foundation"]` only when Date/UUID/URL is in the schema). We dedupe + sort and emit once at the top of the scope file.
|
|
148
|
+
|
|
149
|
+
### 5. No errors file, no index file
|
|
150
|
+
|
|
151
|
+
Mirrors Kotlin: errors are nested as `enum Errors { struct NotFound: Codable { ... } }` per route. No `_errors.swift`, no barrel/index, no factories.
|
|
152
|
+
|
|
153
|
+
### 6. Path interpolation
|
|
154
|
+
|
|
155
|
+
Swift string interpolation is `\(expr)` (backslash + paren). Path builder template:
|
|
156
|
+
```swift
|
|
157
|
+
public static func path(_ p: PathParams) -> String { return "/users/\(p.id)" }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
For no-params: `public static let path = "/users"`.
|
|
161
|
+
|
|
162
|
+
### 7. Source hash header
|
|
163
|
+
|
|
164
|
+
`// Source hash: <md5>` as second line (after the optional `// Generated by ...` line). Same as kotlin/ts.
|
|
165
|
+
|
|
166
|
+
### 8. Stream skipping
|
|
167
|
+
|
|
168
|
+
Same as Kotlin — skip stream routes with a single console log per scope.
|
|
169
|
+
|
|
170
|
+
### 9. Reserved word + identifier sanitization
|
|
171
|
+
|
|
172
|
+
ajsc handles this via `sanitizeSwiftIdentifier` (already verified in ajsc/swift exports). Property and case names are already sanitized in ajsc's output. Our route/scope names use PascalCase already; we don't need to re-sanitize.
|
|
173
|
+
|
|
174
|
+
### 10. Self-contained emitter (no extra runtime files)
|
|
175
|
+
|
|
176
|
+
Like Kotlin: zero runtime files emitted. ajsc handles `AnyCodable` inline when `unsupportedUnions: 'fallback'` is set — we don't emit a separate helper file.
|
|
177
|
+
|
|
178
|
+
## Pipeline integration
|
|
179
|
+
|
|
180
|
+
`src/codegen/pipeline.ts` gets a third branch parallel to kotlin:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
if (options.target === 'kotlin') { ... }
|
|
184
|
+
if (options.target === 'swift') {
|
|
185
|
+
if (options.swiftEmitter == null) throw new Error(...)
|
|
186
|
+
// ... build errorSchemas, iterate groups, call emitSwiftScope, write files
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Naming inside `PipelineOptions`:
|
|
191
|
+
- `target?: 'ts' | 'kotlin' | 'swift'`
|
|
192
|
+
- `swiftSerializer?: 'codable' | 'none'`
|
|
193
|
+
- `swiftAccessLevel?: 'public' | 'internal'`
|
|
194
|
+
- `swiftEmitter?: SwiftEmitter` (test-injection hook, mirrors `kotlinEmitter`)
|
|
195
|
+
|
|
196
|
+
Reused: `unsupportedUnions` (already on options).
|
|
197
|
+
|
|
198
|
+
## CLI integration
|
|
199
|
+
|
|
200
|
+
`src/codegen/bin/cli.ts`:
|
|
201
|
+
|
|
202
|
+
1. Extend `target` parser to accept `'swift'`.
|
|
203
|
+
2. Add flags: `--swift-serializer`, `--swift-access-level`.
|
|
204
|
+
3. Add `swift?: { serializer?, accessLevel? }` to `CodegenConfig` and `ParsedArgs`.
|
|
205
|
+
4. No package validation (Swift has no required field).
|
|
206
|
+
5. Extend `printPostRunHints` for swift target → link `docs/codegen-swift.md`.
|
|
207
|
+
6. Watch-mode: resolve swift emitter once at startup, parallel to kotlin.
|
|
208
|
+
7. Do NOT add a Swift no-op-flag warning for `--unsupported-unions` (it works on Swift).
|
|
209
|
+
|
|
210
|
+
## Tests
|
|
211
|
+
|
|
212
|
+
Mirror the Kotlin test layout:
|
|
213
|
+
|
|
214
|
+
1. **`ajsc-adapter.test.ts`** — stub creator + production resolver (mocking `ajsc` module to assert error messages when missing or `emitSwift` is undefined).
|
|
215
|
+
2. **`format-swift.test.ts`** — `swiftHeader`, `swiftImports` dedupe+sort, `indent`, `pickDefined`.
|
|
216
|
+
3. **`emit-route-swift.test.ts`** — path-builder for params/no-params, slot order (PathParams, Query, Body, Response), Errors namespace, stream-skipping.
|
|
217
|
+
4. **`emit-scope-swift.test.ts`** — PascalCase scope name, file-name `Foo.swift`, imports dedupe across routes, empty scope, option threading.
|
|
218
|
+
5. **`integration.test.ts`** — golden-file test using the `users-envelope.json` fixture and a hand-stubbed emitter, byte-identical against `users-golden.swift`.
|
|
219
|
+
6. **`e2e-compile.test.ts`** — `it.skipIf(no swiftc)` — invokes `swiftc -parse` on the generated file to verify syntactic correctness. Skipped by default; only runs when `swiftc` is on `PATH`.
|
|
220
|
+
|
|
221
|
+
Tests inject `createStubSwiftEmitter()` so output is deterministic and independent of ajsc's per-version evolution. The E2E compile test is the only one that exercises real ajsc.
|
|
222
|
+
|
|
223
|
+
## Documentation
|
|
224
|
+
|
|
225
|
+
1. **`docs/codegen-swift.md`** — full setup guide:
|
|
226
|
+
- Quickstart example
|
|
227
|
+
- SPM/Xcode integration (target setup, file inclusion)
|
|
228
|
+
- JSONDecoder configuration (`.iso8601` for Date)
|
|
229
|
+
- Sample output
|
|
230
|
+
- Discriminated unions (Swift uses `init(from:)` / `encode(to:)`)
|
|
231
|
+
- JSON-key sanitization (CodingKeys)
|
|
232
|
+
- `--swift-serializer none` (when consumers want plain structs for SwiftJSON or hand-rolled coding)
|
|
233
|
+
- Error types (nested under `Errors`)
|
|
234
|
+
- Untagged unions (`fallback` works on Swift, emits `AnyCodable`)
|
|
235
|
+
- Documented limitations
|
|
236
|
+
|
|
237
|
+
2. **`CLAUDE.md`** — add Swift target paragraph parallel to Kotlin; emphasize differences (`--unsupported-unions fallback` works for Swift; no `--swift-package` requirement).
|
|
238
|
+
|
|
239
|
+
3. **`agent_config/claude-code/skills/ts-procedures-swift/SKILL.md`** — parallel to `ts-procedures-kotlin/SKILL.md`. Cross-link both kotlin and swift skills from the main `ts-procedures` skill.
|
|
240
|
+
|
|
241
|
+
## Implementation phases
|
|
242
|
+
|
|
243
|
+
**Phase 1 (foundation):** ajsc-adapter, format helpers, emit-route, emit-scope (+ unit tests for each). Single sub-agent.
|
|
244
|
+
|
|
245
|
+
**Phase 2 (integration):** Wire into pipeline.ts, index.ts, cli.ts. Add integration test with golden file. Single sub-agent depending on Phase 1.
|
|
246
|
+
|
|
247
|
+
**Phase 3 (docs+skill, parallel with Phase 1/2):** Write `docs/codegen-swift.md`, update CLAUDE.md, create agent_config skill. Single sub-agent independent of code.
|
|
248
|
+
|
|
249
|
+
**Phase 4 (verify):** `npm run build && npm test && npm run lint`. Inspect generated golden file. Confirm CLI invocation works.
|
|
250
|
+
|
|
251
|
+
## Risk / open questions
|
|
252
|
+
|
|
253
|
+
- **swiftc on dev machines** — E2E test is skipped by default to avoid CI failures. Same pattern as kotlin's `e2e-compile.test.ts`.
|
|
254
|
+
- **`type: integer` → `Int64`** — Swift idiom is `Int` for general use; `Int64` is heavier visually. Accept ajsc's choice for now (can revisit if mobile devs request `Int`).
|
|
255
|
+
- **`type: number` → `Double`** — for monetary values, mobile devs should use `Decimal`. Document the workaround in `docs/codegen-swift.md`.
|
|
256
|
+
- **CodingKeys are mandatory** when keys differ — ajsc emits these automatically. We just have to make sure our golden file includes them and tests don't lock them away.
|
|
257
|
+
|
|
258
|
+
## Success criteria
|
|
259
|
+
|
|
260
|
+
- `npm test` passes (all kotlin tests still pass, new swift tests pass).
|
|
261
|
+
- `npm run build` succeeds.
|
|
262
|
+
- `npm run lint` clean.
|
|
263
|
+
- Manual run: `npx ts-procedures-codegen --target swift --url ... --out ...` produces a valid `Users.swift` (or whatever scope) that compiles with `swiftc -parse`.
|
|
264
|
+
- Docs and skill files committed; `agent_config/postinstall` will distribute them on next install.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-procedures",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
|
|
5
5
|
"main": "build/exports.js",
|
|
6
6
|
"types": "build/exports.d.ts",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"framework"
|
|
79
79
|
],
|
|
80
80
|
"optionalDependencies": {
|
|
81
|
-
"ajsc": "7.2",
|
|
81
|
+
"ajsc": "7.2.0",
|
|
82
82
|
"express": "^5.2.1",
|
|
83
83
|
"hono": "^4.7.4"
|
|
84
84
|
},
|