Swift client
OnebaseClient is the onebase SDK for Swift apps (iOS 15+ / macOS 12+, no package
dependencies): one WebSocket carries live queries, calls and the auth token. It lives in
clients/swift in the
repository.
Add it by URL — released versions are published to onebase-swift, because SwiftPM needs the package at the root of a repository:
// Package.swift
dependencies: [
.package(url: "https://github.com/abdullah-bl/onebase-swift.git", from: "0.1.0"),
]
// target: .product(name: "OnebaseClient", package: "onebase-swift")Working from a clone of onebase, or before the first release? Use the folder directly:
.package(path: "../onebase/clients/swift"), with package: "swift" in the product.
#Connect and use
import OnebaseClient
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let client = OnebaseClient(url: "https://api.example.com", offlineDirectory: documents)
client.onConnectionChange = { state in } // .connecting / .open / .closed
// Live queries: onUpdate runs with every new result, on the main queue.
var subscription: OnebaseSubscription?
subscription = client.subscribe("notes:list", [:]) { value in
let notes = value?.array ?? []
// render them
}
subscription?.cancel() // when the view goes away
let id = try await client.mutation("notes:add", ["text": "Hello"])
let best = try await client.query("notes:best", [:])
try await client.action("emails:sendWelcome", ["to": "someone@example.com"])Values are OnebaseValue: a JSON value. Build arguments from literals
(["text": "Hello", "count": 3]) or from your own Codable types (try OnebaseValue(note));
read results with value?.array, value?["user", "name"]?.string, value?.int, and so on.
#Optimistic updates
try await client.mutation("notes:add", ["text": "Hello"]) { store in
let current = store.get("notes:list")?.array ?? []
store.set("notes:list", [:], .array(current + [["text": "Hello"]]))
}The closure sees the subscribed queries' current results and overrides them until the mutation
finishes; the server's real results then take over (the server sends a mutation's result and
its query updates together, so there's no flicker). Setting a query to nil shows it as
loading. Keep it a pure function of store — it may run again on top of results that arrive
while the mutation is in flight.
#Auth
Built-in password sign-in goes over HTTP; the token is kept in the Keychain and sent on every (re)connect:
client.onAuthChange = { signedIn in } // false when the token stops checking out
let token = try await client.signInPassword(email: "someone@example.com", password: "correct horse")
let codeFlowToken = try await client.signInPassword(email: "…", password: "…", flow: "signUp") // nil: a code was emailed
try await client.signOut()Any other token (an external provider's) works with client.setToken("…"). Pass your own
OnebaseTokenStore to store it somewhere else.
#Offline
With an offlineDirectory (recommended), the client keeps working through drops in
connectivity and cold starts while offline:
- the newest 100 query results (
OfflineStore(directory:maxCachedQueries:)) are cached on disk and shown while disconnected, then replaced by live data; - mutations made while disconnected are queued on disk and sent, in order, once the connection returns — across app restarts;
client.queuedMutationCountandclient.clearQueuedMutations()manage the queue; failures of mutations queued by an earlier run surface throughonQueuedMutationFailure;- each queued mutation is sent at most once per run — one lost with the connection is dropped, never silently re-sent;
- cached results are held back until the server confirms the same user is asking, and cleared when the user changes.
See Offline for the behavior all clients share, and Client protocol for the wire contract.
#OnebaseValue
public enum OnebaseValue {
case null, bool(Bool), int(Int64), double(Double), string(String)
case array([OnebaseValue]), object([String: OnebaseValue])
}Read it with value?.string, .int, .double, .bool, .array, .object and
value?["user", "name"]; whole numbers arrive as .int, and it's Codable, Equatable and
literal-expressible (let v: OnebaseValue = ["count": 3]).