Client Swift RivetKit
Utilise cette compétence lors de la création de clients Swift qui se connectent à des Rivet Actors avec RivetKitClient.
Version
Version RivetKit : 2.2.0
Politique de gestion des erreurs
- Préférer un comportement fail-fast par défaut.
- Éviter les
do/catchlarges sauf si absolument nécessaire. - Si un bloc catch est utilisé, gérer l'erreur explicitement, au minimum en la journalisant.
Installation
Ajouter la dépendance de package Swift et importer 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")
]
)
]
Client minimal
URL de l'endpoint
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)
Champs explicites
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)
Stateless vs Stateful
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("counter", ["my-counter"])
// Stateless : chaque appel est indépendant
let current: Int = try await handle.action("getCount", as: Int.self)
print("Current count: \(current)")
// Stateful : garder une connexion ouverte pour les événements en temps réel
let conn = handle.connect()
// S'abonner aux événements avec 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()
Obtenir des Actors
import RivetKitClient
struct GameInput: Encodable {
let mode: String
}
let config = try ClientConfig(endpoint: "http://localhost:6420")
let client = RivetKitClient(config: config)
// Obtenir ou créer un actor
let room = client.getOrCreate("chatRoom", ["room-42"])
// Obtenir un actor existant (échoue s'il n'existe pas)
let existing = client.get("chatRoom", ["room-42"])
// Créer un nouvel actor avec une entrée
let created = try await client.create(
"game",
["game-1"],
options: CreateOptions(input: GameInput(mode: "ranked"))
)
// Obtenir un actor par ID
let byId = client.getForId("chatRoom", "actor-id")
// Résoudre l'ID de l'actor
let resolvedId = try await room.resolve()
print("Resolved ID: \(resolvedId)")
await client.dispose()
Les actions supportent des surcharges positionnelles pour 0–5 arguments :
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
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()
Si vous avez besoin de plus de 5 arguments, utilisez la solution JSON brute :
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
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()
Paramètres de connexion
import RivetKitClient
struct ConnParams: Encodable {
let authToken: String
}
let config = try ClientConfig(endpoint: "http://localhost:6420")
let client = RivetKitClient(config: config)
let chat = client.getOrCreate(
"chatRoom",
["general"],
options: GetOrCreateOptions(params: ConnParams(authToken: "jwt-token-here"))
)
let conn = chat.connect()
// Utiliser la connexion...
for await status in await conn.statusChanges() {
print("Status: \(status.rawValue)")
if status == .connected {
break
}
}
await conn.dispose()
await client.dispose()
S'abonner aux événements
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
// S'abonner aux événements avec AsyncStream
let messageTask = Task {
for await (from, body) in await conn.events("message", as: (String, String).self) {
print("\(from): \(body)")
}
}
// Pour les événements uniques, arrêter après réception
let gameOverTask = Task {
for await _ in await conn.events("gameOver", as: Void.self) {
print("done")
break
}
}
// Laisser s'exécuter un peu
try await Task.sleep(for: .seconds(5))
// Annuler une fois terminé
messageTask.cancel()
gameOverTask.cancel()
await conn.dispose()
await client.dispose()
Les flux d'événements supportent 0–5 arguments typés. Si vous avez besoin de valeurs brutes ou de plus de 5 arguments, utilisez JSONValue :
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
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()
Cycle de vie de la connexion
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
let client = RivetKitClient(config: config)
let conn = client.getOrCreate("chatRoom", ["general"]).connect()
// Surveiller les changements de statut (produit immédiatement le statut actuel)
let statusTask = Task {
for await status in await conn.statusChanges() {
print("status: \(status.rawValue)")
}
}
// Surveiller les erreurs
let errorTask = Task {
for await error in await conn.errors() {
print("error: \(error.group).\(error.code)")
}
}
// Surveiller les événements d'ouverture/fermeture
let openTask = Task {
for await _ in await conn.opens() {
print("connected")
}
}
let closeTask = Task {
for await _ in await conn.closes() {
print("disconnected")
}
}
// Vérifier le statut actuel
let current = await conn.currentStatus
print("Current status: \(current.rawValue)")
// Laisser s'exécuter un peu
try await Task.sleep(for: .seconds(5))
// Nettoyage
statusTask.cancel()
errorTask.cancel()
openTask.cancel()
closeTask.cancel()
await conn.dispose()
await client.dispose()
HTTP et WebSocket bas niveau
Pour les actors qui implémentent onRequest ou onWebSocket, vous pouvez les appeler directement :
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
let client = RivetKitClient(config: config)
let handle = client.getOrCreate("chatRoom", ["general"])
// Requête HTTP brute
let response = try await handle.fetch("history")
let history: [String] = try response.json([String].self)
print("History: \(history)")
// Connexion WebSocket brute
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()
Appeler depuis le backend
Utiliser le même client en Swift côté serveur (Vapor, Hummingbird, etc.) :
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
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()
Gestion des erreurs
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
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()
Si vous avez besoin d'une réponse non typée, vous pouvez décoder en JSONValue :
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
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()
Concepts
Clés
Les clés identifient de manière unique les instances d'actors. Utilisez des clés composites (tableaux) pour l'adressage hiérarchique :
import RivetKitClient
let config = try ClientConfig(endpoint: "http://localhost:6420")
let client = RivetKitClient(config: config)
// Utiliser des clés composites pour l'adressage hiérarchique
let room = client.getOrCreate("chatRoom", ["org-acme", "general"])
let actorId = try await room.resolve()
print("Actor ID: \(actorId)")
await client.dispose()
Ne construisez pas les clés avec interpolation de chaîne comme "org:\(userId)" quand userId contient des données utilisateur. Utilisez des tableaux à la place pour prévenir les attaques par injection de clés.
Variables d'environnement
ClientConfig lit les valeurs optionnelles des variables d'environnement :
RIVET_NAMESPACE- Namespace (peut aussi être dans l'URL de l'endpoint)RIVET_TOKEN- Token d'authentification (peut aussi être dans l'URL de l'endpoint)RIVET_RUNNER- Nom du runner (par défaut"default")
Le paramètre endpoint est toujours requis. Il n'y a pas d'endpoint par défaut.
Format de l'endpoint
Les endpoints supportent la syntaxe d'authentification URL :
https://namespace:token@api.rivet.dev
Vous pouvez aussi passer l'endpoint sans authentification et fournir RIVET_NAMESPACE et RIVET_TOKEN séparément. Pour les déploiements serverless, réglez l'endpoint sur l'URL /api/rivet de votre application. Voir Endpoints pour les détails.
Référence API
Client
RivetKitClient(config:)- Créer un client avec une configClientConfig- Configurer l'endpoint, le namespace et le tokenclient.get()/getOrCreate()/getForId()/create()- Obtenir les handles d'actorsclient.dispose()- Disposer le client et toutes les connexions
ActorHandle
handle.action(name, args..., as:)- Appel d'action statelesshandle.connect()- Créer une connexion statefulhandle.resolve()- Obtenir l'ID de l'actorhandle.getGatewayUrl()- Obtenir l'URL brute de la gatewayhandle.fetch(path, request:)- Requête HTTP brutehandle.websocket(path:)- Connexion WebSocket brute
ActorConnection
conn.action(name, args..., as:)- Appel d'action sur WebSocketconn.events(name, as:)- AsyncStream d'événements typésconn.statusChanges()- AsyncStream de changements de statutconn.errors()- AsyncStream d'erreurs de connexionconn.opens()- AsyncStream qui produit à l'ouverture de la connexionconn.closes()- AsyncStream qui produit à la fermeture de la connexionconn.currentStatus- Statut de connexion actuelconn.dispose()- Fermer la connexion
Types
ActorConnStatus- Enum de statut de connexion (.idle,.connecting,.connected,.disconnected,.disposed)ActorError- Erreurs d'actors typées avecgroup,code,message,metadataJSONValue- Valeur JSON brute pour les réponses non typées
Besoin de plus que le client ?
Si vous avez besoin de plus sur les Rivet Actors, les registries, ou RivetKit côté serveur, ajoutez la compétence principale :
npx skills add rivet-dev/skills
Ensuite utilisez la compétence rivetkit pour les conseils backend.