CallableKit provides typesafe rpc with Swift server.
- Swift (with Foundation)
- AsyncHTTPClient also adaptable
- TypeScript (with fetch)
Define interface protocol and share the module to server and client.
Interface protocols must be annotated with @Callable
import CallableKit
@Callable
public protocol EchoServiceProtocol {
func hello(request: EchoHelloRequest) async throws -> EchoHelloResponse
}
public struct EchoHelloRequest: Codable, Sendable {
public init(name: String) {
self.name = name
}
public var name: String
}
public struct EchoHelloResponse: Codable, Sendable {
public init(message: String) {
self.message = message
}
public var message: String
}
On the server side, you prepare the implementation of the protocol and register it for routing using the Transport
module.
import APIDefinition // Your interface module
import CallableKitVaporTransport
import Vapor
struct EchoService: EchoServiceProtocol {
func hello(request: EchoHelloRequest) async throws -> EchoHelloResponse {
return .init(message: "Hello, \(request.name)!")
}
}
let app = try await Application.make()
// Swift macro generates `configureEchoServiceProtocol`
configureEchoServiceProtocol(transport: VaporTransport(router: app.routes) { _ in
EchoService()
})
try await app.execute()
try await app.asyncShutdown()
Client can call the functions through a stub client.
import APIDefinition
import CallableKitURLSessionStub
let stubClient: some StubClientProtocol = URLSessionStubClient(
baseURL: URL(string: "http://127.0.0.1:8080")!
)
// EchoServiceProtocolStub is also generated by Swift macro
let echoClient = EchoServiceProtocolStub(client: stubClient)
let res = try await echoClient.hello(request: .init(name: "Swift"))
print(res.message) // Hello, Swift!
TypeScript client is also supported. It needs manual code generation.
const stub = createStubClient("http://127.0.0.1:8080");
const echoClient = bindEcho(stub);
const res/*: { message: string }*/ = await echoClient.hello({ name: "TypeScript" });
console.log(res.message); // Hello, TypeScript!
Swift types are coverted to TypeScript types powered by CodableToTypeScript
- Checkout CallableKitCodegen repo and simply run executable command
$ swift run codegen Sources/APIDefinition \
--ts_out TSClient/src/Gen
Mint is useful to checkout and run.
or
-
Use from package plugin (see example)
Add plugin target in your Package.swift (or add dedicated Package.swift for independency)
dependencies: [
...
.package(url: "https://github.com/sidepelican/CallableKitCodegen.git", from: "1.0.0"),
],
targets: [
...
.plugin(
name: "CodegenPlugin",
capability: .command(
intent: .custom(verb: "codegen", description: "Generate codes from Sources/APIDefinition"),
permissions: [.writeToPackageDirectory(reason: "Place generated code")]
),
dependencies: [
.product(name: "codegen", package: "CallableKitCodegen"),
]
),
]
swift run codegen