rivetkit-client-swift

Par rivet-dev · skills

Guide client Swift RivetKit. À utiliser pour les clients Swift qui se connectent à des Rivet Actors avec `RivetKitClient`, créent des handles d'actor, appellent des actions ou gèrent des connexions.

npx skills add https://github.com/rivet-dev/skills --skill rivetkit-client-swift

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/catch larges 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 config
  • ClientConfig - Configurer l'endpoint, le namespace et le token
  • client.get() / getOrCreate() / getForId() / create() - Obtenir les handles d'actors
  • client.dispose() - Disposer le client et toutes les connexions

ActorHandle

  • handle.action(name, args..., as:) - Appel d'action stateless
  • handle.connect() - Créer une connexion stateful
  • handle.resolve() - Obtenir l'ID de l'actor
  • handle.getGatewayUrl() - Obtenir l'URL brute de la gateway
  • handle.fetch(path, request:) - Requête HTTP brute
  • handle.websocket(path:) - Connexion WebSocket brute

ActorConnection

  • conn.action(name, args..., as:) - Appel d'action sur WebSocket
  • conn.events(name, as:) - AsyncStream d'événements typés
  • conn.statusChanges() - AsyncStream de changements de statut
  • conn.errors() - AsyncStream d'erreurs de connexion
  • conn.opens() - AsyncStream qui produit à l'ouverture de la connexion
  • conn.closes() - AsyncStream qui produit à la fermeture de la connexion
  • conn.currentStatus - Statut de connexion actuel
  • conn.dispose() - Fermer la connexion

Types

  • ActorConnStatus - Enum de statut de connexion (.idle, .connecting, .connected, .disconnected, .disposed)
  • ActorError - Erreurs d'actors typées avec group, code, message, metadata
  • JSONValue - 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.

Skills similaires