Skip to content

Commit 61b9cea

Browse files
authored
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.
1 parent 5e20cb0 commit 61b9cea

4 files changed

Lines changed: 248 additions & 40 deletions

File tree

API.md

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,20 @@ SELECT cloudsync_network_set_apikey('your_api_key');
481481

482482
---
483483

484+
### Error handling
485+
486+
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+
484498
### `cloudsync_network_send_changes()`
485499

486500
**Description:** Sends all unsent local changes to the remote server.
@@ -490,18 +504,22 @@ SELECT cloudsync_network_set_apikey('your_api_key');
490504
**Returns:** A JSON string with the send result:
491505

492506
```json
493-
{"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": {...}}}
494508
```
495509

496510
- `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"`.
497511
- `send.localVersion`: The latest local database version.
498512
- `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.
499514

500515
**Example:**
501516

502517
```sql
503518
SELECT cloudsync_network_send_changes();
504519
-- '{"send":{"status":"synced","localVersion":5,"serverVersion":5}}'
520+
521+
-- 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"}}}'
505523
```
506524

507525
---
@@ -515,24 +533,28 @@ If a package of new changes is already available for the local site, the server
515533
This function is designed to be called periodically to keep the local database in sync.
516534
To force an update and wait for changes (with a timeout), use [`cloudsync_network_sync(wait_ms, max_retries)`].
517535

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.
519537

520538
**Parameters:** None.
521539

522540
**Returns:** A JSON string with the receive result:
523541

524542
```json
525-
{"receive": {"rows": N, "tables": ["table1", "table2"]}}
543+
{"receive": {"rows": N, "tables": ["table1", "table2"], "error": "..."}}
526544
```
527545

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.
530549

531550
**Example:**
532551

533552
```sql
534553
SELECT cloudsync_network_check_changes();
535554
-- '{"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."}}'
536558
```
537559

538560
---
@@ -553,16 +575,18 @@ SELECT cloudsync_network_check_changes();
553575

554576
```json
555577
{
556-
"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": "..."}
558580
}
559581
```
560582

561583
- `send.status`: The current sync state — `"synced"`, `"syncing"`, `"out-of-sync"`, or `"error"`.
562584
- `send.localVersion`: The latest local database version.
563585
- `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.
566590

567591
**Example:**
568592

@@ -573,6 +597,9 @@ SELECT cloudsync_network_sync();
573597

574598
-- Perform a synchronization cycle with custom retry settings
575599
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."}}'
576603
```
577604

578605
---

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

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).
19+
720
## [1.0.14] - 2026-04-15
821

922
### Fixed

src/cloudsync.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
extern "C" {
1919
#endif
2020

21-
#define CLOUDSYNC_VERSION "1.0.14"
21+
#define CLOUDSYNC_VERSION "1.0.15"
2222
#define CLOUDSYNC_MAX_TABLENAME_LEN 512
2323

2424
#define CLOUDSYNC_VALUE_NOTSET -1

0 commit comments

Comments
 (0)