@g9tm/syncdata 1.2.2 → 1.3.1

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 (56) hide show
  1. package/PARITY.md +26 -2
  2. package/dist/abstractions/errors.d.ts +4 -0
  3. package/dist/abstractions/errors.js +4 -0
  4. package/dist/abstractions/errors.js.map +1 -1
  5. package/dist/client/engine.js +93 -51
  6. package/dist/client/engine.js.map +1 -1
  7. package/dist/client/runs.d.ts +5 -0
  8. package/dist/client/runs.js +8 -7
  9. package/dist/client/runs.js.map +1 -1
  10. package/dist/client/storeTypes.d.ts +22 -1
  11. package/dist/client/storeTypes.js +27 -0
  12. package/dist/client/storeTypes.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/node/index.d.ts +1 -0
  17. package/dist/node/index.js +1 -0
  18. package/dist/node/index.js.map +1 -1
  19. package/dist/node/snapshotFileSink.d.ts +3 -0
  20. package/dist/node/snapshotFileSink.js +79 -0
  21. package/dist/node/snapshotFileSink.js.map +1 -0
  22. package/dist/protocol/dtos.d.ts +76 -0
  23. package/dist/protocol/dtos.js +2 -0
  24. package/dist/protocol/dtos.js.map +1 -1
  25. package/dist/protocol/wire.d.ts +3 -1
  26. package/dist/protocol/wire.js +49 -0
  27. package/dist/protocol/wire.js.map +1 -1
  28. package/dist/sqlite/apply.d.ts +21 -1
  29. package/dist/sqlite/apply.js +218 -114
  30. package/dist/sqlite/apply.js.map +1 -1
  31. package/dist/sqlite/connection.d.ts +5 -0
  32. package/dist/sqlite/connection.js +17 -0
  33. package/dist/sqlite/connection.js.map +1 -1
  34. package/dist/sqlite/index.d.ts +2 -1
  35. package/dist/sqlite/index.js +1 -0
  36. package/dist/sqlite/index.js.map +1 -1
  37. package/dist/sqlite/options.d.ts +39 -1
  38. package/dist/sqlite/options.js +5 -1
  39. package/dist/sqlite/options.js.map +1 -1
  40. package/dist/sqlite/push.js +4 -1
  41. package/dist/sqlite/push.js.map +1 -1
  42. package/dist/sqlite/query.js +44 -15
  43. package/dist/sqlite/query.js.map +1 -1
  44. package/dist/sqlite/revocation.d.ts +1 -1
  45. package/dist/sqlite/revocation.js +4 -2
  46. package/dist/sqlite/revocation.js.map +1 -1
  47. package/dist/sqlite/snapshotFiles.d.ts +51 -0
  48. package/dist/sqlite/snapshotFiles.js +516 -0
  49. package/dist/sqlite/snapshotFiles.js.map +1 -0
  50. package/dist/sqlite/sql.d.ts +1 -1
  51. package/dist/sqlite/sql.js +4 -0
  52. package/dist/sqlite/sql.js.map +1 -1
  53. package/dist/sqlite/store.d.ts +20 -2
  54. package/dist/sqlite/store.js +147 -78
  55. package/dist/sqlite/store.js.map +1 -1
  56. package/package.json +3 -3
@@ -2,7 +2,7 @@ import { G9RevocationPolicy } from '../client/storeTypes.js';
2
2
  import { G9ESqliteDateTimeStorage, G9ESqliteDecimalStorage } from './sql.js';
3
3
  export const DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.fffffff";
4
4
  export function resolveStoreOptions(options) {
5
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
5
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
6
6
  if (!options || !options.driver)
7
7
  throw new TypeError('A SQLite driver is required.');
8
8
  if (typeof options.databasePath !== 'string' || options.databasePath.trim() === '')
@@ -36,6 +36,10 @@ export function resolveStoreOptions(options) {
36
36
  frameDecompressor: (_v = options.frameDecompressor) !== null && _v !== void 0 ? _v : null,
37
37
  frameCompressor: (_w = options.frameCompressor) !== null && _w !== void 0 ? _w : null,
38
38
  logger: (_x = options.logger) !== null && _x !== void 0 ? _x : null,
39
+ snapshotFiles: (_y = options.snapshotFiles) !== null && _y !== void 0 ? _y : null,
40
+ snapshotFileMinRows: (_z = options.snapshotFileMinRows) !== null && _z !== void 0 ? _z : 0,
41
+ snapshotFileMaxBytes: (_0 = options.snapshotFileMaxBytes) !== null && _0 !== void 0 ? _0 : 2 * 1024 * 1024 * 1024,
42
+ snapshotFileCheckpointBytes: (_1 = options.snapshotFileCheckpointBytes) !== null && _1 !== void 0 ? _1 : 8 * 1024 * 1024,
39
43
  };
40
44
  }
