Skip to content

sidepelican/CallableKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CallableKit

CallableKit provides typesafe rpc with Swift server.

Supported server frameworks

Supported clients

  • Swift (with Foundation)
  • TypeScript (with fetch)

Usage

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

Run code generation

$ 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

About

Typesafe rpc with Swift server

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •