@syncular/client 0.30.6 → 0.30.9
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 +12 -2
- package/dist/apply.d.ts +2 -2
- package/dist/apply.js +25 -8
- package/dist/client.d.ts +6 -4
- package/dist/client.js +114 -75
- package/dist/database.d.ts +3 -0
- package/dist/database.js +26 -7
- package/dist/diagnostics.d.ts +1 -1
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +61 -15
- package/dist/failed-overlay.d.ts +10 -0
- package/dist/failed-overlay.js +97 -0
- package/dist/invalidation.d.ts +1 -0
- package/dist/invalidation.js +3 -0
- package/dist/outcomes.d.ts +10 -0
- package/dist/outcomes.js +18 -3
- package/dist/remote.js +7 -2
- package/dist/schema.js +7 -5
- package/dist/wasm-database.js +5 -5
- package/dist/window.js +4 -4
- package/dist/worker-entry.js +6 -2
- package/dist/worker-host.d.ts +1 -0
- package/dist/worker-host.js +3 -0
- package/dist/worker-protocol.d.ts +1 -0
- package/package.json +3 -3
- package/src/apply.ts +46 -13
- package/src/client.ts +184 -171
- package/src/database.ts +24 -8
- package/src/diagnostics.ts +1 -0
- package/src/errors.ts +82 -20
- package/src/failed-overlay.ts +165 -0
- package/src/invalidation.ts +4 -0
- package/src/outcomes.ts +41 -3
- package/src/remote.ts +15 -1
- package/src/schema.ts +7 -9
- package/src/wasm-database.ts +5 -8
- package/src/window.ts +4 -7
- package/src/worker-entry.ts +10 -2
- package/src/worker-host.ts +4 -0
- package/src/worker-protocol.ts +1 -0
package/src/apply.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { retainedBaseWrite } from './failed-overlay';
|
|
1
2
|
/**
|
|
2
3
|
* Local application of server data: `COMMIT` frames (§4.5), rows segments
|
|
3
4
|
* (§5.2, §5.6), and the scope-matched delete shared by the §3.3 purge
|
|
@@ -13,13 +14,14 @@ import {
|
|
|
13
14
|
} from '@syncular/core';
|
|
14
15
|
import type { ClientDatabase, SqlValue } from './database';
|
|
15
16
|
import type { EncryptionConfig } from './encryption';
|
|
16
|
-
import { ClientSyncError } from './errors';
|
|
17
|
+
import { ClientSyncError, invalidRequest } from './errors';
|
|
17
18
|
import {
|
|
18
19
|
type CompiledClientSchema,
|
|
19
20
|
type CompiledClientTable,
|
|
20
21
|
quoteIdent,
|
|
21
22
|
SYNC_VERSION_COLUMN,
|
|
22
23
|
toSqlValue,
|
|
24
|
+
rowValueToJson,
|
|
23
25
|
} from './schema';
|
|
24
26
|
|
|
25
27
|
/** Lets the client wrap the physical write in its revision transaction. */
|
|
@@ -44,7 +46,21 @@ export function upsertLocalRow(
|
|
|
44
46
|
table: CompiledClientTable,
|
|
45
47
|
values: readonly RowValue[],
|
|
46
48
|
syncVersion: number,
|
|
49
|
+
serverWrite = true,
|
|
47
50
|
): void {
|
|
51
|
+
if (serverWrite)
|
|
52
|
+
retainedBaseWrite(
|
|
53
|
+
db,
|
|
54
|
+
table.name,
|
|
55
|
+
String(values[table.primaryKeyIndex]),
|
|
56
|
+
Object.fromEntries(
|
|
57
|
+
table.columns.map((column, index) => [
|
|
58
|
+
column.name,
|
|
59
|
+
rowValueToJson(values[index] ?? null),
|
|
60
|
+
]),
|
|
61
|
+
),
|
|
62
|
+
syncVersion,
|
|
63
|
+
);
|
|
48
64
|
db.exec(upsertSql(table), [...values.map(toSqlValue), syncVersion]);
|
|
49
65
|
}
|
|
50
66
|
|
|
@@ -52,7 +68,9 @@ export function deleteLocalRow(
|
|
|
52
68
|
db: ClientDatabase,
|
|
53
69
|
table: CompiledClientTable,
|
|
54
70
|
rowId: string,
|
|
71
|
+
serverWrite = true,
|
|
55
72
|
): void {
|
|
73
|
+
if (serverWrite) retainedBaseWrite(db, table.name, rowId);
|
|
56
74
|
db.exec(
|
|
57
75
|
`DELETE FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(table.primaryKey)} = ?`,
|
|
58
76
|
[rowId],
|
|
@@ -90,8 +108,7 @@ export async function applyCommitFrame(
|
|
|
90
108
|
for (const change of frame.changes) {
|
|
91
109
|
const tableName = frame.tables[change.tableIndex];
|
|
92
110
|
if (tableName === undefined) {
|
|
93
|
-
throw
|
|
94
|
-
'sync.invalid_request',
|
|
111
|
+
throw invalidRequest(
|
|
95
112
|
`COMMIT change tableIndex ${change.tableIndex} out of range`,
|
|
96
113
|
);
|
|
97
114
|
}
|
|
@@ -107,10 +124,7 @@ export async function applyCommitFrame(
|
|
|
107
124
|
continue;
|
|
108
125
|
}
|
|
109
126
|
if (change.row === undefined || change.rowVersion === undefined) {
|
|
110
|
-
throw
|
|
111
|
-
'sync.invalid_request',
|
|
112
|
-
'upsert change without row payload',
|
|
113
|
-
);
|
|
127
|
+
throw invalidRequest('upsert change without row payload');
|
|
114
128
|
}
|
|
115
129
|
let values = decodeRow(table.columns, change.row);
|
|
116
130
|
if (encryption !== undefined && table.hasEncryptedColumns) {
|
|
@@ -204,6 +218,12 @@ export function deleteScopedRows(
|
|
|
204
218
|
);
|
|
205
219
|
params.push(...values);
|
|
206
220
|
}
|
|
221
|
+
for (const row of db.query(
|
|
222
|
+
`SELECT ${quoteIdent(table.primaryKey)} AS id FROM ${quoteIdent(table.name)} WHERE ${clauses.join(' AND ')}`,
|
|
223
|
+
params,
|
|
224
|
+
)) {
|
|
225
|
+
retainedBaseWrite(db, table.name, String(row.id));
|
|
226
|
+
}
|
|
207
227
|
db.exec(
|
|
208
228
|
`DELETE FROM ${quoteIdent(table.name)} WHERE ${clauses.join(' AND ')}`,
|
|
209
229
|
params,
|
|
@@ -283,10 +303,7 @@ export interface SqliteSegmentDescriptor {
|
|
|
283
303
|
const IMAGE_ALIAS = 'syncular_image';
|
|
284
304
|
|
|
285
305
|
function imageInvalid(detail: string): never {
|
|
286
|
-
throw
|
|
287
|
-
'sync.invalid_request',
|
|
288
|
-
`sqlite segment rejected: ${detail} (§5.3)`,
|
|
289
|
-
);
|
|
306
|
+
throw invalidRequest(`sqlite segment rejected: ${detail} (§5.3)`);
|
|
290
307
|
}
|
|
291
308
|
|
|
292
309
|
/**
|
|
@@ -313,8 +330,7 @@ export async function applySqliteSegment(
|
|
|
313
330
|
): Promise<number> {
|
|
314
331
|
const withImage = db.withSqliteImage?.bind(db);
|
|
315
332
|
if (withImage === undefined) {
|
|
316
|
-
throw
|
|
317
|
-
'sync.invalid_request',
|
|
333
|
+
throw invalidRequest(
|
|
318
334
|
'received a sqlite segment but the database backend cannot import images (§4.2: do not advertise accept bit 2)',
|
|
319
335
|
);
|
|
320
336
|
}
|
|
@@ -422,6 +438,23 @@ export async function applySqliteSegment(
|
|
|
422
438
|
params,
|
|
423
439
|
);
|
|
424
440
|
applied += Number(db.query('SELECT changes() AS n')[0]?.n ?? 0);
|
|
441
|
+
for (const row of db.query(
|
|
442
|
+
`SELECT * FROM ${source} WHERE ${predicates.join(' AND ') || 'true'}`,
|
|
443
|
+
params,
|
|
444
|
+
)) {
|
|
445
|
+
retainedBaseWrite(
|
|
446
|
+
db,
|
|
447
|
+
table.name,
|
|
448
|
+
String(row[table.primaryKey]),
|
|
449
|
+
Object.fromEntries(
|
|
450
|
+
table.columns.map((column) => [
|
|
451
|
+
column.name,
|
|
452
|
+
rowValueToJson(row[column.name] as RowValue),
|
|
453
|
+
]),
|
|
454
|
+
),
|
|
455
|
+
Number(row._syncular_version),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
425
458
|
});
|
|
426
459
|
options.onProgress?.(applied);
|
|
427
460
|
await yieldToHost();
|