Skip to content

Swift: Inspector

client.inspector() builds and memoizes one KizunaSyncInspector per client: the first call subscribes it to the engine's event bus, and every later call on the same client answers the same instance. It pairs the raw Inspect snapshot with a bounded ring of the refusals the client has seen, which is otherwise transient once the screen that showed a toast for it moves on.

Examples#

Read a snapshot#

// TodoApp/TodoListView.swift (excerpt)
import KizunaSync

let inspector = try await kizunasync.inspector()
let snapshot = try await inspector.snapshot()

Show the verdicts that arrived this session#

// TodoApp/TodoListView.swift (excerpt)
import KizunaSync

let inspector = try await kizunasync.inspector()
let stop = inspector.subscribe {
  Task { @MainActor in render(inspector.verdicts()) }
}

Parameters#

KizunaSyncClient.inspector() takes no arguments.

subscribe(_:)#

NameTypeRequiredDescription
onChange() -> VoidYesCalled after every engine event, not only the ones that enter the ring, and after clear(). It carries no payload, so a subscriber re-reads verdicts().

Returns#

NameTypeRequiredDescription
snapshot()() async throws -> KizunaSyncInspectorSnapshot—One coherent read of the queue: queued, depth, lastMutationId, cursor, clientId. lastMutationId is the push watermark, nil until a push lands, not a record of what is queued.
verdicts()() -> [KizunaSyncInspectorVerdict]—A copy of the ring, oldest first. It holds the 50 most recent entries and is not durable.
subscribe(_:)(@escaping () -> Void) -> () -> Void—Registers onChange and returns the unsubscribe function.
clear()() -> Void—Empties the ring and notifies every subscriber. It touches no stored data.

One entry of the ring#

NameTypeRequiredDescription
mutationIdString—The refused mutation, or the offender of a refused batch.
kindKizunaSyncInspectorVerdictKind—.rejected for a per-mutation refusal, .aborted for an atomic batch the server aborted, .overwritten for a column a peer's write took.
reasonString—The server's reason code for .rejected and .aborted. For .overwritten it is "<table>.<column>" instead, because a conflict mode is a configuration fact rather than a verdict.
atDate—When the client recorded the verdict, from the client's own clock.

Errors#

CodeCondition
ENGINE_UNAVAILABLEinspector() could not subscribe to the engine's event bus, because the client has no engine.

snapshot() throws whatever Inspect throws, since it calls that method underneath.

Notes#

KizunaSyncClient.inspector() is safe to call more than once: the second and later calls answer the first instance rather than building a second subscription. Dispose detaches it, so a screen that reads verdicts() after disposal sees whatever the ring held at that point and stops receiving new entries.

The ring is a short-lived read: it holds only the 50 most recent entries and is lost on restart. List overwrites is the durable twin for the .overwritten kind, backed by a table that survives a restart and carries the whole row rather than the ring's four fields alone.

Swift has no hooks at all, as Introduction states. Pair subscribe(_:) with @Published, an ObservableObject, or a Combine subject the way Subscribe to events already is in your app.

Found a problem? Edit this page on GitHub