Skip to content

sidepelican/CallableKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

192 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

  •  
  •  
  •