You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 35c0f50
Browse filesBrowse the repository at this point in the historyBrowse files
feat: bound what one cloudsync_payload_chunks() call prepares (#77)
A single cloudsync_payload_chunks() call drained the whole window, so a large
backlog produced one unbounded run of work -- the shape behind the prepare
stall, where a job kept reaching its deadline before it finished.
Two optional arguments now bound it, declared last so the existing positional
arguments 1..7 keep their meaning:
* max_window_bytes caps the uncompressed bytes one call prepares
* resume_window_bytes carries the budget spent so far, for a stream fetched
one chunk per call
and two outputs report the outcome: window_capped, true when the scan stopped
on the budget rather than because the window was drained, and window_bytes, the
budget spent. Without max_window_bytes the function behaves exactly as before.
A capped call always stops on a db_version boundary, so the receive checkpoint
stays on a complete version, and fragment payloads count toward the budget.
Continuation is the caller's job: nothing loops automatically, and the API docs
qualify what receive.complete=true actually means (#83).
Ships as 1.2.0, with the PostgreSQL migration cloudsync--1.1--1.2.sql. The
migration drops the old 7-argument function before creating the new one:
CREATE OR REPLACE cannot change a return type, and a defaulted new parameter
would leave every 7-argument call ambiguous.
Also pulls the Supabase base image from Docker Hub rather than public.ecr.aws.
Both tags resolve to the same manifest digest, and CI already authenticates to
Docker Hub, so the supabase image jobs stop failing on the shared anonymous
ECR Public data quota.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Copy file name to clipboardExpand all lines: CHANGELOG.md
+7-1Lines changed: 7 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,11 +4,17 @@ All notable changes to this project will be documented in this file.
4
4
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
7
-
## [Unreleased]
7
+
## [1.2.0] - 2026-09-28
8
8
9
9
### Added
10
10
11
11
- **`cloudsync_network_send_changes()` accepts an optional limit on how many local database versions to send**, so a large backlog can be uploaded in bounded steps instead of one batch. A send is all or nothing: the server confirms the window only once every chunk of the batch has applied, and a failed batch is re-sent whole. After a long offline period or a bulk import that batch can be large enough to keep failing, and each attempt re-uploads everything. `cloudsync_network_send_changes(max_db_versions)` sends at most that many local transactions, so each call is an independently confirmed batch and a failure costs one bounded window rather than the whole backlog. Call it repeatedly with the same value until `send.status` leaves `out-of-sync`; `send.localVersion` keeps reporting the newest local version so the remaining backlog stays visible. Received changes share the database version counter, so versions holding no local change are skipped instead of consuming the budget. The no-argument form is unchanged.
12
+
- **`cloudsync_payload_chunks()` can bound how much one call prepares.** Preparing a large history is unbounded work: a big enough tenant cannot finish inside a caller's time budget, and because nothing is durable until the stream reports `is_final`, an attempt that runs out of time keeps no progress and the next one restarts from the first chunk. The new `max_window_bytes` argument ends the stream once roughly that many payload bytes have been emitted, at the next complete database version. What comes back is an ordinary *complete* stream over a smaller window — `is_final` with `watermark_db_version` lowered to that point — so the caller checkpoints there and calls again to continue, with no resumable state to keep anywhere. A new `window_capped` output says the stream stopped on the budget rather than because the window was drained, so more changes exist past the watermark. A caller that fetches one chunk per query and resumes through `resume_*` also passes the `window_bytes` output back as `resume_window_bytes`, which carries the budget spent across those queries exactly as `resume_db_version` carries the stream position; without it each query would count only its own chunk and the budget would never be reached. Two properties are worth knowing: a window always ends on a database version boundary, so a transaction larger than the budget is still emitted whole and the budget is an approximate floor rather than a hard ceiling; and a window never ends empty, so repeated calls always make progress. Reaching that boundary can require ending a chunk before it is full, so a capped drain packs the same changes into slightly more chunks than an uncapped one. Unset, the function behaves exactly as before.
13
+
14
+
### Changed
15
+
16
+
-**SQLite: an explicit `NULL` for `resume_db_version` on `cloudsync_payload_chunks()` now means "not given"**, matching what it has always meant for `filter_site_id` and on PostgreSQL. It was previously read as a resume point of database version 0, which silently ignored `since_db_version` and restarted the scan at the beginning of the window. Reaching a later argument requires passing `NULL` for the ones before it, so this is easy to hit: `cloudsync_payload_chunks(100, NULL, NULL, false, NULL, NULL, NULL, 1048576)` used to replay from the start of the history instead of resuming after version 100.
17
+
-**The PostgreSQL extension version moves to `1.2`.**`cloudsync_payload_chunks()` gained two arguments and two output columns, so existing deployments need `ALTER EXTENSION cloudsync UPDATE;` after installing the new binary. The upgrade script replaces the function: a `CREATE OR REPLACE` cannot change a return type, and leaving the old seven-argument version in place would make every existing call ambiguous against the new eight-argument one.
0 commit comments