@baukit/sync-client 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,14 +53,20 @@ non-Expo consumers never import this subpath.
53
53
 
54
54
  ## Transport
55
55
 
56
- The original `SyncTransport` constructor wraps an injected `fetch` and auth header provider, and
57
- turns every failure into a typed error:
56
+ The fetch-backed `SyncTransport` constructor wraps an injected `fetch` and auth header provider.
57
+ It classifies failures as follows:
58
58
 
59
- - `SyncAuthError` for 401, never retryable;
60
- - `SyncPartitionMismatchError` when the body carries the configured partition-mismatch code,
61
- meaning the local database belongs to an erased or replaced owner;
62
- - `SyncTransportError` otherwise, with `retryable` true for 408, 429, and 5xx, plus network and
63
- body-read failures.
59
+ - `SyncAuthError` for 401, never retryable
60
+ - `SyncPartitionMismatchError` when the body carries the configured partition-mismatch code
61
+ - `SyncRateLimitError` for 429, always retryable and carrying `retryAt` plus the raw `retryAfter`
62
+ header
63
+ - `SyncNetworkError` when `fetch` or response-body reading fails, always retryable
64
+ - `SyncServerError` for other HTTP failures, retryable for 408 and 5xx
65
+ - `SyncPayloadCompatibilityError` when a successful response is not valid JSON, never retryable
66
+
67
+ `Retry-After` accepts integer delta seconds and HTTP dates. Missing, invalid, negative, and past
68
+ values use `now + retryAfterFallbackMs`. The default fallback is 60 seconds. Tests and products
69
+ with another policy can inject `now` and `retryAfterFallbackMs` in the fetch-backed constructor.
64
70
 
65
71
  Auth headers resolve per attempt, so a token refreshed between retries is picked up. The
66
72
  partition header is sent only when the caller passes a `partitionId`, and both its name and the
