@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
@@ -29,7 +29,7 @@ export declare const APPLYING = "(SELECT applying FROM g9_ctx WHERE id = 1) = 0"
29
29
  export declare const SEQ = "(SELECT seq FROM g9_clock WHERE id = 1)";
30
30
  /** Command mode (ADR-0025): local effects of a command being recorded. */
31
31
  export declare const COMMAND_MODE = "(SELECT applying FROM g9_ctx WHERE id = 1) = 2";
32
- export declare const METADATA = "CREATE TABLE IF NOT EXISTS g9_meta (key TEXT NOT NULL PRIMARY KEY, value TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_clock (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL);\nINSERT OR IGNORE INTO g9_clock (id, seq) VALUES (1, 0);\nCREATE TABLE IF NOT EXISTS g9_ctx (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), applying INTEGER NOT NULL DEFAULT 0);\nINSERT OR IGNORE INTO g9_ctx (id, applying) VALUES (1, 0);\nUPDATE g9_ctx SET applying = 0 WHERE id = 1 AND applying <> 0;\nCREATE TABLE IF NOT EXISTS g9_tables (\n tid INTEGER PRIMARY KEY AUTOINCREMENT,\n local_name TEXT NOT NULL UNIQUE,\n server_name TEXT NOT NULL,\n model_hash TEXT NOT NULL,\n server_table_id INTEGER NULL,\n negotiated_json TEXT NULL,\n synced_columns TEXT NULL);\nCREATE TABLE IF NOT EXISTS g9_rows (\n tid INTEGER NOT NULL,\n pk TEXT NOT NULL,\n server_ver INTEGER NOT NULL DEFAULT 0,\n local_seq INTEGER NOT NULL DEFAULT 0,\n dirty INTEGER NOT NULL DEFAULT 0,\n dirty_mask INTEGER NOT NULL DEFAULT 0,\n hlc_ms INTEGER NULL,\n subs INTEGER NOT NULL DEFAULT 0,\n rejected INTEGER NOT NULL DEFAULT 0,\n deleted INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_rows_dirty ON g9_rows (local_seq) WHERE dirty <> 0;\nCREATE INDEX IF NOT EXISTS g9_rows_feed ON g9_rows (tid, local_seq);\nCREATE INDEX IF NOT EXISTS g9_rows_deleted ON g9_rows (local_seq) WHERE deleted = 1;\nCREATE TABLE IF NOT EXISTS g9_subscriptions (\n key TEXT NOT NULL PRIMARY KEY,\n name TEXT NOT NULL,\n params TEXT NOT NULL,\n slot INTEGER NOT NULL,\n ephemeral INTEGER NOT NULL,\n ttl_seconds INTEGER NULL,\n cursor TEXT NULL,\n definition_hash TEXT NULL,\n auth_hash TEXT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n error_code TEXT NULL,\n move_out_policy INTEGER NULL,\n created_utc INTEGER NOT NULL,\n last_success_utc INTEGER NULL,\n last_reconcile_utc INTEGER NULL,\n last_access_utc INTEGER NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_ephemeral_rows (sub_key TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (sub_key, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_ephemeral_rows_row ON g9_ephemeral_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_stage_runs (run_id TEXT NOT NULL PRIMARY KEY, plan_json TEXT NOT NULL, backfills_json TEXT NULL, created_utc INTEGER NOT NULL, complete INTEGER NOT NULL DEFAULT 0, generation TEXT NULL, stage_seq INTEGER NOT NULL DEFAULT 0, from_cursors_json TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_stage (run_id TEXT NOT NULL, seq INTEGER NOT NULL, subscription_key TEXT NULL, row_count INTEGER NOT NULL, continuation_json TEXT NULL, payload BLOB NULL, PRIMARY KEY (run_id, seq)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_rejections (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, code TEXT NOT NULL, message TEXT NULL, sql_state TEXT NULL, constraint_name TEXT NULL,\n column_name TEXT NULL, details TEXT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_runs (run_id TEXT NOT NULL PRIMARY KEY, started_utc INTEGER NOT NULL, report_json TEXT NOT NULL);\nCREATE TABLE IF NOT EXISTS g9_stats (key TEXT NOT NULL PRIMARY KEY, value REAL NOT NULL, samples INTEGER NOT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_commands (\n command_id TEXT NOT NULL PRIMARY KEY,\n seq INTEGER NOT NULL,\n name TEXT NOT NULL,\n version INTEGER NOT NULL,\n payload TEXT NOT NULL,\n created_utc INTEGER NOT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n batch_id INTEGER NULL,\n code TEXT NULL,\n message TEXT NULL,\n result TEXT NULL,\n completed_utc INTEGER NULL) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_commands_pending ON g9_commands (seq) WHERE state = 0;\nCREATE TABLE IF NOT EXISTS g9_command_rows (command_id TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (command_id, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_command_rows_row ON g9_command_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_quarantine (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, row_json TEXT NOT NULL,\n had_pending INTEGER NOT NULL, dirty_mask INTEGER NOT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_revoked (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, disposition INTEGER NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, at_utc INTEGER NOT NULL,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;";
32
+ export declare const METADATA = "CREATE TABLE IF NOT EXISTS g9_meta (key TEXT NOT NULL PRIMARY KEY, value TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_clock (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL);\nINSERT OR IGNORE INTO g9_clock (id, seq) VALUES (1, 0);\nCREATE TABLE IF NOT EXISTS g9_ctx (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), applying INTEGER NOT NULL DEFAULT 0);\nINSERT OR IGNORE INTO g9_ctx (id, applying) VALUES (1, 0);\nUPDATE g9_ctx SET applying = 0 WHERE id = 1 AND applying <> 0;\nCREATE TABLE IF NOT EXISTS g9_tables (\n tid INTEGER PRIMARY KEY AUTOINCREMENT,\n local_name TEXT NOT NULL UNIQUE,\n server_name TEXT NOT NULL,\n model_hash TEXT NOT NULL,\n server_table_id INTEGER NULL,\n negotiated_json TEXT NULL,\n synced_columns TEXT NULL);\nCREATE TABLE IF NOT EXISTS g9_rows (\n tid INTEGER NOT NULL,\n pk TEXT NOT NULL,\n server_ver INTEGER NOT NULL DEFAULT 0,\n local_seq INTEGER NOT NULL DEFAULT 0,\n dirty INTEGER NOT NULL DEFAULT 0,\n dirty_mask INTEGER NOT NULL DEFAULT 0,\n hlc_ms INTEGER NULL,\n subs INTEGER NOT NULL DEFAULT 0,\n rejected INTEGER NOT NULL DEFAULT 0,\n deleted INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_rows_dirty ON g9_rows (local_seq) WHERE dirty <> 0;\nCREATE INDEX IF NOT EXISTS g9_rows_feed ON g9_rows (tid, local_seq);\nCREATE INDEX IF NOT EXISTS g9_rows_deleted ON g9_rows (local_seq) WHERE deleted = 1;\nCREATE TABLE IF NOT EXISTS g9_subscriptions (\n key TEXT NOT NULL PRIMARY KEY,\n name TEXT NOT NULL,\n params TEXT NOT NULL,\n slot INTEGER NOT NULL,\n ephemeral INTEGER NOT NULL,\n ttl_seconds INTEGER NULL,\n cursor TEXT NULL,\n definition_hash TEXT NULL,\n auth_hash TEXT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n error_code TEXT NULL,\n move_out_policy INTEGER NULL,\n created_utc INTEGER NOT NULL,\n last_success_utc INTEGER NULL,\n last_reconcile_utc INTEGER NULL,\n last_access_utc INTEGER NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_ephemeral_rows (sub_key TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (sub_key, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_ephemeral_rows_row ON g9_ephemeral_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_stage_runs (run_id TEXT NOT NULL PRIMARY KEY, plan_json TEXT NOT NULL, backfills_json TEXT NULL, created_utc INTEGER NOT NULL, complete INTEGER NOT NULL DEFAULT 0, generation TEXT NULL, stage_seq INTEGER NOT NULL DEFAULT 0, from_cursors_json TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_stage (run_id TEXT NOT NULL, seq INTEGER NOT NULL, subscription_key TEXT NULL, row_count INTEGER NOT NULL, continuation_json TEXT NULL, payload BLOB NULL, PRIMARY KEY (run_id, seq)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_stage_files (\n run_id TEXT NOT NULL, file_id TEXT NOT NULL, path TEXT NOT NULL, manifest_json TEXT NOT NULL, subscription_key TEXT NULL,\n first_seq INTEGER NOT NULL, segments_done INTEGER NOT NULL DEFAULT 0, bytes_received INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (run_id, file_id)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_rejections (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, code TEXT NOT NULL, message TEXT NULL, sql_state TEXT NULL, constraint_name TEXT NULL,\n column_name TEXT NULL, details TEXT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_runs (run_id TEXT NOT NULL PRIMARY KEY, started_utc INTEGER NOT NULL, report_json TEXT NOT NULL);\nCREATE TABLE IF NOT EXISTS g9_stats (key TEXT NOT NULL PRIMARY KEY, value REAL NOT NULL, samples INTEGER NOT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_commands (\n command_id TEXT NOT NULL PRIMARY KEY,\n seq INTEGER NOT NULL,\n name TEXT NOT NULL,\n version INTEGER NOT NULL,\n payload TEXT NOT NULL,\n created_utc INTEGER NOT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n batch_id INTEGER NULL,\n code TEXT NULL,\n message TEXT NULL,\n result TEXT NULL,\n completed_utc INTEGER NULL) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_commands_pending ON g9_commands (seq) WHERE state = 0;\nCREATE TABLE IF NOT EXISTS g9_command_rows (command_id TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (command_id, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_command_rows_row ON g9_command_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_quarantine (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, row_json TEXT NOT NULL,\n had_pending INTEGER NOT NULL, dirty_mask INTEGER NOT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_revoked (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, disposition INTEGER NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, at_utc INTEGER NOT NULL,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;";
33
33
  /** `[name]` with `]` doubled. */
