Skip to content

Data architecture, source of truth, caching, offline sync, and conflict

In a local-first app, the user must see a successful local edit even when the network is unavailable. Durable device state drives the interface while synchronization reconciles it with server state.

Write locally and enqueue atomically

struct PendingOperation: Codable, Sendable {
let operationID: UUID
let noteID: UUID
let baseRevision: Int
let change: NoteChange
}
protocol LocalNoteStore: Sendable {
func apply(_ change: NoteChange, enqueueing operation: PendingOperation) async throws
func pendingOperations() async throws -> [PendingOperation]
func acknowledge(operationID: UUID, serverRevision: Int) async throws
}

The note change and outbox record must commit together. A crash cannot leave a visible edit that synchronization forgot, or an operation for an edit that never committed.

Make retries idempotent

The server records operationID. Repeating the same upload returns the original result instead of applying the mutation twice. A sync cursor tracks which remote changes the device has incorporated, but it is not a replacement for per-operation acknowledgement.

Choose and expose conflict policy

Last-write-wins is simple but can silently discard work. Field Notes can merge independent fields automatically and present a visible choice when both device and server changed the same body from one base revision. Preserve both versions until resolution succeeds.

A cache is disposable by contract. The note library and pending outbox are durable product state. Calling the network the only source of truth hides offline edits and makes transient availability control the interface.

Series navigation

References

  • SwiftData provides an Apple persistence framework suitable for durable local models.
  • URLSession supplies network transfer tasks and cancellation.