rivetkit-client-swift by rivet-dev/skills
npx skills add https://github.com/rivet-dev/skills --skill rivetkit-client-swift在构建使用 RivetKitClient 连接 Rivet Actors 的 Swift 客户端时使用此技能。
RivetKit 版本:2.1.6
do/catch。添加 Swift 包依赖并导入 RivetKitClient:
// Package.swift
dependencies: [
.package(url: "https://github.com/rivet-dev/rivetkit-swift", from: "2.0.0")
]
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "RivetKitClient", package: "rivetkit-swift")
]
)
]
import RivetKitClient
let config = try ClientConfig(
endpoint: "https://my-namespace:pk_...@api.rivet.dev"
)
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let count: Int = try await handle.action("increment", 1, as: Int.self)
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
import RivetKitClient
let config = try ClientConfig(
endpoint: "https://api.rivet.dev",
namespace: "my-namespace",
token: "pk_..."
)
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let count: Int = try await handle.action("increment", 1, as: Int.self)
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
// 无状态:每次调用都是独立的
let current: Int = try await handle.action("getCount", as: Int.self)
print("Current count: \(current)")
// 有状态:保持连接开放以接收实时事件
let conn = handle.connect()
// 使用 AsyncStream 订阅事件
let eventTask = Task {
for await count in await conn.events("count", as: Int.self) {
print("Event: \(count)")
}
}
_ = try await conn.action("increment", 1, as: Int.self)
eventTask.cancel()
await conn.dispose()
await client.dispose()
import RivetKitClient
struct GameInput: Encodable {
let mode: String
}
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
// 获取或创建一个 actor
let room = client.getOrCreate("chatRoom", ["room-42"])
// 获取一个已存在的 actor(如果未找到则失败)
let existing = client.get("chatRoom", ["room-42"])
// 使用输入创建一个新的 actor
let created = try await client.create(
"game",
["game-1"],
options: CreateOptions(input: GameInput(mode: "ranked"))
)
// 通过 ID 获取 actor
let byId = client.getForId("chatRoom", "actor-id")
// 解析 actor ID
let resolvedId = try await room.resolve()
print("Resolved ID: \(resolvedId)")
await client.dispose()
Actions 支持 0-5 个参数的位置重载:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let count: Int = try await handle.action("getCount")
let updated: String = try await handle.action("rename", "new-name")
let ok: Bool = try await handle.action("setScore", "user-1", 42)
print("Count: \(count), Updated: \(updated), OK: \(ok)")
await client.dispose()
如果你需要超过 5 个参数,请使用原始的 JSON 回退方法:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let args: [JSONValue] = [
.string("user-1"),
.number(.int(42)),
.string("extra"),
.string("more"),
.string("args"),
.string("here")
]
let ok: Bool = try await handle.action("setScore", args: args, as: Bool.self)
print("OK: \(ok)")
await client.dispose()
import RivetKitClient
struct ConnParams: Encodable {
let authToken: String
}
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let chat = client.getOrCreate(
"chatRoom",
["general"],
options: GetOrCreateOptions(params: ConnParams(authToken: "jwt-token-here"))
)
let conn = chat.connect()
// 使用连接...
for await status in await conn.statusChanges() {
print("Status: \(status.rawValue)")
if status == .connected {
break
}
}
await conn.dispose()
await client.dispose()
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
// 使用 AsyncStream 订阅事件
let messageTask = Task {
for await (from, body) in await conn.events("message", as: (String, String).self) {
print("\(from): \(body)")
}
}
// 对于一次性事件,接收后中断
let gameOverTask = Task {
for await _ in await conn.events("gameOver", as: Void.self) {
print("done")
break
}
}
// 让它运行一段时间
try await Task.sleep(for: .seconds(5))
// 完成后取消
messageTask.cancel()
gameOverTask.cancel()
await conn.dispose()
await client.dispose()
事件流支持 0-5 个类型化参数。如果你需要原始值或超过 5 个参数,请使用 JSONValue:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
let rawTask = Task {
for await args in await conn.events("message") {
print(args)
}
}
try await Task.sleep(for: .seconds(5))
rawTask.cancel()
await conn.dispose()
await client.dispose()
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
// 监控状态变化(立即产生当前状态)
let statusTask = Task {
for await status in await conn.statusChanges() {
print("status: \(status.rawValue)")
}
}
// 监控错误
let errorTask = Task {
for await error in await conn.errors() {
print("error: \(error.group).\(error.code)")
}
}
// 监控打开/关闭事件
let openTask = Task {
for await _ in await conn.opens() {
print("connected")
}
}
let closeTask = Task {
for await _ in await conn.closes() {
print("disconnected")
}
}
// 检查当前状态
let current = await conn.currentStatus
print("Current status: \(current.rawValue)")
// 让它运行一段时间
try await Task.sleep(for: .seconds(5))
// 清理
statusTask.cancel()
errorTask.cancel()
openTask.cancel()
closeTask.cancel()
await conn.dispose()
await client.dispose()
对于实现了 onRequest 或 onWebSocket 的 actors,你可以直接调用它们:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("chatRoom", ["general"])
// 原始 HTTP 请求
let response = try await handle.fetch("history")
let history: [String] = try response.json([String].self)
print("History: \(history)")
// 原始 WebSocket 连接
let websocket = try await handle.websocket(path: "stream")
try await websocket.send(text: "hello")
let message = try await websocket.receive()
print("Received: \(message)")
await client.dispose()
在服务器端 Swift(Vapor、Hummingbird 等)中使用相同的客户端:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["server-counter"])
let count: Int = try await handle.action("increment", 1, as: Int.self)
print("Count: \(count)")
await client.dispose()
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
do {
_ = try await client.getOrCreate("user", ["user-123"])
.action("updateUsername", "ab", as: String.self)
} catch let error as ActorError {
print("Error code: \(error.code)")
print("Metadata: \(String(describing: error.metadata))")
}
await client.dispose()
如果你需要一个非类型化的响应,可以解码为 JSONValue:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("data", ["raw"])
let value: JSONValue = try await handle.action("getRawPayload")
print("Raw value: \(value)")
await client.dispose()
键唯一标识 actor 实例。使用复合键(数组)进行分层寻址:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
// 使用复合键进行分层寻址
let room = client.getOrCreate("chatRoom", ["org-acme", "general"])
let actorId = try await room.resolve()
print("Actor ID: \(actorId)")
await client.dispose()
当 userId 包含用户数据时,不要使用字符串插值(如 "org:\(userId)")来构建键。应使用数组来防止键注入攻击。
ClientConfig 从环境变量中读取可选值:
RIVET_NAMESPACE - 命名空间(也可以在端点 URL 中)RIVET_TOKEN - 认证令牌(也可以在端点 URL 中)RIVET_RUNNER - 运行器名称(默认为 "default")endpoint 参数始终是必需的。没有默认端点。
端点支持 URL 认证语法:
https://namespace:token@api.rivet.dev
你也可以传递不带认证信息的端点,并分别提供 RIVET_NAMESPACE 和 RIVET_TOKEN。对于无服务器部署,将端点设置为你的应用的 /api/rivet URL。详情请参阅 端点。
RivetKitClient(config:) - 使用配置创建客户端ClientConfig - 配置端点、命名空间和令牌client.get() / getOrCreate() / getForId() / create() - 获取 actor 句柄client.dispose() - 销毁客户端及其所有连接handle.action(name, args..., as:) - 无状态动作调用handle.connect() - 创建有状态连接handle.resolve() - 获取 actor IDhandle.getGatewayUrl() - 获取原始网关 URLhandle.fetch(path, request:) - 原始 HTTP 请求handle.websocket(path:) - 原始 WebSocket 连接conn.action(name, args..., as:) - 通过 WebSocket 进行动作调用conn.events(name, as:) - 类型化事件的 AsyncStreamconn.statusChanges() - 状态变化的 AsyncStreamconn.errors() - 连接错误的 AsyncStreamconn.opens() - 连接打开时产生的 AsyncStreamconn.closes() - 连接关闭时产生的 AsyncStreamconn.currentStatus - 当前连接状态conn.dispose() - 关闭连接ActorConnStatus - 连接状态枚举(.idle, .connecting, .connected, .disconnected, .disposed)ActorError - 类型化的 actor 错误,包含 group, code, message, metadataJSONValue - 用于非类型化响应的原始 JSON 值如果你需要更多关于 Rivet Actors、注册表或服务器端 RivetKit 的信息,请添加主技能:
npx skills add rivet-dev/skills
然后使用 rivetkit 技能获取后端指导。
每周安装量
1.9K
仓库
GitHub 星标数
8
首次出现
2026 年 1 月 26 日
安全审计
安装于
github-copilot1.9K
antigravity1.1K
codex1.1K
gemini-cli1.1K
opencode1.1K
claude-code1.1K
Use this skill when building Swift clients that connect to Rivet Actors with RivetKitClient.
RivetKit version: 2.1.6
do/catch unless absolutely needed.Add the Swift package dependency and import RivetKitClient:
// Package.swift
dependencies: [
.package(url: "https://github.com/rivet-dev/rivetkit-swift", from: "2.0.0")
]
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "RivetKitClient", package: "rivetkit-swift")
]
)
]
import RivetKitClient
let config = try ClientConfig(
endpoint: "https://my-namespace:pk_...@api.rivet.dev"
)
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let count: Int = try await handle.action("increment", 1, as: Int.self)
import RivetKitClient
let config = try ClientConfig(
endpoint: "https://api.rivet.dev",
namespace: "my-namespace",
token: "pk_..."
)
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let count: Int = try await handle.action("increment", 1, as: Int.self)
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
// Stateless: each call is independent
let current: Int = try await handle.action("getCount", as: Int.self)
print("Current count: \(current)")
// Stateful: keep a connection open for realtime events
let conn = handle.connect()
// Subscribe to events using AsyncStream
let eventTask = Task {
for await count in await conn.events("count", as: Int.self) {
print("Event: \(count)")
}
}
_ = try await conn.action("increment", 1, as: Int.self)
eventTask.cancel()
await conn.dispose()
await client.dispose()
import RivetKitClient
struct GameInput: Encodable {
let mode: String
}
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
// Get or create an actor
let room = client.getOrCreate("chatRoom", ["room-42"])
// Get an existing actor (fails if not found)
let existing = client.get("chatRoom", ["room-42"])
// Create a new actor with input
let created = try await client.create(
"game",
["game-1"],
options: CreateOptions(input: GameInput(mode: "ranked"))
)
// Get actor by ID
let byId = client.getForId("chatRoom", "actor-id")
// Resolve actor ID
let resolvedId = try await room.resolve()
print("Resolved ID: \(resolvedId)")
await client.dispose()
Actions support positional overloads for 0–5 args:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let count: Int = try await handle.action("getCount")
let updated: String = try await handle.action("rename", "new-name")
let ok: Bool = try await handle.action("setScore", "user-1", 42)
print("Count: \(count), Updated: \(updated), OK: \(ok)")
await client.dispose()
If you need more than 5 arguments, use the raw JSON fallback:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
let args: [JSONValue] = [
.string("user-1"),
.number(.int(42)),
.string("extra"),
.string("more"),
.string("args"),
.string("here")
]
let ok: Bool = try await handle.action("setScore", args: args, as: Bool.self)
print("OK: \(ok)")
await client.dispose()
import RivetKitClient
struct ConnParams: Encodable {
let authToken: String
}
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let chat = client.getOrCreate(
"chatRoom",
["general"],
options: GetOrCreateOptions(params: ConnParams(authToken: "jwt-token-here"))
)
let conn = chat.connect()
// Use the connection...
for await status in await conn.statusChanges() {
print("Status: \(status.rawValue)")
if status == .connected {
break
}
}
await conn.dispose()
await client.dispose()
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
// Subscribe to events using AsyncStream
let messageTask = Task {
for await (from, body) in await conn.events("message", as: (String, String).self) {
print("\(from): \(body)")
}
}
// For one-time events, break after receiving
let gameOverTask = Task {
for await _ in await conn.events("gameOver", as: Void.self) {
print("done")
break
}
}
// Let it run for a bit
try await Task.sleep(for: .seconds(5))
// Cancel when done
messageTask.cancel()
gameOverTask.cancel()
await conn.dispose()
await client.dispose()
Event streams support 0–5 typed arguments. If you need raw values or more than 5 arguments, use JSONValue:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
let rawTask = Task {
for await args in await conn.events("message") {
print(args)
}
}
try await Task.sleep(for: .seconds(5))
rawTask.cancel()
await conn.dispose()
await client.dispose()
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
// Monitor status changes (immediately yields current status)
let statusTask = Task {
for await status in await conn.statusChanges() {
print("status: \(status.rawValue)")
}
}
// Monitor errors
let errorTask = Task {
for await error in await conn.errors() {
print("error: \(error.group).\(error.code)")
}
}
// Monitor open/close events
let openTask = Task {
for await _ in await conn.opens() {
print("connected")
}
}
let closeTask = Task {
for await _ in await conn.closes() {
print("disconnected")
}
}
// Check current status
let current = await conn.currentStatus
print("Current status: \(current.rawValue)")
// Let it run for a bit
try await Task.sleep(for: .seconds(5))
// Cleanup
statusTask.cancel()
errorTask.cancel()
openTask.cancel()
closeTask.cancel()
await conn.dispose()
await client.dispose()
For actors that implement onRequest or onWebSocket, you can call them directly:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("chatRoom", ["general"])
// Raw HTTP request
let response = try await handle.fetch("history")
let history: [String] = try response.json([String].self)
print("History: \(history)")
// Raw WebSocket connection
let websocket = try await handle.websocket(path: "stream")
try await websocket.send(text: "hello")
let message = try await websocket.receive()
print("Received: \(message)")
await client.dispose()
Use the same client in server-side Swift (Vapor, Hummingbird, etc.):
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["server-counter"])
let count: Int = try await handle.action("increment", 1, as: Int.self)
print("Count: \(count)")
await client.dispose()
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
do {
_ = try await client.getOrCreate("user", ["user-123"])
.action("updateUsername", "ab", as: String.self)
} catch let error as ActorError {
print("Error code: \(error.code)")
print("Metadata: \(String(describing: error.metadata))")
}
await client.dispose()
If you need an untyped response, you can decode to JSONValue:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("data", ["raw"])
let value: JSONValue = try await handle.action("getRawPayload")
print("Raw value: \(value)")
await client.dispose()
Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing:
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet")
let client = RivetKitClient(config: config)
// Use compound keys for hierarchical addressing
let room = client.getOrCreate("chatRoom", ["org-acme", "general"])
let actorId = try await room.resolve()
print("Actor ID: \(actorId)")
await client.dispose()
Don't build keys with string interpolation like "org:\(userId)" when userId contains user data. Use arrays instead to prevent key injection attacks.
ClientConfig reads optional values from environment variables:
RIVET_NAMESPACE - Namespace (can also be in endpoint URL)RIVET_TOKEN - Authentication token (can also be in endpoint URL)RIVET_RUNNER - Runner name (defaults to "default")The endpoint parameter is always required. There is no default endpoint.
Endpoints support URL auth syntax:
https://namespace:token@api.rivet.dev
You can also pass the endpoint without auth and provide RIVET_NAMESPACE and RIVET_TOKEN separately. For serverless deployments, set the endpoint to your app's /api/rivet URL. See Endpoints for details.
RivetKitClient(config:) - Create a client with a configClientConfig - Configure endpoint, namespace, and tokenclient.get() / getOrCreate() / getForId() / create() - Get actor handlesclient.dispose() - Dispose the client and all connectionshandle.action(name, args..., as:) - Stateless action callhandle.connect() - Create a stateful connectionhandle.resolve() - Get the actor IDhandle.getGatewayUrl() - Get the raw gateway URLhandle.fetch(path, request:) - Raw HTTP requesthandle.websocket(path:) - Raw WebSocket connectionconn.action(name, args..., as:) - Action call over WebSocketconn.events(name, as:) - AsyncStream of typed eventsconn.statusChanges() - AsyncStream of status changesconn.errors() - AsyncStream of connection errorsconn.opens() - AsyncStream that yields on connection openconn.closes() - AsyncStream that yields on connection closeconn.currentStatus - Current connection statusconn.dispose() - Close the connectionActorConnStatus - Connection status enum (.idle, .connecting, .connected, .disconnected, .disposed)ActorError - Typed actor errors with group, code, message, metadataIf you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:
npx skills add rivet-dev/skills
Then use the rivetkit skill for backend guidance.
Weekly Installs
1.9K
Repository
GitHub Stars
8
First Seen
Jan 26, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
github-copilot1.9K
antigravity1.1K
codex1.1K
gemini-cli1.1K
opencode1.1K
claude-code1.1K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
JSONValue