@g9tm/syncdata 1.2.1 → 1.3.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.
Files changed (57) hide show
  1. package/PARITY.md +26 -2
  2. package/README.md +1 -1
  3. package/dist/abstractions/errors.d.ts +4 -0
  4. package/dist/abstractions/errors.js +4 -0
  5. package/dist/abstractions/errors.js.map +1 -1
  6. package/dist/client/engine.js +93 -51
  7. package/dist/client/engine.js.map +1 -1
  8. package/dist/client/runs.d.ts +5 -0
  9. package/dist/client/runs.js +8 -7
  10. package/dist/client/runs.js.map +1 -1
  11. package/dist/client/storeTypes.d.ts +22 -1
  12. package/dist/client/storeTypes.js +27 -0
  13. package/dist/client/storeTypes.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/node/index.d.ts +1 -0
  18. package/dist/node/index.js +1 -0
  19. package/dist/node/index.js.map +1 -1
  20. package/dist/node/snapshotFileSink.d.ts +3 -0
  21. package/dist/node/snapshotFileSink.js +79 -0
  22. package/dist/node/snapshotFileSink.js.map +1 -0
  23. package/dist/protocol/dtos.d.ts +76 -0
  24. package/dist/protocol/dtos.js +2 -0
  25. package/dist/protocol/dtos.js.map +1 -1
  26. package/dist/protocol/wire.d.ts +3 -1
  27. package/dist/protocol/wire.js +49 -0
  28. package/dist/protocol/wire.js.map +1 -1
  29. package/dist/sqlite/apply.d.ts +21 -1
  30. package/dist/sqlite/apply.js +218 -114
  31. package/dist/sqlite/apply.js.map +1 -1
  32. package/dist/sqlite/connection.d.ts +5 -0
  33. package/dist/sqlite/connection.js +17 -0
  34. package/dist/sqlite/connection.js.map +1 -1
  35. package/dist/sqlite/index.d.ts +2 -1
  36. package/dist/sqlite/index.js +1 -0
  37. package/dist/sqlite/index.js.map +1 -1
  38. package/dist/sqlite/options.d.ts +39 -1
  39. package/dist/sqlite/options.js +5 -1
  40. package/dist/sqlite/options.js.map +1 -1
  41. package/dist/sqlite/push.js +4 -1
  42. package/dist/sqlite/push.js.map +1 -1
  43. package/dist/sqlite/query.js +44 -15
  44. package/dist/sqlite/query.js.map +1 -1
  45. package/dist/sqlite/revocation.d.ts +1 -1
  46. package/dist/sqlite/revocation.js +4 -2
  47. package/dist/sqlite/revocation.js.map +1 -1
  48. package/dist/sqlite/snapshotFiles.d.ts +51 -0
  49. package/dist/sqlite/snapshotFiles.js +516 -0
  50. package/dist/sqlite/snapshotFiles.js.map +1 -0
  51. package/dist/sqlite/sql.d.ts +1 -1
  52. package/dist/sqlite/sql.js +4 -0
  53. package/dist/sqlite/sql.js.map +1 -1
  54. package/dist/sqlite/store.d.ts +20 -2
  55. package/dist/sqlite/store.js +147 -78
  56. package/dist/sqlite/store.js.map +1 -1
  57. package/package.json +3 -3