@@ -86,8 +92,8 @@ await transport.request('/api/v1/sync/push', {
86
92
 
87
93
  This mode builds the relative path, query string, JSON body, and partition header, then delegates.
88
94
  The request function owns decoding and errors, which keeps the product API client's existing auth
89
- recovery intact. The fetch-backed constructor remains unchanged for products that want
90
- `SyncTransportError` classification from response status and body.
95
+ recovery intact. Use the fetch-backed constructor when the product wants package-owned response
96
+ classification.
91
97
 
92
98
  ## Status store
93
99
 
@@ -99,13 +105,24 @@ wrong:
99
105
  still reads `attention`;
100
106
  - unsent work outranks a bare error, so a failed run with pending changes reads `pending`,
101
107
  not `error`;
102
- - the first run is the initial pull; later runs are settled activity.
108
+ - the first run is the initial pull; later runs are settled activity;
109
+ - a failure updates `lastAttemptAt` without changing `lastSuccessAt` or clearing `pendingCount`.
110
+
111
+ Call `setSyncing(attemptAt)` when an attempt starts. `setIdle(successAt)` records a successful
112
+ attempt and clears work only after the product's success predicate passes. `setFailure` accepts
113
+ machine-readable metadata with one of these kinds: `auth`, `partition_mismatch`, `rate_limited`,
114
+ `network`, `server`, `payload_compatibility`, or `local_apply`. Product copy remains in the
115
+ separate `error` field.
116
+
117
+ Rate-limit metadata contains `retryAt`. Other retryable failures can pass `retryAt` in the
118
+ `SyncFailureUpdate` argument or call `setRetrying(retryAt)`. Snapshots expose both `retrying` and
119
+ `retryAt`, so a screen can distinguish scheduled retries and rate limits from an offline state.
103
120
 
104
121
  `refreshRevision` increments whenever a run delivers a new authoritative snapshot.
105
122
  `deriveLocalStoreReadiness` compares it against what a consumer has actually rendered, which is
106
123
  how a cold start avoids flashing an empty state over data that is still arriving.
107
- `deriveInitialSyncState` maps a snapshot onto the contract's `unknown` / `syncing` /
108
- `offline-cached` / `settled` vocabulary.
124
+ `deriveInitialSyncState` maps a snapshot onto `unknown`, `syncing`, `offline-cached`,
125
+ `sync-error-cached`, or `settled`. Only a first-run `network` failure maps to `offline-cached`.
109
126
 
110
127
  The store is generic over its attention item and defaults to the existing
111
128
  `{ entityType, entityId }` shape. This keeps one store API while allowing a product to retain the
@@ -127,7 +144,12 @@ For stores and screens that use API-style names, `toSnakeCaseSnapshot` returns a
127
144
  ```ts
128
145
  const snapshot = toSnakeCaseSnapshot(status.getSnapshot());
129
146
 
147
+ snapshot.last_attempt_at;
148
+ snapshot.last_success_at;
130
149
  snapshot.last_sync_at;
150
+ snapshot.failure;
151
+ snapshot.retrying;
152
+ snapshot.retry_at;
131
153
  snapshot.pending_count;
132
154
  snapshot.initial_pull_status;
133
155
  snapshot.refresh_revision;
@@ -137,6 +159,29 @@ snapshot.security_block;
137
159
  `status`, `error`, and `attention` keep their names. Attention items also keep their original
138
160
  shape.
139
161
 
162
+ ### Timestamp migration
163
+
164
+ `lastSyncAt` and `last_sync_at` are deprecated compatibility fields for one release cycle. They
165
+ are derived from `lastSuccessAt` and `last_success_at`. New code must not write or persist them as
166
+ attempt timestamps.
167
+
168
+ Replace `snapshot.lastSyncAt` with `snapshot.lastSuccessAt`. Replace `snapshot.last_sync_at` with
169
+ `snapshot.last_success_at`. Persist both attempt and success timestamps, then hydrate with an
170
+ object:
171
+
172
+ ```ts
173
+ status.hydrate({
174
+ lastAttemptAt: persisted.lastAttemptAt,
175
+ lastSuccessAt: persisted.lastSuccessAt,
176
+ attention: persisted.attention,
177
+ pendingCount: persisted.pendingCount,
178
+ });
179
+ ```
180
+
181
+ The deprecated `hydrate(lastSyncAt, attention, pendingCount)` overload treats the old value as
182
+ both the last attempt and last success. Remove that overload call when the persisted schema has
183
+ both fields.
184
+
140
185
  ## Push batch ranking
141
186
 
142
187
  `rankPushBatch` orders one push batch so a parent is never sent after its children.
@@ -163,3 +208,48 @@ missing. Held-back entities do not consume batch-size slots.
163
208
 
164
209
  Entity names, the dependency order, and the hold-back predicate are all product inputs. Baukit
165
210
  knows only that a rank exists.
211
+
212
+ ## Payload validators
213
+
214
+ Call `validatePushOutcomeCoverage` before acknowledging any submitted change. Pass one key reader
215
+ for submitted entities and one for outcomes, then combine all accepted and rejected outcomes in
216
+ the second argument. The function throws `SyncPayloadCompatibilityError` if any submitted entity
217
+ has no outcome.
218
+
219
+ Call `validatePullPage(currentCursor, page, compare)` before following `hasMore`. Cursors are
220
+ opaque. The injected comparator defines their order. A regressing cursor, a non-finite comparison,
221
+ or `hasMore` without cursor progress throws `SyncPayloadCompatibilityError`.
222
+
223
+ `commitCursorAfterLocalTransaction` runs the supplied local transaction first. It invokes
224
+ `commitCursor` only after that promise resolves, and wraps local failures in `SyncLocalApplyError`.
225
+ This ordering prevents a failed local apply from skipping remote changes on the next pull.
226
+
227
+ ## Conformance harness
228
+
229
+ `@baukit/sync-client/conformance` exports `createSyncConformanceTests`. It returns plain async test
230
+ cases and has no Vitest or Jest dependency. Register the returned cases with the runner already in
231
+ the product:
232
+
233
+ ```ts
234
+ import { createSyncConformanceTests } from '@baukit/sync-client/conformance';
235
+
236
+ describe('product sync contract', () => {
237
+ for (const testCase of createSyncConformanceTests(productAdapter)) {
238
+ it(testCase.name, testCase.run);
239
+ }
240
+ });
241
+ ```
242
+
243
+ The adapter supplies two isolated clients and one fake server per case. It also supplies callbacks
244
+ for outbox enqueue, listing, acknowledgement, and rejection storage; atomic pull application and
245
+ cursor storage; pending-state reporting; wire encoding and decoding; and fake-server push, pull,
246
+ seeding, snapshots, and fault injection. Canonical fixture changes contain opaque entity names,
247
+ values, logical times, dependencies, and tombstones. The adapter maps them to the product's schema
248
+ and payloads.
249
+
250
+ The cases cover replay, transaction rollback, paged cursor progress, stalled and regressing
251
+ cursors, complete push outcomes, pending state after network, server, and local failures,
252
+ actionable rejections, dependency-safe push order, and two-client convergence with a tombstone.
253
+ `wire.decodePush` must call `validatePushOutcomeCoverage` before returning acknowledged rows. The
254
+ local apply callback must commit the page and cursor in one transaction. Its injected failure must
255
+ roll both back.
@@ -0,0 +1,80 @@
1
+ export type SyncConformanceOperation = 'upsert' | 'delete';
2
+ export type SyncConformancePushFailure = 'network' | 'server';
3
+ export type SyncConformancePullFault = 'regress' | 'stall';
4
+ export interface SyncConformanceEntityRef {
5
+ entityType: string;
6
+ entityId: string;
7
+ }
8
+ export interface SyncConformanceChange extends SyncConformanceEntityRef {
9
+ changeId: string;
10
+ operation: SyncConformanceOperation;
11
+ value: string | null;
12
+ logicalTime: number;
13
+ dependsOn?: SyncConformanceEntityRef;
14
+ }
15
+ export interface SyncConformanceRow extends SyncConformanceEntityRef {
16
+ value: string | null;
17
+ logicalTime: number;
18
+ deleted: boolean;
19
+ }
20
+ export interface SyncConformanceRejection extends SyncConformanceEntityRef {
21
+ reason: string;
22
+ }
23
+ export interface SyncConformancePullPage<TChange, TCursor> {
24
+ changes: readonly TChange[];
25
+ nextCursor: TCursor;
26
+ hasMore: boolean;
27
+ }
28
+ export interface SyncConformancePushResult<TPending> {
29
+ acknowledged: readonly TPending[];
30
+ rejected: readonly SyncConformanceRejection[];
31
+ }
32
+ export interface SyncConformancePendingState {
33
+ pending: boolean;
34
+ pendingCount: number;
35
+ }
36
+ export interface SyncConformanceLocalApplyOptions {
37
+ failAfterChanges?: number;
38
+ }
39
+ export interface SyncConformanceScenario<TClient, TServer> {
40
+ clients: readonly [TClient, TClient];
41
+ server: TServer;
42
+ }
43
+ export interface SyncConformanceAdapter<TClient, TServer, TPending, TPushRequest, TPushResponse, TPullResponse, TRemoteChange, TCursor> {
44
+ createScenario(): Promise<SyncConformanceScenario<TClient, TServer>> | SyncConformanceScenario<TClient, TServer>;
45
+ disposeScenario?(scenario: SyncConformanceScenario<TClient, TServer>): Promise<void> | void;
46
+ outbox: {
47
+ enqueue(client: TClient, change: SyncConformanceChange): Promise<void> | void;
48
+ listPending(client: TClient): Promise<readonly TPending[]> | readonly TPending[];
49
+ markAcknowledged(client: TClient, pending: readonly TPending[]): Promise<void> | void;
50
+ recordRejected(client: TClient, rejected: readonly SyncConformanceRejection[]): Promise<void> | void;
51
+ listRejected(client: TClient): Promise<readonly SyncConformanceRejection[]> | readonly SyncConformanceRejection[];
52
+ };
53
+ local: {
54
+ readCursor(client: TClient): Promise<TCursor> | TCursor;
55
+ applyPullPage(client: TClient, page: SyncConformancePullPage<TRemoteChange, TCursor>, options?: SyncConformanceLocalApplyOptions): Promise<void> | void;
56
+ snapshot(client: TClient): Promise<readonly SyncConformanceRow[]> | readonly SyncConformanceRow[];
57
+ readPendingState(client: TClient): Promise<SyncConformancePendingState> | SyncConformancePendingState;
58
+ };
59
+ wire: {
60
+ encodePush(pending: readonly TPending[]): Promise<TPushRequest> | TPushRequest;
61
+ decodePush(response: TPushResponse, submitted: readonly TPending[]): Promise<SyncConformancePushResult<TPending>> | SyncConformancePushResult<TPending>;
62
+ decodePull(response: TPullResponse): Promise<SyncConformancePullPage<TRemoteChange, TCursor>> | SyncConformancePullPage<TRemoteChange, TCursor>;
63
+ compareCursors(left: TCursor, right: TCursor): number;
64
+ };
65
+ server: {
66
+ acceptPush(server: TServer, request: TPushRequest): Promise<TPushResponse>;
67
+ servePull(server: TServer, cursor: TCursor, pageSize: number): Promise<TPullResponse>;
68
+ seed(server: TServer, change: SyncConformanceChange): Promise<void> | void;
69
+ snapshot(server: TServer): Promise<readonly SyncConformanceRow[]> | readonly SyncConformanceRow[];
70
+ failNextPush(server: TServer, failure: SyncConformancePushFailure): Promise<void> | void;
71
+ omitNextPushOutcome(server: TServer): Promise<void> | void;
72
+ faultNextPull(server: TServer, fault: SyncConformancePullFault): Promise<void> | void;
73
+ };
74
+ }
75
+ export interface SyncConformanceTestCase {
76
+ readonly name: string;
77
+ readonly run: () => Promise<void>;
78
+ }
79
+ export declare function createSyncConformanceTests<TClient, TServer, TPending, TPushRequest, TPushResponse, TPullResponse, TRemoteChange, TCursor>(adapter: SyncConformanceAdapter<TClient, TServer, TPending, TPushRequest, TPushResponse, TPullResponse, TRemoteChange, TCursor>): readonly SyncConformanceTestCase[];
80
+ //# sourceMappingURL=conformance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,251 @@
1
+ import { validatePullPage } from './transport.js';
2
+ const PAGE_SIZE = 2;
3
+ const parentChange = {
4
+ changeId: 'change-parent',
5
+ entityType: 'container',
6
+ entityId: 'parent',
7
+ operation: 'upsert',
8
+ value: 'parent value',
9
+ logicalTime: 10,
10
+ };
11
+ const childChange = {
12
+ changeId: 'change-child',
13
+ entityType: 'item',
14
+ entityId: 'child',
15
+ operation: 'upsert',
16
+ value: 'child value',
17
+ logicalTime: 11,
18
+ dependsOn: { entityType: 'container', entityId: 'parent' },
19
+ };
20
+ function change(changeId, entityId, logicalTime, value) {
21
+ return {
22
+ changeId,
23
+ entityType: 'record',
24
+ entityId,
25
+ operation: 'upsert',
26
+ value,
27
+ logicalTime,
28
+ };
29
+ }
30
+ function tombstone(changeId, entityId, logicalTime) {
31
+ return {
32
+ changeId,
33
+ entityType: 'record',
34
+ entityId,
35
+ operation: 'delete',
36
+ value: null,
37
+ logicalTime,
38
+ };
39
+ }
40
+ function fail(message) {
41
+ throw new Error(`Sync conformance failed: ${message}`);
42
+ }
43
+ function assert(condition, message) {
44
+ if (!condition)
45
+ fail(message);
46
+ }
47
+ function sortedRows(rows) {
48
+ return [...rows].sort((left, right) => left.entityType.localeCompare(right.entityType) ||
49
+ left.entityId.localeCompare(right.entityId));
50
+ }
51
+ function sameRows(left, right) {
52
+ return JSON.stringify(sortedRows(left)) === JSON.stringify(sortedRows(right));
53
+ }
54
+ async function expectFailure(run, message) {
55
+ try {
56
+ await run();
57
+ }
58
+ catch {
59
+ return;
60
+ }
61
+ fail(message);
62
+ }
63
+ export function createSyncConformanceTests(adapter) {
64
+ async function inScenario(run) {
65
+ const scenario = await adapter.createScenario();
66
+ const [clientA, clientB] = scenario.clients;
67
+ try {
68
+ await run({ clientA, clientB, server: scenario.server });
69
+ }
70
+ finally {
71
+ await adapter.disposeScenario?.(scenario);
72
+ }
73
+ }
74
+ async function pending(client) {
75
+ return adapter.outbox.listPending(client);
76
+ }
77
+ async function assertPendingState(client, expectedCount) {
78
+ const actual = await pending(client);
79
+ const reported = await adapter.local.readPendingState(client);
80
+ assert(actual.length === expectedCount, `expected ${String(expectedCount)} pending changes`);
81
+ assert(reported.pendingCount === actual.length, 'reported pending count differs from the outbox');
82
+ assert(reported.pending === actual.length > 0, 'reported pending flag differs from the outbox');
83
+ }
84
+ async function push(client, server) {
85
+ const submitted = await pending(client);
86
+ if (submitted.length === 0)
87
+ return;
88
+ const request = await adapter.wire.encodePush(submitted);
89
+ const response = await adapter.server.acceptPush(server, request);
90
+ const outcome = await adapter.wire.decodePush(response, submitted);
91
+ await adapter.outbox.markAcknowledged(client, outcome.acknowledged);
92
+ await adapter.outbox.recordRejected(client, outcome.rejected);
93
+ }
94
+ async function pullPage(client, server, options) {
95
+ const cursor = (await adapter.local.readCursor(client));
96
+ const response = await adapter.server.servePull(server, cursor, PAGE_SIZE);
97
+ const page = await adapter.wire.decodePull(response);
98
+ validatePullPage(cursor, page, (left, right) => adapter.wire.compareCursors(left, right));
99
+ await adapter.local.applyPullPage(client, page, options);
100
+ return page.hasMore;
101
+ }
102
+ async function pullAll(client, server) {
103
+ let hasMore = true;
104
+ while (hasMore)
105
+ hasMore = await pullPage(client, server);
106
+ }
107
+ return [
108
+ {
109
+ name: 'replayed pushes create one server change',
110
+ run: () => inScenario(async ({ clientA, server }) => {
111
+ await adapter.outbox.enqueue(clientA, change('change-replay', 'replay', 10, 'once'));
112
+ const submitted = await pending(clientA);
113
+ const request = await adapter.wire.encodePush(submitted);
114
+ const first = await adapter.server.acceptPush(server, request);
115
+ const second = await adapter.server.acceptPush(server, request);
116
+ await adapter.wire.decodePush(first, submitted);
117
+ await adapter.wire.decodePush(second, submitted);
118
+ const rows = (await adapter.server.snapshot(server)).filter((row) => row.entityType === 'record' && row.entityId === 'replay');
119
+ assert(rows.length === 1, 'replay created a duplicate server row');
120
+ }),
121
+ },
122
+ {
123
+ name: 'an interrupted local transaction preserves data, cursor, and pending work',
124
+ run: () => inScenario(async ({ clientA, server }) => {
125
+ await adapter.outbox.enqueue(clientA, change('change-pending', 'pending', 30, 'local'));
126
+ await adapter.server.seed(server, change('seed-first', 'first', 10, 'remote first'));
127
+ await adapter.server.seed(server, change('seed-second', 'second', 20, 'remote second'));
128
+ const cursorBefore = await adapter.local.readCursor(clientA);
129
+ const rowsBefore = await adapter.local.snapshot(clientA);
130
+ await expectFailure(() => pullPage(clientA, server, { failAfterChanges: 1 }).then(() => undefined), 'local apply failure resolved successfully');
131
+ const cursorAfter = await adapter.local.readCursor(clientA);
132
+ const rowsAfter = await adapter.local.snapshot(clientA);
133
+ assert(adapter.wire.compareCursors(cursorAfter, cursorBefore) === 0, 'local apply failure advanced the cursor');
134
+ assert(sameRows(rowsAfter, rowsBefore), 'local apply failure committed part of the page');
135
+ await assertPendingState(clientA, 1);
136
+ }),
137
+ },
138
+ {
139
+ name: 'pull cursors advance monotonically across pages',
140
+ run: () => inScenario(async ({ clientA, server }) => {
141
+ await adapter.server.seed(server, change('seed-one', 'one', 10, 'one'));
142
+ await adapter.server.seed(server, change('seed-two', 'two', 20, 'two'));
143
+ await adapter.server.seed(server, change('seed-three', 'three', 30, 'three'));
144
+ const start = await adapter.local.readCursor(clientA);
145
+ await pullAll(clientA, server);
146
+ const end = await adapter.local.readCursor(clientA);
147
+ assert(adapter.wire.compareCursors(end, start) > 0, 'pull did not advance the cursor');
148
+ assert(sameRows(await adapter.local.snapshot(clientA), await adapter.server.snapshot(server)), 'paged pull skipped or duplicated server state');
149
+ }),
150
+ },
151
+ {
152
+ name: 'a regressing pull cursor is rejected before local apply',
153
+ run: () => inScenario(async ({ clientA, server }) => {
154
+ await adapter.server.seed(server, change('seed-regress', 'regress', 10, 'remote'));
155
+ await pullAll(clientA, server);
156
+ const cursor = await adapter.local.readCursor(clientA);
157
+ const rows = await adapter.local.snapshot(clientA);
158
+ await adapter.server.faultNextPull(server, 'regress');
159
+ await expectFailure(() => pullPage(clientA, server).then(() => undefined), 'regressing cursor was accepted');
160
+ assert(adapter.wire.compareCursors(await adapter.local.readCursor(clientA), cursor) === 0, 'regressing page changed the cursor');
161
+ assert(sameRows(await adapter.local.snapshot(clientA), rows), 'regressing page changed data');
162
+ }),
163
+ },
164
+ {
165
+ name: 'hasMore requires cursor progress',
166
+ run: () => inScenario(async ({ clientA, server }) => {
167
+ const cursor = await adapter.local.readCursor(clientA);
168
+ await adapter.server.faultNextPull(server, 'stall');
169
+ await expectFailure(() => pullPage(clientA, server).then(() => undefined), 'hasMore without cursor progress was accepted');
170
+ assert(adapter.wire.compareCursors(await adapter.local.readCursor(clientA), cursor) === 0, 'stalled page changed the cursor');
171
+ }),
172
+ },
173
+ {
174
+ name: 'partial push outcomes are rejected before acknowledgement',
175
+ run: () => inScenario(async ({ clientA, server }) => {
176
+ await adapter.outbox.enqueue(clientA, change('change-covered-a', 'covered-a', 10, 'a'));
177
+ await adapter.outbox.enqueue(clientA, change('change-covered-b', 'covered-b', 20, 'b'));
178
+ await adapter.server.omitNextPushOutcome(server);
179
+ await expectFailure(() => push(clientA, server), 'partial push outcomes were accepted');
180
+ await assertPendingState(clientA, 2);
181
+ }),
182
+ },
183
+ {
184
+ name: 'network failure keeps pending state truthful',
185
+ run: () => inScenario(async ({ clientA, server }) => {
186
+ await adapter.outbox.enqueue(clientA, change('change-network', 'network', 10, 'local'));
187
+ await adapter.server.failNextPush(server, 'network');
188
+ await expectFailure(() => push(clientA, server), 'network failure resolved successfully');
189
+ await assertPendingState(clientA, 1);
190
+ }),
191
+ },
192
+ {
193
+ name: 'server failure keeps pending state truthful',
194
+ run: () => inScenario(async ({ clientA, server }) => {
195
+ await adapter.outbox.enqueue(clientA, change('change-server', 'server', 10, 'local'));
196
+ await adapter.server.failNextPush(server, 'server');
197
+ await expectFailure(() => push(clientA, server), 'server failure resolved successfully');
198
+ await assertPendingState(clientA, 1);
199
+ }),
200
+ },
201
+ {
202
+ name: 'complete rejection outcomes are recorded without hidden pending work',
203
+ run: () => inScenario(async ({ clientA, server }) => {
204
+ await adapter.server.seed(server, change('seed-newer', 'rejected', 20, 'server'));
205
+ await adapter.outbox.enqueue(clientA, change('change-older', 'rejected', 10, 'local'));
206
+ await push(clientA, server);
207
+ await assertPendingState(clientA, 0);
208
+ const rejected = await adapter.outbox.listRejected(clientA);
209
+ assert(rejected.length === 1, 'actionable rejection was not recorded');
210
+ assert(rejected[0]?.entityId === 'rejected', 'wrong rejection was recorded');
211
+ }),
212
+ },
213
+ {
214
+ name: 'pushes referenced records before dependent records',
215
+ run: () => inScenario(async ({ clientA, server }) => {
216
+ await adapter.outbox.enqueue(clientA, childChange);
217
+ await adapter.outbox.enqueue(clientA, parentChange);
218
+ await push(clientA, server);
219
+ await assertPendingState(clientA, 0);
220
+ const rows = await adapter.server.snapshot(server);
221
+ assert(rows.some((row) => row.entityType === 'container' && row.entityId === 'parent'), 'referenced record did not reach the server');
222
+ assert(rows.some((row) => row.entityType === 'item' && row.entityId === 'child'), 'dependent record was rejected because push order was unsafe');
223
+ }),
224
+ },
225
+ {
226
+ name: 'two clients converge after alternating sync, including a tombstone',
227
+ run: () => inScenario(async ({ clientA, clientB, server }) => {
228
+ await adapter.outbox.enqueue(clientA, change('change-a', 'alpha', 10, 'from A'));
229
+ await adapter.outbox.enqueue(clientB, change('change-b', 'beta', 20, 'from B'));
230
+ await push(clientA, server);
231
+ await pullAll(clientB, server);
232
+ await push(clientB, server);
233
+ await pullAll(clientA, server);
234
+ await adapter.outbox.enqueue(clientB, tombstone('change-delete', 'alpha', 30));
235
+ await push(clientB, server);
236
+ await pullAll(clientA, server);
237
+ await pullAll(clientB, server);
238
+ const rowsA = await adapter.local.snapshot(clientA);
239
+ const rowsB = await adapter.local.snapshot(clientB);
240
+ const serverRows = await adapter.server.snapshot(server);
241
+ assert(sameRows(rowsA, rowsB), 'clients did not converge to the same state');
242
+ assert(sameRows(rowsA, serverRows), 'clients did not converge to server state');
243
+ const deleted = rowsA.find((row) => row.entityType === 'record' && row.entityId === 'alpha');
244
+ assert(deleted?.deleted === true, 'tombstone did not converge');
245
+ await assertPendingState(clientA, 0);
246
+ await assertPendingState(clientB, 0);
247
+ }),
248
+ },
249
+ ];
250
+ }
251
+ //# sourceMappingURL=conformance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance.js","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"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"}
package/dist/error.d.ts CHANGED
@@ -1,3 +1,27 @@
1
+ /** Machine-readable failure metadata. Products own any message shown to users. */
2
+ export type SyncFailure = {
3
+ readonly kind: 'auth';
4
+ } | {
5
+ readonly kind: 'partition_mismatch';
6
+ } | {
7
+ readonly kind: 'rate_limited';
8
+ readonly retryAt: string;
9
+ } | {
10
+ readonly kind: 'network';
11
+ } | {
12
+ readonly kind: 'server';
13
+ } | {
14
+ readonly kind: 'payload_compatibility';
15
+ } | {
16
+ readonly kind: 'local_apply';
17
+ };
18
+ /** Snake-case projection of {@link SyncFailure}. */
19
+ export type SnakeCaseSyncFailure = Exclude<SyncFailure, {
20
+ readonly kind: 'rate_limited';
21
+ }> | {
22
+ readonly kind: 'rate_limited';
23
+ readonly retry_at: string;
24
+ };
1
25
  /** A sync request failed. `retryable` decides whether a later attempt may win. */
2
26
  export declare class SyncTransportError extends Error {
3
27
  readonly retryable: boolean;
@@ -9,12 +33,42 @@ export declare class SyncAuthError extends SyncTransportError {
9
33
  readonly kind = "auth";
10
34
  constructor(message: string, cause?: unknown);
11
35
  }
36
+ /** The request did not reach a usable HTTP response. */
37
+ export declare class SyncNetworkError extends SyncTransportError {
38
+ readonly kind = "network";
39
+ constructor(message: string, cause?: unknown);
40
+ }
41
+ /** The server returned an HTTP failure other than authentication or rate limiting. */
42
+ export declare class SyncServerError extends SyncTransportError {
43
+ readonly kind = "server";
44
+ constructor(message: string, retryable: boolean, cause?: unknown);
45
+ }
46
+ /** HTTP 429 with its raw `Retry-After` value and resolved retry time. */
47
+ export declare class SyncRateLimitError extends SyncTransportError {
48
+ readonly retryAt: string;
49
+ readonly retryAfter: string | null;
50
+ readonly kind = "rate_limited";
51
+ constructor(message: string, retryAt: string, retryAfter: string | null, cause?: unknown);
52
+ }
12
53
  /**
13
54
  * The server rejected the caller's local-data partition, so the local database
14
55
  * belongs to an erased or replaced owner and must not be reused.
15
56
  */
16
57
  export declare class SyncPartitionMismatchError extends SyncTransportError {
17
- readonly kind = "partition-mismatch";
58
+ readonly kind = "partition_mismatch";
59
+ constructor(message: string, cause?: unknown);
60
+ }
61
+ /** A decoded response violates the sync payload contract. */
62
+ export declare class SyncPayloadCompatibilityError extends SyncTransportError {
63
+ readonly kind = "payload_compatibility";
64
+ constructor(message: string, cause?: unknown);
65
+ }
66
+ /** Remote data or a cursor could not be committed to local storage. */
67
+ export declare class SyncLocalApplyError extends Error {
68
+ readonly kind = "local_apply";
18
69
  constructor(message: string, cause?: unknown);
19
70
  }
71
+ /** Maps package errors onto store failure metadata. */
72
+ export declare function syncFailureFromError(error: unknown): SyncFailure;
73
+ export declare function toSnakeCaseFailure(failure: SyncFailure): SnakeCaseSyncFailure;
20
74
  //# sourceMappingURL=error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,SAAS,EAAE,OAAO;aACT,KAAK,CAAC,EAAE,OAAO;gBAFjC,OAAO,EAAE,MAAM,EACN,SAAS,EAAE,OAAO,EACT,KAAK,CAAC,EAAE,OAAO,YAAA;CAKpC;AAED,4EAA4E;AAC5E,qBAAa,aAAc,SAAQ,kBAAkB;IACnD,QAAQ,CAAC,IAAI,UAAU;gBAEX,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,kBAAkB;IAChE,QAAQ,CAAC,IAAI,wBAAwB;gBAEzB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE,GACvC;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE,GAC1C;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAErC,oDAAoD;AACpD,MAAM,MAAM,oBAAoB,GAC5B,OAAO,CAAC,WAAW,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE,kFAAkF;AAClF,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,SAAS,EAAE,OAAO;aACT,KAAK,CAAC,EAAE,OAAO;gBAFjC,OAAO,EAAE,MAAM,EACN,SAAS,EAAE,OAAO,EACT,KAAK,CAAC,EAAE,OAAO,YAAA;CAKpC;AAED,4EAA4E;AAC5E,qBAAa,aAAc,SAAQ,kBAAkB;IACnD,QAAQ,CAAC,IAAI,UAAU;gBAEX,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,wDAAwD;AACxD,qBAAa,gBAAiB,SAAQ,kBAAkB;IACtD,QAAQ,CAAC,IAAI,aAAa;gBAEd,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,sFAAsF;AACtF,qBAAa,eAAgB,SAAQ,kBAAkB;IACrD,QAAQ,CAAC,IAAI,YAAY;gBAEb,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,OAAO;CAIjE;AAED,yEAAyE;AACzE,qBAAa,kBAAmB,SAAQ,kBAAkB;IAKtD,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IALpC,QAAQ,CAAC,IAAI,kBAAkB;gBAG7B,OAAO,EAAE,MAAM,EACN,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GAAG,IAAI,EAClC,KAAK,CAAC,EAAE,OAAO;CAKlB;AAED;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,kBAAkB;IAChE,QAAQ,CAAC,IAAI,wBAAwB;gBAEzB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,6DAA6D;AAC7D,qBAAa,6BAA8B,SAAQ,kBAAkB;IACnE,QAAQ,CAAC,IAAI,2BAA2B;gBAE5B,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,uEAAuE;AACvE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,iBAAiB;gBAElB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,uDAAuD;AACvD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAchE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,oBAAoB,CAI7E"}