41
45
  /** A marker in a file, written whole and moved into place (a marker cut short by a crash would read as another installation). */
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/sqlite/options.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStoreOptions.cs. Durations are milliseconds.\nimport type { G9SyncLogger } from '../client/runs.js';\nimport { G9RevocationPolicy } from '../client/storeTypes.js';\nimport type { G9FrameDecompressor } from '../protocol/rowBatch/reader.js';\nimport type { G9FrameCompressor } from '../protocol/rowBatch/writer.js';\nimport type { G9SqliteDriver } from './driver.js';\nimport { G9ESqliteDateTimeStorage, G9ESqliteDecimalStorage } from './sql.js';\n\n/** Where the installation's incarnation lives, outside the platform's backup set (ADR-0067). */\nexport interface G9IncarnationMarker {\n /** The marker's value, or null when there is none (or it cannot be read). */\n read(): Promise<string | null>;\n /** Writes the marker whole; false when it cannot be written (restore detection is then off for the run). */\n write(value: string): Promise<boolean>;\n}\n\n/** The file operations a file-based marker needs (structurally `G9FileSystem` of @g9tm/signalr-supernetcore-client). */\nexport interface G9MarkerFileSystem {\n stat(path: string): Promise<{ readonly size: number } | null>;\n read(path: string, offset: number, length: number): Promise<Uint8Array | ArrayBuffer>;\n write(path: string, offset: number, bytes: Uint8Array, truncate?: boolean): Promise<void>;\n move(from: string, to: string): Promise<void>;\n remove(path: string): Promise<void>;\n}\n\nexport interface G9SqliteSyncStoreOptions {\n /** The SQLite driver (Lynx native, Lynxtron, or `nodeSqliteDriver()` from `@g9tm/syncdata/node`). */\n driver: G9SqliteDriver;\n /** Database file path. */\n databasePath: string;\n dateTimeStorage?: G9ESqliteDateTimeStorage;\n /** Text format for DateTime values; only the .NET default `yyyy-MM-dd'T'HH:mm:ss.fffffff` is supported. */\n dateTimeFormat?: string;\n decimalStorage?: G9ESqliteDecimalStorage;\n /** Restore/copy detection (ADR-0067): a marker outside the backup set. Null (default) turns the check off. */\n incarnationMarker?: G9IncarnationMarker | null;\n /** How long a writer waits for the database lock (the app may be writing). */\n busyTimeoutMs?: number;\n useWriteAheadLog?: boolean;\n /** What happens to a row no subscription holds any more (ADR-0026). */\n revocation?: G9RevocationPolicy;\n /** The largest decoded body accepted from one frame the store applies (ADR-0051, ADR-0066). */\n maxDecodedFrameBytes?: number;\n /** Changed keys reported per table in an apply report. */\n maxChangedKeysPerTable?: number;\n /**\n * Large applies run without evaluating the library's own triggers (ADR-0034): the TypeScript store has no\n * SQLITE_DBCONFIG_ENABLE_TRIGGER, so it drops its own capture and command triggers inside the apply transaction and\n * re-creates them from their stored SQL before commit (PARITY.md). Never while the database holds a foreign trigger.\n */\n skipTriggersDuringApply?: boolean;\n /** The smallest staged pull (rows) for which triggers are skipped; smaller applies are not worth the schema change. */\n skipTriggersMinRows?: number;\n rebuildIndexesAfterRows?: number;\n rebuildIndexesMinimumGrowth?: number;\n /** Rows per committed slice of a staged pull, or 0 to apply it in one transaction (ADR-0053). */\n applySliceRows?: number;\n applySliceRowsMax?: number;\n maintenanceAfterRows?: number;\n synchronousNormalForDownloads?: boolean;\n /** journal_size_limit of the store's connections; negative = no limit. */\n walSizeLimitBytes?: number;\n /** Statements per driver call in the apply loop (one bridge round trip each). */\n statementsPerBatch?: number;\n /** Brotli decoder for downloaded frames; the process default (setDefaultFrameDecompressor) when omitted. */\n frameDecompressor?: G9FrameDecompressor | null;\n /** Brotli encoder for push frames; uncompressed frames when omitted (every server accepts them). */\n frameCompressor?: G9FrameCompressor | null;\n logger?: G9SyncLogger | null;\n}\n\nexport type G9ResolvedSqliteStoreOptions = Required<Omit<G9SqliteSyncStoreOptions, 'incarnationMarker' | 'frameDecompressor' | 'frameCompressor' | 'logger'>> & {\n incarnationMarker: G9IncarnationMarker | null;\n frameDecompressor: G9FrameDecompressor | null;\n frameCompressor: G9FrameCompressor | null;\n logger: G9SyncLogger | null;\n};\n\nexport const DEFAULT_DATE_TIME_FORMAT = \"yyyy-MM-dd'T'HH:mm:ss.fffffff\";\n\nexport function resolveStoreOptions(options: G9SqliteSyncStoreOptions): G9ResolvedSqliteStoreOptions {\n if (!options || !options.driver) throw new TypeError('A SQLite driver is required.');\n if (typeof options.databasePath !== 'string' || options.databasePath.trim() === '') throw new TypeError('Set databasePath.');\n const format = options.dateTimeFormat ?? DEFAULT_DATE_TIME_FORMAT;\n if (format !== DEFAULT_DATE_TIME_FORMAT) {\n throw new RangeError(`Only the DateTime format ${DEFAULT_DATE_TIME_FORMAT} is supported by the TypeScript store (PARITY.md).`);\n }\n return {\n driver: options.driver,\n databasePath: options.databasePath,\n dateTimeStorage: options.dateTimeStorage ?? G9ESqliteDateTimeStorage.Text,\n dateTimeFormat: format,\n decimalStorage: options.decimalStorage ?? G9ESqliteDecimalStorage.Real,\n incarnationMarker: options.incarnationMarker ?? null,\n busyTimeoutMs: options.busyTimeoutMs ?? 30000,\n useWriteAheadLog: options.useWriteAheadLog ?? true,\n revocation: options.revocation ?? G9RevocationPolicy.default,\n maxDecodedFrameBytes: options.maxDecodedFrameBytes ?? 64 * 1024 * 1024,\n maxChangedKeysPerTable: options.maxChangedKeysPerTable ?? 5000,\n skipTriggersDuringApply: options.skipTriggersDuringApply ?? true,\n skipTriggersMinRows: options.skipTriggersMinRows ?? 5000,\n rebuildIndexesAfterRows: options.rebuildIndexesAfterRows ?? 20000,\n rebuildIndexesMinimumGrowth: options.rebuildIndexesMinimumGrowth ?? 1,\n applySliceRows: options.applySliceRows ?? 0,\n applySliceRowsMax: options.applySliceRowsMax ?? 32000,\n maintenanceAfterRows: options.maintenanceAfterRows ?? 10000,\n synchronousNormalForDownloads: options.synchronousNormalForDownloads ?? true,\n walSizeLimitBytes: options.walSizeLimitBytes ?? 64 * 1024 * 1024,\n statementsPerBatch: options.statementsPerBatch ?? 1500,\n frameDecompressor: options.frameDecompressor ?? null,\n frameCompressor: options.frameCompressor ?? null,\n logger: options.logger ?? null,\n };\n}\n\n/** A marker in a file, written whole and moved into place (a marker cut short by a crash would read as another installation). */\nexport function fileIncarnationMarker(fileSystem: G9MarkerFileSystem, path: string): G9IncarnationMarker {\n return {\n async read(): Promise<string | null> {\n try {\n const stat = await fileSystem.stat(path);\n if (!stat) return null;\n const data = await fileSystem.read(path, 0, stat.size);\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n let text = '';\n for (let i = 0; i < bytes.length; i++) text += String.fromCharCode(bytes[i] as number);\n text = text.trim();\n return text.length === 0 ? null : text;\n } catch {\n return null;\n }\n },\n async write(value: string): Promise<boolean> {\n try {\n const bytes = new Uint8Array(value.length);\n for (let i = 0; i < value.length; i++) bytes[i] = value.charCodeAt(i) & 0x7f;\n const temporary = path + '.tmp';\n await fileSystem.write(temporary, 0, bytes, true);\n await fileSystem.remove(path);\n await fileSystem.move(temporary, path);\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n"]}
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/sqlite/options.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStoreOptions.cs. Durations are milliseconds.\nimport type { G9SyncLogger } from '../client/runs.js';\nimport { G9RevocationPolicy } from '../client/storeTypes.js';\nimport type { G9FrameDecompressor } from '../protocol/rowBatch/reader.js';\nimport type { G9FrameCompressor } from '../protocol/rowBatch/writer.js';\nimport type { G9SqliteDriver } from './driver.js';\nimport { G9ESqliteDateTimeStorage, G9ESqliteDecimalStorage } from './sql.js';\n\n/** Where the installation's incarnation lives, outside the platform's backup set (ADR-0067). */\nexport interface G9IncarnationMarker {\n /** The marker's value, or null when there is none (or it cannot be read). */\n read(): Promise<string | null>;\n /** Writes the marker whole; false when it cannot be written (restore detection is then off for the run). */\n write(value: string): Promise<boolean>;\n}\n\n/** The file operations a file-based marker needs (structurally `G9FileSystem` of @g9tm/signalr-supernetcore-client). */\nexport interface G9MarkerFileSystem {\n stat(path: string): Promise<{ readonly size: number } | null>;\n read(path: string, offset: number, length: number): Promise<Uint8Array | ArrayBuffer>;\n write(path: string, offset: number, bytes: Uint8Array, truncate?: boolean): Promise<void>;\n move(from: string, to: string): Promise<void>;\n remove(path: string): Promise<void>;\n}\n\n/**\n * Where a device writes downloaded snapshot files (ADR-0072). The bytes of a file never pass through the store's own code:\n * each segment goes straight from the transport to `writeSegment`, which a platform implements natively (Lynx: the\n * `G9SyncDataLynxModule` of @g9tm/syncdata-lynx; Node: `nodeSnapshotFileSink` of @g9tm/syncdata/node). The store reads the\n * finished file through SQLite's ATTACH, so the sink never needs to read a file back except its 100-byte header.\n */\nexport interface G9SnapshotFileSink {\n /** The directory staged files go to (created when missing). Use a cache directory, outside the backup set. */\n readonly directory: string;\n /** Creates `path` (or truncates it) as `size` zero bytes. */\n create(path: string, size: number): Promise<void>;\n /**\n * Decompresses one Brotli segment (each segment is compressed on its own), checks that it is `length` bytes whose SHA-256 is\n * `sha256` (lower-case hex), and writes it at `offset` in `path`. With `sync`, the file is flushed to disk before the promise\n * settles. Rejects - writing nothing - when the segment does not decompress to `length` bytes or fails its hash.\n */\n writeSegment(path: string, offset: number, compressed: Uint8Array, length: number, sha256: string, sync: boolean): Promise<void>;\n /** The file's size, or null when it does not exist. */\n size(path: string): Promise<number | null>;\n /** The first `length` bytes of the file (the SQLite header check). */\n head(path: string, length: number): Promise<Uint8Array>;\n /** Deletes the file; a missing file is not an error. */\n remove(path: string): Promise<void>;\n /** The full paths of the files in `directory` (for the sweep of files no stage refers to). */\n list(directory: string): Promise<string[]>;\n}\n\nexport interface G9SqliteSyncStoreOptions {\n /** The SQLite driver (Lynx native, Lynxtron, or `nodeSqliteDriver()` from `@g9tm/syncdata/node`). */\n driver: G9SqliteDriver;\n /** Database file path. */\n databasePath: string;\n dateTimeStorage?: G9ESqliteDateTimeStorage;\n /** Text format for DateTime values; only the .NET default `yyyy-MM-dd'T'HH:mm:ss.fffffff` is supported. */\n dateTimeFormat?: string;\n decimalStorage?: G9ESqliteDecimalStorage;\n /** Restore/copy detection (ADR-0067): a marker outside the backup set. Null (default) turns the check off. */\n incarnationMarker?: G9IncarnationMarker | null;\n /** How long a writer waits for the database lock (the app may be writing). */\n busyTimeoutMs?: number;\n useWriteAheadLog?: boolean;\n /** What happens to a row no subscription holds any more (ADR-0026). */\n revocation?: G9RevocationPolicy;\n /** The largest decoded body accepted from one frame the store applies (ADR-0051, ADR-0066). */\n maxDecodedFrameBytes?: number;\n /** Changed keys reported per table in an apply report. */\n maxChangedKeysPerTable?: number;\n /**\n * Large applies run without evaluating the library's own triggers (ADR-0034): the TypeScript store has no\n * SQLITE_DBCONFIG_ENABLE_TRIGGER, so it drops its own capture and command triggers inside the apply transaction and\n * re-creates them from their stored SQL before commit (PARITY.md). Never while the database holds a foreign trigger.\n */\n skipTriggersDuringApply?: boolean;\n /** The smallest staged pull (rows) for which triggers are skipped; smaller applies are not worth the schema change. */\n skipTriggersMinRows?: number;\n rebuildIndexesAfterRows?: number;\n rebuildIndexesMinimumGrowth?: number;\n /** Rows per committed slice of a staged pull, or 0 to apply it in one transaction (ADR-0053). */\n applySliceRows?: number;\n applySliceRowsMax?: number;\n maintenanceAfterRows?: number;\n synchronousNormalForDownloads?: boolean;\n /** journal_size_limit of the store's connections; negative = no limit. */\n walSizeLimitBytes?: number;\n /** Statements per driver call in the apply loop (one bridge round trip each). */\n statementsPerBatch?: number;\n /** Brotli decoder for downloaded frames; the process default (setDefaultFrameDecompressor) when omitted. */\n frameDecompressor?: G9FrameDecompressor | null;\n /** Brotli encoder for push frames; uncompressed frames when omitted (every server accepts them). */\n frameCompressor?: G9FrameCompressor | null;\n logger?: G9SyncLogger | null;\n /**\n * Where snapshot files are written (ADR-0072). Null (default): the store does not offer to receive them and every pull is\n * rows. With a sink, large first downloads can arrive as a SQLite file the apply copies instead of inserting row by row.\n */\n snapshotFiles?: G9SnapshotFileSink | null;\n /** A subscription is sent as a file only when its snapshot is at least this many rows; 0 leaves it to the server. */\n snapshotFileMinRows?: number;\n /** The largest snapshot file accepted, uncompressed (default 2 GiB). */\n snapshotFileMaxBytes?: number;\n /** Bytes between two durable resume points of a file download (flush, then the stage row). Default 8 MiB. */\n snapshotFileCheckpointBytes?: number;\n}\n\nexport type G9ResolvedSqliteStoreOptions = Required<Omit<G9SqliteSyncStoreOptions, 'incarnationMarker' | 'frameDecompressor' | 'frameCompressor' | 'logger' | 'snapshotFiles'>> & {\n incarnationMarker: G9IncarnationMarker | null;\n frameDecompressor: G9FrameDecompressor | null;\n frameCompressor: G9FrameCompressor | null;\n logger: G9SyncLogger | null;\n snapshotFiles: G9SnapshotFileSink | null;\n};\n\nexport const DEFAULT_DATE_TIME_FORMAT = \"yyyy-MM-dd'T'HH:mm:ss.fffffff\";\n\nexport function resolveStoreOptions(options: G9SqliteSyncStoreOptions): G9ResolvedSqliteStoreOptions {\n if (!options || !options.driver) throw new TypeError('A SQLite driver is required.');\n if (typeof options.databasePath !== 'string' || options.databasePath.trim() === '') throw new TypeError('Set databasePath.');\n const format = options.dateTimeFormat ?? DEFAULT_DATE_TIME_FORMAT;\n if (format !== DEFAULT_DATE_TIME_FORMAT) {\n throw new RangeError(`Only the DateTime format ${DEFAULT_DATE_TIME_FORMAT} is supported by the TypeScript store (PARITY.md).`);\n }\n return {\n driver: options.driver,\n databasePath: options.databasePath,\n dateTimeStorage: options.dateTimeStorage ?? G9ESqliteDateTimeStorage.Text,\n dateTimeFormat: format,\n decimalStorage: options.decimalStorage ?? G9ESqliteDecimalStorage.Real,\n incarnationMarker: options.incarnationMarker ?? null,\n busyTimeoutMs: options.busyTimeoutMs ?? 30000,\n useWriteAheadLog: options.useWriteAheadLog ?? true,\n revocation: options.revocation ?? G9RevocationPolicy.default,\n maxDecodedFrameBytes: options.maxDecodedFrameBytes ?? 64 * 1024 * 1024,\n maxChangedKeysPerTable: options.maxChangedKeysPerTable ?? 5000,\n skipTriggersDuringApply: options.skipTriggersDuringApply ?? true,\n skipTriggersMinRows: options.skipTriggersMinRows ?? 5000,\n rebuildIndexesAfterRows: options.rebuildIndexesAfterRows ?? 20000,\n rebuildIndexesMinimumGrowth: options.rebuildIndexesMinimumGrowth ?? 1,\n applySliceRows: options.applySliceRows ?? 0,\n applySliceRowsMax: options.applySliceRowsMax ?? 32000,\n maintenanceAfterRows: options.maintenanceAfterRows ?? 10000,\n synchronousNormalForDownloads: options.synchronousNormalForDownloads ?? true,\n walSizeLimitBytes: options.walSizeLimitBytes ?? 64 * 1024 * 1024,\n statementsPerBatch: options.statementsPerBatch ?? 1500,\n frameDecompressor: options.frameDecompressor ?? null,\n frameCompressor: options.frameCompressor ?? null,\n logger: options.logger ?? null,\n snapshotFiles: options.snapshotFiles ?? null,\n snapshotFileMinRows: options.snapshotFileMinRows ?? 0,\n snapshotFileMaxBytes: options.snapshotFileMaxBytes ?? 2 * 1024 * 1024 * 1024,\n snapshotFileCheckpointBytes: options.snapshotFileCheckpointBytes ?? 8 * 1024 * 1024,\n };\n}\n\n/** A marker in a file, written whole and moved into place (a marker cut short by a crash would read as another installation). */\nexport function fileIncarnationMarker(fileSystem: G9MarkerFileSystem, path: string): G9IncarnationMarker {\n return {\n async read(): Promise<string | null> {\n try {\n const stat = await fileSystem.stat(path);\n if (!stat) return null;\n const data = await fileSystem.read(path, 0, stat.size);\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n let text = '';\n for (let i = 0; i < bytes.length; i++) text += String.fromCharCode(bytes[i] as number);\n text = text.trim();\n return text.length === 0 ? null : text;\n } catch {\n return null;\n }\n },\n async write(value: string): Promise<boolean> {\n try {\n const bytes = new Uint8Array(value.length);\n for (let i = 0; i < value.length; i++) bytes[i] = value.charCodeAt(i) & 0x7f;\n const temporary = path + '.tmp';\n await fileSystem.write(temporary, 0, bytes, true);\n await fileSystem.remove(path);\n await fileSystem.move(temporary, path);\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n"]}
@@ -43,7 +43,10 @@ export async function buildPushBatch(store, tables, maxRows, maxBytes) {
43
43
  const offsetText = await meta(connection, 'server_offset_ms');
44
44
  const offsetMs = offsetText !== null && /^[+-]?\d+$/.test(offsetText) ? Number(offsetText) : 0;
45
45
  const tids = Array.from(uploadable.keys());
46
- const sql = 'SELECT tid, pk, dirty, dirty_mask, server_ver, local_seq, coalesce(hlc_ms, 0) FROM g9_rows WHERE dirty <> 0 AND rejected = 0 AND tid IN (' +
46
+ // `+tid`: the unary plus keeps the tid list from driving the plan. Without it SQLite searches the primary key by
47
+ // tid — every row of every uploadable table (486k on a farm device, 16 s on the bridge) — and sorts them, instead
48
+ // of walking the partial g9_rows_dirty index, which holds only the pending rows and is already in local_seq order.
49
+ const sql = 'SELECT tid, pk, dirty, dirty_mask, server_ver, local_seq, coalesce(hlc_ms, 0) FROM g9_rows WHERE dirty <> 0 AND rejected = 0 AND +tid IN (' +
47
50
  tids.join(',') +
48
51
  ')' +
49
52
  (inflightMax === null ? '' : ' AND local_seq <= $imax') +
@@ -1 +1 @@
1
- {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/sqlite/push.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStore.Push.cs: dirty rows in local change order become push frames; the\n// result clears exactly the rows that were sent (rows edited meanwhile stay dirty), flags rejections and applies canonical\n// server rows.\nimport { G9ESyncDirection } from '../abstractions/enums.js';\nimport { G9SyncErrorCodes } from '../abstractions/errors.js';\nimport { G9Long } from '../abstractions/long.js';\nimport { utf8Encode } from '../internal/utf8.js';\nimport { G9ApplyReport, type G9PushBatch } from '../client/storeTypes.js';\nimport type { G9DtNegotiatedTable, G9DtPushResult, G9DtTableColumns } from '../protocol/dtos.js';\nimport { G9EPushRowOp, G9ERowBatchOp } from '../protocol/rowBatch/format.js';\nimport { G9RowBatchWriter } from '../protocol/rowBatch/writer.js';\nimport { applyFrame, beginApply, endApply } from './apply.js';\nimport { completeCommands, readPendingCommands } from './commands.js';\nimport { asLong, asNumber, asString } from './connection.js';\nimport type { G9SqlValue } from './driver.js';\nimport { findByServerName, getLocal, keyValue, meta, nowMs, setMeta, type LocalTable, type StoreInternals } from './internals.js';\nimport { quote } from './sql.js';\nimport { estimateEncodedBytes, pushSelectExpression, writeLocalValue } from './values.js';\n\ninterface PushColumn {\n name: string;\n type: number;\n local: number;\n}\n\ninterface PushSection {\n table: LocalTable;\n negotiated: G9DtNegotiatedTable;\n columns: PushColumn[];\n writer: G9RowBatchWriter;\n select: string;\n rows: number;\n}\n\nconst ROW_OVERHEAD = 48;\n\n/** `BuildPushBatchAsync`: the next batch of pending changes, oldest first, within the row and byte budgets. */\nexport async function buildPushBatch(store: StoreInternals, tables: readonly G9DtNegotiatedTable[], maxRows: number, maxBytes: number): Promise<G9PushBatch | null> {\n store.ensureInitialized();\n const uploadable = new Map<number, { table: LocalTable; negotiated: G9DtNegotiatedTable; columns: PushColumn[] }>();\n for (const negotiated of tables) {\n if (negotiated.direction === G9ESyncDirection.DownloadOnly) continue;\n const local = findByServerName(store, negotiated.name);\n if (!local) continue;\n const readOnly = new Set<string>();\n for (const c of negotiated.readOnlyColumns) readOnly.add(c.toLowerCase());\n for (const c of negotiated.computedColumns) readOnly.add(c.toLowerCase());\n const columns = negotiated.columns\n .filter((c) => !readOnly.has(c.name.toLowerCase()) && local.model.indexOf(c.name) >= 0)\n .map((c) => ({ name: c.name, type: c.type, local: local.model.indexOf(c.name) }));\n uploadable.set(local.tid, { table: local, negotiated, columns });\n }\n\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n const inflightBatch = await meta(connection, 'inflight_batch_id');\n const inflightMax = await meta(connection, 'inflight_max_seq');\n const offsetText = await meta(connection, 'server_offset_ms');\n const offsetMs = offsetText !== null && /^[+-]?\\d+$/.test(offsetText) ? Number(offsetText) : 0;\n\n const tids = Array.from(uploadable.keys());\n const sql =\n 'SELECT tid, pk, dirty, dirty_mask, server_ver, local_seq, coalesce(hlc_ms, 0) FROM g9_rows WHERE dirty <> 0 AND rejected = 0 AND tid IN (' +\n tids.join(',') +\n ')' +\n (inflightMax === null ? '' : ' AND local_seq <= $imax') +\n ' ORDER BY local_seq LIMIT $max';\n const commands = await readPendingCommands(store, connection, inflightBatch, Math.max(1, maxRows));\n const pending =\n uploadable.size === 0\n ? []\n : await connection.query(sql, inflightMax === null ? { $max: Math.max(1, maxRows) } : { $max: Math.max(1, maxRows), $imax: Number(inflightMax) });\n const rows = pending.map((r) => ({\n tid: asNumber(r[0]),\n key: asString(r[1]),\n dirty: asNumber(r[2]),\n mask: asLong(r[3]),\n base: asNumber(r[4]),\n seq: asNumber(r[5]),\n hlc: asNumber(r[6]),\n }));\n\n if (rows.length === 0 && commands.length === 0) {\n if (inflightBatch !== null) {\n // Everything the in-flight batch carried has been resolved another way; forget it.\n await connection.exec(\"DELETE FROM g9_meta WHERE key IN ('inflight_batch_id', 'inflight_max_seq')\");\n }\n return null;\n }\n\n // Rows are encoded in local_seq order and the batch ends at the first row past the byte budget, so a batch is always\n // a PREFIX of the pending changes. One section (frame) per table.\n const sections = new Map<number, PushSection>();\n let included = 0;\n let includedMaxSequence = 0;\n let budgetUsed = 0;\n let truncated = false;\n const tooLarge: { tid: number; key: string; bytes: number }[] = [];\n for (const row of rows) {\n const entry = uploadable.get(row.tid);\n if (!entry) continue;\n let section = sections.get(row.tid);\n if (!section) {\n const select =\n entry.columns.length === 0\n ? `SELECT 1 FROM ${entry.table.quoted} WHERE ${entry.table.keyQuoted} = $k`\n : `SELECT ${entry.columns.map((c) => pushSelectExpression(quote(c.name), c.type)).join(', ')} FROM ${entry.table.quoted} WHERE ${entry.table.keyQuoted} = $k`;\n const writer = new G9RowBatchWriter();\n writer.beginSection(\n G9ERowBatchOp.Push,\n entry.negotiated.tableId,\n entry.table.model.keyType,\n entry.columns.map((c) => c.type),\n );\n section = { table: entry.table, negotiated: entry.negotiated, columns: entry.columns, writer, select, rows: 0 };\n sections.set(row.tid, section);\n }\n\n const hlc = row.hlc === 0 ? 0 : row.hlc + offsetMs;\n const keyBytes = utf8Encode(row.key).length;\n if (row.dirty === 2) {\n const deleteBytes = ROW_OVERHEAD + keyBytes;\n if (included > 0 && budgetUsed + deleteBytes > maxBytes) {\n truncated = true;\n break;\n }\n section.writer.beginPushRow(row.key, G9EPushRowOp.Delete, row.seq, row.base, G9Long.ZERO, hlc);\n section.writer.endRow();\n section.rows++;\n included++;\n includedMaxSequence = row.seq;\n budgetUsed += deleteBytes;\n continue;\n }\n\n const found = await connection.query(section.select, { $k: keyValue(section.table, row.key) });\n const values = found[0];\n if (!values) {\n section.writer.beginPushRow(row.key, G9EPushRowOp.Delete, row.seq, row.base, G9Long.ZERO, hlc);\n section.writer.endRow();\n section.rows++;\n included++;\n includedMaxSequence = row.seq;\n budgetUsed += ROW_OVERHEAD + keyBytes;\n continue;\n }\n\n let rowBytes = ROW_OVERHEAD + keyBytes;\n for (let j = 0; j < section.columns.length; j++) rowBytes += estimateEncodedBytes(values[j] as G9SqlValue, (section.columns[j] as PushColumn).type);\n if (rowBytes > maxBytes) {\n // Can never fit, whatever else the batch carries: reject it here so it does not block every later push.\n tooLarge.push({ tid: row.tid, key: row.key, bytes: rowBytes });\n continue;\n }\n if (included > 0 && budgetUsed + rowBytes > maxBytes) {\n truncated = true;\n break;\n }\n\n let pushMask = G9Long.ZERO;\n for (let j = 0; j < section.columns.length; j++) {\n if (row.mask.testBit((section.columns[j] as PushColumn).local)) pushMask = pushMask.or(G9Long.ONE.shiftLeft(j));\n }\n section.writer.beginPushRow(row.key, G9EPushRowOp.Upsert, row.seq, row.base, pushMask, hlc);\n for (let j = 0; j < section.columns.length; j++) writeLocalValue(section.writer, values[j] as G9SqlValue, (section.columns[j] as PushColumn).type, store.options);\n section.writer.endRow();\n section.rows++;\n included++;\n includedMaxSequence = row.seq;\n budgetUsed += rowBytes;\n }\n\n for (const { tid, key, bytes } of tooLarge) {\n await connection.exec('UPDATE g9_rows SET rejected = 1 WHERE tid = $t AND pk = $pk', { $t: tid, $pk: key });\n await connection.exec(\n 'INSERT OR REPLACE INTO g9_rejections (tid, pk, code, message, sql_state, constraint_name, column_name, details, at_utc) ' +\n 'VALUES ($t, $pk, $code, $message, NULL, NULL, NULL, NULL, $at)',\n {\n $t: tid,\n $pk: key,\n $code: G9SyncErrorCodes.RejectTooLarge,\n $message: `The row needs about ${bytes} bytes; the server accepts at most ${maxBytes} bytes per push.`,\n $at: nowMs(),\n },\n );\n }\n\n if (included === 0 && commands.length === 0) {\n if (tooLarge.length === 0) return null;\n return { batchId: 0, maxSequence: 0, frames: [], tables: [], rowCount: 0, truncated: false, rejectedLocally: tooLarge.length, commands: [] };\n }\n\n const frames: Uint8Array[] = [];\n const usedTables: G9DtTableColumns[] = [];\n sections.forEach((section) => {\n if (section.rows === 0) return;\n section.writer.endSection();\n frames.push(section.writer.toFrame({ compressor: store.options.frameCompressor }));\n usedTables.push({ tableId: section.negotiated.tableId, columns: section.columns.map((c) => c.name) });\n });\n\n let batchId: number;\n let maxSequence: number;\n if (inflightBatch !== null) {\n batchId = Number(inflightBatch);\n maxSequence = Number(inflightMax);\n } else {\n batchId = Number((await meta(connection, 'next_batch_id')) ?? '1');\n maxSequence = includedMaxSequence;\n await setMeta(connection, 'next_batch_id', String(batchId + 1));\n await setMeta(connection, 'inflight_batch_id', String(batchId));\n await setMeta(connection, 'inflight_max_seq', String(maxSequence));\n }\n for (const item of commands) {\n await connection.exec('UPDATE g9_commands SET batch_id = $b WHERE command_id = $id', { $b: batchId, $id: item.commandId });\n }\n return { batchId, maxSequence, frames, tables: usedTables, rowCount: included, truncated, rejectedLocally: tooLarge.length, commands };\n }),\n );\n}\n\n/** `ForgetUploadedRowsAsync`: removes uploaded rows of an UPLOAD-ONLY table without uploading a delete. */\nexport async function forgetUploadedRows(store: StoreInternals, localTable: string, keys: readonly string[] | null): Promise<number> {\n store.ensureInitialized();\n const table = getLocal(store, localTable);\n if (!table.negotiated || table.negotiated.direction !== G9ESyncDirection.UploadOnly) {\n throw new Error(`'${localTable}' is not upload-only: deleting its rows locally is a change the server must receive.`);\n }\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n await connection.exec('UPDATE g9_ctx SET applying = 1 WHERE id = 1'); // a library write: not captured\n await connection.exec('CREATE TEMP TABLE IF NOT EXISTS g9_forget (pk TEXT PRIMARY KEY); DELETE FROM g9_forget');\n if (keys === null) {\n await connection.exec('INSERT INTO g9_forget (pk) SELECT pk FROM g9_rows WHERE tid = $t AND dirty = 0', { $t: table.tid });\n } else {\n for (const key of keys) {\n await connection.exec('INSERT OR IGNORE INTO g9_forget (pk) SELECT pk FROM g9_rows WHERE tid = $t AND pk = $pk AND dirty = 0', { $t: table.tid, $pk: key });\n }\n }\n const forgotten = asNumber(await connection.scalar('SELECT count(*) FROM g9_forget'));\n await connection.exec(`DELETE FROM ${table.quoted} WHERE CAST(${table.keyQuoted} AS TEXT) IN (SELECT pk FROM g9_forget)`);\n await connection.exec('DELETE FROM g9_rows WHERE tid = $t AND pk IN (SELECT pk FROM g9_forget); DELETE FROM g9_forget', { $t: table.tid });\n await connection.exec('UPDATE g9_ctx SET applying = 0 WHERE id = 1');\n return forgotten;\n }),\n );\n}\n\n/** `ApplyPushResultAsync`. */\nexport async function applyPushResult(store: StoreInternals, batch: G9PushBatch, result: G9DtPushResult, signal?: AbortSignal): Promise<G9ApplyReport> {\n store.ensureInitialized();\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n // Before BeginApply: finished commands release their rows, so the canonical rows below replace the optimistic ones.\n const completed = await completeCommands(connection, result.commandResults);\n const context = await beginApply(store, connection, { dirtyAfterSequence: batch.maxSequence, backfills: [], signal });\n context.report.commandsCompleted = completed;\n\n const acceptSql =\n 'UPDATE g9_rows SET server_ver = $v, ' +\n 'dirty = CASE WHEN local_seq <= $m THEN 0 ELSE dirty END, ' +\n 'dirty_mask = CASE WHEN local_seq <= $m THEN 0 ELSE dirty_mask END, ' +\n 'deleted = CASE WHEN local_seq <= $m AND dirty = 2 THEN 1 ELSE deleted END, ' +\n 'rejected = 0 WHERE tid = $t AND pk = $pk; ' +\n 'DELETE FROM g9_rejections WHERE tid = $t AND pk = $pk';\n for (const accepted of result.accepted) {\n const table = store.byServerId.get(accepted.tableId);\n if (!table) continue;\n for (let i = 0; i < accepted.keys.length; i++) {\n await context.queue(acceptSql, { $v: i < accepted.versions.length ? (accepted.versions[i] as number) : 0, $m: batch.maxSequence, $t: table.tid, $pk: accepted.keys[i] as string });\n context.report.accepted++;\n }\n }\n\n const rejectSql =\n 'UPDATE g9_rows SET rejected = 1 WHERE tid = $t AND pk = $pk AND local_seq <= $m; ' +\n 'INSERT OR REPLACE INTO g9_rejections (tid, pk, code, message, sql_state, constraint_name, column_name, details, at_utc) ' +\n 'VALUES ($t, $pk, $code, $message, $state, $constraint, $column, $details, $now)';\n const now = nowMs();\n for (const rejection of result.rejections) {\n const table = store.byServerId.get(rejection.tableId);\n if (!table) continue;\n await context.queue(rejectSql, {\n $t: table.tid,\n $pk: rejection.key,\n $m: batch.maxSequence,\n $code: rejection.code,\n $message: rejection.message ?? null,\n $state: rejection.sqlState ?? null,\n $constraint: rejection.constraint ?? null,\n $column: rejection.column ?? null,\n $details: rejection.details ?? null,\n $now: now,\n });\n context.report.rejected++;\n }\n\n context.report.conflicts = result.conflicts.length;\n await context.flush();\n for (const frame of result.canonicalFrames) await applyFrame(store, context, frame, null);\n\n await endApply(context);\n await connection.exec(\"DELETE FROM g9_meta WHERE key IN ('inflight_batch_id', 'inflight_max_seq')\");\n return context.report;\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/sqlite/push.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStore.Push.cs: dirty rows in local change order become push frames; the\n// result clears exactly the rows that were sent (rows edited meanwhile stay dirty), flags rejections and applies canonical\n// server rows.\nimport { G9ESyncDirection } from '../abstractions/enums.js';\nimport { G9SyncErrorCodes } from '../abstractions/errors.js';\nimport { G9Long } from '../abstractions/long.js';\nimport { utf8Encode } from '../internal/utf8.js';\nimport { G9ApplyReport, type G9PushBatch } from '../client/storeTypes.js';\nimport type { G9DtNegotiatedTable, G9DtPushResult, G9DtTableColumns } from '../protocol/dtos.js';\nimport { G9EPushRowOp, G9ERowBatchOp } from '../protocol/rowBatch/format.js';\nimport { G9RowBatchWriter } from '../protocol/rowBatch/writer.js';\nimport { applyFrame, beginApply, endApply } from './apply.js';\nimport { completeCommands, readPendingCommands } from './commands.js';\nimport { asLong, asNumber, asString } from './connection.js';\nimport type { G9SqlValue } from './driver.js';\nimport { findByServerName, getLocal, keyValue, meta, nowMs, setMeta, type LocalTable, type StoreInternals } from './internals.js';\nimport { quote } from './sql.js';\nimport { estimateEncodedBytes, pushSelectExpression, writeLocalValue } from './values.js';\n\ninterface PushColumn {\n name: string;\n type: number;\n local: number;\n}\n\ninterface PushSection {\n table: LocalTable;\n negotiated: G9DtNegotiatedTable;\n columns: PushColumn[];\n writer: G9RowBatchWriter;\n select: string;\n rows: number;\n}\n\nconst ROW_OVERHEAD = 48;\n\n/** `BuildPushBatchAsync`: the next batch of pending changes, oldest first, within the row and byte budgets. */\nexport async function buildPushBatch(store: StoreInternals, tables: readonly G9DtNegotiatedTable[], maxRows: number, maxBytes: number): Promise<G9PushBatch | null> {\n store.ensureInitialized();\n const uploadable = new Map<number, { table: LocalTable; negotiated: G9DtNegotiatedTable; columns: PushColumn[] }>();\n for (const negotiated of tables) {\n if (negotiated.direction === G9ESyncDirection.DownloadOnly) continue;\n const local = findByServerName(store, negotiated.name);\n if (!local) continue;\n const readOnly = new Set<string>();\n for (const c of negotiated.readOnlyColumns) readOnly.add(c.toLowerCase());\n for (const c of negotiated.computedColumns) readOnly.add(c.toLowerCase());\n const columns = negotiated.columns\n .filter((c) => !readOnly.has(c.name.toLowerCase()) && local.model.indexOf(c.name) >= 0)\n .map((c) => ({ name: c.name, type: c.type, local: local.model.indexOf(c.name) }));\n uploadable.set(local.tid, { table: local, negotiated, columns });\n }\n\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n const inflightBatch = await meta(connection, 'inflight_batch_id');\n const inflightMax = await meta(connection, 'inflight_max_seq');\n const offsetText = await meta(connection, 'server_offset_ms');\n const offsetMs = offsetText !== null && /^[+-]?\\d+$/.test(offsetText) ? Number(offsetText) : 0;\n\n const tids = Array.from(uploadable.keys());\n // `+tid`: the unary plus keeps the tid list from driving the plan. Without it SQLite searches the primary key by\n // tid — every row of every uploadable table (486k on a farm device, 16 s on the bridge) — and sorts them, instead\n // of walking the partial g9_rows_dirty index, which holds only the pending rows and is already in local_seq order.\n const sql =\n 'SELECT tid, pk, dirty, dirty_mask, server_ver, local_seq, coalesce(hlc_ms, 0) FROM g9_rows WHERE dirty <> 0 AND rejected = 0 AND +tid IN (' +\n tids.join(',') +\n ')' +\n (inflightMax === null ? '' : ' AND local_seq <= $imax') +\n ' ORDER BY local_seq LIMIT $max';\n const commands = await readPendingCommands(store, connection, inflightBatch, Math.max(1, maxRows));\n const pending =\n uploadable.size === 0\n ? []\n : await connection.query(sql, inflightMax === null ? { $max: Math.max(1, maxRows) } : { $max: Math.max(1, maxRows), $imax: Number(inflightMax) });\n const rows = pending.map((r) => ({\n tid: asNumber(r[0]),\n key: asString(r[1]),\n dirty: asNumber(r[2]),\n mask: asLong(r[3]),\n base: asNumber(r[4]),\n seq: asNumber(r[5]),\n hlc: asNumber(r[6]),\n }));\n\n if (rows.length === 0 && commands.length === 0) {\n if (inflightBatch !== null) {\n // Everything the in-flight batch carried has been resolved another way; forget it.\n await connection.exec(\"DELETE FROM g9_meta WHERE key IN ('inflight_batch_id', 'inflight_max_seq')\");\n }\n return null;\n }\n\n // Rows are encoded in local_seq order and the batch ends at the first row past the byte budget, so a batch is always\n // a PREFIX of the pending changes. One section (frame) per table.\n const sections = new Map<number, PushSection>();\n let included = 0;\n let includedMaxSequence = 0;\n let budgetUsed = 0;\n let truncated = false;\n const tooLarge: { tid: number; key: string; bytes: number }[] = [];\n for (const row of rows) {\n const entry = uploadable.get(row.tid);\n if (!entry) continue;\n let section = sections.get(row.tid);\n if (!section) {\n const select =\n entry.columns.length === 0\n ? `SELECT 1 FROM ${entry.table.quoted} WHERE ${entry.table.keyQuoted} = $k`\n : `SELECT ${entry.columns.map((c) => pushSelectExpression(quote(c.name), c.type)).join(', ')} FROM ${entry.table.quoted} WHERE ${entry.table.keyQuoted} = $k`;\n const writer = new G9RowBatchWriter();\n writer.beginSection(\n G9ERowBatchOp.Push,\n entry.negotiated.tableId,\n entry.table.model.keyType,\n entry.columns.map((c) => c.type),\n );\n section = { table: entry.table, negotiated: entry.negotiated, columns: entry.columns, writer, select, rows: 0 };\n sections.set(row.tid, section);\n }\n\n const hlc = row.hlc === 0 ? 0 : row.hlc + offsetMs;\n const keyBytes = utf8Encode(row.key).length;\n if (row.dirty === 2) {\n const deleteBytes = ROW_OVERHEAD + keyBytes;\n if (included > 0 && budgetUsed + deleteBytes > maxBytes) {\n truncated = true;\n break;\n }\n section.writer.beginPushRow(row.key, G9EPushRowOp.Delete, row.seq, row.base, G9Long.ZERO, hlc);\n section.writer.endRow();\n section.rows++;\n included++;\n includedMaxSequence = row.seq;\n budgetUsed += deleteBytes;\n continue;\n }\n\n const found = await connection.query(section.select, { $k: keyValue(section.table, row.key) });\n const values = found[0];\n if (!values) {\n section.writer.beginPushRow(row.key, G9EPushRowOp.Delete, row.seq, row.base, G9Long.ZERO, hlc);\n section.writer.endRow();\n section.rows++;\n included++;\n includedMaxSequence = row.seq;\n budgetUsed += ROW_OVERHEAD + keyBytes;\n continue;\n }\n\n let rowBytes = ROW_OVERHEAD + keyBytes;\n for (let j = 0; j < section.columns.length; j++) rowBytes += estimateEncodedBytes(values[j] as G9SqlValue, (section.columns[j] as PushColumn).type);\n if (rowBytes > maxBytes) {\n // Can never fit, whatever else the batch carries: reject it here so it does not block every later push.\n tooLarge.push({ tid: row.tid, key: row.key, bytes: rowBytes });\n continue;\n }\n if (included > 0 && budgetUsed + rowBytes > maxBytes) {\n truncated = true;\n break;\n }\n\n let pushMask = G9Long.ZERO;\n for (let j = 0; j < section.columns.length; j++) {\n if (row.mask.testBit((section.columns[j] as PushColumn).local)) pushMask = pushMask.or(G9Long.ONE.shiftLeft(j));\n }\n section.writer.beginPushRow(row.key, G9EPushRowOp.Upsert, row.seq, row.base, pushMask, hlc);\n for (let j = 0; j < section.columns.length; j++) writeLocalValue(section.writer, values[j] as G9SqlValue, (section.columns[j] as PushColumn).type, store.options);\n section.writer.endRow();\n section.rows++;\n included++;\n includedMaxSequence = row.seq;\n budgetUsed += rowBytes;\n }\n\n for (const { tid, key, bytes } of tooLarge) {\n await connection.exec('UPDATE g9_rows SET rejected = 1 WHERE tid = $t AND pk = $pk', { $t: tid, $pk: key });\n await connection.exec(\n 'INSERT OR REPLACE INTO g9_rejections (tid, pk, code, message, sql_state, constraint_name, column_name, details, at_utc) ' +\n 'VALUES ($t, $pk, $code, $message, NULL, NULL, NULL, NULL, $at)',\n {\n $t: tid,\n $pk: key,\n $code: G9SyncErrorCodes.RejectTooLarge,\n $message: `The row needs about ${bytes} bytes; the server accepts at most ${maxBytes} bytes per push.`,\n $at: nowMs(),\n },\n );\n }\n\n if (included === 0 && commands.length === 0) {\n if (tooLarge.length === 0) return null;\n return { batchId: 0, maxSequence: 0, frames: [], tables: [], rowCount: 0, truncated: false, rejectedLocally: tooLarge.length, commands: [] };\n }\n\n const frames: Uint8Array[] = [];\n const usedTables: G9DtTableColumns[] = [];\n sections.forEach((section) => {\n if (section.rows === 0) return;\n section.writer.endSection();\n frames.push(section.writer.toFrame({ compressor: store.options.frameCompressor }));\n usedTables.push({ tableId: section.negotiated.tableId, columns: section.columns.map((c) => c.name) });\n });\n\n let batchId: number;\n let maxSequence: number;\n if (inflightBatch !== null) {\n batchId = Number(inflightBatch);\n maxSequence = Number(inflightMax);\n } else {\n batchId = Number((await meta(connection, 'next_batch_id')) ?? '1');\n maxSequence = includedMaxSequence;\n await setMeta(connection, 'next_batch_id', String(batchId + 1));\n await setMeta(connection, 'inflight_batch_id', String(batchId));\n await setMeta(connection, 'inflight_max_seq', String(maxSequence));\n }\n for (const item of commands) {\n await connection.exec('UPDATE g9_commands SET batch_id = $b WHERE command_id = $id', { $b: batchId, $id: item.commandId });\n }\n return { batchId, maxSequence, frames, tables: usedTables, rowCount: included, truncated, rejectedLocally: tooLarge.length, commands };\n }),\n );\n}\n\n/** `ForgetUploadedRowsAsync`: removes uploaded rows of an UPLOAD-ONLY table without uploading a delete. */\nexport async function forgetUploadedRows(store: StoreInternals, localTable: string, keys: readonly string[] | null): Promise<number> {\n store.ensureInitialized();\n const table = getLocal(store, localTable);\n if (!table.negotiated || table.negotiated.direction !== G9ESyncDirection.UploadOnly) {\n throw new Error(`'${localTable}' is not upload-only: deleting its rows locally is a change the server must receive.`);\n }\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n await connection.exec('UPDATE g9_ctx SET applying = 1 WHERE id = 1'); // a library write: not captured\n await connection.exec('CREATE TEMP TABLE IF NOT EXISTS g9_forget (pk TEXT PRIMARY KEY); DELETE FROM g9_forget');\n if (keys === null) {\n await connection.exec('INSERT INTO g9_forget (pk) SELECT pk FROM g9_rows WHERE tid = $t AND dirty = 0', { $t: table.tid });\n } else {\n for (const key of keys) {\n await connection.exec('INSERT OR IGNORE INTO g9_forget (pk) SELECT pk FROM g9_rows WHERE tid = $t AND pk = $pk AND dirty = 0', { $t: table.tid, $pk: key });\n }\n }\n const forgotten = asNumber(await connection.scalar('SELECT count(*) FROM g9_forget'));\n await connection.exec(`DELETE FROM ${table.quoted} WHERE CAST(${table.keyQuoted} AS TEXT) IN (SELECT pk FROM g9_forget)`);\n await connection.exec('DELETE FROM g9_rows WHERE tid = $t AND pk IN (SELECT pk FROM g9_forget); DELETE FROM g9_forget', { $t: table.tid });\n await connection.exec('UPDATE g9_ctx SET applying = 0 WHERE id = 1');\n return forgotten;\n }),\n );\n}\n\n/** `ApplyPushResultAsync`. */\nexport async function applyPushResult(store: StoreInternals, batch: G9PushBatch, result: G9DtPushResult, signal?: AbortSignal): Promise<G9ApplyReport> {\n store.ensureInitialized();\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n // Before BeginApply: finished commands release their rows, so the canonical rows below replace the optimistic ones.\n const completed = await completeCommands(connection, result.commandResults);\n const context = await beginApply(store, connection, { dirtyAfterSequence: batch.maxSequence, backfills: [], signal });\n context.report.commandsCompleted = completed;\n\n const acceptSql =\n 'UPDATE g9_rows SET server_ver = $v, ' +\n 'dirty = CASE WHEN local_seq <= $m THEN 0 ELSE dirty END, ' +\n 'dirty_mask = CASE WHEN local_seq <= $m THEN 0 ELSE dirty_mask END, ' +\n 'deleted = CASE WHEN local_seq <= $m AND dirty = 2 THEN 1 ELSE deleted END, ' +\n 'rejected = 0 WHERE tid = $t AND pk = $pk; ' +\n 'DELETE FROM g9_rejections WHERE tid = $t AND pk = $pk';\n for (const accepted of result.accepted) {\n const table = store.byServerId.get(accepted.tableId);\n if (!table) continue;\n for (let i = 0; i < accepted.keys.length; i++) {\n await context.queue(acceptSql, { $v: i < accepted.versions.length ? (accepted.versions[i] as number) : 0, $m: batch.maxSequence, $t: table.tid, $pk: accepted.keys[i] as string });\n context.report.accepted++;\n }\n }\n\n const rejectSql =\n 'UPDATE g9_rows SET rejected = 1 WHERE tid = $t AND pk = $pk AND local_seq <= $m; ' +\n 'INSERT OR REPLACE INTO g9_rejections (tid, pk, code, message, sql_state, constraint_name, column_name, details, at_utc) ' +\n 'VALUES ($t, $pk, $code, $message, $state, $constraint, $column, $details, $now)';\n const now = nowMs();\n for (const rejection of result.rejections) {\n const table = store.byServerId.get(rejection.tableId);\n if (!table) continue;\n await context.queue(rejectSql, {\n $t: table.tid,\n $pk: rejection.key,\n $m: batch.maxSequence,\n $code: rejection.code,\n $message: rejection.message ?? null,\n $state: rejection.sqlState ?? null,\n $constraint: rejection.constraint ?? null,\n $column: rejection.column ?? null,\n $details: rejection.details ?? null,\n $now: now,\n });\n context.report.rejected++;\n }\n\n context.report.conflicts = result.conflicts.length;\n await context.flush();\n for (const frame of result.canonicalFrames) await applyFrame(store, context, frame, null);\n\n await endApply(context);\n await connection.exec(\"DELETE FROM g9_meta WHERE key IN ('inflight_batch_id', 'inflight_max_seq')\");\n return context.report;\n }),\n );\n}\n"]}
@@ -5,9 +5,10 @@ import { G9SyncKey } from '../abstractions/key.js';
5
5
  import { G9Long, parseJson } from '../abstractions/long.js';
6
6
  import { guidToN, tryNormalizeGuid } from '../abstractions/guid.js';
7
7
  import { readContinuation, readPullPlan } from '../protocol/wire.js';
8
- import { asLong, asNumber, asOptionalString, asString } from './connection.js';
8
+ import { asLong, asNumber, asOptionalString, asString, statementsOf } from './connection.js';
9
9
  import { findByServerName, getLocal, persistedJson, readSubscriptions } from './internals.js';
10
10
  import { allColumnsMask, bit, keyExpression, literal, NOW_MS, quote } from './sql.js';
11
+ import { deleteFiles, forgetPendingSegments, persistedPlan, stageFileSegment, takeStagedFiles } from './snapshotFiles.js';
11
12
  import { formatTicks, ticksOf, ticksToUnixMs } from './values.js';
12
13
  // ================================================================ staging
13
14
  export async function readStagedRun(connection, runId) {
@@ -37,35 +38,47 @@ export async function getStagedPull(store, generation) {
37
38
  return runId === null ? null : readStagedRun(connection, asString(runId));
38
39
  }
39
40
  export async function pruneStagedPulls(store, olderThanUtcMs, maxBytes, keepGeneration) {
40
- return store.writeLock.run(() => store.writer().transaction('immediate', async () => {
41
+ let files = [];
42
+ const pruned = await store.writeLock.run(() => store.writer().transaction('immediate', async () => {
41
43
  const connection = store.writer();
42
44
  const doomed = (await connection.query('SELECT run_id FROM g9_stage_runs WHERE created_utc < $c', { $c: olderThanUtcMs })).map((r) => asString(r[0]));
43
45
  // The byte cap is for the stages nobody is about to resume: newest first, past the cap they go (ADR-0060).
44
46
  let kept = 0;
45
- for (const r of await connection.query('SELECT r.run_id, (SELECT coalesce(sum(length(payload)), 0) FROM g9_stage s WHERE s.run_id = r.run_id) FROM g9_stage_runs r ' +
47
+ for (const r of await connection.query('SELECT r.run_id, (SELECT coalesce(sum(length(payload)), 0) FROM g9_stage s WHERE s.run_id = r.run_id) ' +
48
+ '+ (SELECT coalesce(sum(bytes_received), 0) FROM g9_stage_files f WHERE f.run_id = r.run_id) FROM g9_stage_runs r ' +
46
49
  'WHERE r.created_utc >= $c AND (r.generation IS NULL OR r.generation <> $g) ORDER BY r.created_utc DESC', { $c: olderThanUtcMs, $g: keepGeneration !== null && keepGeneration !== void 0 ? keepGeneration : '' })) {
47
50
  kept += asNumber(r[1]);
48
51
  if (kept > Math.max(0, maxBytes))
49
52
  doomed.push(asString(r[0]));
50
53
  }
51
- for (const runId of doomed)
54
+ for (const runId of doomed) {
55
+ files = files.concat(await takeStagedFiles(connection, '$r', { $r: runId }));
52
56
  await connection.exec('DELETE FROM g9_stage WHERE run_id = $r; DELETE FROM g9_stage_runs WHERE run_id = $r', { $r: runId });
57
+ }
53
58
  return doomed.length;
54
59
  }));
60
+ await deleteFiles(store, files);
61
+ return pruned;
55
62
  }
56
63
  export async function discardAllStagedPulls(store) {
57
- return store.writeLock.run(() => store.writer().transaction('immediate', async () => {
64
+ let files = [];
65
+ const count = await store.writeLock.run(() => store.writer().transaction('immediate', async () => {
58
66
  const connection = store.writer();
59
67
  const count = asNumber(await connection.scalar('SELECT count(*) FROM g9_stage_runs'));
68
+ files = await takeStagedFiles(connection, 'SELECT run_id FROM g9_stage_files');
60
69
  await connection.exec('DELETE FROM g9_stage; DELETE FROM g9_stage_runs;');
61
70
  return count;
62
71
  }));
72
+ await deleteFiles(store, files);
73
+ return count;
63
74
  }
64
75
  export async function beginStagedPull(store, pull) {
76
+ let replaced = [];
65
77
  await store.writeLock.run(() => store.writer().transaction('immediate', async () => {
66
78
  var _a;
67
79
  const connection = store.writer();
68
80
  // Only the stage this one replaces goes: the same generation (ADR-0060).
81
+ replaced = await takeStagedFiles(connection, 'SELECT run_id FROM g9_stage_runs WHERE generation IS $g', { $g: pull.generation });
69
82
  await connection.exec('DELETE FROM g9_stage WHERE run_id IN (SELECT run_id FROM g9_stage_runs WHERE generation IS $g); DELETE FROM g9_stage_runs WHERE generation IS $g', { $g: pull.generation });
70
83
  // Read inside the transaction that inserts the run: rows written after it are newer than the stage (ADR-0046).
71
84
  const stageSequence = asNumber(await connection.scalar('SELECT seq FROM g9_clock WHERE id = 1'));
@@ -74,7 +87,7 @@ export async function beginStagedPull(store, pull) {
74
87
  fromCursors[key] = (_a = pull.plannedFromCursors[key]) !== null && _a !== void 0 ? _a : null;
75
88
  await connection.exec('INSERT INTO g9_stage_runs (run_id, plan_json, backfills_json, created_utc, complete, generation, stage_seq, from_cursors_json) VALUES ($r, $p, $b, $c, 0, $g, $s, $f)', {
76
89
  $r: pull.runId,
77
- $p: persistedJson(pull.plan),
90
+ $p: persistedPlan(pull.plan),
78
91
  $b: persistedJson(pull.backfills),
79
92
  $c: pull.createdUtcMs,
80
93
  $g: pull.generation,
@@ -83,25 +96,31 @@ export async function beginStagedPull(store, pull) {
83
96
  $f: JSON.stringify(fromCursors),
84
97
  });
85
98
  }));
99
+ await deleteFiles(store, replaced);
86
100
  }
87
101
  export async function stageChunk(store, runId, chunk, durability) {
88
102
  await store.writeLock.run(async () => {
103
+ var _a, _b;
89
104
  const connection = store.writer();
105
+ if (chunk.file) {
106
+ // A snapshot-file segment goes to its place in the staged file, not into g9_stage (ADR-0072).
107
+ await stageFileSegment(store, runId, chunk);
108
+ return;
109
+ }
90
110
  const restore = await durability(connection);
91
111
  try {
92
- await connection.transaction('immediate', async () => {
93
- var _a, _b;
94
- await connection.exec('INSERT OR REPLACE INTO g9_stage (run_id, seq, subscription_key, row_count, continuation_json, payload) VALUES ($r, $s, $k, $n, $c, $p)', {
112
+ // One transaction, one bridge call (a chunk used to cost ~7: PRAGMA reads, begin, insert, commit — LES-0056).
113
+ await connection.runAtomic([
114
+ ...statementsOf('INSERT OR REPLACE INTO g9_stage (run_id, seq, subscription_key, row_count, continuation_json, payload) VALUES ($r, $s, $k, $n, $c, $p)', {
95
115
  $r: runId,
96
116
  $s: chunk.sequence,
97
117
  $k: (_a = chunk.subscriptionKey) !== null && _a !== void 0 ? _a : null,
98
118
  $n: chunk.rowCount,
99
119
  $c: chunk.continuation ? persistedJson(chunk.continuation) : null,
100
120
  $p: (_b = chunk.payload) !== null && _b !== void 0 ? _b : null,
101
- });
102
- if (chunk.isFinal)
103
- await connection.exec('UPDATE g9_stage_runs SET complete = 1 WHERE run_id = $r', { $r: runId });
104
- });
121
+ }),
122
+ ...(chunk.isFinal ? statementsOf('UPDATE g9_stage_runs SET complete = 1 WHERE run_id = $r', { $r: runId }) : []),
123
+ ]);
105
124
  }
106
125
  finally {
107
126
  if (restore)
@@ -114,6 +133,8 @@ export async function getResumePoint(store, runId) {
114
133
  const connection = store.writer();
115
134
  const flag = await connection.scalar('SELECT complete FROM g9_stage_runs WHERE run_id = $r', { $r: runId });
116
135
  const complete = typeof flag === 'number' && flag !== 0;
136
+ // A resumed stream starts at the last durable checkpoint of a snapshot file.
137
+ forgetPendingSegments(store, runId);
117
138
  let continuation = null;
118
139
  let lastSequence = -1;
119
140
  if (!complete) {
@@ -125,12 +146,20 @@ export async function getResumePoint(store, runId) {
125
146
  }
126
147
  await connection.exec('DELETE FROM g9_stage WHERE run_id = $r AND seq > $s', { $r: runId, $s: lastSequence });
127
148
  }
128
- const totals = (await connection.query('SELECT coalesce(sum(row_count), 0), coalesce(sum(length(payload)), 0) FROM g9_stage WHERE run_id = $r', { $r: runId }))[0];
149
+ const totals = (await connection.query('SELECT coalesce(sum(row_count), 0), coalesce(sum(length(payload)), 0) + (SELECT coalesce(sum(bytes_received), 0) FROM g9_stage_files WHERE run_id = $r) ' +
150
+ 'FROM g9_stage WHERE run_id = $r', { $r: runId }))[0];
129
151
  return { continuation, stagedRows: totals ? asNumber(totals[0]) : 0, stagedBytes: totals ? asNumber(totals[1]) : 0, complete };
130
152
  }));
131
153
  }
132
154
  export async function discardStagedPull(store, runId) {
133
- await store.writeLock.run(() => store.writer().exec('DELETE FROM g9_stage WHERE run_id = $r; DELETE FROM g9_stage_runs WHERE run_id = $r', { $r: runId }));
155
+ let files = [];
156
+ await store.writeLock.run(() => store.writer().transaction('immediate', async () => {
157
+ const connection = store.writer();
158
+ files = await takeStagedFiles(connection, '$r', { $r: runId });
159
+ await connection.exec('DELETE FROM g9_stage WHERE run_id = $r; DELETE FROM g9_stage_runs WHERE run_id = $r', { $r: runId });
160
+ }));
161
+ forgetPendingSegments(store, runId);
162
+ await deleteFiles(store, files);
134
163
  }
135
164
  // ================================================================ reconciliation reads
136
165
  function computedColumns(local, table) {