Swift
Generate a native Swift client from your routes, using URLSession and Codable.
@kizunajs/swift generates a native Swift client from your Kizuna routes. The generated client uses URLSession and Codable, with no third-party Swift dependencies required.
Add it to your config
Point swiftClient at the file you want written:
| Option | Description |
|---|---|
output | Where the generated .swift file goes |
namespace | Public enum wrapping all generated types, API by default |
camelCaseProperties | Convert wire field names to camelCase properties with CodingKeys |
unknownEnumCase | Emit enums with an unknown(String) fallback so new server values don't break decoding |
The path is relative to the config, so a client can land outside the TypeScript project. When the iOS app lives in the same repository, ../ios/MyApp/Generated/APIClient.swift writes straight into it. An app in its own repository takes the generated file the way it takes any other vendored source, by copying it in from a build step or committing it.
Add it to your project
Add the generated file to your Xcode project or Swift package as a regular source file. It needs no third-party Swift dependencies.
Call a route
Request inputs are passed as components named after the route's input groups: .params (path), .body, .query, and .headers:
Use it in a screen
A schema wrapped in Kizuna.model generates a named type, so a view can name it in its signature and every route that returns a user hands back the same API.User:
Branded IDs
A branded ID becomes a struct, API.UserId, encoded as the bare string. The parameter's type shows it, and passing a String does not compile.
An ID from a response is already an API.UserId, so it passes into the next call as it is:
A String from outside the API, like a deep link, is wrapped once:
rawValue reads the string back:
Optional and null fields
An .optional() or .nullish() field is a String? that defaults to nil and is left out of the request. A .nullable() field is a String? with no default, sent as null when you pass nil:
See Optional and null fields for which to declare.
Property naming
By default, field names are emitted verbatim, so total_count on the wire stays total_count in the generated type.
Pass --camel-case to convert wire fields to camelCase properties (total_count becomes totalCount), preserving the wire name via CodingKeys.
Unknown enum case (open enums)
By default a z.enum is a closed Swift enum that throws on a wire value it doesn't know, failing the whole response. Pass --unknown-enum-case to make enums forward-compatible: each gains an unknown(String) case, so an unrecognised value decodes to .unknown("newValue") instead of throwing:
The raw value is preserved, so re-encoding writes it back unchanged. Discriminated unions still throw on an unknown discriminator.
Generated client
The generated file exports a Sendable client with methods for each route and sub-clients for grouped routes. It is Sendable, so sharing it across tasks needs no actor hop per request. Each request group is a nested struct with a group-named factory:
Cancellation
Failure.cancelled covers a cancelled request, which SwiftUI produces routinely: a view carrying a .task cancels it on disappear, so a screen that loads and then closes arrives in your error path. An error path shared across several routes can check (error as? any KizunaFailure)?.isCancelled instead of matching each route's own enum.
Deprecation
Routes with deprecated and fields with .meta({ deprecated: ... }) emit @available(*, deprecated) in the generated Swift code, with the message when one is set:
Streams
A route whose response streams returns its body as an AsyncThrowingStream. Named events become an Event enum with one case per event, each carrying its decoded payload:
A stream of one schema yields that type directly, a text/* stream yields one String per line, and a binary stream yields Data chunks. The status and headers are checked before the method returns, so a 400 throws its Failure as on any route. Cancelling the task that reads the stream closes the connection. A route that mixes a streamed status with another 2xx is left out of the client, with a warning.
Keeping it current
kizuna generate writes every client the config declares, so the Swift one is never generated on its own:
Run kizuna generate --check in CI, and a Swift client nobody regenerated fails the build instead of drifting from the routes it was generated from.