@@ -0,0 +1,516 @@
1
+ // Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStore.SnapshotFiles.cs (ADR-0072): a large first download arrives as a
2
+ // SQLite file built on the server with this store's own DDL; its segments go straight from the transport to the platform's
3
+ // G9SnapshotFileSink, and the apply copies the file's tables in SQL - one statement per table, SQLite's transfer optimisation
4
+ // when the table is empty - instead of three statements per row. On Lynx that is one bridge call per table instead of one per
5
+ // few thousand statements: the whole reason this exists for the TypeScript client.
6
+ import { G9SyncErrorCodes, G9SyncException } from '../abstractions/errors.js';
7
+ import { parseJson } from '../abstractions/long.js';
8
+ import { FILE_MODE_COLUMNS, FILE_MODE_COPY, FILE_MODE_MERGE } from '../client/storeTypes.js';
9
+ import { G9EPullPartKind } from '../protocol/dtos.js';
10
+ import { readFileManifest } from '../protocol/wire.js';
11
+ import { G9ESyncDirection } from '../abstractions/enums.js';
12
+ import { asNumber, asOptionalString, asString, statementsOf } from './connection.js';
13
+ import { findByServerName, persistedJson } from './internals.js';
14
+ import { REVOKED_REJECTION_CODE } from './revocation.js';
15
+ import { bit, G9ESqliteDateTimeStorage, G9ESqliteDecimalStorage, keyExpression, quote } from './sql.js';
16
+ /** The protocol's snapshot-file format this build reads (`G9SyncProtocol.SnapshotFileFormatVersion`). */
17
+ export const SNAPSHOT_FILE_FORMAT_VERSION = 1;
18
+ /** "G9SF": `PRAGMA application_id` of a snapshot file (`G9CSnapshotFileWriter.ApplicationId`). */
19
+ export const SNAPSHOT_FILE_APPLICATION_ID = 0x47395346;
20
+ const MAX_FILES_PER_PLAN = 8;
21
+ /** Segments written since a file's last durable checkpoint, per run and file (under the write lock). */
22
+ const pendingSegments = new WeakMap();
23
+ function pendingOf(store) {
24
+ let map = pendingSegments.get(store);
25
+ if (!map)
26
+ pendingSegments.set(store, (map = new Map()));
27
+ return map;
28
+ }
29
+ function corrupt(message) {
30
+ return new G9SyncException(G9SyncErrorCodes.SnapshotFileCorrupt, message);
31
+ }
32
+ /** The device's offer (`DescribeSnapshotFiles`): storage forms and, per received table, the shape the store creates it with. */
33
+ export function describeSnapshotFiles(store, negotiated) {
34
+ if (!store.options.snapshotFiles)
35
+ return null;
36
+ const tables = [];
37
+ for (const table of negotiated) {
38
+ if (table.direction === G9ESyncDirection.UploadOnly)
39
+ continue;
40
+ const local = findByServerName(store, table.name);
41
+ if (!local)
42
+ continue;
43
+ const model = local.model;
44
+ tables.push({
45
+ tableId: table.tableId,
46
+ localName: model.localName,
47
+ keyColumn: model.keyColumn,
48
+ keyType: model.keyType,
49
+ columns: model.columns.map((c) => ({ name: c.name, type: c.type, nullable: c.nullable })),
50
+ indexes: model.indexes.map((i) => ({ name: i.name, columns: i.columns.slice(), unique: i.unique })),
51
+ });
52
+ }
53
+ if (tables.length === 0)
54
+ return null;
55
+ return {
56
+ formatVersion: SNAPSHOT_FILE_FORMAT_VERSION,
57
+ storage: {
58
+ dateTimeAsTicks: store.options.dateTimeStorage === G9ESqliteDateTimeStorage.Ticks,
59
+ dateTimeFormat: store.options.dateTimeFormat,
60
+ decimalAsText: store.options.decimalStorage === G9ESqliteDecimalStorage.Text,
61
+ },
62
+ tables,
63
+ minRows: store.options.snapshotFileMinRows,
64
+ maxBytes: store.options.snapshotFileMaxBytes,
65
+ maxFiles: MAX_FILES_PER_PLAN,
66
+ };
67
+ }
68
+ function sinkOf(store) {
69
+ const sink = store.options.snapshotFiles;
70
+ if (!sink)
71
+ throw corrupt('A snapshot-file segment arrived, but this store has no snapshot-file sink.');
72
+ return sink;
73
+ }
74
+ /** A .NET DateTime in the JSON form System.Text.Json writes, so persisted rows read the same on both sides. */
75
+ function dotnetUtc(ms) {
76
+ return new Date(ms).toISOString().replace(/\.(\d{3})Z$/, '.$10000Z');
77
+ }
78
+ /** The persisted (.NET) form of a manifest: `builtUtc` as a DateTime. */
79
+ function persistedManifest(manifest) {
80
+ const { builtUtcMs, ...rest } = manifest;
81
+ return persistedJson({ ...rest, builtUtc: dotnetUtc(builtUtcMs) });
82
+ }
83
+ /** The persisted (.NET) form of a plan: each planned file's `snapshotUtc` as a DateTime. */
84
+ export function persistedPlan(plan) {
85
+ return persistedJson({
86
+ ...plan,
87
+ parts: plan.parts.map((part) => {
88
+ if (!part.file)
89
+ return part;
90
+ const { snapshotUtcMs, ...file } = part.file;
91
+ return { ...part, file: { ...file, snapshotUtc: dotnetUtc(snapshotUtcMs) } };
92
+ }),
93
+ });
94
+ }
95
+ function validateManifest(manifest, fileId) {
96
+ if (manifest.fileId !== fileId)
97
+ throw corrupt(`Snapshot file ${fileId} came with the manifest of ${manifest.fileId}.`);
98
+ if (manifest.formatVersion < 1 || manifest.formatVersion > SNAPSHOT_FILE_FORMAT_VERSION) {
99
+ throw corrupt(`Snapshot file ${fileId} has format ${manifest.formatVersion}; this build reads up to ${SNAPSHOT_FILE_FORMAT_VERSION}.`);
100
+ }
101
+ if (manifest.compression !== 'br')
102
+ throw corrupt(`Snapshot file ${fileId} uses the unknown codec '${manifest.compression}'.`);
103
+ if (manifest.bytes <= 0 ||
104
+ manifest.segmentBytes <= 0 ||
105
+ manifest.segments <= 0 ||
106
+ manifest.segments * manifest.segmentBytes < manifest.bytes ||
107
+ (manifest.segments - 1) * manifest.segmentBytes >= manifest.bytes ||
108
+ manifest.sha256.length !== 64) {
109
+ throw corrupt(`Snapshot file ${fileId} has an inconsistent manifest.`);
110
+ }
111
+ }
112
+ function safeFileName(fileId) {
113
+ return fileId.replace(/[^A-Za-z0-9]/g, '_');
114
+ }
115
+ async function readStagedFile(connection, runId, fileId) {
116
+ const rows = await connection.query('SELECT file_id, path, manifest_json, subscription_key, first_seq, segments_done FROM g9_stage_files WHERE run_id = $r AND file_id = $f', { $r: runId, $f: fileId });
117
+ return rows[0] ? stagedFileOf(rows[0]) : null;
118
+ }
119
+ /** The staged files of a run, in plan order. */
120
+ export async function readStagedFiles(connection, runId) {
121
+ const rows = await connection.query('SELECT file_id, path, manifest_json, subscription_key, first_seq, segments_done FROM g9_stage_files WHERE run_id = $r ORDER BY first_seq', {
122
+ $r: runId,
123
+ });
124
+ return rows.map(stagedFileOf);
125
+ }
126
+ function stagedFileOf(r) {
127
+ const manifest = readFileManifest(parseJson(asString(r[2])));
128
+ if (!manifest)
129
+ throw corrupt('A staged snapshot file has no manifest.');
130
+ return { fileId: asString(r[0]), path: asString(r[1]), manifest, subscriptionKey: asOptionalString(r[3]), firstSequence: asNumber(r[4]), segmentsDone: asNumber(r[5]) };
131
+ }
132
+ /**
133
+ * `StageFileSegment`: one segment to its place in the staged file, checked against its SHA-256 by the sink BEFORE it is
134
+ * written. Durable only at checkpoints - every `snapshotFileCheckpointBytes` and at the last segment - in that order: the file
135
+ * is flushed first, then the stage row records the resume point.
136
+ */
137
+ export async function stageFileSegment(store, runId, chunk) {
138
+ var _a, _b;
139
+ const segment = chunk.file;
140
+ if (!segment)
141
+ return;
142
+ const sink = sinkOf(store);
143
+ const connection = store.writer();
144
+ const key = runId + '\u0000' + segment.fileId;
145
+ const pending = pendingOf(store);
146
+ let staged = await readStagedFile(connection, runId, segment.fileId);
147
+ const manifest = segment.manifest;
148
+ if (manifest) {
149
+ validateManifest(manifest, segment.fileId);
150
+ if (!staged || staged.manifest.sha256 !== manifest.sha256) {
151
+ // A new file - or a different build of it after a resume on another server instance: the file starts over.
152
+ const path = staged ? staged.path : joinPath(sink.directory, runId + '-' + safeFileName(segment.fileId) + '.sqlite');
153
+ await sink.create(path, manifest.bytes);
154
+ await connection.exec('INSERT INTO g9_stage_files (run_id, file_id, path, manifest_json, subscription_key, first_seq, segments_done, bytes_received) ' +
155
+ 'VALUES ($r, $f, $p, $m, $k, $s, 0, 0) ON CONFLICT(run_id, file_id) DO UPDATE SET manifest_json = excluded.manifest_json, segments_done = 0, bytes_received = 0', { $r: runId, $f: segment.fileId, $p: path, $m: persistedManifest(manifest), $k: (_a = chunk.subscriptionKey) !== null && _a !== void 0 ? _a : null, $s: chunk.sequence });
156
+ pending.delete(key);
157
+ staged = await readStagedFile(connection, runId, segment.fileId);
158
+ }
159
+ }
160
+ if (!staged)
161
+ throw corrupt(`Segment ${segment.index} of snapshot file ${segment.fileId} arrived without its manifest.`);
162
+ const layout = staged.manifest;
163
+ let state = pending.get(key);
164
+ if (!state)
165
+ pending.set(key, (state = { segments: 0, rows: 0, bytes: 0, unsynced: 0 }));
166
+ const expected = staged.segmentsDone + state.segments;
167
+ if (segment.index !== expected)
168
+ throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} arrived where ${expected} was expected.`);
169
+ if (segment.offset !== segment.index * layout.segmentBytes ||
170
+ segment.length <= 0 ||
171
+ segment.length > layout.segmentBytes ||
172
+ segment.offset + segment.length > layout.bytes ||
173
+ segment.index >= layout.segments) {
174
+ throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} does not fit the manifest.`);
175
+ }
176
+ const payload = chunk.payload;
177
+ if (!payload)
178
+ throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} has no payload.`);
179
+ const last = segment.index === layout.segments - 1;
180
+ const sync = last || state.unsynced + segment.length >= Math.max(1, store.options.snapshotFileCheckpointBytes);
181
+ try {
182
+ await sink.writeSegment(staged.path, segment.offset, payload, segment.length, segment.sha256, sync);
183
+ }
184
+ catch (error) {
185
+ throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} was refused (${error instanceof Error ? error.message : String(error)}).`);
186
+ }
187
+ state.segments++;
188
+ state.rows += chunk.rowCount;
189
+ state.bytes += payload.length;
190
+ state.unsynced += segment.length;
191
+ if (!sync)
192
+ return;
193
+ // The checkpoint, in one atomic call: the data is on disk, so the record that it is there may follow.
194
+ await connection.runAtomic([
195
+ ...statementsOf('INSERT OR REPLACE INTO g9_stage (run_id, seq, subscription_key, row_count, continuation_json, payload) VALUES ($r, $s, $k, $n, $c, NULL)', {
196
+ $r: runId,
197
+ $s: chunk.sequence,
198
+ $k: (_b = chunk.subscriptionKey) !== null && _b !== void 0 ? _b : null,
199
+ $n: state.rows,
200
+ $c: chunk.continuation ? persistedJson(chunk.continuation) : null,
201
+ }),
202
+ ...statementsOf('UPDATE g9_stage_files SET segments_done = segments_done + $n, bytes_received = bytes_received + $b WHERE run_id = $r AND file_id = $f', {
203
+ $n: state.segments,
204
+ $b: state.bytes,
205
+ $r: runId,
206
+ $f: segment.fileId,
207
+ }),
208
+ ]);
209
+ pending.delete(key);
210
+ }
211
+ /** A resumed stream starts at a file's last checkpoint: segments written after it are written again. */
212
+ export function forgetPendingSegments(store, runId) {
213
+ const pending = pendingOf(store);
214
+ pending.forEach((_, key) => {
215
+ if (key.slice(0, runId.length + 1) === runId + '\u0000')
216
+ pending.delete(key);
217
+ });
218
+ }
219
+ /** The staged files of the runs a statement is about to delete (their rows go in the same transaction, the files after it). */
220
+ export async function takeStagedFiles(connection, runFilterSql, params) {
221
+ const paths = (await connection.query('SELECT path FROM g9_stage_files WHERE run_id IN (' + runFilterSql + ')', params)).map((r) => asString(r[0]));
222
+ await connection.exec('DELETE FROM g9_stage_files WHERE run_id IN (' + runFilterSql + ')', params);
223
+ return paths;
224
+ }
225
+ export async function deleteFiles(store, paths) {
226
+ const sink = store.options.snapshotFiles;
227
+ if (!sink)
228
+ return;
229
+ for (const path of paths) {
230
+ try {
231
+ await sink.remove(path);
232
+ }
233
+ catch (error) {
234
+ store.log('warning', 20122, `A staged snapshot file could not be deleted and is retried at the next start: ${path} (${error instanceof Error ? error.message : String(error)})`);
235
+ }
236
+ }
237
+ }
238
+ /** Deletes staged files no stage refers to any more (a crash between a stage's commit and its files' deletion). */
239
+ export async function sweepOrphanSnapshotFiles(store, connection) {
240
+ const sink = store.options.snapshotFiles;
241
+ if (!sink)
242
+ return;
243
+ let files;
244
+ try {
245
+ files = await sink.list(sink.directory);
246
+ }
247
+ catch {
248
+ return;
249
+ }
250
+ const referenced = {};
251
+ for (const r of await connection.query('SELECT path FROM g9_stage_files'))
252
+ referenced[normalizePath(asString(r[0]))] = true;
253
+ // Only names this store writes (<run id>-<file id>.sqlite and SQLite's companions of it): the directory may be shared with
254
+ // other files - even the database itself - and a sweep must never touch what it did not create.
255
+ await deleteFiles(store, files.filter((path) => {
256
+ const match = STAGED_FILE_NAME.exec(path);
257
+ return match !== null && referenced[normalizePath(path.substring(0, path.length - (match[1] ? match[1].length : 0)))] !== true;
258
+ }));
259
+ }
260
+ /** `<32 hex run id>-<file id>.sqlite`, or its -journal/-wal/-shm companion (group 1), at the end of a path. */
261
+ const STAGED_FILE_NAME = /(?:^|[\\/])[0-9a-fA-F]{32}-[A-Za-z0-9_]+\.sqlite(-journal|-wal|-shm)?$/i;
262
+ function normalizePath(path) {
263
+ return path.replace(/\\/g, '/').toLowerCase();
264
+ }
265
+ function joinPath(directory, name) {
266
+ return directory.length === 0 ? name : directory.replace(/[\\/]+$/, '') + '/' + name;
267
+ }
268
+ /** `VerifyStagedFile`: every segment arrived, the size is the manifest's, and it is a snapshot file (header + "G9SF"). */
269
+ export async function verifyStagedFile(store, file) {
270
+ const sink = sinkOf(store);
271
+ const manifest = file.manifest;
272
+ if (file.segmentsDone !== manifest.segments)
273
+ throw corrupt(`Snapshot file ${file.fileId} has ${file.segmentsDone} of ${manifest.segments} segments.`);
274
+ const size = await sink.size(file.path);
275
+ if (size !== manifest.bytes)
276
+ throw corrupt(`Snapshot file ${file.fileId} is missing or not ${manifest.bytes} bytes.`);
277
+ const header = await sink.head(file.path, 100);
278
+ const magic = 'SQLite format 3\u0000';
279
+ for (let i = 0; i < magic.length; i++) {
280
+ if (header[i] !== magic.charCodeAt(i))
281
+ throw corrupt(`Snapshot file ${file.fileId} has no SQLite header.`);
282
+ }
283
+ const id = ((header[68] << 24) | (header[69] << 16) | (header[70] << 8) | header[71]) >>> 0;
284
+ if (id !== SNAPSHOT_FILE_APPLICATION_ID)
285
+ throw corrupt(`Snapshot file ${file.fileId} is not a G9SyncData snapshot file.`);
286
+ }
287
+ /** `AttachedFiles.Schema`. */
288
+ export function attachedSchema(index) {
289
+ return 'g9snap' + index;
290
+ }
291
+ /** ATTACHes the staged files (outside any transaction) and returns the detach action. */
292
+ export async function attachFiles(connection, files) {
293
+ let count = 0;
294
+ const detach = async () => {
295
+ for (; count > 0; count--) {
296
+ try {
297
+ await connection.exec('DETACH DATABASE ' + attachedSchema(count - 1));
298
+ }
299
+ catch {
300
+ // Left attached: the session closes with the store, and the file is swept at the next start.
301
+ }
302
+ }
303
+ };
304
+ try {
305
+ for (const file of files) {
306
+ await connection.exec('ATTACH DATABASE $p AS ' + attachedSchema(count), { $p: file.path });
307
+ count++;
308
+ }
309
+ }
310
+ catch (error) {
311
+ await detach();
312
+ throw error;
313
+ }
314
+ return detach;
315
+ }
316
+ /** The first snapshot-file time per subscription of a plan (the reconcile clock of a file-based subscription starts there). */
317
+ export function fileSnapshotTimes(plan) {
318
+ const out = {};
319
+ for (const part of plan.parts) {
320
+ if (part.kind !== G9EPullPartKind.SnapshotFile || !part.file)
321
+ continue;
322
+ const at = part.file.snapshotUtcMs;
323
+ const existing = out[part.subscriptionKey];
324
+ out[part.subscriptionKey] = existing === undefined ? at : Math.min(existing, at);
325
+ }
326
+ return out;
327
+ }
328
+ async function tableColumns(connection, table, schema) {
329
+ return (await connection.query('SELECT name FROM pragma_table_info($t, $s) ORDER BY cid', { $t: table, $s: schema })).map((r) => asString(r[0]));
330
+ }
331
+ async function indexSignature(connection, index, unique, schema) {
332
+ let text = unique ? 'u' : 'n';
333
+ for (const r of await connection.query('SELECT seqno, coalesce(name, \'\'), "desc", coalesce(coll, \'\'), key FROM pragma_index_xinfo($i, $s) ORDER BY seqno', { $i: index, $s: schema })) {
334
+ text += '|' + asNumber(r[0]) + ':' + asString(r[1]).toUpperCase() + ':' + asNumber(r[2]) + ':' + asString(r[3]).toUpperCase() + ':' + asNumber(r[4]);
335
+ }
336
+ return text;
337
+ }
338
+ async function listIndexes(connection, table, schema) {
339
+ return (await connection.query('SELECT name, "unique", partial FROM pragma_index_list($t, $s) WHERE origin = \'c\'', { $t: table, $s: schema })).map((r) => ({
340
+ name: asString(r[0]),
341
+ unique: asNumber(r[1]) !== 0,
342
+ partial: asNumber(r[2]) !== 0,
343
+ }));
344
+ }
345
+ /** `PrepareIndexesForCopy`: drops each index the file cannot supply; they are rebuilt at the end of the apply. */
346
+ async function prepareIndexesForCopy(context, table, sourceTable, schema) {
347
+ var _a;
348
+ const connection = context.connection;
349
+ const available = {};
350
+ for (const index of await listIndexes(connection, sourceTable, schema)) {
351
+ if (!index.partial)
352
+ available[await indexSignature(connection, index.name, index.unique, schema)] = true;
353
+ }
354
+ let dropped = 0;
355
+ for (const index of await listIndexes(connection, table.model.localName, 'main')) {
356
+ if (!index.partial && available[await indexSignature(connection, index.name, index.unique, 'main')] === true)
357
+ continue;
358
+ if (context.deferredIndexes.some((d) => d.name.toLowerCase() === index.name.toLowerCase()))
359
+ continue;
360
+ const sql = await connection.scalar("SELECT sql FROM main.sqlite_master WHERE type = 'index' AND name = $n", { $n: index.name });
361
+ if (typeof sql !== 'string')
362
+ continue;
363
+ await connection.exec('DROP INDEX main.' + quote(index.name));
364
+ context.deferredIndexes.push({ name: index.name, sql });
365
+ dropped++;
366
+ }
367
+ if (dropped > 0) {
368
+ const name = table.model.localName;
369
+ context.report.diagnostics.deferredIndexes[name] = ((_a = context.report.diagnostics.deferredIndexes[name]) !== null && _a !== void 0 ? _a : 0) + dropped;
370
+ }
371
+ }
372
+ /** `ApplySnapshotFile`: copy, columns or merge per table, metadata set-based, inside the apply's transaction. */
373
+ export async function applySnapshotFile(store, context, schema, file) {
374
+ await context.flush();
375
+ const connection = context.connection;
376
+ const started = Date.now();
377
+ let slot = -1;
378
+ if (file.subscriptionKey !== null) {
379
+ const subscription = context.subscriptions.get(file.subscriptionKey);
380
+ if (subscription) {
381
+ if (subscription.ephemeral)
382
+ throw corrupt(`Snapshot file ${file.fileId} is for an ephemeral subscription.`);
383
+ slot = subscription.slot;
384
+ }
385
+ }
386
+ const bitValue = slot >= 0 ? bit(slot).toSqlValue() : 0;
387
+ const diagnostics = context.report.diagnostics;
388
+ for (const info of file.manifest.tables) {
389
+ const table = store.byServerId.get(info.tableId);
390
+ if (!table || !table.negotiated)
391
+ continue;
392
+ const tableStarted = Date.now();
393
+ const source = schema + '.' + quote(info.name);
394
+ // (pk, ver, r) of every row in the file, in arrival order: the version table is keyed by the data table's rowid.
395
+ const sourceKey = keyExpression(table.model, 's');
396
+ const pairs = `(SELECT ${sourceKey} AS pk, v.ver AS ver, s.rowid AS r FROM ${source} AS s JOIN ${schema}.${quote(info.versionTable)} AS v ON v.rid = s.rowid)`;
397
+ const sourceColumns = await tableColumns(connection, info.name, schema);
398
+ if (sourceColumns.length === 0)
399
+ throw corrupt(`Snapshot file ${file.fileId} does not contain ${info.name}.`);
400
+ const versionColumns = (await tableColumns(connection, info.versionTable, schema)).map((c) => c.toLowerCase());
401
+ if (versionColumns.indexOf('rid') < 0 || versionColumns.indexOf('ver') < 0) {
402
+ // Not this format's version table (rid → ver): a file this build cannot number - rows instead.
403
+ throw corrupt(`Snapshot file ${file.fileId}: ${info.versionTable} is not a version table of format ${SNAPSHOT_FILE_FORMAT_VERSION}.`);
404
+ }
405
+ const localColumns = await tableColumns(connection, table.model.localName, 'main');
406
+ const localLower = {};
407
+ for (const c of localColumns)
408
+ localLower[c.toLowerCase()] = true;
409
+ const shared = sourceColumns.filter((c) => localLower[c.toLowerCase()] === true);
410
+ if (!shared.some((c) => c.toLowerCase() === table.model.keyColumn.toLowerCase()))
411
+ throw corrupt(`Snapshot file ${file.fileId}: ${info.name} has no key column.`);
412
+ const isProtected = context.hasDirty(table.tid);
413
+ const empty = (await connection.scalar(`SELECT 1 FROM ${table.quoted} LIMIT 1`)) === null;
414
+ let rows;
415
+ let mode;
416
+ if (empty && !isProtected) {
417
+ await prepareIndexesForCopy(context, table, info.name, schema);
418
+ const identical = sourceColumns.length === localColumns.length && sourceColumns.every((c, i) => c.toLowerCase() === localColumns[i].toLowerCase());
419
+ mode = identical ? FILE_MODE_COPY : FILE_MODE_COLUMNS;
420
+ const list = shared.map(quote).join(', ');
421
+ rows = await connection.exec(identical ? `INSERT INTO main.${table.quoted} SELECT * FROM ${source}` : `INSERT INTO main.${table.quoted} (${list}) SELECT ${list} FROM ${source}`);
422
+ const clock = asNumber(await connection.scalar('SELECT seq FROM g9_clock WHERE id = 1'));
423
+ const numbered = await connection.exec('INSERT INTO g9_rows (tid, pk, server_ver, local_seq, subs, dirty, dirty_mask, rejected, deleted) ' +
424
+ `SELECT $t, v.pk, v.ver, $base + row_number() OVER (ORDER BY v.r), $bit, 0, 0, 0, 0 FROM ${pairs} AS v WHERE true ` +
425
+ 'ON CONFLICT(tid, pk) DO UPDATE SET server_ver = excluded.server_ver, local_seq = excluded.local_seq, subs = subs | excluded.subs, ' +
426
+ 'dirty = 0, dirty_mask = 0, rejected = 0, deleted = 0', { $t: table.tid, $base: clock, $bit: bitValue });
427
+ await connection.exec('UPDATE g9_clock SET seq = seq + $n WHERE id = 1', { $n: numbered });
428
+ if (context.wantsChangedKey(table.model.localName))
429
+ await addChangedKeys(context, table, `SELECT ${sourceKey} FROM ${source} AS s ORDER BY s.rowid`);
430
+ }
431
+ else {
432
+ mode = FILE_MODE_MERGE;
433
+ rows = await mergeSnapshotTable(context, table, source, pairs, shared, bitValue, isProtected);
434
+ }
435
+ if (context.hasRevoked && slot >= 0) {
436
+ await connection.exec(`DELETE FROM g9_revoked WHERE tid = $t AND pk IN (SELECT ${sourceKey} FROM ${source} AS s); ` +
437
+ `DELETE FROM g9_rejections WHERE tid = $t AND code = $code AND pk IN (SELECT ${sourceKey} FROM ${source} AS s)`, { $t: table.tid, $code: REVOKED_REJECTION_CODE });
438
+ }
439
+ context.report.upserts += rows;
440
+ context.advance(rows);
441
+ diagnostics.fileRows += rows;
442
+ const before = diagnostics.fileTables[table.model.localName];
443
+ diagnostics.fileTables[table.model.localName] = before === undefined ? mode : before.split('+').indexOf(mode) >= 0 ? before : before + '+' + mode;
444
+ context.addTableTime(table.model.localName, rows, Date.now() - tableStarted);
445
+ store.log('debug', 20121, `Snapshot file: ${table.model.localName} received ${rows} row(s) (${mode})`);
446
+ }
447
+ diagnostics.snapshotFiles++;
448
+ diagnostics.fileCopyMs += Date.now() - started;
449
+ }
450
+ /** `MergeSnapshotTable`: changed rows only; protected rows gain membership; unchanged rows keep their feed position. */
451
+ async function mergeSnapshotTable(context, table, source, pairs, columns, bitValue, isProtected) {
452
+ const connection = context.connection;
453
+ const key = quote(table.model.keyColumn);
454
+ const sourceKey = keyExpression(table.model, 's');
455
+ // The file's (pk, ver, r) by key, once: the statements below look versions up by key.
456
+ const versions = 'temp.g9_file_versions';
457
+ await connection.exec('DROP TABLE IF EXISTS temp.g9_file_versions; CREATE TEMP TABLE g9_file_versions (pk TEXT NOT NULL PRIMARY KEY, ver INTEGER NOT NULL, r INTEGER NOT NULL) WITHOUT ROWID; ' +
458
+ `INSERT OR REPLACE INTO temp.g9_file_versions (pk, ver, r) SELECT pk, ver, r FROM ${pairs}`);
459
+ await connection.exec('DROP TABLE IF EXISTS temp.g9_file_protect; CREATE TEMP TABLE g9_file_protect (pk TEXT NOT NULL PRIMARY KEY) WITHOUT ROWID');
460
+ if (isProtected) {
461
+ const prefix = table.tid + '\u0000';
462
+ const keys = [];
463
+ context.dirty.forEach((entry) => {
464
+ if (entry.slice(0, prefix.length) === prefix)
465
+ keys.push(entry.slice(prefix.length));
466
+ });
467
+ if (keys.length > 0)
468
+ await connection.exec('INSERT OR IGNORE INTO temp.g9_file_protect (pk) SELECT value FROM json_each($k)', { $k: JSON.stringify(keys) });
469
+ }
470
+ const nonKey = columns.filter((c) => c.toLowerCase() !== table.model.keyColumn.toLowerCase());
471
+ const differs = nonKey.map((c) => 'm.' + quote(c) + ' IS NOT s.' + quote(c)).join(' OR ');
472
+ await connection.exec('DROP TABLE IF EXISTS temp.g9_file_changed; CREATE TEMP TABLE g9_file_changed (r INTEGER PRIMARY KEY, pk TEXT NOT NULL UNIQUE)');
473
+ await connection.exec(`INSERT INTO temp.g9_file_changed (r, pk) SELECT s.rowid, ${sourceKey} FROM ${source} AS s LEFT JOIN main.${table.quoted} AS m ON m.${key} = s.${key} ` +
474
+ `WHERE NOT EXISTS (SELECT 1 FROM temp.g9_file_protect AS p WHERE p.pk = ${sourceKey}) ` +
475
+ `AND (m.${key} IS NULL${differs.length === 0 ? '' : ' OR ' + differs} ` +
476
+ `OR NOT EXISTS (SELECT 1 FROM g9_rows AS r WHERE r.tid = $t AND r.pk = ${sourceKey} AND r.deleted = 0))`, { $t: table.tid });
477
+ const list = columns.map(quote).join(', ');
478
+ const updates = nonKey.map((c) => quote(c) + ' = excluded.' + quote(c));
479
+ await connection.exec(`INSERT INTO main.${table.quoted} (${list}) SELECT ${list} FROM ${source} AS s WHERE s.rowid IN (SELECT r FROM temp.g9_file_changed) ` +
480
+ (updates.length === 0 ? `ON CONFLICT(${key}) DO NOTHING` : `ON CONFLICT(${key}) DO UPDATE SET ${updates.join(', ')}`));
481
+ const clock = asNumber(await connection.scalar('SELECT seq FROM g9_clock WHERE id = 1'));
482
+ const numbered = await connection.exec('INSERT INTO g9_rows (tid, pk, server_ver, local_seq, subs, dirty, dirty_mask, rejected, deleted) ' +
483
+ `SELECT $t, v.pk, v.ver, $base + row_number() OVER (ORDER BY v.r), $bit, 0, 0, 0, 0 FROM ${versions} AS v ` +
484
+ 'WHERE v.pk IN (SELECT pk FROM temp.g9_file_changed) ' +
485
+ 'ON CONFLICT(tid, pk) DO UPDATE SET server_ver = excluded.server_ver, local_seq = excluded.local_seq, subs = subs | excluded.subs, ' +
486
+ 'dirty = 0, dirty_mask = 0, rejected = 0, deleted = 0', { $t: table.tid, $base: clock, $bit: bitValue });
487
+ await connection.exec('UPDATE g9_clock SET seq = seq + $n WHERE id = 1', { $n: numbered });
488
+ await connection.exec(`UPDATE g9_rows SET server_ver = (SELECT v.ver FROM ${versions} AS v WHERE v.pk = g9_rows.pk), subs = subs | $bit, ` +
489
+ 'dirty = 0, dirty_mask = 0, rejected = 0, deleted = 0 ' +
490
+ `WHERE tid = $t AND pk IN (SELECT pk FROM ${versions}) ` +
491
+ 'AND pk NOT IN (SELECT pk FROM temp.g9_file_changed) AND pk NOT IN (SELECT pk FROM temp.g9_file_protect) ' +
492
+ `AND (server_ver IS NOT (SELECT v.ver FROM ${versions} AS v WHERE v.pk = g9_rows.pk) OR (subs | $bit) <> subs ` +
493
+ 'OR dirty <> 0 OR dirty_mask <> 0 OR rejected <> 0 OR deleted <> 0)', { $t: table.tid, $bit: bitValue });
494
+ let skipped = 0;
495
+ if (isProtected) {
496
+ skipped = asNumber(await connection.scalar(`SELECT count(*) FROM temp.g9_file_protect AS p WHERE p.pk IN (SELECT pk FROM ${versions})`));
497
+ if (bitValue !== 0) {
498
+ await connection.exec(`UPDATE g9_rows SET subs = subs | $bit WHERE tid = $t AND deleted = 0 AND pk IN (SELECT p.pk FROM temp.g9_file_protect AS p WHERE p.pk IN (SELECT pk FROM ${versions}))`, { $t: table.tid, $bit: bitValue });
499
+ }
500
+ }
501
+ const total = asNumber(await connection.scalar(`SELECT count(*) FROM ${versions}`));
502
+ context.report.skippedDirty += skipped;
503
+ context.report.unchangedRows += total - skipped - numbered;
504
+ if (context.wantsChangedKey(table.model.localName))
505
+ await addChangedKeys(context, table, 'SELECT pk FROM temp.g9_file_changed ORDER BY r');
506
+ await connection.exec('DROP TABLE IF EXISTS temp.g9_file_changed; DROP TABLE IF EXISTS temp.g9_file_protect; DROP TABLE IF EXISTS temp.g9_file_versions');
507
+ return total - skipped;
508
+ }
509
+ async function addChangedKeys(context, table, sql) {
510
+ for (const r of await context.connection.query(sql + ' LIMIT $limit', { $limit: context.maxChangedKeys })) {
511
+ if (!context.wantsChangedKey(table.model.localName))
512
+ break;
513
+ context.addChanged(table.model.localName, asString(r[0]));
514
+ }
515
+ }
516
+ //# sourceMappingURL=snapshotFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshotFiles.js","sourceRoot":"","sources":["../../src/sqlite/snapshotFiles.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStore.SnapshotFiles.cs (ADR-0072): a large first download arrives as a\n// SQLite file built on the server with this store's own DDL; its segments go straight from the transport to the platform's\n// G9SnapshotFileSink, and the apply copies the file's tables in SQL - one statement per table, SQLite's transfer optimisation\n// when the table is empty - instead of three statements per row. On Lynx that is one bridge call per table instead of one per\n// few thousand statements: the whole reason this exists for the TypeScript client.\nimport { G9SyncErrorCodes, G9SyncException } from '../abstractions/errors.js';\nimport { parseJson } from '../abstractions/long.js';\nimport { FILE_MODE_COLUMNS, FILE_MODE_COPY, FILE_MODE_MERGE } from '../client/storeTypes.js';\nimport { G9EPullPartKind, type G9DtNegotiatedTable, type G9DtPullPlan, type G9DtSnapshotFileManifest, type G9DtSnapshotFileRequest, type G9DtSyncChunk } from '../protocol/dtos.js';\nimport { readFileManifest } from '../protocol/wire.js';\nimport { G9ESyncDirection } from '../abstractions/enums.js';\nimport { asNumber, asOptionalString, asString, statementsOf, type G9SqliteConnection } from './connection.js';\nimport type { G9SqlValue } from './driver.js';\nimport { findByServerName, persistedJson, type LocalTable, type StoreInternals } from './internals.js';\nimport type { G9SnapshotFileSink } from './options.js';\nimport { REVOKED_REJECTION_CODE } from './revocation.js';\nimport { bit, G9ESqliteDateTimeStorage, G9ESqliteDecimalStorage, keyExpression, quote } from './sql.js';\nimport type { ApplyContext } from './apply.js';\n\n/** The protocol's snapshot-file format this build reads (`G9SyncProtocol.SnapshotFileFormatVersion`). */\nexport const SNAPSHOT_FILE_FORMAT_VERSION = 1;\n/** \"G9SF\": `PRAGMA application_id` of a snapshot file (`G9CSnapshotFileWriter.ApplicationId`). */\nexport const SNAPSHOT_FILE_APPLICATION_ID = 0x47395346;\nconst MAX_FILES_PER_PLAN = 8;\n\n/** A staged file (`StagedFile`). */\nexport interface G9StagedFile {\n fileId: string;\n path: string;\n manifest: G9DtSnapshotFileManifest;\n subscriptionKey: string | null;\n firstSequence: number;\n segmentsDone: number;\n}\n\n/** Segments written since a file's last durable checkpoint, per run and file (under the write lock). */\nconst pendingSegments = new WeakMap<StoreInternals, Map<string, { segments: number; rows: number; bytes: number; unsynced: number }>>();\n\nfunction pendingOf(store: StoreInternals): Map<string, { segments: number; rows: number; bytes: number; unsynced: number }> {\n let map = pendingSegments.get(store);\n if (!map) pendingSegments.set(store, (map = new Map()));\n return map;\n}\n\nfunction corrupt(message: string): G9SyncException {\n return new G9SyncException(G9SyncErrorCodes.SnapshotFileCorrupt, message);\n}\n\n/** The device's offer (`DescribeSnapshotFiles`): storage forms and, per received table, the shape the store creates it with. */\nexport function describeSnapshotFiles(store: StoreInternals, negotiated: readonly G9DtNegotiatedTable[]): G9DtSnapshotFileRequest | null {\n if (!store.options.snapshotFiles) return null;\n const tables = [];\n for (const table of negotiated) {\n if (table.direction === G9ESyncDirection.UploadOnly) continue;\n const local = findByServerName(store, table.name);\n if (!local) continue;\n const model = local.model;\n tables.push({\n tableId: table.tableId,\n localName: model.localName,\n keyColumn: model.keyColumn,\n keyType: model.keyType,\n columns: model.columns.map((c) => ({ name: c.name, type: c.type, nullable: c.nullable })),\n indexes: model.indexes.map((i) => ({ name: i.name, columns: i.columns.slice(), unique: i.unique })),\n });\n }\n if (tables.length === 0) return null;\n return {\n formatVersion: SNAPSHOT_FILE_FORMAT_VERSION,\n storage: {\n dateTimeAsTicks: store.options.dateTimeStorage === G9ESqliteDateTimeStorage.Ticks,\n dateTimeFormat: store.options.dateTimeFormat,\n decimalAsText: store.options.decimalStorage === G9ESqliteDecimalStorage.Text,\n },\n tables,\n minRows: store.options.snapshotFileMinRows,\n maxBytes: store.options.snapshotFileMaxBytes,\n maxFiles: MAX_FILES_PER_PLAN,\n };\n}\n\nfunction sinkOf(store: StoreInternals): G9SnapshotFileSink {\n const sink = store.options.snapshotFiles;\n if (!sink) throw corrupt('A snapshot-file segment arrived, but this store has no snapshot-file sink.');\n return sink;\n}\n\n/** A .NET DateTime in the JSON form System.Text.Json writes, so persisted rows read the same on both sides. */\nfunction dotnetUtc(ms: number): string {\n return new Date(ms).toISOString().replace(/\\.(\\d{3})Z$/, '.$10000Z');\n}\n\n/** The persisted (.NET) form of a manifest: `builtUtc` as a DateTime. */\nfunction persistedManifest(manifest: G9DtSnapshotFileManifest): string {\n const { builtUtcMs, ...rest } = manifest;\n return persistedJson({ ...rest, builtUtc: dotnetUtc(builtUtcMs) });\n}\n\n/** The persisted (.NET) form of a plan: each planned file's `snapshotUtc` as a DateTime. */\nexport function persistedPlan(plan: G9DtPullPlan): string {\n return persistedJson({\n ...plan,\n parts: plan.parts.map((part) => {\n if (!part.file) return part;\n const { snapshotUtcMs, ...file } = part.file;\n return { ...part, file: { ...file, snapshotUtc: dotnetUtc(snapshotUtcMs) } };\n }),\n });\n}\n\nfunction validateManifest(manifest: G9DtSnapshotFileManifest, fileId: string): void {\n if (manifest.fileId !== fileId) throw corrupt(`Snapshot file ${fileId} came with the manifest of ${manifest.fileId}.`);\n if (manifest.formatVersion < 1 || manifest.formatVersion > SNAPSHOT_FILE_FORMAT_VERSION) {\n throw corrupt(`Snapshot file ${fileId} has format ${manifest.formatVersion}; this build reads up to ${SNAPSHOT_FILE_FORMAT_VERSION}.`);\n }\n if (manifest.compression !== 'br') throw corrupt(`Snapshot file ${fileId} uses the unknown codec '${manifest.compression}'.`);\n if (\n manifest.bytes <= 0 ||\n manifest.segmentBytes <= 0 ||\n manifest.segments <= 0 ||\n manifest.segments * manifest.segmentBytes < manifest.bytes ||\n (manifest.segments - 1) * manifest.segmentBytes >= manifest.bytes ||\n manifest.sha256.length !== 64\n ) {\n throw corrupt(`Snapshot file ${fileId} has an inconsistent manifest.`);\n }\n}\n\nfunction safeFileName(fileId: string): string {\n return fileId.replace(/[^A-Za-z0-9]/g, '_');\n}\n\nasync function readStagedFile(connection: G9SqliteConnection, runId: string, fileId: string): Promise<G9StagedFile | null> {\n const rows = await connection.query(\n 'SELECT file_id, path, manifest_json, subscription_key, first_seq, segments_done FROM g9_stage_files WHERE run_id = $r AND file_id = $f',\n { $r: runId, $f: fileId },\n );\n return rows[0] ? stagedFileOf(rows[0]) : null;\n}\n\n/** The staged files of a run, in plan order. */\nexport async function readStagedFiles(connection: G9SqliteConnection, runId: string): Promise<G9StagedFile[]> {\n const rows = await connection.query('SELECT file_id, path, manifest_json, subscription_key, first_seq, segments_done FROM g9_stage_files WHERE run_id = $r ORDER BY first_seq', {\n $r: runId,\n });\n return rows.map(stagedFileOf);\n}\n\nfunction stagedFileOf(r: readonly G9SqlValue[]): G9StagedFile {\n const manifest = readFileManifest(parseJson(asString(r[2])));\n if (!manifest) throw corrupt('A staged snapshot file has no manifest.');\n return { fileId: asString(r[0]), path: asString(r[1]), manifest, subscriptionKey: asOptionalString(r[3]), firstSequence: asNumber(r[4]), segmentsDone: asNumber(r[5]) };\n}\n\n/**\n * `StageFileSegment`: one segment to its place in the staged file, checked against its SHA-256 by the sink BEFORE it is\n * written. Durable only at checkpoints - every `snapshotFileCheckpointBytes` and at the last segment - in that order: the file\n * is flushed first, then the stage row records the resume point.\n */\nexport async function stageFileSegment(store: StoreInternals, runId: string, chunk: G9DtSyncChunk): Promise<void> {\n const segment = chunk.file;\n if (!segment) return;\n const sink = sinkOf(store);\n const connection = store.writer();\n const key = runId + '\\u0000' + segment.fileId;\n const pending = pendingOf(store);\n let staged = await readStagedFile(connection, runId, segment.fileId);\n const manifest = segment.manifest;\n if (manifest) {\n validateManifest(manifest, segment.fileId);\n if (!staged || staged.manifest.sha256 !== manifest.sha256) {\n // A new file - or a different build of it after a resume on another server instance: the file starts over.\n const path = staged ? staged.path : joinPath(sink.directory, runId + '-' + safeFileName(segment.fileId) + '.sqlite');\n await sink.create(path, manifest.bytes);\n await connection.exec(\n 'INSERT INTO g9_stage_files (run_id, file_id, path, manifest_json, subscription_key, first_seq, segments_done, bytes_received) ' +\n 'VALUES ($r, $f, $p, $m, $k, $s, 0, 0) ON CONFLICT(run_id, file_id) DO UPDATE SET manifest_json = excluded.manifest_json, segments_done = 0, bytes_received = 0',\n { $r: runId, $f: segment.fileId, $p: path, $m: persistedManifest(manifest), $k: chunk.subscriptionKey ?? null, $s: chunk.sequence },\n );\n pending.delete(key);\n staged = await readStagedFile(connection, runId, segment.fileId);\n }\n }\n if (!staged) throw corrupt(`Segment ${segment.index} of snapshot file ${segment.fileId} arrived without its manifest.`);\n const layout = staged.manifest;\n let state = pending.get(key);\n if (!state) pending.set(key, (state = { segments: 0, rows: 0, bytes: 0, unsynced: 0 }));\n const expected = staged.segmentsDone + state.segments;\n if (segment.index !== expected) throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} arrived where ${expected} was expected.`);\n if (\n segment.offset !== segment.index * layout.segmentBytes ||\n segment.length <= 0 ||\n segment.length > layout.segmentBytes ||\n segment.offset + segment.length > layout.bytes ||\n segment.index >= layout.segments\n ) {\n throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} does not fit the manifest.`);\n }\n const payload = chunk.payload;\n if (!payload) throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} has no payload.`);\n\n const last = segment.index === layout.segments - 1;\n const sync = last || state.unsynced + segment.length >= Math.max(1, store.options.snapshotFileCheckpointBytes);\n try {\n await sink.writeSegment(staged.path, segment.offset, payload, segment.length, segment.sha256, sync);\n } catch (error) {\n throw corrupt(`Snapshot file ${segment.fileId}: segment ${segment.index} was refused (${error instanceof Error ? error.message : String(error)}).`);\n }\n state.segments++;\n state.rows += chunk.rowCount;\n state.bytes += payload.length;\n state.unsynced += segment.length;\n if (!sync) return;\n\n // The checkpoint, in one atomic call: the data is on disk, so the record that it is there may follow.\n await connection.runAtomic([\n ...statementsOf('INSERT OR REPLACE INTO g9_stage (run_id, seq, subscription_key, row_count, continuation_json, payload) VALUES ($r, $s, $k, $n, $c, NULL)', {\n $r: runId,\n $s: chunk.sequence,\n $k: chunk.subscriptionKey ?? null,\n $n: state.rows,\n $c: chunk.continuation ? persistedJson(chunk.continuation) : null,\n }),\n ...statementsOf('UPDATE g9_stage_files SET segments_done = segments_done + $n, bytes_received = bytes_received + $b WHERE run_id = $r AND file_id = $f', {\n $n: state.segments,\n $b: state.bytes,\n $r: runId,\n $f: segment.fileId,\n }),\n ]);\n pending.delete(key);\n}\n\n/** A resumed stream starts at a file's last checkpoint: segments written after it are written again. */\nexport function forgetPendingSegments(store: StoreInternals, runId: string): void {\n const pending = pendingOf(store);\n pending.forEach((_, key) => {\n if (key.slice(0, runId.length + 1) === runId + '\\u0000') pending.delete(key);\n });\n}\n\n/** The staged files of the runs a statement is about to delete (their rows go in the same transaction, the files after it). */\nexport async function takeStagedFiles(connection: G9SqliteConnection, runFilterSql: string, params?: { [name: string]: G9SqlValue }): Promise<string[]> {\n const paths = (await connection.query('SELECT path FROM g9_stage_files WHERE run_id IN (' + runFilterSql + ')', params)).map((r) => asString(r[0]));\n await connection.exec('DELETE FROM g9_stage_files WHERE run_id IN (' + runFilterSql + ')', params);\n return paths;\n}\n\nexport async function deleteFiles(store: StoreInternals, paths: readonly string[]): Promise<void> {\n const sink = store.options.snapshotFiles;\n if (!sink) return;\n for (const path of paths) {\n try {\n await sink.remove(path);\n } catch (error) {\n store.log('warning', 20122, `A staged snapshot file could not be deleted and is retried at the next start: ${path} (${error instanceof Error ? error.message : String(error)})`);\n }\n }\n}\n\n/** Deletes staged files no stage refers to any more (a crash between a stage's commit and its files' deletion). */\nexport async function sweepOrphanSnapshotFiles(store: StoreInternals, connection: G9SqliteConnection): Promise<void> {\n const sink = store.options.snapshotFiles;\n if (!sink) return;\n let files: string[];\n try {\n files = await sink.list(sink.directory);\n } catch {\n return;\n }\n const referenced: { [path: string]: true } = {};\n for (const r of await connection.query('SELECT path FROM g9_stage_files')) referenced[normalizePath(asString(r[0]))] = true;\n // Only names this store writes (<run id>-<file id>.sqlite and SQLite's companions of it): the directory may be shared with\n // other files - even the database itself - and a sweep must never touch what it did not create.\n await deleteFiles(\n store,\n files.filter((path) => {\n const match = STAGED_FILE_NAME.exec(path);\n return match !== null && referenced[normalizePath(path.substring(0, path.length - (match[1] ? match[1].length : 0)))] !== true;\n }),\n );\n}\n\n/** `<32 hex run id>-<file id>.sqlite`, or its -journal/-wal/-shm companion (group 1), at the end of a path. */\nconst STAGED_FILE_NAME = /(?:^|[\\\\/])[0-9a-fA-F]{32}-[A-Za-z0-9_]+\\.sqlite(-journal|-wal|-shm)?$/i;\n\nfunction normalizePath(path: string): string {\n return path.replace(/\\\\/g, '/').toLowerCase();\n}\n\nfunction joinPath(directory: string, name: string): string {\n return directory.length === 0 ? name : directory.replace(/[\\\\/]+$/, '') + '/' + name;\n}\n\n/** `VerifyStagedFile`: every segment arrived, the size is the manifest's, and it is a snapshot file (header + \"G9SF\"). */\nexport async function verifyStagedFile(store: StoreInternals, file: G9StagedFile): Promise<void> {\n const sink = sinkOf(store);\n const manifest = file.manifest;\n if (file.segmentsDone !== manifest.segments) throw corrupt(`Snapshot file ${file.fileId} has ${file.segmentsDone} of ${manifest.segments} segments.`);\n const size = await sink.size(file.path);\n if (size !== manifest.bytes) throw corrupt(`Snapshot file ${file.fileId} is missing or not ${manifest.bytes} bytes.`);\n const header = await sink.head(file.path, 100);\n const magic = 'SQLite format 3\\u0000';\n for (let i = 0; i < magic.length; i++) {\n if (header[i] !== magic.charCodeAt(i)) throw corrupt(`Snapshot file ${file.fileId} has no SQLite header.`);\n }\n const id = (((header[68] as number) << 24) | ((header[69] as number) << 16) | ((header[70] as number) << 8) | (header[71] as number)) >>> 0;\n if (id !== SNAPSHOT_FILE_APPLICATION_ID) throw corrupt(`Snapshot file ${file.fileId} is not a G9SyncData snapshot file.`);\n}\n\n/** `AttachedFiles.Schema`. */\nexport function attachedSchema(index: number): string {\n return 'g9snap' + index;\n}\n\n/** ATTACHes the staged files (outside any transaction) and returns the detach action. */\nexport async function attachFiles(connection: G9SqliteConnection, files: readonly G9StagedFile[]): Promise<() => Promise<void>> {\n let count = 0;\n const detach = async (): Promise<void> => {\n for (; count > 0; count--) {\n try {\n await connection.exec('DETACH DATABASE ' + attachedSchema(count - 1));\n } catch {\n // Left attached: the session closes with the store, and the file is swept at the next start.\n }\n }\n };\n try {\n for (const file of files) {\n await connection.exec('ATTACH DATABASE $p AS ' + attachedSchema(count), { $p: file.path });\n count++;\n }\n } catch (error) {\n await detach();\n throw error;\n }\n return detach;\n}\n\n/** The first snapshot-file time per subscription of a plan (the reconcile clock of a file-based subscription starts there). */\nexport function fileSnapshotTimes(plan: G9DtPullPlan): { [subscriptionKey: string]: number } {\n const out: { [key: string]: number } = {};\n for (const part of plan.parts) {\n if (part.kind !== G9EPullPartKind.SnapshotFile || !part.file) continue;\n const at = part.file.snapshotUtcMs;\n const existing = out[part.subscriptionKey];\n out[part.subscriptionKey] = existing === undefined ? at : Math.min(existing, at);\n }\n return out;\n}\n\nasync function tableColumns(connection: G9SqliteConnection, table: string, schema: string): Promise<string[]> {\n return (await connection.query('SELECT name FROM pragma_table_info($t, $s) ORDER BY cid', { $t: table, $s: schema })).map((r) => asString(r[0]));\n}\n\nasync function indexSignature(connection: G9SqliteConnection, index: string, unique: boolean, schema: string): Promise<string> {\n let text = unique ? 'u' : 'n';\n for (const r of await connection.query(\n 'SELECT seqno, coalesce(name, \\'\\'), \"desc\", coalesce(coll, \\'\\'), key FROM pragma_index_xinfo($i, $s) ORDER BY seqno',\n { $i: index, $s: schema },\n )) {\n text += '|' + asNumber(r[0]) + ':' + asString(r[1]).toUpperCase() + ':' + asNumber(r[2]) + ':' + asString(r[3]).toUpperCase() + ':' + asNumber(r[4]);\n }\n return text;\n}\n\nasync function listIndexes(connection: G9SqliteConnection, table: string, schema: string): Promise<{ name: string; unique: boolean; partial: boolean }[]> {\n return (await connection.query('SELECT name, \"unique\", partial FROM pragma_index_list($t, $s) WHERE origin = \\'c\\'', { $t: table, $s: schema })).map((r) => ({\n name: asString(r[0]),\n unique: asNumber(r[1]) !== 0,\n partial: asNumber(r[2]) !== 0,\n }));\n}\n\n/** `PrepareIndexesForCopy`: drops each index the file cannot supply; they are rebuilt at the end of the apply. */\nasync function prepareIndexesForCopy(context: ApplyContext, table: LocalTable, sourceTable: string, schema: string): Promise<void> {\n const connection = context.connection;\n const available: { [signature: string]: true } = {};\n for (const index of await listIndexes(connection, sourceTable, schema)) {\n if (!index.partial) available[await indexSignature(connection, index.name, index.unique, schema)] = true;\n }\n let dropped = 0;\n for (const index of await listIndexes(connection, table.model.localName, 'main')) {\n if (!index.partial && available[await indexSignature(connection, index.name, index.unique, 'main')] === true) continue;\n if (context.deferredIndexes.some((d) => d.name.toLowerCase() === index.name.toLowerCase())) continue;\n const sql = await connection.scalar(\"SELECT sql FROM main.sqlite_master WHERE type = 'index' AND name = $n\", { $n: index.name });\n if (typeof sql !== 'string') continue;\n await connection.exec('DROP INDEX main.' + quote(index.name));\n context.deferredIndexes.push({ name: index.name, sql });\n dropped++;\n }\n if (dropped > 0) {\n const name = table.model.localName;\n context.report.diagnostics.deferredIndexes[name] = (context.report.diagnostics.deferredIndexes[name] ?? 0) + dropped;\n }\n}\n\n/** `ApplySnapshotFile`: copy, columns or merge per table, metadata set-based, inside the apply's transaction. */\nexport async function applySnapshotFile(store: StoreInternals, context: ApplyContext, schema: string, file: G9StagedFile): Promise<void> {\n await context.flush();\n const connection = context.connection;\n const started = Date.now();\n let slot = -1;\n if (file.subscriptionKey !== null) {\n const subscription = context.subscriptions.get(file.subscriptionKey);\n if (subscription) {\n if (subscription.ephemeral) throw corrupt(`Snapshot file ${file.fileId} is for an ephemeral subscription.`);\n slot = subscription.slot;\n }\n }\n const bitValue = slot >= 0 ? bit(slot).toSqlValue() : 0;\n const diagnostics = context.report.diagnostics;\n\n for (const info of file.manifest.tables) {\n const table = store.byServerId.get(info.tableId);\n if (!table || !table.negotiated) continue;\n const tableStarted = Date.now();\n const source = schema + '.' + quote(info.name);\n // (pk, ver, r) of every row in the file, in arrival order: the version table is keyed by the data table's rowid.\n const sourceKey = keyExpression(table.model, 's');\n const pairs = `(SELECT ${sourceKey} AS pk, v.ver AS ver, s.rowid AS r FROM ${source} AS s JOIN ${schema}.${quote(info.versionTable)} AS v ON v.rid = s.rowid)`;\n const sourceColumns = await tableColumns(connection, info.name, schema);\n if (sourceColumns.length === 0) throw corrupt(`Snapshot file ${file.fileId} does not contain ${info.name}.`);\n const versionColumns = (await tableColumns(connection, info.versionTable, schema)).map((c) => c.toLowerCase());\n if (versionColumns.indexOf('rid') < 0 || versionColumns.indexOf('ver') < 0) {\n // Not this format's version table (rid → ver): a file this build cannot number - rows instead.\n throw corrupt(`Snapshot file ${file.fileId}: ${info.versionTable} is not a version table of format ${SNAPSHOT_FILE_FORMAT_VERSION}.`);\n }\n const localColumns = await tableColumns(connection, table.model.localName, 'main');\n const localLower: { [name: string]: true } = {};\n for (const c of localColumns) localLower[c.toLowerCase()] = true;\n const shared = sourceColumns.filter((c) => localLower[c.toLowerCase()] === true);\n if (!shared.some((c) => c.toLowerCase() === table.model.keyColumn.toLowerCase())) throw corrupt(`Snapshot file ${file.fileId}: ${info.name} has no key column.`);\n\n const isProtected = context.hasDirty(table.tid);\n const empty = (await connection.scalar(`SELECT 1 FROM ${table.quoted} LIMIT 1`)) === null;\n let rows: number;\n let mode: string;\n if (empty && !isProtected) {\n await prepareIndexesForCopy(context, table, info.name, schema);\n const identical = sourceColumns.length === localColumns.length && sourceColumns.every((c, i) => c.toLowerCase() === (localColumns[i] as string).toLowerCase());\n mode = identical ? FILE_MODE_COPY : FILE_MODE_COLUMNS;\n const list = shared.map(quote).join(', ');\n rows = await connection.exec(identical ? `INSERT INTO main.${table.quoted} SELECT * FROM ${source}` : `INSERT INTO main.${table.quoted} (${list}) SELECT ${list} FROM ${source}`);\n const clock = asNumber(await connection.scalar('SELECT seq FROM g9_clock WHERE id = 1'));\n const numbered = await connection.exec(\n 'INSERT INTO g9_rows (tid, pk, server_ver, local_seq, subs, dirty, dirty_mask, rejected, deleted) ' +\n `SELECT $t, v.pk, v.ver, $base + row_number() OVER (ORDER BY v.r), $bit, 0, 0, 0, 0 FROM ${pairs} AS v WHERE true ` +\n 'ON CONFLICT(tid, pk) DO UPDATE SET server_ver = excluded.server_ver, local_seq = excluded.local_seq, subs = subs | excluded.subs, ' +\n 'dirty = 0, dirty_mask = 0, rejected = 0, deleted = 0',\n { $t: table.tid, $base: clock, $bit: bitValue },\n );\n await connection.exec('UPDATE g9_clock SET seq = seq + $n WHERE id = 1', { $n: numbered });\n if (context.wantsChangedKey(table.model.localName)) await addChangedKeys(context, table, `SELECT ${sourceKey} FROM ${source} AS s ORDER BY s.rowid`);\n } else {\n mode = FILE_MODE_MERGE;\n rows = await mergeSnapshotTable(context, table, source, pairs, shared, bitValue, isProtected);\n }\n\n if (context.hasRevoked && slot >= 0) {\n await connection.exec(\n `DELETE FROM g9_revoked WHERE tid = $t AND pk IN (SELECT ${sourceKey} FROM ${source} AS s); ` +\n `DELETE FROM g9_rejections WHERE tid = $t AND code = $code AND pk IN (SELECT ${sourceKey} FROM ${source} AS s)`,\n { $t: table.tid, $code: REVOKED_REJECTION_CODE },\n );\n }\n\n context.report.upserts += rows;\n context.advance(rows);\n diagnostics.fileRows += rows;\n const before = diagnostics.fileTables[table.model.localName];\n diagnostics.fileTables[table.model.localName] = before === undefined ? mode : before.split('+').indexOf(mode) >= 0 ? before : before + '+' + mode;\n context.addTableTime(table.model.localName, rows, Date.now() - tableStarted);\n store.log('debug', 20121, `Snapshot file: ${table.model.localName} received ${rows} row(s) (${mode})`);\n }\n\n diagnostics.snapshotFiles++;\n diagnostics.fileCopyMs += Date.now() - started;\n}\n\n/** `MergeSnapshotTable`: changed rows only; protected rows gain membership; unchanged rows keep their feed position. */\nasync function mergeSnapshotTable(\n context: ApplyContext,\n table: LocalTable,\n source: string,\n pairs: string,\n columns: string[],\n bitValue: G9SqlValue,\n isProtected: boolean,\n): Promise<number> {\n const connection = context.connection;\n const key = quote(table.model.keyColumn);\n const sourceKey = keyExpression(table.model, 's');\n // The file's (pk, ver, r) by key, once: the statements below look versions up by key.\n const versions = 'temp.g9_file_versions';\n await connection.exec(\n 'DROP TABLE IF EXISTS temp.g9_file_versions; CREATE TEMP TABLE g9_file_versions (pk TEXT NOT NULL PRIMARY KEY, ver INTEGER NOT NULL, r INTEGER NOT NULL) WITHOUT ROWID; ' +\n `INSERT OR REPLACE INTO temp.g9_file_versions (pk, ver, r) SELECT pk, ver, r FROM ${pairs}`,\n );\n await connection.exec('DROP TABLE IF EXISTS temp.g9_file_protect; CREATE TEMP TABLE g9_file_protect (pk TEXT NOT NULL PRIMARY KEY) WITHOUT ROWID');\n if (isProtected) {\n const prefix = table.tid + '\\u0000';\n const keys: string[] = [];\n context.dirty.forEach((entry) => {\n if (entry.slice(0, prefix.length) === prefix) keys.push(entry.slice(prefix.length));\n });\n if (keys.length > 0) await connection.exec('INSERT OR IGNORE INTO temp.g9_file_protect (pk) SELECT value FROM json_each($k)', { $k: JSON.stringify(keys) });\n }\n\n const nonKey = columns.filter((c) => c.toLowerCase() !== table.model.keyColumn.toLowerCase());\n const differs = nonKey.map((c) => 'm.' + quote(c) + ' IS NOT s.' + quote(c)).join(' OR ');\n await connection.exec('DROP TABLE IF EXISTS temp.g9_file_changed; CREATE TEMP TABLE g9_file_changed (r INTEGER PRIMARY KEY, pk TEXT NOT NULL UNIQUE)');\n await connection.exec(\n `INSERT INTO temp.g9_file_changed (r, pk) SELECT s.rowid, ${sourceKey} FROM ${source} AS s LEFT JOIN main.${table.quoted} AS m ON m.${key} = s.${key} ` +\n `WHERE NOT EXISTS (SELECT 1 FROM temp.g9_file_protect AS p WHERE p.pk = ${sourceKey}) ` +\n `AND (m.${key} IS NULL${differs.length === 0 ? '' : ' OR ' + differs} ` +\n `OR NOT EXISTS (SELECT 1 FROM g9_rows AS r WHERE r.tid = $t AND r.pk = ${sourceKey} AND r.deleted = 0))`,\n { $t: table.tid },\n );\n\n const list = columns.map(quote).join(', ');\n const updates = nonKey.map((c) => quote(c) + ' = excluded.' + quote(c));\n await connection.exec(\n `INSERT INTO main.${table.quoted} (${list}) SELECT ${list} FROM ${source} AS s WHERE s.rowid IN (SELECT r FROM temp.g9_file_changed) ` +\n (updates.length === 0 ? `ON CONFLICT(${key}) DO NOTHING` : `ON CONFLICT(${key}) DO UPDATE SET ${updates.join(', ')}`),\n );\n\n const clock = asNumber(await connection.scalar('SELECT seq FROM g9_clock WHERE id = 1'));\n const numbered = await connection.exec(\n 'INSERT INTO g9_rows (tid, pk, server_ver, local_seq, subs, dirty, dirty_mask, rejected, deleted) ' +\n `SELECT $t, v.pk, v.ver, $base + row_number() OVER (ORDER BY v.r), $bit, 0, 0, 0, 0 FROM ${versions} AS v ` +\n 'WHERE v.pk IN (SELECT pk FROM temp.g9_file_changed) ' +\n 'ON CONFLICT(tid, pk) DO UPDATE SET server_ver = excluded.server_ver, local_seq = excluded.local_seq, subs = subs | excluded.subs, ' +\n 'dirty = 0, dirty_mask = 0, rejected = 0, deleted = 0',\n { $t: table.tid, $base: clock, $bit: bitValue },\n );\n await connection.exec('UPDATE g9_clock SET seq = seq + $n WHERE id = 1', { $n: numbered });\n\n await connection.exec(\n `UPDATE g9_rows SET server_ver = (SELECT v.ver FROM ${versions} AS v WHERE v.pk = g9_rows.pk), subs = subs | $bit, ` +\n 'dirty = 0, dirty_mask = 0, rejected = 0, deleted = 0 ' +\n `WHERE tid = $t AND pk IN (SELECT pk FROM ${versions}) ` +\n 'AND pk NOT IN (SELECT pk FROM temp.g9_file_changed) AND pk NOT IN (SELECT pk FROM temp.g9_file_protect) ' +\n `AND (server_ver IS NOT (SELECT v.ver FROM ${versions} AS v WHERE v.pk = g9_rows.pk) OR (subs | $bit) <> subs ` +\n 'OR dirty <> 0 OR dirty_mask <> 0 OR rejected <> 0 OR deleted <> 0)',\n { $t: table.tid, $bit: bitValue },\n );\n\n let skipped = 0;\n if (isProtected) {\n skipped = asNumber(await connection.scalar(`SELECT count(*) FROM temp.g9_file_protect AS p WHERE p.pk IN (SELECT pk FROM ${versions})`));\n if (bitValue !== 0) {\n await connection.exec(\n `UPDATE g9_rows SET subs = subs | $bit WHERE tid = $t AND deleted = 0 AND pk IN (SELECT p.pk FROM temp.g9_file_protect AS p WHERE p.pk IN (SELECT pk FROM ${versions}))`,\n { $t: table.tid, $bit: bitValue },\n );\n }\n }\n\n const total = asNumber(await connection.scalar(`SELECT count(*) FROM ${versions}`));\n context.report.skippedDirty += skipped;\n context.report.unchangedRows += total - skipped - numbered;\n if (context.wantsChangedKey(table.model.localName)) await addChangedKeys(context, table, 'SELECT pk FROM temp.g9_file_changed ORDER BY r');\n await connection.exec('DROP TABLE IF EXISTS temp.g9_file_changed; DROP TABLE IF EXISTS temp.g9_file_protect; DROP TABLE IF EXISTS temp.g9_file_versions');\n return total - skipped;\n}\n\nasync function addChangedKeys(context: ApplyContext, table: LocalTable, sql: string): Promise<void> {\n for (const r of await context.connection.query(sql + ' LIMIT $limit', { $limit: context.maxChangedKeys })) {\n if (!context.wantsChangedKey(table.model.localName)) break;\n context.addChanged(table.model.localName, asString(r[0]));\n }\n}\n"]}