From d54e0e2ea08f7508e818679c9c44018f7c35478f Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 27 Oct 2026 05:34:32 +0000 Subject: [PATCH 1/2] docs(zero-swift): main-thread storage waits, and schemas sharing a table name DESIGN.md says how long the main thread waits on a locked or busy database: about 5 s to open one, up to about 10 s in flush() or close() (the queued write, then one retry; it said two retries, but busyRetries is 1). The codegen README says two generated schemas with a table of the same name can't share a module, since both declare tx.mutate.. Amp-Thread-ID: https://ampcode.com/threads/T-01a17664-8fd0-76be-8fd5-75ac6e7aa0d8 Co-authored-by: tmm --- .changeset/storage-and-codegen-notes.md | 5 +++++ packages/zero-swift-cli/README.md | 5 +++++ swift/DESIGN.md | 10 +++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .changeset/storage-and-codegen-notes.md diff --git a/.changeset/storage-and-codegen-notes.md b/.changeset/storage-and-codegen-notes.md new file mode 100644 index 0000000000..bd68c39558 --- /dev/null +++ b/.changeset/storage-and-codegen-notes.md @@ -0,0 +1,5 @@ +--- +'zero-swift': patch +--- + +Documents that the main thread waits up to about 5 s when it opens a database another program holds locked, and up to about 10 s in `flush()` or `close()` on a busy one, and that two generated schemas with a table of the same name need modules of their own. diff --git a/packages/zero-swift-cli/README.md b/packages/zero-swift-cli/README.md index 8206c21c1f..a9f25da0e3 100644 --- a/packages/zero-swift-cli/README.md +++ b/packages/zero-swift-cli/README.md @@ -56,6 +56,11 @@ Rename a row struct whose name clashes with a type you use. A table named `list` generates `struct List`, which hides SwiftUI's `List`; set `tables.list.type` to `NoteList`, or set a `typePrefix`. +Two generated schemas with a table of the same name can't share a module: +both declare `tx.mutate.
`. Generate each into a module of its own. +Code that imports both names the row type, as in +`TypedTableMutator(tx.mutate).insert(…)`. + ### Names Tables, columns, relationships, JSON fields and enumeration values keep diff --git a/swift/DESIGN.md b/swift/DESIGN.md index 41a12e1892..def08aa1f8 100644 --- a/swift/DESIGN.md +++ b/swift/DESIGN.md @@ -219,10 +219,18 @@ Rationale: - If the store can't be opened, the client runs on memory and calls `onStorageFailure`, as JS does. A failed write stops writing and calls it too. A write that finds the database busy is kept and written with the - next one, or by `flush()` and `close()`, which try twice more before they + next one, or by `flush()` and `close()`, which try once more before they call it a failure. Stored data that doesn't decode is lost client state, as a corrupt ref count is in JS: the client deletes the database, starts a new one, and calls `onClientStateNotFound` if the app set it. +- These waits are on the main thread, each up to SQLite's 5 s busy + timeout. Opening a database another connection holds locked blocks + `Zero.init` for about 5 s before the client falls back to memory, and + `flush()` or `close()` on a busy database blocks for up to about 10 s: + the queued write, then the retry. With one process per store, only + another program with the file open, such as a SQLite browser, holds + such a lock. Opening and closing the store off the main actor would + avoid the waits; that is deferred. - A client writes a poke's snapshot only when its cookie is newer than the stored one, as Replicache persists, so a client of the group that is behind another doesn't overwrite it. From 9a41927d444d2dee128e7bba025688a1fec32392 Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 27 Oct 2026 05:48:53 +0000 Subject: [PATCH 2/2] docs(zero-swift): a flipped whereExists stopping a limit early JS's FlippedJoin reads only the first child row of a pushed parent, and when that read goes through a Take whose partition wasn't fetched before, the Take asserts 'Unexpected early return prevented full hydration'. Swift keeps working. Against JS patched to read the rest of such a fetch (as the comment by the assertion proposes), Swift matched on 126 such fuzz cases and 1,988 controls, seeds 1 to 10; the patched JS's incremental views matched a fresh hydration after every batch. Amp-Thread-ID: https://ampcode.com/threads/T-01a17664-8fd0-76be-8fd5-75ac6e7aa0d8 Co-authored-by: tmm --- .changeset/storage-and-codegen-notes.md | 2 +- swift/Sources/Zero/Zero.docc/JSParity.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.changeset/storage-and-codegen-notes.md b/.changeset/storage-and-codegen-notes.md index bd68c39558..4b9ff11045 100644 --- a/.changeset/storage-and-codegen-notes.md +++ b/.changeset/storage-and-codegen-notes.md @@ -2,4 +2,4 @@ 'zero-swift': patch --- -Documents that the main thread waits up to about 5 s when it opens a database another program holds locked, and up to about 10 s in `flush()` or `close()` on a busy one, and that two generated schemas with a table of the same name need modules of their own. +Documents that the main thread waits up to about 5 s when it opens a database another program holds locked, and up to about 10 s in `flush()` or `close()` on a busy one; that two generated schemas with a table of the same name need modules of their own; and that a flipped `whereExists` reaching a `limit` early, where JS asserts "Unexpected early return prevented full hydration", keeps working in Swift. diff --git a/swift/Sources/Zero/Zero.docc/JSParity.md b/swift/Sources/Zero/Zero.docc/JSParity.md index 86c3524a43..0873affe40 100644 --- a/swift/Sources/Zero/Zero.docc/JSParity.md +++ b/swift/Sources/Zero/Zero.docc/JSParity.md @@ -163,6 +163,13 @@ is written or sent. connection in the `error` state, a mutation that fails is committed and its `client` outcome is the error, and later mutations, `materialize`, and `run` fail with the same message. +- **A flipped `whereExists` stopping a `limit` early.** To learn whether a + row a change brings has a match, a flipped `whereExists` reads its + subquery's first row only. When that read goes through a `limit` whose + rows for this row weren't read before, JS asserts ("Unexpected early + return prevented full hydration") and the change fails as above. Swift + keeps working and shows the rows JS shows once its `Take` reads the rest + of such a fetch, as the comment by that assertion proposes. - **Nulls in columns that can't be null.** When the server sends null for a column the schema says isn't optional, a `String`, `Double`, `Int`, or `Bool` property reads as `""`, `0`, or `false`. JS hands the null