Cached log: a remote log with a local replica

The board reads from a local replica and writes to the host. CachedLog is the SyncLog that makes that one object: a remote log (the host, through RemoteLog) and a cache (the IndexedDB log in a browser, a MemoryLog anywhere else).

A pull while the host is unreachable answers from the replica alone — a degraded, read-only view, announced through onRemoteError (the board shows offline over real cards; the CLI answers read verbs) — unless the replica is empty, in which case there is nothing to show and the pull fails. A refused credential (401/403) is not an outage and is never absorbed: the pull fails so the caller can forget the token or ask for a sign-in, instead of serving a replica the reader is no longer allowed to see. And while the replica is stale a push is refused (StaleReplicaError): a writer must not decide what is new against a view the host has moved past — an importer would resend ids the host holds. Sync, then retry. There is no offline write queue: a push while the host is unreachable fails, and the card stays as it was. That is the design's "not designed here" (DESIGN.md §7.2) and it stays that way until it hurts.

const log = new CachedLog({
  remote: new RemoteLog({ client: new ServiceClient({ host, token }) }),
  cache: new IndexedDBLog(replicaDbName(new URL(host).host, principal)),
})
const store = new VirtaStore({ log, identity })
await store.load()          // the cache, then the delta
setInterval(() => store.sync(), 10_000)   // other writers' events arrive here