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
fix: surface send lastFailure and receive errors in sync JSON output
Forward the server's lastFailure object in the output of
cloudsync_network_send_changes() and cloudsync_network_sync() so callers
can see why the server rejected an apply job.
When cloudsync_payload_apply fails (unknown schema hash, invalid
checksum, decompression error), all three sync functions now return
structured JSON with a receive.error field instead of failing silently.
Endpoint/network errors still raise a SQL error.
Error-handling contract: if you get JSON back, the server was reachable.
If you get a SQL error, connectivity or configuration is broken.
The sync functions follow a consistent error-handling contract:
487
+
488
+
| Error type | Behavior |
489
+
|---|---|
490
+
|**Endpoint/network errors** (server unreachable, auth failure, bad URL) | SQL error — the function could not execute. |
491
+
|**Apply errors** (`cloudsync_payload_apply` failures — unknown schema hash, invalid checksum, decompression error) | Structured JSON — a `receive.error` string field is included in the response. |
492
+
|**Server-reported apply job failures** (the server processed the request but its own apply job failed) | Structured JSON — a `send.lastFailure` object is included in the response. |
493
+
494
+
This means: if you get JSON back, the server was reachable and the network protocol ran. If you get a SQL error, connectivity or configuration is broken.
495
+
496
+
---
497
+
484
498
### `cloudsync_network_send_changes()`
485
499
486
500
**Description:** Sends all unsent local changes to the remote server.
{"send": {"status": "synced|syncing|out-of-sync|error", "localVersion": N, "serverVersion": N}}
507
+
{"send": {"status": "synced|syncing|out-of-sync|error", "localVersion": N, "serverVersion": N, "lastFailure": {...}}}
494
508
```
495
509
496
510
-`send.status`: The current sync state — `"synced"` (all changes confirmed), `"syncing"` (changes sent but not yet confirmed), `"out-of-sync"` (local changes pending or gaps detected), or `"error"`.
497
511
-`send.localVersion`: The latest local database version.
498
512
-`send.serverVersion`: The latest version confirmed by the server.
513
+
-`send.lastFailure` (optional): Present only when the server reports a failed apply job. The object is forwarded verbatim from the server and typically includes `jobId`, `code`, `message`, `retryable`, and `failedAt`. It is emitted regardless of `status` so callers can detect server-side failures during `"syncing"` or even after the state has nominally recovered.
-- With a server-reported failure (e.g. unknown schema hash on the server side):
522
+
-- '{"send":{"status":"out-of-sync","localVersion":1,"serverVersion":0,"lastFailure":{"jobId":44961,"code":"internal_error","message":"cloudsync operation failed: Cannot apply the received payload because the schema hash is unknown 4288148391734624266.","retryable":true,"failedAt":"2026-04-15T22:21:09.018606Z"}}}'
505
523
```
506
524
507
525
---
@@ -515,24 +533,28 @@ If a package of new changes is already available for the local site, the server
515
533
This function is designed to be called periodically to keep the local database in sync.
516
534
To force an update and wait for changes (with a timeout), use [`cloudsync_network_sync(wait_ms, max_retries)`].
517
535
518
-
If the network is misconfigured or the remote server is unreachable, the function returns an error.
536
+
If the network is misconfigured or the remote server is unreachable, the function raises a SQL error. If the received payload cannot be applied locally (for example because of an unknown schema hash), the error is returned as a `receive.error` field in the JSON response.
519
537
520
538
**Parameters:** None.
521
539
522
540
**Returns:** A JSON string with the receive result:
523
541
524
542
```json
525
-
{"receive": {"rows": N, "tables": ["table1", "table2"]}}
543
+
{"receive": {"rows": N, "tables": ["table1", "table2"], "error": "..."}}
526
544
```
527
545
528
-
-`receive.rows`: The number of rows received and applied to the local database.
529
-
-`receive.tables`: An array of table names that received changes. Empty (`[]`) if no changes were applied.
546
+
-`receive.rows`: The number of rows received and applied to the local database. `0` when the receive phase failed.
547
+
-`receive.tables`: An array of table names that received changes. Empty (`[]`) if no changes were applied or the receive phase failed.
548
+
-`receive.error` (optional): Present when `cloudsync_payload_apply` failed. Contains a human-readable error message describing why the received payload could not be applied.
530
549
531
550
**Example:**
532
551
533
552
```sql
534
553
SELECT cloudsync_network_check_changes();
535
554
-- '{"receive":{"rows":3,"tables":["tasks"]}}'
555
+
556
+
-- With an apply error:
557
+
-- '{"receive":{"rows":0,"tables":[],"error":"Cannot apply the received payload because the schema hash is unknown 7218827471400075525."}}'
"send": {"status": "synced|syncing|out-of-sync|error", "localVersion": N, "serverVersion": N},
557
-
"receive": {"rows": N, "tables": ["table1", "table2"]}
578
+
"send": {"status": "synced|syncing|out-of-sync|error", "localVersion": N, "serverVersion": N, "lastFailure": {...}},
579
+
"receive": {"rows": N, "tables": ["table1", "table2"], "error": "..."}
558
580
}
559
581
```
560
582
561
583
-`send.status`: The current sync state — `"synced"`, `"syncing"`, `"out-of-sync"`, or `"error"`.
562
584
-`send.localVersion`: The latest local database version.
563
585
-`send.serverVersion`: The latest version confirmed by the server.
564
-
-`receive.rows`: The number of rows received and applied during the check phase.
565
-
-`receive.tables`: An array of table names that received changes. Empty (`[]`) if no changes were applied.
586
+
-`send.lastFailure` (optional): Same semantics as in [`cloudsync_network_send_changes()`](#cloudsync_network_send_changes) — forwarded verbatim from the server whenever a failed apply job is reported, regardless of `status`.
587
+
-`receive.rows`: The number of rows received and applied during the check phase. `0` when the receive phase failed.
588
+
-`receive.tables`: An array of table names that received changes. Empty (`[]`) if no changes were applied or the receive phase failed.
589
+
-`receive.error` (optional): Present when `cloudsync_payload_apply` failed (for example `"Cannot apply the received payload because the schema hash is unknown 7218827471400075525."`). The send result is always preserved so the caller can tell that local changes reached the server even when applying incoming changes failed. The retry loop breaks immediately on apply errors, since failures like schema-hash mismatches do not heal across retries. Endpoint/network errors during the receive phase raise a SQL error instead.
-- Perform a synchronization cycle with custom retry settings
575
599
SELECT cloudsync_network_sync(500, 3);
600
+
601
+
-- Receive phase failed but send phase completed — the error is surfaced in JSON, not as a SQL error:
602
+
-- '{"send":{"status":"synced","localVersion":5,"serverVersion":5},"receive":{"rows":0,"tables":[],"error":"Cannot apply the received payload because the schema hash is unknown 7218827471400075525."}}'
Copy file name to clipboardExpand all lines: CHANGELOG.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,19 @@ 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
+
## [1.0.15] - 2026-04-16
8
+
9
+
### Fixed
10
+
11
+
-**Silent receive failures**: When `cloudsync_payload_apply` failed during the receive phase (for example with an unknown schema hash, invalid checksum, or decompression error), the error was stored only on the internal cloudsync context and never propagated to the SQL caller. Both `cloudsync_network_check_changes()` and `cloudsync_network_sync()` silently returned no result. Apply errors are now surfaced as a `receive.error` field in the JSON response.
12
+
13
+
### Changed
14
+
15
+
-**Error handling contract**: endpoint/network errors (server unreachable, auth failure, bad URL) always raise a SQL error. Processing errors (`cloudsync_payload_apply` failures) are returned as structured JSON via `receive.error` or `send.lastFailure`, so callers can inspect and log them without try/catch logic.
16
+
-**`cloudsync_network_send_changes()` output** now includes a `send.lastFailure` object whenever the server reports one (raw pass-through of the server's `lastFailure` — `jobId`, `code`, `message`, `retryable`, `failedAt`, …), regardless of whether the computed `send.status` is `synced`, `syncing`, or `out-of-sync`. The field is omitted when the server does not report a failure.
17
+
-**`cloudsync_network_check_changes()` output** now includes a `receive.error` string when `cloudsync_payload_apply` fails, instead of silently returning NULL. Endpoint/network errors still raise a SQL error.
18
+
-**`cloudsync_network_sync()` output** now mirrors the same `send.lastFailure` field and, if the receive phase has a processing error (`cloudsync_payload_apply` failure), returns structured JSON with a `receive.error` string rather than failing silently. The send result is always preserved so callers can tell that their local changes reached the server even when applying incoming changes failed. Endpoint/network errors during the receive phase still raise a SQL error. The receive retry loop breaks immediately on processing errors (a schema-hash mismatch will not heal across retries).
0 commit comments