34
34
  export declare function quote(identifier: string): string;
35
35
  /** The membership bit of a slot (slot 63 is the sign bit). */
@@ -75,6 +75,10 @@ CREATE TABLE IF NOT EXISTS g9_ephemeral_rows (sub_key TEXT NOT NULL, tid INTEGER
75
75
  CREATE INDEX IF NOT EXISTS g9_ephemeral_rows_row ON g9_ephemeral_rows (tid, pk);
76
76
  CREATE TABLE IF NOT EXISTS g9_stage_runs (run_id TEXT NOT NULL PRIMARY KEY, plan_json TEXT NOT NULL, backfills_json TEXT NULL, created_utc INTEGER NOT NULL, complete INTEGER NOT NULL DEFAULT 0, generation TEXT NULL, stage_seq INTEGER NOT NULL DEFAULT 0, from_cursors_json TEXT NULL) WITHOUT ROWID;
77
77
  CREATE TABLE IF NOT EXISTS g9_stage (run_id TEXT NOT NULL, seq INTEGER NOT NULL, subscription_key TEXT NULL, row_count INTEGER NOT NULL, continuation_json TEXT NULL, payload BLOB NULL, PRIMARY KEY (run_id, seq)) WITHOUT ROWID;
78
+ CREATE TABLE IF NOT EXISTS g9_stage_files (
79
+ run_id TEXT NOT NULL, file_id TEXT NOT NULL, path TEXT NOT NULL, manifest_json TEXT NOT NULL, subscription_key TEXT NULL,
80
+ first_seq INTEGER NOT NULL, segments_done INTEGER NOT NULL DEFAULT 0, bytes_received INTEGER NOT NULL DEFAULT 0,
81
+ PRIMARY KEY (run_id, file_id)) WITHOUT ROWID;
78
82
  CREATE TABLE IF NOT EXISTS g9_rejections (
79
83
  tid INTEGER NOT NULL, pk TEXT NOT NULL, code TEXT NOT NULL, message TEXT NULL, sql_state TEXT NULL, constraint_name TEXT NULL,
80
84
  column_name TEXT NULL, details TEXT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;
@@ -1 +1 @@
1
- {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sqlite/sql.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/Internal/SqliteSql.cs: the store's schema, capture triggers and model hash. The text\n// is the SAME as .NET's (the golden vectors compare it), so a database written by the MAUI app and opened by the Lynx app\n// (or the other way round) has exactly the objects each side expects.\nimport { G9ESyncColumnType, columnTypeName } from '../abstractions/enums.js';\nimport { G9Long } from '../abstractions/long.js';\nimport { sha256, toHex } from '../internal/sha256.js';\nimport { utf8Encode } from '../internal/utf8.js';\nimport type { G9SyncClientColumn, G9SyncClientTable } from '../client/model.js';\n\n/** How DateTime values are stored locally (`G9ESqliteDateTimeStorage`). */\nexport const G9ESqliteDateTimeStorage = {\n /** ISO-like text in the DateTimeFormat (sqlite-net's text format by default). */\n Text: 0,\n /** .NET ticks as INTEGER (sqlite-net storeDateTimeAsTicks). */\n Ticks: 1,\n} as const;\nexport type G9ESqliteDateTimeStorage = (typeof G9ESqliteDateTimeStorage)[keyof typeof G9ESqliteDateTimeStorage];\n\n/** How Decimal values are stored locally (`G9ESqliteDecimalStorage`). */\nexport const G9ESqliteDecimalStorage = {\n /** REAL, like sqlite-net. */\n Real: 0,\n /** Invariant text, exact. */\n Text: 1,\n} as const;\nexport type G9ESqliteDecimalStorage = (typeof G9ESqliteDecimalStorage)[keyof typeof G9ESqliteDecimalStorage];\n\n/** The storage options the schema depends on. */\nexport interface G9SqliteStorageOptions {\n dateTimeStorage: G9ESqliteDateTimeStorage;\n decimalStorage: G9ESqliteDecimalStorage;\n /** Only the .NET default `yyyy-MM-dd'T'HH:mm:ss.fffffff` is supported (see PARITY.md). */\n dateTimeFormat: string;\n}\n\nexport const EPHEMERAL_SLOT = 63;\nexport const NOW_MS = \"CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)\";\nexport const APPLYING = '(SELECT applying FROM g9_ctx WHERE id = 1) = 0';\nexport const SEQ = '(SELECT seq FROM g9_clock WHERE id = 1)';\n/** Command mode (ADR-0025): local effects of a command being recorded. */\nexport const COMMAND_MODE = '(SELECT applying FROM g9_ctx WHERE id = 1) = 2';\n\nexport const METADATA = `CREATE TABLE IF NOT EXISTS g9_meta (key TEXT NOT NULL PRIMARY KEY, value TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_clock (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL);\nINSERT OR IGNORE INTO g9_clock (id, seq) VALUES (1, 0);\nCREATE TABLE IF NOT EXISTS g9_ctx (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), applying INTEGER NOT NULL DEFAULT 0);\nINSERT OR IGNORE INTO g9_ctx (id, applying) VALUES (1, 0);\nUPDATE g9_ctx SET applying = 0 WHERE id = 1 AND applying <> 0;\nCREATE TABLE IF NOT EXISTS g9_tables (\n tid INTEGER PRIMARY KEY AUTOINCREMENT,\n local_name TEXT NOT NULL UNIQUE,\n server_name TEXT NOT NULL,\n model_hash TEXT NOT NULL,\n server_table_id INTEGER NULL,\n negotiated_json TEXT NULL,\n synced_columns TEXT NULL);\nCREATE TABLE IF NOT EXISTS g9_rows (\n tid INTEGER NOT NULL,\n pk TEXT NOT NULL,\n server_ver INTEGER NOT NULL DEFAULT 0,\n local_seq INTEGER NOT NULL DEFAULT 0,\n dirty INTEGER NOT NULL DEFAULT 0,\n dirty_mask INTEGER NOT NULL DEFAULT 0,\n hlc_ms INTEGER NULL,\n subs INTEGER NOT NULL DEFAULT 0,\n rejected INTEGER NOT NULL DEFAULT 0,\n deleted INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_rows_dirty ON g9_rows (local_seq) WHERE dirty <> 0;\nCREATE INDEX IF NOT EXISTS g9_rows_feed ON g9_rows (tid, local_seq);\nCREATE INDEX IF NOT EXISTS g9_rows_deleted ON g9_rows (local_seq) WHERE deleted = 1;\nCREATE TABLE IF NOT EXISTS g9_subscriptions (\n key TEXT NOT NULL PRIMARY KEY,\n name TEXT NOT NULL,\n params TEXT NOT NULL,\n slot INTEGER NOT NULL,\n ephemeral INTEGER NOT NULL,\n ttl_seconds INTEGER NULL,\n cursor TEXT NULL,\n definition_hash TEXT NULL,\n auth_hash TEXT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n error_code TEXT NULL,\n move_out_policy INTEGER NULL,\n created_utc INTEGER NOT NULL,\n last_success_utc INTEGER NULL,\n last_reconcile_utc INTEGER NULL,\n last_access_utc INTEGER NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_ephemeral_rows (sub_key TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (sub_key, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_ephemeral_rows_row ON g9_ephemeral_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_stage_runs (run_id TEXT NOT NULL PRIMARY KEY, plan_json TEXT NOT NULL, backfills_json TEXT NULL, created_utc INTEGER NOT NULL, complete INTEGER NOT NULL DEFAULT 0, generation TEXT NULL, stage_seq INTEGER NOT NULL DEFAULT 0, from_cursors_json TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_stage (run_id TEXT NOT NULL, seq INTEGER NOT NULL, subscription_key TEXT NULL, row_count INTEGER NOT NULL, continuation_json TEXT NULL, payload BLOB NULL, PRIMARY KEY (run_id, seq)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_rejections (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, code TEXT NOT NULL, message TEXT NULL, sql_state TEXT NULL, constraint_name TEXT NULL,\n column_name TEXT NULL, details TEXT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_runs (run_id TEXT NOT NULL PRIMARY KEY, started_utc INTEGER NOT NULL, report_json TEXT NOT NULL);\nCREATE TABLE IF NOT EXISTS g9_stats (key TEXT NOT NULL PRIMARY KEY, value REAL NOT NULL, samples INTEGER NOT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_commands (\n command_id TEXT NOT NULL PRIMARY KEY,\n seq INTEGER NOT NULL,\n name TEXT NOT NULL,\n version INTEGER NOT NULL,\n payload TEXT NOT NULL,\n created_utc INTEGER NOT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n batch_id INTEGER NULL,\n code TEXT NULL,\n message TEXT NULL,\n result TEXT NULL,\n completed_utc INTEGER NULL) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_commands_pending ON g9_commands (seq) WHERE state = 0;\nCREATE TABLE IF NOT EXISTS g9_command_rows (command_id TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (command_id, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_command_rows_row ON g9_command_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_quarantine (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, row_json TEXT NOT NULL,\n had_pending INTEGER NOT NULL, dirty_mask INTEGER NOT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_revoked (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, disposition INTEGER NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, at_utc INTEGER NOT NULL,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;`;\n\n/** `[name]` with `]` doubled. */\nexport function quote(identifier: string): string {\n return '[' + identifier.split(']').join(']]') + ']';\n}\n\n/** The membership bit of a slot (slot 63 is the sign bit). */\nexport function bit(slot: number): G9Long {\n return slot >= 63 ? G9Long.MIN_VALUE : G9Long.ONE.shiftLeft(slot);\n}\n\n/** Every column of a table set in the dirty mask. */\nexport function allColumnsMask(count: number): G9Long {\n return count >= 63 ? G9Long.MINUS_ONE : G9Long.ONE.shiftLeft(count).subtract(G9Long.ONE);\n}\n\nexport function literal(value: number | G9Long): string {\n return (value instanceof G9Long ? value : G9Long.fromNumber(value)).toString();\n}\n\nexport function columnType(type: number, options: G9SqliteStorageOptions): string {\n switch (type) {\n case G9ESyncColumnType.Boolean:\n case G9ESyncColumnType.Int32:\n case G9ESyncColumnType.Int64:\n case G9ESyncColumnType.TimeSpan:\n return 'INTEGER';\n case G9ESyncColumnType.Double:\n return 'REAL';\n case G9ESyncColumnType.Decimal:\n return options.decimalStorage === G9ESqliteDecimalStorage.Real ? 'REAL' : 'TEXT';\n case G9ESyncColumnType.DateTime:\n return options.dateTimeStorage === G9ESqliteDateTimeStorage.Ticks ? 'INTEGER' : 'TEXT';\n case G9ESyncColumnType.Bytes:\n return 'BLOB';\n default:\n return 'TEXT';\n }\n}\n\n/** DateTime.MinValue in the default text format. */\nexport const DATE_TIME_MIN_TEXT = '0001-01-01T00:00:00.0000000';\n\nexport function defaultLiteral(type: number, options: G9SqliteStorageOptions): string {\n switch (columnType(type, options)) {\n case 'INTEGER':\n case 'REAL':\n return '0';\n case 'BLOB':\n return \"x''\";\n default:\n if (type === G9ESyncColumnType.Guid) return \"'00000000-0000-0000-0000-000000000000'\";\n if (type === G9ESyncColumnType.DateTime) return \"'\" + DATE_TIME_MIN_TEXT + \"'\";\n if (type === G9ESyncColumnType.Decimal) return \"'0'\";\n return \"''\";\n }\n}\n\nexport function columnDefinition(column: G9SyncClientColumn, options: G9SqliteStorageOptions): string {\n // A GUID is stored as canonical LOWER-case text; NOCASE so another casing still matches (ADR-0054).\n return (\n quote(column.name) +\n ' ' +\n columnType(column.type, options) +\n (column.type === G9ESyncColumnType.Guid ? ' COLLATE NOCASE' : '') +\n (column.nullable ? '' : ' NOT NULL DEFAULT ' + defaultLiteral(column.type, options))\n );\n}\n\nexport function createTable(table: G9SyncClientTable, options: G9SqliteStorageOptions): string {\n const integerKey = table.keyType === G9ESyncColumnType.Int32 || table.keyType === G9ESyncColumnType.Int64;\n const key =\n quote(table.keyColumn) + ' ' + (integerKey ? 'INTEGER' : 'TEXT') + ' NOT NULL' + (table.keyType === G9ESyncColumnType.Guid ? ' COLLATE NOCASE' : '') + ' PRIMARY KEY';\n return 'CREATE TABLE IF NOT EXISTS ' + quote(table.localName) + ' (' + key + table.columns.map((c) => ', ' + columnDefinition(c, options)).join('') + ')';\n}\n\n/** Canonical key text of a row alias (`NEW`, `OLD`, a table). */\nexport function keyExpression(table: G9SyncClientTable, alias: string): string {\n return table.keyType === G9ESyncColumnType.Guid ? 'lower(' + alias + '.' + quote(table.keyColumn) + ')' : 'CAST(' + alias + '.' + quote(table.keyColumn) + ' AS TEXT)';\n}\n\nexport function triggerName(table: G9SyncClientTable, suffix: string): string {\n return quote('g9_' + table.localName + '_' + suffix);\n}\n\nexport function dropTriggers(table: G9SyncClientTable): string {\n return (\n `DROP TRIGGER IF EXISTS ${triggerName(table, 'ai')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'au')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'ad')}; ` +\n `DROP TRIGGER IF EXISTS ${triggerName(table, 'ci')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'cu')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'cd')};`\n );\n}\n\n/** Command-mode triggers (ADR-0025), on every table regardless of direction. */\nexport function createCommandTriggers(table: G9SyncClientTable, tid: number): string {\n const name = quote(table.localName);\n const tidLiteral = literal(tid);\n const command = '(SELECT command_id FROM g9_ctx WHERE id = 1)';\n return `CREATE TRIGGER ${triggerName(table, 'ci')} AFTER INSERT ON ${name} WHEN ${COMMAND_MODE}\nBEGIN\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'NEW')});\nEND;\nCREATE TRIGGER ${triggerName(table, 'cu')} AFTER UPDATE ON ${name} WHEN ${COMMAND_MODE}\nBEGIN\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'NEW')});\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'OLD')});\nEND;\nCREATE TRIGGER ${triggerName(table, 'cd')} AFTER DELETE ON ${name} WHEN ${COMMAND_MODE}\nBEGIN\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'OLD')});\nEND;`;\n}\n\n/** The capture triggers (D-03). */\nexport function createTriggers(table: G9SyncClientTable, tid: number): string {\n const name = quote(table.localName);\n const tidLiteral = literal(tid);\n const newKey = keyExpression(table, 'NEW');\n const oldKey = keyExpression(table, 'OLD');\n const all = literal(allColumnsMask(table.columns.length));\n const mask =\n table.columns.length === 0\n ? '0'\n : table.columns\n .map((c, i) => `(CASE WHEN OLD.${quote(c.name)} IS NOT NEW.${quote(c.name)} THEN ${literal(G9Long.ONE.shiftLeft(i))} ELSE 0 END)`)\n .join(' | ');\n return `CREATE TRIGGER ${triggerName(table, 'ai')} AFTER INSERT ON ${name} WHEN ${APPLYING}\nBEGIN\n UPDATE g9_clock SET seq = seq + 1 WHERE id = 1;\n INSERT OR IGNORE INTO g9_rows (tid, pk) VALUES (${tidLiteral}, ${newKey});\n UPDATE g9_rows SET local_seq = ${SEQ}, dirty = 1, dirty_mask = ${all}, hlc_ms = ${NOW_MS}, rejected = 0, deleted = 0\n WHERE tid = ${tidLiteral} AND pk = ${newKey};\nEND;\nCREATE TRIGGER ${triggerName(table, 'au')} AFTER UPDATE ON ${name} WHEN ${APPLYING} AND (${mask}) <> 0\nBEGIN\n UPDATE g9_clock SET seq = seq + 1 WHERE id = 1;\n INSERT OR IGNORE INTO g9_rows (tid, pk) VALUES (${tidLiteral}, ${newKey});\n UPDATE g9_rows SET local_seq = ${SEQ}, dirty = CASE WHEN dirty = 2 THEN 2 ELSE 1 END, dirty_mask = dirty_mask | (${mask}),\n hlc_ms = ${NOW_MS}, rejected = 0, deleted = 0\n WHERE tid = ${tidLiteral} AND pk = ${newKey};\nEND;\nCREATE TRIGGER ${triggerName(table, 'ad')} AFTER DELETE ON ${name} WHEN ${APPLYING}\nBEGIN\n UPDATE g9_clock SET seq = seq + 1 WHERE id = 1;\n UPDATE g9_rows SET local_seq = ${SEQ}, dirty = 2, dirty_mask = 0, hlc_ms = ${NOW_MS}, rejected = 0\n WHERE tid = ${tidLiteral} AND pk = ${oldKey} AND NOT (server_ver = 0 AND subs = 0 AND dirty = 1);\n DELETE FROM g9_rows WHERE tid = ${tidLiteral} AND pk = ${oldKey} AND server_ver = 0 AND subs = 0 AND dirty = 1;\nEND;`;\n}\n\n/** SHA-256 (first 16 bytes, lower hex) of the table's model text: a change re-creates the triggers. */\nexport function modelHash(table: G9SyncClientTable): string {\n let text = 'v1|' + table.serverName + '|' + table.localName + '|' + table.keyColumn + ':' + columnTypeName(table.keyType);\n for (const column of table.columns) text += '|' + column.name + ':' + columnTypeName(column.type) + ':' + (column.nullable ? 'True' : 'False');\n for (const index of table.indexes) text += '|ix:' + index.name + ':' + (index.unique ? 'True' : 'False') + ':' + index.columns.join(',');\n return toHex(sha256(utf8Encode(text)).subarray(0, 16));\n}\n\n/**\n * Splits a script into single statements (the driver runs one statement per entry). Semicolons inside strings, quoted\n * identifiers, comments and a trigger's BEGIN … END body do not split.\n */\nexport function splitSql(script: string): string[] {\n const statements: string[] = [];\n let start = 0;\n let depth = 0;\n let i = 0;\n const n = script.length;\n let words: string[] = [];\n const flush = (end: number): void => {\n const text = script.slice(start, end).trim();\n if (text !== '') statements.push(text);\n start = end + 1;\n depth = 0;\n words = [];\n };\n while (i < n) {\n const c = script.charAt(i);\n if (c === \"'\" || c === '\"' || c === '`') {\n const close = c;\n i++;\n while (i < n) {\n if (script.charAt(i) === close) {\n if (script.charAt(i + 1) === close) i += 2;\n else break;\n } else i++;\n }\n i++;\n continue;\n }\n if (c === '[') {\n const close = script.indexOf(']', i + 1);\n i = close < 0 ? n : close + 1;\n continue;\n }\n if (c === '-' && script.charAt(i + 1) === '-') {\n const end = script.indexOf('\\n', i);\n i = end < 0 ? n : end + 1;\n continue;\n }\n if (c === '/' && script.charAt(i + 1) === '*') {\n const end = script.indexOf('*/', i + 2);\n i = end < 0 ? n : end + 2;\n continue;\n }\n if (/[A-Za-z_]/.test(c)) {\n let j = i + 1;\n while (j < n && /[A-Za-z0-9_]/.test(script.charAt(j))) j++;\n const word = script.slice(i, j).toUpperCase();\n if (words.length < 4) words.push(word);\n const isTrigger = words[0] === 'CREATE' && (words[1] === 'TRIGGER' || words[2] === 'TRIGGER' || words[3] === 'TRIGGER');\n if (isTrigger) {\n if (word === 'BEGIN' || word === 'CASE') depth++;\n else if (word === 'END' && depth > 0) depth--;\n }\n i = j;\n continue;\n }\n if (c === ';' && depth === 0) {\n flush(i);\n i++;\n continue;\n }\n i++;\n }\n flush(n);\n return statements;\n}\n"]}
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sqlite/sql.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/Internal/SqliteSql.cs: the store's schema, capture triggers and model hash. The text\n// is the SAME as .NET's (the golden vectors compare it), so a database written by the MAUI app and opened by the Lynx app\n// (or the other way round) has exactly the objects each side expects.\nimport { G9ESyncColumnType, columnTypeName } from '../abstractions/enums.js';\nimport { G9Long } from '../abstractions/long.js';\nimport { sha256, toHex } from '../internal/sha256.js';\nimport { utf8Encode } from '../internal/utf8.js';\nimport type { G9SyncClientColumn, G9SyncClientTable } from '../client/model.js';\n\n/** How DateTime values are stored locally (`G9ESqliteDateTimeStorage`). */\nexport const G9ESqliteDateTimeStorage = {\n /** ISO-like text in the DateTimeFormat (sqlite-net's text format by default). */\n Text: 0,\n /** .NET ticks as INTEGER (sqlite-net storeDateTimeAsTicks). */\n Ticks: 1,\n} as const;\nexport type G9ESqliteDateTimeStorage = (typeof G9ESqliteDateTimeStorage)[keyof typeof G9ESqliteDateTimeStorage];\n\n/** How Decimal values are stored locally (`G9ESqliteDecimalStorage`). */\nexport const G9ESqliteDecimalStorage = {\n /** REAL, like sqlite-net. */\n Real: 0,\n /** Invariant text, exact. */\n Text: 1,\n} as const;\nexport type G9ESqliteDecimalStorage = (typeof G9ESqliteDecimalStorage)[keyof typeof G9ESqliteDecimalStorage];\n\n/** The storage options the schema depends on. */\nexport interface G9SqliteStorageOptions {\n dateTimeStorage: G9ESqliteDateTimeStorage;\n decimalStorage: G9ESqliteDecimalStorage;\n /** Only the .NET default `yyyy-MM-dd'T'HH:mm:ss.fffffff` is supported (see PARITY.md). */\n dateTimeFormat: string;\n}\n\nexport const EPHEMERAL_SLOT = 63;\nexport const NOW_MS = \"CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)\";\nexport const APPLYING = '(SELECT applying FROM g9_ctx WHERE id = 1) = 0';\nexport const SEQ = '(SELECT seq FROM g9_clock WHERE id = 1)';\n/** Command mode (ADR-0025): local effects of a command being recorded. */\nexport const COMMAND_MODE = '(SELECT applying FROM g9_ctx WHERE id = 1) = 2';\n\nexport const METADATA = `CREATE TABLE IF NOT EXISTS g9_meta (key TEXT NOT NULL PRIMARY KEY, value TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_clock (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL);\nINSERT OR IGNORE INTO g9_clock (id, seq) VALUES (1, 0);\nCREATE TABLE IF NOT EXISTS g9_ctx (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), applying INTEGER NOT NULL DEFAULT 0);\nINSERT OR IGNORE INTO g9_ctx (id, applying) VALUES (1, 0);\nUPDATE g9_ctx SET applying = 0 WHERE id = 1 AND applying <> 0;\nCREATE TABLE IF NOT EXISTS g9_tables (\n tid INTEGER PRIMARY KEY AUTOINCREMENT,\n local_name TEXT NOT NULL UNIQUE,\n server_name TEXT NOT NULL,\n model_hash TEXT NOT NULL,\n server_table_id INTEGER NULL,\n negotiated_json TEXT NULL,\n synced_columns TEXT NULL);\nCREATE TABLE IF NOT EXISTS g9_rows (\n tid INTEGER NOT NULL,\n pk TEXT NOT NULL,\n server_ver INTEGER NOT NULL DEFAULT 0,\n local_seq INTEGER NOT NULL DEFAULT 0,\n dirty INTEGER NOT NULL DEFAULT 0,\n dirty_mask INTEGER NOT NULL DEFAULT 0,\n hlc_ms INTEGER NULL,\n subs INTEGER NOT NULL DEFAULT 0,\n rejected INTEGER NOT NULL DEFAULT 0,\n deleted INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_rows_dirty ON g9_rows (local_seq) WHERE dirty <> 0;\nCREATE INDEX IF NOT EXISTS g9_rows_feed ON g9_rows (tid, local_seq);\nCREATE INDEX IF NOT EXISTS g9_rows_deleted ON g9_rows (local_seq) WHERE deleted = 1;\nCREATE TABLE IF NOT EXISTS g9_subscriptions (\n key TEXT NOT NULL PRIMARY KEY,\n name TEXT NOT NULL,\n params TEXT NOT NULL,\n slot INTEGER NOT NULL,\n ephemeral INTEGER NOT NULL,\n ttl_seconds INTEGER NULL,\n cursor TEXT NULL,\n definition_hash TEXT NULL,\n auth_hash TEXT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n error_code TEXT NULL,\n move_out_policy INTEGER NULL,\n created_utc INTEGER NOT NULL,\n last_success_utc INTEGER NULL,\n last_reconcile_utc INTEGER NULL,\n last_access_utc INTEGER NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_ephemeral_rows (sub_key TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (sub_key, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_ephemeral_rows_row ON g9_ephemeral_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_stage_runs (run_id TEXT NOT NULL PRIMARY KEY, plan_json TEXT NOT NULL, backfills_json TEXT NULL, created_utc INTEGER NOT NULL, complete INTEGER NOT NULL DEFAULT 0, generation TEXT NULL, stage_seq INTEGER NOT NULL DEFAULT 0, from_cursors_json TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_stage (run_id TEXT NOT NULL, seq INTEGER NOT NULL, subscription_key TEXT NULL, row_count INTEGER NOT NULL, continuation_json TEXT NULL, payload BLOB NULL, PRIMARY KEY (run_id, seq)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_stage_files (\n run_id TEXT NOT NULL, file_id TEXT NOT NULL, path TEXT NOT NULL, manifest_json TEXT NOT NULL, subscription_key TEXT NULL,\n first_seq INTEGER NOT NULL, segments_done INTEGER NOT NULL DEFAULT 0, bytes_received INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (run_id, file_id)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_rejections (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, code TEXT NOT NULL, message TEXT NULL, sql_state TEXT NULL, constraint_name TEXT NULL,\n column_name TEXT NULL, details TEXT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_runs (run_id TEXT NOT NULL PRIMARY KEY, started_utc INTEGER NOT NULL, report_json TEXT NOT NULL);\nCREATE TABLE IF NOT EXISTS g9_stats (key TEXT NOT NULL PRIMARY KEY, value REAL NOT NULL, samples INTEGER NOT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_commands (\n command_id TEXT NOT NULL PRIMARY KEY,\n seq INTEGER NOT NULL,\n name TEXT NOT NULL,\n version INTEGER NOT NULL,\n payload TEXT NOT NULL,\n created_utc INTEGER NOT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n batch_id INTEGER NULL,\n code TEXT NULL,\n message TEXT NULL,\n result TEXT NULL,\n completed_utc INTEGER NULL) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_commands_pending ON g9_commands (seq) WHERE state = 0;\nCREATE TABLE IF NOT EXISTS g9_command_rows (command_id TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (command_id, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_command_rows_row ON g9_command_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_quarantine (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, row_json TEXT NOT NULL,\n had_pending INTEGER NOT NULL, dirty_mask INTEGER NOT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_revoked (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, disposition INTEGER NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, at_utc INTEGER NOT NULL,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;`;\n\n/** `[name]` with `]` doubled. */\nexport function quote(identifier: string): string {\n return '[' + identifier.split(']').join(']]') + ']';\n}\n\n/** The membership bit of a slot (slot 63 is the sign bit). */\nexport function bit(slot: number): G9Long {\n return slot >= 63 ? G9Long.MIN_VALUE : G9Long.ONE.shiftLeft(slot);\n}\n\n/** Every column of a table set in the dirty mask. */\nexport function allColumnsMask(count: number): G9Long {\n return count >= 63 ? G9Long.MINUS_ONE : G9Long.ONE.shiftLeft(count).subtract(G9Long.ONE);\n}\n\nexport function literal(value: number | G9Long): string {\n return (value instanceof G9Long ? value : G9Long.fromNumber(value)).toString();\n}\n\nexport function columnType(type: number, options: G9SqliteStorageOptions): string {\n switch (type) {\n case G9ESyncColumnType.Boolean:\n case G9ESyncColumnType.Int32:\n case G9ESyncColumnType.Int64:\n case G9ESyncColumnType.TimeSpan:\n return 'INTEGER';\n case G9ESyncColumnType.Double:\n return 'REAL';\n case G9ESyncColumnType.Decimal:\n return options.decimalStorage === G9ESqliteDecimalStorage.Real ? 'REAL' : 'TEXT';\n case G9ESyncColumnType.DateTime:\n return options.dateTimeStorage === G9ESqliteDateTimeStorage.Ticks ? 'INTEGER' : 'TEXT';\n case G9ESyncColumnType.Bytes:\n return 'BLOB';\n default:\n return 'TEXT';\n }\n}\n\n/** DateTime.MinValue in the default text format. */\nexport const DATE_TIME_MIN_TEXT = '0001-01-01T00:00:00.0000000';\n\nexport function defaultLiteral(type: number, options: G9SqliteStorageOptions): string {\n switch (columnType(type, options)) {\n case 'INTEGER':\n case 'REAL':\n return '0';\n case 'BLOB':\n return \"x''\";\n default:\n if (type === G9ESyncColumnType.Guid) return \"'00000000-0000-0000-0000-000000000000'\";\n if (type === G9ESyncColumnType.DateTime) return \"'\" + DATE_TIME_MIN_TEXT + \"'\";\n if (type === G9ESyncColumnType.Decimal) return \"'0'\";\n return \"''\";\n }\n}\n\nexport function columnDefinition(column: G9SyncClientColumn, options: G9SqliteStorageOptions): string {\n // A GUID is stored as canonical LOWER-case text; NOCASE so another casing still matches (ADR-0054).\n return (\n quote(column.name) +\n ' ' +\n columnType(column.type, options) +\n (column.type === G9ESyncColumnType.Guid ? ' COLLATE NOCASE' : '') +\n (column.nullable ? '' : ' NOT NULL DEFAULT ' + defaultLiteral(column.type, options))\n );\n}\n\nexport function createTable(table: G9SyncClientTable, options: G9SqliteStorageOptions): string {\n const integerKey = table.keyType === G9ESyncColumnType.Int32 || table.keyType === G9ESyncColumnType.Int64;\n const key =\n quote(table.keyColumn) + ' ' + (integerKey ? 'INTEGER' : 'TEXT') + ' NOT NULL' + (table.keyType === G9ESyncColumnType.Guid ? ' COLLATE NOCASE' : '') + ' PRIMARY KEY';\n return 'CREATE TABLE IF NOT EXISTS ' + quote(table.localName) + ' (' + key + table.columns.map((c) => ', ' + columnDefinition(c, options)).join('') + ')';\n}\n\n/** Canonical key text of a row alias (`NEW`, `OLD`, a table). */\nexport function keyExpression(table: G9SyncClientTable, alias: string): string {\n return table.keyType === G9ESyncColumnType.Guid ? 'lower(' + alias + '.' + quote(table.keyColumn) + ')' : 'CAST(' + alias + '.' + quote(table.keyColumn) + ' AS TEXT)';\n}\n\nexport function triggerName(table: G9SyncClientTable, suffix: string): string {\n return quote('g9_' + table.localName + '_' + suffix);\n}\n\nexport function dropTriggers(table: G9SyncClientTable): string {\n return (\n `DROP TRIGGER IF EXISTS ${triggerName(table, 'ai')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'au')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'ad')}; ` +\n `DROP TRIGGER IF EXISTS ${triggerName(table, 'ci')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'cu')}; DROP TRIGGER IF EXISTS ${triggerName(table, 'cd')};`\n );\n}\n\n/** Command-mode triggers (ADR-0025), on every table regardless of direction. */\nexport function createCommandTriggers(table: G9SyncClientTable, tid: number): string {\n const name = quote(table.localName);\n const tidLiteral = literal(tid);\n const command = '(SELECT command_id FROM g9_ctx WHERE id = 1)';\n return `CREATE TRIGGER ${triggerName(table, 'ci')} AFTER INSERT ON ${name} WHEN ${COMMAND_MODE}\nBEGIN\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'NEW')});\nEND;\nCREATE TRIGGER ${triggerName(table, 'cu')} AFTER UPDATE ON ${name} WHEN ${COMMAND_MODE}\nBEGIN\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'NEW')});\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'OLD')});\nEND;\nCREATE TRIGGER ${triggerName(table, 'cd')} AFTER DELETE ON ${name} WHEN ${COMMAND_MODE}\nBEGIN\n INSERT OR IGNORE INTO g9_command_rows (command_id, tid, pk) VALUES (${command}, ${tidLiteral}, ${keyExpression(table, 'OLD')});\nEND;`;\n}\n\n/** The capture triggers (D-03). */\nexport function createTriggers(table: G9SyncClientTable, tid: number): string {\n const name = quote(table.localName);\n const tidLiteral = literal(tid);\n const newKey = keyExpression(table, 'NEW');\n const oldKey = keyExpression(table, 'OLD');\n const all = literal(allColumnsMask(table.columns.length));\n const mask =\n table.columns.length === 0\n ? '0'\n : table.columns\n .map((c, i) => `(CASE WHEN OLD.${quote(c.name)} IS NOT NEW.${quote(c.name)} THEN ${literal(G9Long.ONE.shiftLeft(i))} ELSE 0 END)`)\n .join(' | ');\n return `CREATE TRIGGER ${triggerName(table, 'ai')} AFTER INSERT ON ${name} WHEN ${APPLYING}\nBEGIN\n UPDATE g9_clock SET seq = seq + 1 WHERE id = 1;\n INSERT OR IGNORE INTO g9_rows (tid, pk) VALUES (${tidLiteral}, ${newKey});\n UPDATE g9_rows SET local_seq = ${SEQ}, dirty = 1, dirty_mask = ${all}, hlc_ms = ${NOW_MS}, rejected = 0, deleted = 0\n WHERE tid = ${tidLiteral} AND pk = ${newKey};\nEND;\nCREATE TRIGGER ${triggerName(table, 'au')} AFTER UPDATE ON ${name} WHEN ${APPLYING} AND (${mask}) <> 0\nBEGIN\n UPDATE g9_clock SET seq = seq + 1 WHERE id = 1;\n INSERT OR IGNORE INTO g9_rows (tid, pk) VALUES (${tidLiteral}, ${newKey});\n UPDATE g9_rows SET local_seq = ${SEQ}, dirty = CASE WHEN dirty = 2 THEN 2 ELSE 1 END, dirty_mask = dirty_mask | (${mask}),\n hlc_ms = ${NOW_MS}, rejected = 0, deleted = 0\n WHERE tid = ${tidLiteral} AND pk = ${newKey};\nEND;\nCREATE TRIGGER ${triggerName(table, 'ad')} AFTER DELETE ON ${name} WHEN ${APPLYING}\nBEGIN\n UPDATE g9_clock SET seq = seq + 1 WHERE id = 1;\n UPDATE g9_rows SET local_seq = ${SEQ}, dirty = 2, dirty_mask = 0, hlc_ms = ${NOW_MS}, rejected = 0\n WHERE tid = ${tidLiteral} AND pk = ${oldKey} AND NOT (server_ver = 0 AND subs = 0 AND dirty = 1);\n DELETE FROM g9_rows WHERE tid = ${tidLiteral} AND pk = ${oldKey} AND server_ver = 0 AND subs = 0 AND dirty = 1;\nEND;`;\n}\n\n/** SHA-256 (first 16 bytes, lower hex) of the table's model text: a change re-creates the triggers. */\nexport function modelHash(table: G9SyncClientTable): string {\n let text = 'v1|' + table.serverName + '|' + table.localName + '|' + table.keyColumn + ':' + columnTypeName(table.keyType);\n for (const column of table.columns) text += '|' + column.name + ':' + columnTypeName(column.type) + ':' + (column.nullable ? 'True' : 'False');\n for (const index of table.indexes) text += '|ix:' + index.name + ':' + (index.unique ? 'True' : 'False') + ':' + index.columns.join(',');\n return toHex(sha256(utf8Encode(text)).subarray(0, 16));\n}\n\n/**\n * Splits a script into single statements (the driver runs one statement per entry). Semicolons inside strings, quoted\n * identifiers, comments and a trigger's BEGIN … END body do not split.\n */\nexport function splitSql(script: string): string[] {\n const statements: string[] = [];\n let start = 0;\n let depth = 0;\n let i = 0;\n const n = script.length;\n let words: string[] = [];\n const flush = (end: number): void => {\n const text = script.slice(start, end).trim();\n if (text !== '') statements.push(text);\n start = end + 1;\n depth = 0;\n words = [];\n };\n while (i < n) {\n const c = script.charAt(i);\n if (c === \"'\" || c === '\"' || c === '`') {\n const close = c;\n i++;\n while (i < n) {\n if (script.charAt(i) === close) {\n if (script.charAt(i + 1) === close) i += 2;\n else break;\n } else i++;\n }\n i++;\n continue;\n }\n if (c === '[') {\n const close = script.indexOf(']', i + 1);\n i = close < 0 ? n : close + 1;\n continue;\n }\n if (c === '-' && script.charAt(i + 1) === '-') {\n const end = script.indexOf('\\n', i);\n i = end < 0 ? n : end + 1;\n continue;\n }\n if (c === '/' && script.charAt(i + 1) === '*') {\n const end = script.indexOf('*/', i + 2);\n i = end < 0 ? n : end + 2;\n continue;\n }\n if (/[A-Za-z_]/.test(c)) {\n let j = i + 1;\n while (j < n && /[A-Za-z0-9_]/.test(script.charAt(j))) j++;\n const word = script.slice(i, j).toUpperCase();\n if (words.length < 4) words.push(word);\n const isTrigger = words[0] === 'CREATE' && (words[1] === 'TRIGGER' || words[2] === 'TRIGGER' || words[3] === 'TRIGGER');\n if (isTrigger) {\n if (word === 'BEGIN' || word === 'CASE') depth++;\n else if (word === 'END' && depth > 0) depth--;\n }\n i = j;\n continue;\n }\n if (c === ';' && depth === 0) {\n flush(i);\n i++;\n continue;\n }\n i++;\n }\n flush(n);\n return statements;\n}\n"]}
@@ -4,7 +4,7 @@ import { G9SyncParameters } from '../abstractions/parameters.js';
4
4
  import type { G9SyncClientModel } from '../client/model.js';
5
5
  import type { G9SyncLogLevel } from '../client/runs.js';
6
6
  import { G9ApplyReport, type G9ApplySlice, type G9ChangeFeedEntry, type G9ClientServerState, type G9CommandRequest, type G9ELocalCommandState, type G9LocalCommand, type G9LocalCommandWriter, type G9LocalRowEntry, type G9PendingSummary, type G9PushBatch, type G9QuarantinedRow, type G9ResumePoint, type G9RetainedRow, type G9StagedPull, type G9SubscriptionUpdate, type G9SyncClientStore, type G9SyncRejection, type G9SyncRunReport, type G9SyncSubscriptionRecord } from '../client/storeTypes.js';
7
- import type { G9DtBackfillRequest, G9DtHelloResult, G9DtNegotiatedTable, G9DtPushResult, G9DtSyncChunk, G9DtTableDigest } from '../protocol/dtos.js';
7
+ import type { G9DtBackfillRequest, G9DtHelloResult, G9DtNegotiatedTable, G9DtPushResult, G9DtSnapshotFileRequest, G9DtSyncChunk, G9DtTableDigest } from '../protocol/dtos.js';
8
8
  import { G9AsyncLock, G9SqliteConnection } from './connection.js';
9
9
  import type { G9SessionToken } from './driver.js';
10
10
  import { LocalTable, type StoreInternals } from './internals.js';
@@ -38,11 +38,29 @@ export declare class G9SqliteSyncStore implements G9SyncClientStore, StoreIntern
38
38
  /** The PRAGMAs the .NET store sets on every connection it opens. */
39
39
  private _configure;
40
40
  initialize(model: G9SyncClientModel): Promise<void>;
41
- private _provisionTable;
41
+ /** The store's offer to receive snapshot files (ADR-0072); null without a `snapshotFiles` sink. */
42
+ describeSnapshotFiles(negotiated: readonly G9DtNegotiatedTable[]): G9DtSnapshotFileRequest | null;
43
+ /**
44
+ * Provisions every model table (`ProvisionTable` of the .NET store, for all tables at once).
45
+ *
46
+ * The .NET store asks SQLite per table (~6 statements each) because a statement costs nothing in process. Through a
47
+ * bridged driver every call is a round trip (~5.7 ms on the Android emulator: 296 calls = 1.7 s on EVERY app start, 534
48
+ * on the first), so this reads everything the decisions need in ONE call — g9_tables, the tables, their columns, the
49
+ * triggers and the AUTOINCREMENT counter — and runs the resulting statements in ONE batch, in exactly the order the
50
+ * per-table path runs them. New tables get their tid explicitly (the value AUTOINCREMENT would hand out, in model
51
+ * order), so the triggers can be written in the same batch. Same objects, same rows, same order (PARITY.md).
52
+ */
53
+ private _provisionTables;
42
54
  /** ADR-0067: a database restored from a backup or copied from another device gets a new identity. */
43
55
  private _verifyIncarnation;
44
56
  getClientId(): Promise<string>;
45
57
  getServerState(): Promise<G9ClientServerState>;
58
+ /**
59
+ * Records a Hello (`SaveHello`): epoch, clock offset, definitions, every negotiated table and the capture each needs.
60
+ * The .NET store writes table by table; through a bridged driver that was ~90 calls on EVERY run for AgriPad's 47
61
+ * tables (LES-0055), so the writes go in ONE batch, in the same order, after one read for the only per-table
62
+ * question (does a table with newly negotiated columns hold rows?). Same database.
63
+ */
46
64
  saveHello(hello: G9DtHelloResult): Promise<void>;
47
65
  getPendingBackfills(): Promise<G9DtBackfillRequest[]>;
48
66
  getSubscriptions(): Promise<G9SyncSubscriptionRecord[]>;
@@ -9,12 +9,13 @@ import { G9SyncParameters } from '../abstractions/parameters.js';
9
9
  import { G9ApplyReport, G9ERevocationReason, G9ESubscriptionState, } from '../client/storeTypes.js';
10
10
  import * as apply from './apply.js';
11
11
  import * as commands from './commands.js';
12
- import { asNumber, asOptionalString, asString, G9AsyncLock, G9SqliteConnection } from './connection.js';
12
+ import { asNumber, asOptionalString, asString, G9AsyncLock, G9SqliteConnection, G9StatementBatch, statementsOf } from './connection.js';
13
13
  import { byTid, LocalTable, logTo, meta, nowMs, persistedJson, readLocalValues, readPersistedDefinitions, readPersistedNegotiated, readSubscriptions, serializeValues, setMeta, } from './internals.js';
14
14
  import { resolveStoreOptions } from './options.js';
15
15
  import * as push from './push.js';
16
16
  import * as query from './query.js';
17
17
  import * as revocation from './revocation.js';
18
+ import { describeSnapshotFiles, sweepOrphanSnapshotFiles } from './snapshotFiles.js';
18
19
  import { allColumnsMask, bit, columnDefinition, createCommandTriggers, createTable, createTriggers, dropTriggers, EPHEMERAL_SLOT, keyExpression, METADATA, modelHash, quote } from './sql.js';
19
20
  /**
20
21
  * The SQLite client store over a G9SqliteDriver. Library writes are serialised in-process; app writes on other
@@ -109,83 +110,124 @@ export class G9SqliteSyncStore {
109
110
  if ((await meta(connection, 'next_batch_id')) === null)
110
111
  await setMeta(connection, 'next_batch_id', '1');
111
112
  await this._verifyIncarnation(connection);
112
- for (const table of model.tables)
113
- tables.push(await this._provisionTable(connection, table));
113
+ for (const table of await this._provisionTables(connection, model.tables))
114
+ tables.push(table);
114
115
  });
115
116
  this._model = model;
116
117
  this.byLocal = new Map(tables.map((t) => [t.model.localName.toLowerCase(), t]));
117
118
  this.byServerId = new Map(tables.filter((t) => t.negotiated !== null).map((t) => [t.negotiated.tableId, t]));
119
+ await sweepOrphanSnapshotFiles(this, connection);
118
120
  this.log('information', 20101, `G9SyncData SQLite store initialised with ${tables.length} tables`);
119
121
  });
120
122
  }
121
- async _provisionTable(connection, table) {
122
- const hash = modelHash(table);
123
- let tid = -1;
124
- let existingHash = null;
125
- let negotiatedJson = null;
126
- let syncedJson = null;
127
- let isNew = false;
128
- const found = (await connection.query('SELECT tid, model_hash, negotiated_json, synced_columns FROM g9_tables WHERE local_name = $n', { $n: table.localName }))[0];
129
- if (found) {
130
- tid = asNumber(found[0]);
131
- existingHash = asString(found[1]);
132
- negotiatedJson = asOptionalString(found[2]);
133
- syncedJson = asOptionalString(found[3]);
134
- }
135
- if (tid < 0) {
136
- await connection.exec("INSERT INTO g9_tables (local_name, server_name, model_hash) VALUES ($n, $s, '')", { $n: table.localName, $s: table.serverName });
137
- tid = asNumber(await connection.scalar('SELECT last_insert_rowid()'));
138
- isNew = true;
139
- }
140
- else {
141
- await connection.exec('UPDATE g9_tables SET server_name = $s WHERE tid = $t', { $s: table.serverName, $t: tid });
142
- }
143
- const exists = (await connection.scalar("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = $n COLLATE NOCASE", { $n: table.localName })) !== null;
144
- if (!exists) {
145
- await connection.exec(createTable(table, this.options));
123
+ /** The store's offer to receive snapshot files (ADR-0072); null without a `snapshotFiles` sink. */
124
+ describeSnapshotFiles(negotiated) {
125
+ if (!this._model)
126
+ return null;
127
+ return describeSnapshotFiles(this, negotiated);
128
+ }
129
+ /**
130
+ * Provisions every model table (`ProvisionTable` of the .NET store, for all tables at once).
131
+ *
132
+ * The .NET store asks SQLite per table (~6 statements each) because a statement costs nothing in process. Through a
133
+ * bridged driver every call is a round trip (~5.7 ms on the Android emulator: 296 calls = 1.7 s on EVERY app start, 534
134
+ * on the first), so this reads everything the decisions need in ONE call — g9_tables, the tables, their columns, the
135
+ * triggers and the AUTOINCREMENT counter — and runs the resulting statements in ONE batch, in exactly the order the
136
+ * per-table path runs them. New tables get their tid explicitly (the value AUTOINCREMENT would hand out, in model
137
+ * order), so the triggers can be written in the same batch. Same objects, same rows, same order (PARITY.md).
138
+ */
139
+ async _provisionTables(connection, models) {
140
+ var _a, _b, _c, _d, _e, _f;
141
+ const [registered, tableNames, columns, triggers, sequence] = await connection.runStatements([
142
+ ...statementsOf('SELECT tid, local_name, model_hash, negotiated_json, synced_columns, server_name FROM g9_tables'),
143
+ ...statementsOf("SELECT name FROM sqlite_master WHERE type = 'table'"),
144
+ ...statementsOf("SELECT m.name, p.name FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS p WHERE m.type = 'table'"),
145
+ ...statementsOf("SELECT name FROM sqlite_master WHERE type = 'trigger'"),
146
+ ...statementsOf("SELECT (SELECT seq FROM sqlite_sequence WHERE name = 'g9_tables'), (SELECT max(tid) FROM g9_tables)"),
147
+ ]);
148
+ const known = new Map();
149
+ for (const row of (_a = registered === null || registered === void 0 ? void 0 : registered.rows) !== null && _a !== void 0 ? _a : [])
150
+ known.set(asString(row[1]), row);
151
+ const existingTables = new Set(((_b = tableNames === null || tableNames === void 0 ? void 0 : tableNames.rows) !== null && _b !== void 0 ? _b : []).map((r) => asString(r[0]).toLowerCase()));
152
+ const columnsByTable = new Map();
153
+ for (const row of (_c = columns === null || columns === void 0 ? void 0 : columns.rows) !== null && _c !== void 0 ? _c : []) {
154
+ const table = asString(row[0]).toLowerCase();
155
+ let set = columnsByTable.get(table);
156
+ if (!set)
157
+ columnsByTable.set(table, (set = new Set()));
158
+ set.add(asString(row[1]).toLowerCase());
146
159
  }
147
- else {
148
- const existing = new Set((await connection.query('SELECT name FROM pragma_table_info($n)', { $n: table.localName })).map((r) => asString(r[0]).toLowerCase()));
149
- for (const column of table.columns) {
150
- if (existing.has(column.name.toLowerCase()))
151
- continue;
152
- await connection.exec('ALTER TABLE ' + quote(table.localName) + ' ADD COLUMN ' + columnDefinition(column, this.options));
153
- this.log('information', 20102, `Added column ${table.localName}.${column.name} from the client model`);
160
+ const triggerNames = new Set(((_d = triggers === null || triggers === void 0 ? void 0 : triggers.rows) !== null && _d !== void 0 ? _d : []).map((r) => asString(r[0])));
161
+ const counters = (_e = sequence === null || sequence === void 0 ? void 0 : sequence.rows[0]) !== null && _e !== void 0 ? _e : [];
162
+ let nextTid = Math.max(asNumber(counters[0]), asNumber(counters[1])) + 1;
163
+ const batch = new G9StatementBatch(connection, Number.MAX_SAFE_INTEGER);
164
+ const result = [];
165
+ for (const table of models) {
166
+ const hash = modelHash(table);
167
+ const found = known.get(table.localName);
168
+ let tid;
169
+ let existingHash = null;
170
+ let negotiatedJson = null;
171
+ let syncedJson = null;
172
+ const isNew = !found;
173
+ if (found) {
174
+ tid = asNumber(found[0]);
175
+ existingHash = asString(found[2]);
176
+ negotiatedJson = asOptionalString(found[3]);
177
+ syncedJson = asOptionalString(found[4]);
178
+ if (asString(found[5]) !== table.serverName)
179
+ batch.add('UPDATE g9_tables SET server_name = $s WHERE tid = $t', { $s: table.serverName, $t: tid });
154
180
  }
155
- }
156
- for (const index of table.indexes) {
157
- await connection.exec(`CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX IF NOT EXISTS ${quote(index.name)} ON ${quote(table.localName)} (${index.columns.map(quote).join(', ')})`);
158
- }
159
- const negotiated = readPersistedNegotiated(negotiatedJson);
160
- const captureWanted = !negotiated || negotiated.direction !== G9ESyncDirection.DownloadOnly;
161
- const triggerCount = asNumber(await connection.scalar("SELECT count(*) FROM sqlite_master WHERE type = 'trigger' AND name IN ($a, $b, $c)", {
162
- $a: 'g9_' + table.localName + '_ai',
163
- $b: 'g9_' + table.localName + '_au',
164
- $c: 'g9_' + table.localName + '_ad',
165
- }));
166
- const commandTriggerCount = asNumber(await connection.scalar("SELECT count(*) FROM sqlite_master WHERE type = 'trigger' AND name IN ($a, $b, $c)", {
167
- $a: 'g9_' + table.localName + '_ci',
168
- $b: 'g9_' + table.localName + '_cu',
169
- $c: 'g9_' + table.localName + '_cd',
170
- }));
171
- if (existingHash !== hash || (captureWanted && triggerCount !== 3) || commandTriggerCount !== 3) {
172
- await connection.exec(dropTriggers(table));
173
- if (captureWanted)
174
- await connection.exec(createTriggers(table, tid));
175
- await connection.exec(createCommandTriggers(table, tid));
176
- if (existingHash !== null && existingHash.length > 0 && existingHash !== hash) {
177
- // Column order may have changed: pending rows upload every column rather than trusting old mask bits.
178
- await connection.exec('UPDATE g9_rows SET dirty_mask = $m WHERE tid = $t AND dirty = 1', { $m: allColumnsMask(table.columns.length), $t: tid });
181
+ else {
182
+ tid = nextTid++;
183
+ batch.add("INSERT INTO g9_tables (tid, local_name, server_name, model_hash) VALUES ($t, $n, $s, '')", { $t: tid, $n: table.localName, $s: table.serverName });
179
184
  }
180
- await connection.exec('UPDATE g9_tables SET model_hash = $h WHERE tid = $t', { $h: hash, $t: tid });
181
- }
182
- if (isNew && exists) {
183
- const adopted = await connection.exec(`INSERT OR IGNORE INTO g9_rows (tid, pk) SELECT $t, ${keyExpression(table, 'b')} FROM ${quote(table.localName)} AS b`, { $t: tid });
184
- if (adopted > 0)
185
- this.log('information', 20103, `Adopted ${adopted} existing rows of ${table.localName} into change capture`);
185
+ const exists = existingTables.has(table.localName.toLowerCase());
186
+ if (!exists) {
187
+ batch.add(createTable(table, this.options));
188
+ }
189
+ else {
190
+ const existing = (_f = columnsByTable.get(table.localName.toLowerCase())) !== null && _f !== void 0 ? _f : new Set();
191
+ for (const column of table.columns) {
192
+ if (existing.has(column.name.toLowerCase()))
193
+ continue;
194
+ batch.add('ALTER TABLE ' + quote(table.localName) + ' ADD COLUMN ' + columnDefinition(column, this.options));
195
+ this.log('information', 20102, `Added column ${table.localName}.${column.name} from the client model`);
196
+ }
197
+ }
198
+ for (const index of table.indexes) {
199
+ batch.add(`CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX IF NOT EXISTS ${quote(index.name)} ON ${quote(table.localName)} (${index.columns.map(quote).join(', ')})`);
200
+ }
201
+ const negotiated = readPersistedNegotiated(negotiatedJson);
202
+ const captureWanted = !negotiated || negotiated.direction !== G9ESyncDirection.DownloadOnly;
203
+ const has = (suffix) => (triggerNames.has('g9_' + table.localName + '_' + suffix) ? 1 : 0);
204
+ const triggerCount = has('ai') + has('au') + has('ad');
205
+ const commandTriggerCount = has('ci') + has('cu') + has('cd');
206
+ if (existingHash !== hash || (captureWanted && triggerCount !== 3) || commandTriggerCount !== 3) {
207
+ batch.add(dropTriggers(table));
208
+ if (captureWanted)
209
+ batch.add(createTriggers(table, tid));
210
+ batch.add(createCommandTriggers(table, tid));
211
+ if (existingHash !== null && existingHash.length > 0 && existingHash !== hash) {
212
+ // Column order may have changed: pending rows upload every column rather than trusting old mask bits.
213
+ batch.add('UPDATE g9_rows SET dirty_mask = $m WHERE tid = $t AND dirty = 1', { $m: allColumnsMask(table.columns.length), $t: tid });
214
+ }
215
+ batch.add('UPDATE g9_tables SET model_hash = $h WHERE tid = $t', { $h: hash, $t: tid });
216
+ }
217
+ if (isNew && exists) {
218
+ const localName = table.localName;
219
+ batch.add(`INSERT OR IGNORE INTO g9_rows (tid, pk) SELECT $t, ${keyExpression(table, 'b')} FROM ${quote(localName)} AS b`, { $t: tid }, (results) => {
220
+ var _a, _b;
221
+ const adopted = (_b = (_a = results[0]) === null || _a === void 0 ? void 0 : _a.changes) !== null && _b !== void 0 ? _b : 0;
222
+ if (adopted > 0)
223
+ this.log('information', 20103, `Adopted ${adopted} existing rows of ${localName} into change capture`);
224
+ });
225
+ }
226
+ const synced = syncedJson === null ? [] : JSON.parse(syncedJson);
227
+ result.push(new LocalTable(table, tid, negotiated, Array.isArray(synced) ? synced.map(String) : []));
186
228
  }
187
- const synced = syncedJson === null ? [] : JSON.parse(syncedJson);
188
- return new LocalTable(table, tid, negotiated, Array.isArray(synced) ? synced.map(String) : []);
229
+ await batch.flush();
230
+ return result;
189
231
  }
190
232
  /** ADR-0067: a database restored from a backup or copied from another device gets a new identity. */
191
233
  async _verifyIncarnation(connection) {
@@ -240,53 +282,80 @@ export class G9SqliteSyncStore {
240
282
  tables: Array.isArray(parsedTables) ? parsedTables.map((t) => readPersistedNegotiated(JSON.stringify(t))) : [],
241
283
  };
242
284
  }
285
+ /**
286
+ * Records a Hello (`SaveHello`): epoch, clock offset, definitions, every negotiated table and the capture each needs.
287
+ * The .NET store writes table by table; through a bridged driver that was ~90 calls on EVERY run for AgriPad's 47
288
+ * tables (LES-0055), so the writes go in ONE batch, in the same order, after one read for the only per-table
289
+ * question (does a table with newly negotiated columns hold rows?). Same database.
290
+ */
243
291
  async saveHello(hello) {
244
292
  const model = this.ensureInitialized();
245
293
  await this.writeLock.run(async () => {
246
294
  const connection = this.writer();
247
295
  const updates = [];
248
296
  await connection.transaction('immediate', async () => {
249
- await setMeta(connection, 'epoch', hello.epoch);
250
- await setMeta(connection, 'last_hello_utc', String(nowMs()));
251
- await setMeta(connection, 'server_offset_ms', String(Math.trunc(hello.serverTimeUtcMs - Date.now())));
252
- await setMeta(connection, 'definitions_json', persistedJson(hello.definitions));
253
- await setMeta(connection, 'tables_json', persistedJson(hello.tables));
297
+ const batch = new G9StatementBatch(connection, this.options.statementsPerBatch);
298
+ const metaUpsert = 'INSERT INTO g9_meta (key, value) VALUES ($k, $v) ON CONFLICT(key) DO UPDATE SET value = excluded.value';
299
+ batch.add(metaUpsert, { $k: 'epoch', $v: hello.epoch });
300
+ batch.add(metaUpsert, { $k: 'last_hello_utc', $v: String(nowMs()) });
301
+ batch.add(metaUpsert, { $k: 'server_offset_ms', $v: String(Math.trunc(hello.serverTimeUtcMs - Date.now())) });
302
+ batch.add(metaUpsert, { $k: 'definitions_json', $v: persistedJson(hello.definitions) });
303
+ batch.add(metaUpsert, { $k: 'tables_json', $v: persistedJson(hello.tables) });
254
304
  // Kept per subscription: a caller who loses access stops being told the definition (ADR-0048).
255
305
  for (const definition of hello.definitions) {
256
- await connection.exec('UPDATE g9_subscriptions SET move_out_policy = $p WHERE name = $n', { $p: definition.moveOutPolicy, $n: definition.name });
306
+ batch.add('UPDATE g9_subscriptions SET move_out_policy = $p WHERE name = $n', { $p: definition.moveOutPolicy, $n: definition.name });
257
307
  }
308
+ // Which tables get new columns, and which of those already hold rows: one read for all of them.
309
+ const resolved = [];
258
310
  for (const negotiated of hello.tables) {
259
311
  const clientTable = model.findByServerName(negotiated.name);
260
312
  const local = clientTable ? this.byLocal.get(clientTable.localName.toLowerCase()) : undefined;
261
313
  if (!local)
262
314
  continue;
263
315
  const names = negotiated.columns.map((c) => c.name);
316
+ const added = local.negotiated === null ? [] : names.filter((n) => !local.syncedColumns.some((s) => s.toLowerCase() === n.toLowerCase()));
317
+ resolved.push({ local, negotiated, names, added });
318
+ }
319
+ const askHolding = resolved.filter((r) => r.added.length > 0).map((r) => r.local.tid);
320
+ const holding = new Set();
321
+ if (askHolding.length > 0) {
322
+ const rows = await connection.query('SELECT DISTINCT tid FROM g9_rows WHERE deleted = 0 AND tid IN (SELECT value FROM json_each($tids))', { $tids: JSON.stringify(askHolding) });
323
+ for (const r of rows)
324
+ holding.add(asNumber(r[0]));
325
+ }
326
+ for (const { local, negotiated, names, added } of resolved) {
264
327
  let synced;
265
328
  if (local.negotiated === null) {
266
329
  synced = names;
267
330
  }
268
331
  else {
269
332
  synced = local.syncedColumns.slice();
270
- const added = names.filter((n) => !synced.some((s) => s.toLowerCase() === n.toLowerCase()));
271
- if (added.length > 0 && (await connection.scalar('SELECT 1 FROM g9_rows WHERE tid = $t AND deleted = 0 LIMIT 1', { $t: local.tid })) === null) {
333
+ if (added.length > 0 && !holding.has(local.tid)) {
272
334
  for (const a of added)
273
335
  synced.push(a);
274
336
  }
275
337
  }
276
- await connection.exec('UPDATE g9_tables SET server_table_id = $s, negotiated_json = $j, synced_columns = $c WHERE tid = $t', {
338
+ batch.add('UPDATE g9_tables SET server_table_id = $s, negotiated_json = $j, synced_columns = $c WHERE tid = $t', {
277
339
  $s: negotiated.tableId,
278
340
  $j: persistedJson(negotiated),
279
341
  $c: persistedJson(synced),
280
342
  $t: local.tid,
281
343
  });
282
344
  if (negotiated.direction === G9ESyncDirection.DownloadOnly) {
283
- await connection.exec(dropTriggers(local.model));
345
+ batch.add(dropTriggers(local.model));
346
+ // A row marked as a local change in a download-only table can never be sent: it would stay pending for ever and hold
347
+ // the server's updates off. The engine's capture cannot make one there (no triggers); rows imported from outside it
348
+ // can. The server's copy wins. In the batch, not behind a read: the partial dirty index makes it free when there is
349
+ // nothing to clear. (The C# twin, SaveHelloAsync, also logs the count as 20109.)
350
+ batch.add('UPDATE g9_rows SET dirty = 0, dirty_mask = 0, rejected = 0 WHERE tid = $t AND dirty <> 0', { $t: local.tid });
351
+ batch.add('DELETE FROM g9_rejections WHERE tid = $t', { $t: local.tid });
284
352
  }
285
353
  else if (local.negotiated && local.negotiated.direction === G9ESyncDirection.DownloadOnly) {
286
- await connection.exec(dropTriggers(local.model) + createTriggers(local.model, local.tid));
354
+ batch.add(dropTriggers(local.model) + createTriggers(local.model, local.tid));
287
355
  }
288
356
  updates.push({ local, negotiated, synced });
289
357
  }
358
+ await batch.flush();
290
359
  });
291
360
  for (const { local, negotiated, synced } of updates) {
292
361
  local.negotiated = negotiated;