Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion content/docs/(reference)/swift-sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ When a catch-up learns that the event log no longer reaches the copy's cursor, i

Call `hydrate(types:tier:edgeTypes:)` with the slice the copy was built under. The copy is filled again **over the rows it holds**, without clearing them first, and the `HydrateReport` says `inPlace`. The copy stays `.stale` through the fill, and through one that was interrupted, until a fill completes. If the instance, the credential's reach or the slice differs, the copy is cleared first and `inPlace` is `false`.

**While the app holds a `changes()` stream, the copy refills itself.** A copy can go stale while the device sleeps or is offline for longer than the instance keeps events. When the follow finds that the event log no longer holds the copy's cursor, it fills the copy again over the slice it holds, as `hydrate` does, and the streams hear `.refreshed(.hydrated)` instead of a stop. An attempt that fails because the network or the server failed is announced as `.refreshed(.unfinished)` and retried after one second, then further apart, up to five minutes apart.

The copy refills itself only when it expects the refill to be in place, so the streams are told `.stopped(.copyExpired)` instead in these cases:

- Another instance answers at the server's address. The reason is `instance_changed`.
- The copy was already stale when the follow started, for example after `useKey(_:)` or when the app opens a stale store. The reason is `catchup_too_old`. The SDK can't confirm that the credential still reads under the reach the copy was built under, so the refill is left to the app.

If the instance refuses the refill, for example because the credential ended, the streams are told `.stopped` with that error.

**A copy runs one hydration at a time.** A `hydrate` call made while the refill runs waits for it and then runs. If the refill already filled the same slice under the same key and the copy is still whole, the call returns the refill's report instead of filling the copy again. A refill that finds the app's own hydration running waits for it, and leaves a copy that hydration made whole. A `catchUp` call made while the refill runs also waits for it, then catches up from the cursor the refill left.

## Read and write

Reads come from the copy. Every write shows in the copy at once, with or without a connection, and waits in the queue as a `QueuedWrite`.
Expand Down Expand Up @@ -195,7 +206,7 @@ Each `Change` names the `itemId` or `edgeId` it concerns, and its `origin` says
| `.queueDrained`, `.queueNotDrained` | The drain the stream ran when the server came back ended, or stopped with an error. | Nothing: the stream drains by itself. |
| `.stopped(MarfaError)` | What fed the stream stopped. Local writes are still told. | See below. |

**Two stops need the app's help.** On `.copyExpired`, hydrate again: the queue survives. Its `reason`, such as `reach_changed` or `instance_changed`, is an open string, so keep a `default`. On `.unauthorized` or `.signedOut`, give the copy a new credential with `useKey(_:)`.
**Two stops need the app's help.** On `.copyExpired`, hydrate again: the queue survives. Read `status().hydration` first: a `.stale` copy is told this stop only when it [can't refill itself](#refill-a-stale-copy), and hydrating the same slice fills it again in place where the instance and reach allow. Any other expired copy must be filled from scratch. The `reason`, such as `reach_changed`, `instance_changed` or `catchup_too_old`, is an open string, so keep a `default`. On `.unauthorized` or `.signedOut`, give the copy a new credential with `useKey(_:)`.

## Send writes and read verdicts

Expand Down
2 changes: 2 additions & 0 deletions content/docs/guides/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ This is how the CLI's device commands report `catchup_too_old` and `reach_change

Run `marfa device hydrate` again. After `catchup_too_old`, `marfa device status` reports the copy's hydration as `stale`, and the copy keeps answering reads, marked `stale`, while the hydrate fills it in place. Until the hydrate finishes, `marfa device drain` and `marfa device catch-up` keep answering `copy_expired` with `catchup_too_old`. If the credential no longer reads one of the types you held, leave that type out of `--types`, or the hydrate is refused with `type_not_permitted`.

An app built on the Swift SDK that holds a `changes()` stream refills a stale copy by itself. [Refill a stale copy](/swift-sdk#refill-a-stale-copy) says how.

For a folder, run `marfa folders push` again instead: it builds the copy again itself. [Sync a folder](/guides/sync-a-folder#replace-a-folders-key) covers a key you replaced.

</Accordion>
Expand Down
Loading