@rocicorp/zero 1.8.0 → 1.9.0-canary.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 (179) hide show
  1. package/out/shared/src/cache.d.ts +8 -1
  2. package/out/shared/src/cache.d.ts.map +1 -1
  3. package/out/shared/src/cache.js +25 -9
  4. package/out/shared/src/cache.js.map +1 -1
  5. package/out/shared/src/objects.d.ts +6 -1
  6. package/out/shared/src/objects.d.ts.map +1 -1
  7. package/out/shared/src/objects.js +19 -4
  8. package/out/shared/src/objects.js.map +1 -1
  9. package/out/z2s/src/compiler.d.ts.map +1 -1
  10. package/out/z2s/src/compiler.js +1 -22
  11. package/out/z2s/src/compiler.js.map +1 -1
  12. package/out/zero/package.js +2 -19
  13. package/out/zero/package.js.map +1 -1
  14. package/out/zero-cache/src/config/zero-config.d.ts +4 -0
  15. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  16. package/out/zero-cache/src/config/zero-config.js +21 -6
  17. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  18. package/out/zero-cache/src/custom/fetch.js +1 -1
  19. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  20. package/out/zero-cache/src/custom-queries/transform-query.d.ts +6 -0
  21. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  22. package/out/zero-cache/src/custom-queries/transform-query.js +8 -0
  23. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  24. package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
  25. package/out/zero-cache/src/db/migration-lite.js +5 -2
  26. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  27. package/out/zero-cache/src/db/migration.d.ts.map +1 -1
  28. package/out/zero-cache/src/db/migration.js +3 -1
  29. package/out/zero-cache/src/db/migration.js.map +1 -1
  30. package/out/zero-cache/src/db/sqlite-corruption.d.ts +11 -0
  31. package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -0
  32. package/out/zero-cache/src/db/sqlite-corruption.js +191 -0
  33. package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -0
  34. package/out/zero-cache/src/server/anonymous-otel-start.d.ts +1 -1
  35. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  36. package/out/zero-cache/src/server/backup-watermark-reader.js +1 -1
  37. package/out/zero-cache/src/server/backup-watermark-reader.js.map +1 -1
  38. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  39. package/out/zero-cache/src/server/change-streamer.js +7 -2
  40. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  41. package/out/zero-cache/src/server/logging.d.ts +8 -0
  42. package/out/zero-cache/src/server/logging.d.ts.map +1 -1
  43. package/out/zero-cache/src/server/logging.js +27 -5
  44. package/out/zero-cache/src/server/logging.js.map +1 -1
  45. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  46. package/out/zero-cache/src/server/main.js +6 -1
  47. package/out/zero-cache/src/server/main.js.map +1 -1
  48. package/out/zero-cache/src/server/mutator.js +1 -1
  49. package/out/zero-cache/src/server/mutator.js.map +1 -1
  50. package/out/zero-cache/src/server/otel-log-sink.d.ts +7 -1
  51. package/out/zero-cache/src/server/otel-log-sink.d.ts.map +1 -1
  52. package/out/zero-cache/src/server/otel-log-sink.js +7 -2
  53. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  54. package/out/zero-cache/src/server/reaper.js +1 -1
  55. package/out/zero-cache/src/server/reaper.js.map +1 -1
  56. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  57. package/out/zero-cache/src/server/replicator.js +11 -1
  58. package/out/zero-cache/src/server/replicator.js.map +1 -1
  59. package/out/zero-cache/src/server/shadow-syncer.js +1 -1
  60. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
  61. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  62. package/out/zero-cache/src/server/syncer.js +6 -1
  63. package/out/zero-cache/src/server/syncer.js.map +1 -1
  64. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  65. package/out/zero-cache/src/services/change-source/common/replica-schema.js +8 -5
  66. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  67. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +36 -2
  68. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  69. package/out/zero-cache/src/services/change-source/pg/change-source.js +76 -33
  70. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  71. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +15 -0
  72. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  73. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +41 -10
  74. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  75. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +21 -0
  76. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  77. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +34 -5
  78. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  79. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +2 -2
  80. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  81. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  82. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +61 -87
  83. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  84. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +17 -12
  85. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  86. package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
  87. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  88. package/out/zero-cache/src/services/life-cycle.js +15 -3
  89. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  90. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/litestream/commands.js +26 -23
  92. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  93. package/out/zero-cache/src/services/litestream/config.yml +3 -2
  94. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +2 -2
  95. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  96. package/out/zero-cache/src/services/mutagen/mutagen.js +4 -1
  97. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  98. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  99. package/out/zero-cache/src/services/replicator/change-processor.js +29 -16
  100. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  101. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +1 -1
  102. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
  103. package/out/zero-cache/src/services/replicator/reporter/recorder.js +5 -12
  104. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  105. package/out/zero-cache/src/services/replicator/write-worker.js +38 -7
  106. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  107. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  108. package/out/zero-cache/src/services/view-syncer/cvr.js +8 -1
  109. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  110. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  111. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +66 -26
  112. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  113. package/out/zero-cache/src/services/view-syncer/query-covering.d.ts +28 -0
  114. package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +1 -0
  115. package/out/zero-cache/src/services/view-syncer/query-covering.js +159 -0
  116. package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -0
  117. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  118. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  119. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +2 -0
  120. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  121. package/out/zero-cache/src/services/view-syncer/view-syncer.js +71 -1
  122. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  123. package/out/zero-cache/src/types/lite.d.ts.map +1 -1
  124. package/out/zero-cache/src/types/lite.js +4 -1
  125. package/out/zero-cache/src/types/lite.js.map +1 -1
  126. package/out/zero-cache/src/types/pg.d.ts +18 -0
  127. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  128. package/out/zero-cache/src/types/pg.js +50 -0
  129. package/out/zero-cache/src/types/pg.js.map +1 -1
  130. package/out/zero-cache/src/workers/syncer.d.ts +1 -1
  131. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  132. package/out/zero-cache/src/workers/syncer.js +1 -0
  133. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  134. package/out/zero-client/src/client/crud.d.ts.map +1 -1
  135. package/out/zero-client/src/client/crud.js +3 -4
  136. package/out/zero-client/src/client/crud.js.map +1 -1
  137. package/out/zero-client/src/client/version.js +1 -1
  138. package/out/zero-client/src/client/zero.d.ts +6 -7
  139. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  140. package/out/zero-client/src/client/zero.js +125 -85
  141. package/out/zero-client/src/client/zero.js.map +1 -1
  142. package/out/zero-schema/src/builder/relationship-builder.d.ts.map +1 -1
  143. package/out/zero-schema/src/builder/relationship-builder.js +3 -2
  144. package/out/zero-schema/src/builder/relationship-builder.js.map +1 -1
  145. package/out/zero-schema/src/builder/schema-builder.d.ts.map +1 -1
  146. package/out/zero-schema/src/builder/schema-builder.js +7 -7
  147. package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
  148. package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
  149. package/out/zero-schema/src/builder/table-builder.js +2 -1
  150. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  151. package/out/zero-server/src/custom.js +10 -3
  152. package/out/zero-server/src/custom.js.map +1 -1
  153. package/out/zero-server/src/schema.d.ts +8 -0
  154. package/out/zero-server/src/schema.d.ts.map +1 -1
  155. package/out/zero-server/src/schema.js +63 -24
  156. package/out/zero-server/src/schema.js.map +1 -1
  157. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  158. package/out/zql/src/ivm/view-apply-change.js +2 -1
  159. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  160. package/out/zql/src/mutate/crud.d.ts +10 -8
  161. package/out/zql/src/mutate/crud.d.ts.map +1 -1
  162. package/out/zql/src/mutate/crud.js +3 -4
  163. package/out/zql/src/mutate/crud.js.map +1 -1
  164. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  165. package/out/zql/src/query/query-delegate-base.js +5 -1
  166. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  167. package/out/zqlite/src/db.js +2 -2
  168. package/out/zqlite/src/internal/statement-cache.d.ts +25 -1
  169. package/out/zqlite/src/internal/statement-cache.d.ts.map +1 -1
  170. package/out/zqlite/src/internal/statement-cache.js +48 -18
  171. package/out/zqlite/src/internal/statement-cache.js.map +1 -1
  172. package/out/zqlite/src/query-builder.js +3 -3
  173. package/out/zqlite/src/query-builder.js.map +1 -1
  174. package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
  175. package/out/zqlite/src/resolve-scalar-subqueries.js +13 -1
  176. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  177. package/out/zqlite/src/sqlite-cost-model.js +5 -5
  178. package/out/zqlite/src/table-source.js +3 -3
  179. package/package.json +2 -33
@@ -1,9 +1,9 @@
1
1
  import { populateFromExistingTables } from "../../replicator/schema/column-metadata.js";
2
2
  import { listTables } from "../../../db/lite-tables.js";
3
3
  import { CREATE_RUNTIME_EVENTS_TABLE, recordEvent } from "../../replicator/schema/replication-state.js";
4
+ import { isSQLiteCorruption, logSQLiteCorruptionDiagnostics } from "../../../db/sqlite-corruption.js";
4
5
  import { runSchemaMigrations } from "../../../db/migration-lite.js";
5
6
  import { AutoResetSignal } from "../../change-streamer/schema/tables.js";
6
- import { SqliteError } from "@rocicorp/zero-sqlite3";
7
7
  //#region ../zero-cache/src/services/change-source/common/replica-schema.ts
8
8
  async function initReplica(log, debugName, dbPath, initialSync) {
9
9
  const setupMigration = {
@@ -13,7 +13,7 @@ async function initReplica(log, debugName, dbPath, initialSync) {
13
13
  try {
14
14
  await runSchemaMigrations(log, debugName, dbPath, setupMigration, schemaVersionMigrationMap);
15
15
  } catch (e) {
16
- throwAutoResetForCorruption(e);
16
+ throwAutoResetForCorruption(log, debugName, dbPath, e);
17
17
  }
18
18
  }
19
19
  async function upgradeReplica(log, debugName, dbPath) {
@@ -22,11 +22,14 @@ async function upgradeReplica(log, debugName, dbPath) {
22
22
  throw new Error("This should only be called for already synced replicas");
23
23
  } }, schemaVersionMigrationMap);
24
24
  } catch (e) {
25
- throwAutoResetForCorruption(e);
25
+ throwAutoResetForCorruption(log, debugName, dbPath, e);
26
26
  }
27
27
  }
28
- function throwAutoResetForCorruption(e) {
29
- if (e instanceof SqliteError && e.code === "SQLITE_CORRUPT") throw new AutoResetSignal(`replica database appears corrupt: ${String(e)}`, { cause: e });
28
+ function throwAutoResetForCorruption(log, debugName, dbPath, e) {
29
+ if (isSQLiteCorruption(e)) {
30
+ logSQLiteCorruptionDiagnostics(log, debugName, dbPath, e);
31
+ throw new AutoResetSignal(`replica database appears corrupt: ${String(e)}`, { cause: e });
32
+ }
30
33
  throw e;
31
34
  }
32
35
  var CREATE_V6_COLUMN_METADATA_TABLE = `
@@ -1 +1 @@
1
- {"version":3,"file":"replica-schema.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/common/replica-schema.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport type {Database} from '../../../../../zqlite/src/db.ts';\nimport {listTables} from '../../../db/lite-tables.ts';\nimport {\n runSchemaMigrations,\n type IncrementalMigrationMap,\n type Migration,\n} from '../../../db/migration-lite.ts';\nimport {AutoResetSignal} from '../../change-streamer/schema/tables.ts';\nimport {populateFromExistingTables} from '../../replicator/schema/column-metadata.ts';\nimport {\n CREATE_RUNTIME_EVENTS_TABLE,\n recordEvent,\n} from '../../replicator/schema/replication-state.ts';\n\nexport async function initReplica(\n log: LogContext,\n debugName: string,\n dbPath: string,\n initialSync: (lc: LogContext, tx: Database) => Promise<void>,\n): Promise<void> {\n const setupMigration: Migration = {\n migrateSchema: (log, tx) => initialSync(log, tx),\n minSafeVersion: 1,\n };\n\n try {\n await runSchemaMigrations(\n log,\n debugName,\n dbPath,\n setupMigration,\n schemaVersionMigrationMap,\n );\n } catch (e) {\n throwAutoResetForCorruption(e);\n }\n}\n\nexport async function upgradeReplica(\n log: LogContext,\n debugName: string,\n dbPath: string,\n) {\n try {\n await runSchemaMigrations(\n log,\n debugName,\n dbPath,\n // setupMigration should never be invoked\n {\n migrateSchema: () => {\n throw new Error(\n 'This should only be called for already synced replicas',\n );\n },\n },\n schemaVersionMigrationMap,\n );\n } catch (e) {\n throwAutoResetForCorruption(e);\n }\n}\n\nfunction throwAutoResetForCorruption(e: unknown): never {\n if (e instanceof SqliteError && e.code === 'SQLITE_CORRUPT') {\n throw new AutoResetSignal(\n `replica database appears corrupt: ${String(e)}`,\n {cause: e},\n );\n }\n throw e;\n}\n\nexport const CREATE_V6_COLUMN_METADATA_TABLE = /*sql*/ `\n CREATE TABLE \"_zero.column_metadata\" (\n table_name TEXT NOT NULL,\n column_name TEXT NOT NULL,\n upstream_type TEXT NOT NULL,\n is_not_null INTEGER NOT NULL,\n is_enum INTEGER NOT NULL,\n is_array INTEGER NOT NULL,\n character_max_length INTEGER,\n PRIMARY KEY (table_name, column_name)\n );\n`;\n\nexport const CREATE_V7_CHANGE_LOG = /*sql*/ `\n CREATE TABLE \"_zero.changeLog2\" (\n \"stateVersion\" TEXT NOT NULL,\n \"pos\" INT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"rowKey\" TEXT NOT NULL,\n \"op\" TEXT NOT NULL,\n PRIMARY KEY(\"stateVersion\", \"pos\"),\n UNIQUE(\"table\", \"rowKey\")\n );\n`;\n\nexport const CREATE_V9_TABLE_METADATA_TABLE = /*sql*/ `\n CREATE TABLE \"_zero.tableMetadata\" (\n \"schema\" TEXT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"metadata\" TEXT NOT NULL,\n PRIMARY KEY (\"schema\", \"table\")\n );\n`;\n\nexport const schemaVersionMigrationMap: IncrementalMigrationMap = {\n // There's no incremental migration from v1. Just reset the replica.\n 4: {\n migrateSchema: () => {\n throw new AutoResetSignal('upgrading replica to new schema');\n },\n minSafeVersion: 3,\n },\n\n 5: {\n migrateSchema: (_, db) => {\n db.exec(CREATE_RUNTIME_EVENTS_TABLE);\n },\n migrateData: (_, db) => {\n recordEvent(db, 'upgrade');\n },\n },\n\n // Revised in the migration to v8 because the v6 code was incomplete.\n 6: {},\n\n 7: {\n migrateSchema: (_, db) => {\n // Note: The original \"changeLog\" table is kept so that the replica file\n // is compatible with older zero-caches. However, it is truncated for\n // space savings (since historic changes were never read).\n db.exec(`DELETE FROM \"_zero.changeLog\"`);\n // First version of changeLog2\n db.exec(CREATE_V7_CHANGE_LOG);\n },\n },\n\n 8: {\n migrateSchema: (_, db) => {\n const tableExists = db\n .prepare(\n `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_zero.column_metadata'`,\n )\n .get();\n\n if (!tableExists) {\n db.exec(CREATE_V6_COLUMN_METADATA_TABLE);\n }\n },\n migrateData: (_, db) => {\n // Re-populate the ColumnMetadataStore; the original migration\n // at v6 was incomplete, as covered replicas migrated from earlier\n // versions but did not initialize the table for new replicas.\n db.exec(/*sql*/ `DELETE FROM \"_zero.column_metadata\"`);\n\n const tables = listTables(db, false, false);\n populateFromExistingTables(db, tables);\n },\n },\n\n 9: {\n migrateSchema: (_, db) => {\n db.exec(\n /*sql*/ `\n ALTER TABLE \"_zero.changeLog2\" \n ADD COLUMN \"backfillingColumnVersions\" TEXT DEFAULT '{}';\n ALTER TABLE \"_zero.column_metadata\"\n ADD COLUMN backfill TEXT;\n ` + CREATE_V9_TABLE_METADATA_TABLE,\n );\n },\n },\n\n 10: {\n migrateSchema: (_, db) => {\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.replicationConfig\" \n ADD COLUMN \"initialSyncContext\" TEXT DEFAULT '{}';\n `);\n },\n },\n\n 11: {\n migrateSchema: (_, db) => {\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.tableMetadata\"\n ADD COLUMN \"minRowVersion\" TEXT NOT NULL DEFAULT '00';\n\n -- Removing the NOT NULL constraint from \"metadata\" requires copying\n -- the column. We piggyback the rename to \"upstreamMetadata\" here.\n ALTER TABLE \"_zero.tableMetadata\"\n ADD COLUMN \"upstreamMetadata\" TEXT;\n UPDATE \"_zero.tableMetadata\" SET \"upstreamMetadata\" = \"metadata\";\n ALTER TABLE \"_zero.tableMetadata\" DROP \"metadata\";\n `);\n },\n },\n\n 12: {\n migrateSchema: (_, db) => {\n // Bring back the \"metadata\" column removed in v11, but as a NULL-able column.\n // It is needed for backwards compatibility.\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.tableMetadata\"\n ADD COLUMN \"metadata\" TEXT;\n `);\n },\n\n migrateData: (_, db) => {\n // For rollback then roll forward, re-copy anything written to metadata.\n db.exec(/*sql*/ `\n UPDATE \"_zero.tableMetadata\" \n SET \"upstreamMetadata\" = COALESCE(\"metadata\", \"upstreamMetadata\"),\n \"metadata\" = NULL;\n `);\n },\n },\n\n 13: {\n migrateSchema: (_, db) => {\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.replicationState\" ADD COLUMN writeTimeMs INTEGER;\n `);\n },\n\n migrateData: (_, db) => {\n db.exec(/*sql*/ `\n UPDATE \"_zero.replicationState\" \n SET writeTimeMs = COALESCE(writeTimeMs, unixepoch('subsec') * 1000)`);\n },\n },\n};\n\n// Referenced in tests.\nexport const CURRENT_SCHEMA_VERSION = Object.keys(\n schemaVersionMigrationMap,\n).reduce((prev, curr) => Math.max(prev, parseInt(curr)), 0);\n"],"mappings":";;;;;;;AAgBA,eAAsB,YACpB,KACA,WACA,QACA,aACe;CACf,MAAM,iBAA4B;EAChC,gBAAgB,KAAK,OAAO,YAAY,KAAK,EAAE;EAC/C,gBAAgB;CAClB;CAEA,IAAI;EACF,MAAM,oBACJ,KACA,WACA,QACA,gBACA,yBACF;CACF,SAAS,GAAG;EACV,4BAA4B,CAAC;CAC/B;AACF;AAEA,eAAsB,eACpB,KACA,WACA,QACA;CACA,IAAI;EACF,MAAM,oBACJ,KACA,WACA,QAEA,EACE,qBAAqB;GACnB,MAAM,IAAI,MACR,wDACF;EACF,EACF,GACA,yBACF;CACF,SAAS,GAAG;EACV,4BAA4B,CAAC;CAC/B;AACF;AAEA,SAAS,4BAA4B,GAAmB;CACtD,IAAI,aAAa,eAAe,EAAE,SAAS,kBACzC,MAAM,IAAI,gBACR,qCAAqC,OAAO,CAAC,KAC7C,EAAC,OAAO,EAAC,CACX;CAEF,MAAM;AACR;AAEA,IAAa,kCAA0C;;;;;;;;;;;;AAavD,IAAa,uBAA+B;;;;;;;;;;;AAY5C,IAAa,iCAAyC;;;;;;;;AAStD,IAAa,4BAAqD;CAEhE,GAAG;EACD,qBAAqB;GACnB,MAAM,IAAI,gBAAgB,iCAAiC;EAC7D;EACA,gBAAgB;CAClB;CAEA,GAAG;EACD,gBAAgB,GAAG,OAAO;GACxB,GAAG,KAAK,2BAA2B;EACrC;EACA,cAAc,GAAG,OAAO;GACtB,YAAY,IAAI,SAAS;EAC3B;CACF;CAGA,GAAG,CAAC;CAEJ,GAAG,EACD,gBAAgB,GAAG,OAAO;EAIxB,GAAG,KAAK,+BAA+B;EAEvC,GAAG,KAAK,oBAAoB;CAC9B,EACF;CAEA,GAAG;EACD,gBAAgB,GAAG,OAAO;GAOxB,IAAI,CANgB,GACjB,QACC,qFACF,EACC,IAEE,GACH,GAAG,KAAK,+BAA+B;EAE3C;EACA,cAAc,GAAG,OAAO;GAItB,GAAG,KAAa,qCAAqC;GAGrD,2BAA2B,IADZ,WAAW,IAAI,OAAO,KACN,CAAM;EACvC;CACF;CAEA,GAAG,EACD,gBAAgB,GAAG,OAAO;EACxB,GAAG,KACO;;;;;UAKN,8BACJ;CACF,EACF;CAEA,IAAI,EACF,gBAAgB,GAAG,OAAO;EACxB,GAAG,KAAa;;;OAGf;CACH,EACF;CAEA,IAAI,EACF,gBAAgB,GAAG,OAAO;EACxB,GAAG,KAAa;;;;;;;;;;OAUf;CACH,EACF;CAEA,IAAI;EACF,gBAAgB,GAAG,OAAO;GAGxB,GAAG,KAAa;;;OAGf;EACH;EAEA,cAAc,GAAG,OAAO;GAEtB,GAAG,KAAa;;;;OAIf;EACH;CACF;CAEA,IAAI;EACF,gBAAgB,GAAG,OAAO;GACxB,GAAG,KAAa;;OAEf;EACH;EAEA,cAAc,GAAG,OAAO;GACtB,GAAG,KAAa;;8EAEwD;EAC1E;CACF;AACF;AAGsC,OAAO,KAC3C,yBACF,EAAE,QAAQ,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,IAAI,CAAC,GAAG,CAAC"}
1
+ {"version":3,"file":"replica-schema.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/common/replica-schema.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {Database} from '../../../../../zqlite/src/db.ts';\nimport {listTables} from '../../../db/lite-tables.ts';\nimport {\n runSchemaMigrations,\n type IncrementalMigrationMap,\n type Migration,\n} from '../../../db/migration-lite.ts';\nimport {\n isSQLiteCorruption,\n logSQLiteCorruptionDiagnostics,\n} from '../../../db/sqlite-corruption.ts';\nimport {AutoResetSignal} from '../../change-streamer/schema/tables.ts';\nimport {populateFromExistingTables} from '../../replicator/schema/column-metadata.ts';\nimport {\n CREATE_RUNTIME_EVENTS_TABLE,\n recordEvent,\n} from '../../replicator/schema/replication-state.ts';\n\nexport async function initReplica(\n log: LogContext,\n debugName: string,\n dbPath: string,\n initialSync: (lc: LogContext, tx: Database) => Promise<void>,\n): Promise<void> {\n const setupMigration: Migration = {\n migrateSchema: (log, tx) => initialSync(log, tx),\n minSafeVersion: 1,\n };\n\n try {\n await runSchemaMigrations(\n log,\n debugName,\n dbPath,\n setupMigration,\n schemaVersionMigrationMap,\n );\n } catch (e) {\n throwAutoResetForCorruption(log, debugName, dbPath, e);\n }\n}\n\nexport async function upgradeReplica(\n log: LogContext,\n debugName: string,\n dbPath: string,\n) {\n try {\n await runSchemaMigrations(\n log,\n debugName,\n dbPath,\n // setupMigration should never be invoked\n {\n migrateSchema: () => {\n throw new Error(\n 'This should only be called for already synced replicas',\n );\n },\n },\n schemaVersionMigrationMap,\n );\n } catch (e) {\n throwAutoResetForCorruption(log, debugName, dbPath, e);\n }\n}\n\nfunction throwAutoResetForCorruption(\n log: LogContext,\n debugName: string,\n dbPath: string,\n e: unknown,\n): never {\n if (isSQLiteCorruption(e)) {\n logSQLiteCorruptionDiagnostics(log, debugName, dbPath, e);\n throw new AutoResetSignal(\n `replica database appears corrupt: ${String(e)}`,\n {cause: e},\n );\n }\n throw e;\n}\n\nexport const CREATE_V6_COLUMN_METADATA_TABLE = /*sql*/ `\n CREATE TABLE \"_zero.column_metadata\" (\n table_name TEXT NOT NULL,\n column_name TEXT NOT NULL,\n upstream_type TEXT NOT NULL,\n is_not_null INTEGER NOT NULL,\n is_enum INTEGER NOT NULL,\n is_array INTEGER NOT NULL,\n character_max_length INTEGER,\n PRIMARY KEY (table_name, column_name)\n );\n`;\n\nexport const CREATE_V7_CHANGE_LOG = /*sql*/ `\n CREATE TABLE \"_zero.changeLog2\" (\n \"stateVersion\" TEXT NOT NULL,\n \"pos\" INT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"rowKey\" TEXT NOT NULL,\n \"op\" TEXT NOT NULL,\n PRIMARY KEY(\"stateVersion\", \"pos\"),\n UNIQUE(\"table\", \"rowKey\")\n );\n`;\n\nexport const CREATE_V9_TABLE_METADATA_TABLE = /*sql*/ `\n CREATE TABLE \"_zero.tableMetadata\" (\n \"schema\" TEXT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"metadata\" TEXT NOT NULL,\n PRIMARY KEY (\"schema\", \"table\")\n );\n`;\n\nexport const schemaVersionMigrationMap: IncrementalMigrationMap = {\n // There's no incremental migration from v1. Just reset the replica.\n 4: {\n migrateSchema: () => {\n throw new AutoResetSignal('upgrading replica to new schema');\n },\n minSafeVersion: 3,\n },\n\n 5: {\n migrateSchema: (_, db) => {\n db.exec(CREATE_RUNTIME_EVENTS_TABLE);\n },\n migrateData: (_, db) => {\n recordEvent(db, 'upgrade');\n },\n },\n\n // Revised in the migration to v8 because the v6 code was incomplete.\n 6: {},\n\n 7: {\n migrateSchema: (_, db) => {\n // Note: The original \"changeLog\" table is kept so that the replica file\n // is compatible with older zero-caches. However, it is truncated for\n // space savings (since historic changes were never read).\n db.exec(`DELETE FROM \"_zero.changeLog\"`);\n // First version of changeLog2\n db.exec(CREATE_V7_CHANGE_LOG);\n },\n },\n\n 8: {\n migrateSchema: (_, db) => {\n const tableExists = db\n .prepare(\n `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_zero.column_metadata'`,\n )\n .get();\n\n if (!tableExists) {\n db.exec(CREATE_V6_COLUMN_METADATA_TABLE);\n }\n },\n migrateData: (_, db) => {\n // Re-populate the ColumnMetadataStore; the original migration\n // at v6 was incomplete, as covered replicas migrated from earlier\n // versions but did not initialize the table for new replicas.\n db.exec(/*sql*/ `DELETE FROM \"_zero.column_metadata\"`);\n\n const tables = listTables(db, false, false);\n populateFromExistingTables(db, tables);\n },\n },\n\n 9: {\n migrateSchema: (_, db) => {\n db.exec(\n /*sql*/ `\n ALTER TABLE \"_zero.changeLog2\" \n ADD COLUMN \"backfillingColumnVersions\" TEXT DEFAULT '{}';\n ALTER TABLE \"_zero.column_metadata\"\n ADD COLUMN backfill TEXT;\n ` + CREATE_V9_TABLE_METADATA_TABLE,\n );\n },\n },\n\n 10: {\n migrateSchema: (_, db) => {\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.replicationConfig\" \n ADD COLUMN \"initialSyncContext\" TEXT DEFAULT '{}';\n `);\n },\n },\n\n 11: {\n migrateSchema: (_, db) => {\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.tableMetadata\"\n ADD COLUMN \"minRowVersion\" TEXT NOT NULL DEFAULT '00';\n\n -- Removing the NOT NULL constraint from \"metadata\" requires copying\n -- the column. We piggyback the rename to \"upstreamMetadata\" here.\n ALTER TABLE \"_zero.tableMetadata\"\n ADD COLUMN \"upstreamMetadata\" TEXT;\n UPDATE \"_zero.tableMetadata\" SET \"upstreamMetadata\" = \"metadata\";\n ALTER TABLE \"_zero.tableMetadata\" DROP \"metadata\";\n `);\n },\n },\n\n 12: {\n migrateSchema: (_, db) => {\n // Bring back the \"metadata\" column removed in v11, but as a NULL-able column.\n // It is needed for backwards compatibility.\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.tableMetadata\"\n ADD COLUMN \"metadata\" TEXT;\n `);\n },\n\n migrateData: (_, db) => {\n // For rollback then roll forward, re-copy anything written to metadata.\n db.exec(/*sql*/ `\n UPDATE \"_zero.tableMetadata\" \n SET \"upstreamMetadata\" = COALESCE(\"metadata\", \"upstreamMetadata\"),\n \"metadata\" = NULL;\n `);\n },\n },\n\n 13: {\n migrateSchema: (_, db) => {\n db.exec(/*sql*/ `\n ALTER TABLE \"_zero.replicationState\" ADD COLUMN writeTimeMs INTEGER;\n `);\n },\n\n migrateData: (_, db) => {\n db.exec(/*sql*/ `\n UPDATE \"_zero.replicationState\" \n SET writeTimeMs = COALESCE(writeTimeMs, unixepoch('subsec') * 1000)`);\n },\n },\n};\n\n// Referenced in tests.\nexport const CURRENT_SCHEMA_VERSION = Object.keys(\n schemaVersionMigrationMap,\n).reduce((prev, curr) => Math.max(prev, parseInt(curr)), 0);\n"],"mappings":";;;;;;;AAmBA,eAAsB,YACpB,KACA,WACA,QACA,aACe;CACf,MAAM,iBAA4B;EAChC,gBAAgB,KAAK,OAAO,YAAY,KAAK,EAAE;EAC/C,gBAAgB;CAClB;CAEA,IAAI;EACF,MAAM,oBACJ,KACA,WACA,QACA,gBACA,yBACF;CACF,SAAS,GAAG;EACV,4BAA4B,KAAK,WAAW,QAAQ,CAAC;CACvD;AACF;AAEA,eAAsB,eACpB,KACA,WACA,QACA;CACA,IAAI;EACF,MAAM,oBACJ,KACA,WACA,QAEA,EACE,qBAAqB;GACnB,MAAM,IAAI,MACR,wDACF;EACF,EACF,GACA,yBACF;CACF,SAAS,GAAG;EACV,4BAA4B,KAAK,WAAW,QAAQ,CAAC;CACvD;AACF;AAEA,SAAS,4BACP,KACA,WACA,QACA,GACO;CACP,IAAI,mBAAmB,CAAC,GAAG;EACzB,+BAA+B,KAAK,WAAW,QAAQ,CAAC;EACxD,MAAM,IAAI,gBACR,qCAAqC,OAAO,CAAC,KAC7C,EAAC,OAAO,EAAC,CACX;CACF;CACA,MAAM;AACR;AAEA,IAAa,kCAA0C;;;;;;;;;;;;AAavD,IAAa,uBAA+B;;;;;;;;;;;AAY5C,IAAa,iCAAyC;;;;;;;;AAStD,IAAa,4BAAqD;CAEhE,GAAG;EACD,qBAAqB;GACnB,MAAM,IAAI,gBAAgB,iCAAiC;EAC7D;EACA,gBAAgB;CAClB;CAEA,GAAG;EACD,gBAAgB,GAAG,OAAO;GACxB,GAAG,KAAK,2BAA2B;EACrC;EACA,cAAc,GAAG,OAAO;GACtB,YAAY,IAAI,SAAS;EAC3B;CACF;CAGA,GAAG,CAAC;CAEJ,GAAG,EACD,gBAAgB,GAAG,OAAO;EAIxB,GAAG,KAAK,+BAA+B;EAEvC,GAAG,KAAK,oBAAoB;CAC9B,EACF;CAEA,GAAG;EACD,gBAAgB,GAAG,OAAO;GAOxB,IAAI,CANgB,GACjB,QACC,qFACF,EACC,IAEE,GACH,GAAG,KAAK,+BAA+B;EAE3C;EACA,cAAc,GAAG,OAAO;GAItB,GAAG,KAAa,qCAAqC;GAGrD,2BAA2B,IADZ,WAAW,IAAI,OAAO,KACN,CAAM;EACvC;CACF;CAEA,GAAG,EACD,gBAAgB,GAAG,OAAO;EACxB,GAAG,KACO;;;;;UAKN,8BACJ;CACF,EACF;CAEA,IAAI,EACF,gBAAgB,GAAG,OAAO;EACxB,GAAG,KAAa;;;OAGf;CACH,EACF;CAEA,IAAI,EACF,gBAAgB,GAAG,OAAO;EACxB,GAAG,KAAa;;;;;;;;;;OAUf;CACH,EACF;CAEA,IAAI;EACF,gBAAgB,GAAG,OAAO;GAGxB,GAAG,KAAa;;;OAGf;EACH;EAEA,cAAc,GAAG,OAAO;GAEtB,GAAG,KAAa;;;;OAIf;EACH;CACF;CAEA,IAAI;EACF,gBAAgB,GAAG,OAAO;GACxB,GAAG,KAAa;;OAEf;EACH;EAEA,cAAc,GAAG,OAAO;GACtB,GAAG,KAAa;;8EAEwD;EAC1E;CACF;AACF;AAGsC,OAAO,KAC3C,yBACF,EAAE,QAAQ,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,IAAI,CAAC,GAAG,CAAC"}
@@ -2,14 +2,16 @@ import type { LogContext } from '@rocicorp/logger';
2
2
  import * as v from '../../../../../shared/src/valita.ts';
3
3
  import type { PublishedTableSpec } from '../../../db/specs.ts';
4
4
  import { type LexiVersion } from '../../../types/lexi-version.ts';
5
- import { type ShardConfig } from '../../../types/shards.ts';
5
+ import { type PostgresDB } from '../../../types/pg.ts';
6
+ import { type ShardConfig, type ShardID } from '../../../types/shards.ts';
6
7
  import type { Sink } from '../../../types/streams.ts';
7
8
  import { type SubscriptionState } from '../../replicator/schema/replication-state.ts';
8
9
  import type { ChangeSource } from '../change-source.ts';
9
10
  import { type Listener } from '../common/change-stream-multiplexer.ts';
11
+ import type { DownstreamStatusMessage } from '../protocol/current.ts';
10
12
  import type { ChangeStreamMessage } from '../protocol/current/downstream.ts';
11
13
  import { type InitialSyncOptions, type ServerContext } from './initial-sync.ts';
12
- import type { MessageRelation as PostgresRelation } from './logical-replication/pgoutput.types.ts';
14
+ import type { MessageMessage, MessageRelation as PostgresRelation } from './logical-replication/pgoutput.types.ts';
13
15
  /**
14
16
  * Initializes a Postgres change source, including the initial sync of the
15
17
  * replica, before streaming changes from the corresponding logical replication
@@ -31,6 +33,38 @@ declare const lagReportSchema: v.ObjectType<{
31
33
  commitTimeMs: v.Type<number>;
32
34
  }, undefined>;
33
35
  export type LagReport = v.Infer<typeof lagReportSchema>;
36
+ export declare class LagReporter {
37
+ #private;
38
+ static readonly MESSAGE_SUFFIX = "/lag-report/v1";
39
+ readonly messagePrefix: string;
40
+ constructor(lc: LogContext, shard: ShardID, db: PostgresDB, lagIntervalMs: number);
41
+ get pgVersion(): number | undefined;
42
+ initiateLagReport(log?: boolean): Promise<{
43
+ nextSendTimeMs: number;
44
+ }>;
45
+ /**
46
+ * In Postgres < 17, the pg_logical_emit_message lacks an immediate "flush"
47
+ * option, which can cause messages to be missed when the replication stream
48
+ * starts up:
49
+ *
50
+ * ```
51
+ * * emit message → WAL write (buffered, not flushed)
52
+ * * walsender reads up to current flush LSN
53
+ * * emitted message's LSN is beyond flush LSN → not yet visible
54
+ * * stream feedback/acknowledgment advances slot
55
+ * * WAL eventually flushes → but slot has already moved past it
56
+ * ```
57
+ *
58
+ * This has been seen to happen for the initial `wal_writer_delay` interval
59
+ * of a replication session.
60
+ *
61
+ * To account for this, the last emitted lag report is considered "received"
62
+ * if the stream has advanced beyond the LSN of the report.
63
+ */
64
+ checkCurrentLSN(lsn: bigint): DownstreamStatusMessage | undefined;
65
+ stop(): void;
66
+ processLagReportMessage(msg: MessageMessage): DownstreamStatusMessage;
67
+ }
34
68
  export declare function relationDifferent(a: PublishedTableSpec, b: PostgresRelation): boolean;
35
69
  export {};
36
70
  //# sourceMappingURL=change-source.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"change-source.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/change-source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAcjD,OAAO,KAAK,CAAC,MAAM,qCAAqC,CAAC;AAMzD,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAQhE,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,2BAA2B,CAAC;AAEpD,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,8CAA8C,CAAC;AACtD,OAAO,KAAK,EAAC,YAAY,EAAe,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAEL,KAAK,QAAQ,EACd,MAAM,wCAAwC,CAAC;AAchD,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAGV,eAAe,IAAI,gBAAgB,EACpC,MAAM,yCAAyC,CAAC;AA0BjD;;;;GAIG;AACH,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,UAAU,EACd,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,kBAAkB,EAC/B,OAAO,EAAE,aAAa,EACtB,mBAAmB,SAAI,GACtB,OAAO,CAAC;IAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAC,CAAC,CAuC7E;AAuaD,qBAAa,KAAM,YAAW,QAAQ;;gBAIxB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;IAI9B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAgC3C,GAAG,CAAC,SAAS,EAAE,WAAW;CAoB3B;AAED,QAAA,MAAM,eAAe;;;;aAInB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAqzBxD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,gBAAgB,WAwB3E"}
1
+ {"version":3,"file":"change-source.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/change-source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAcjD,OAAO,KAAK,CAAC,MAAM,qCAAqC,CAAC;AAMzD,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,OAAO,EACb,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,2BAA2B,CAAC;AAEpD,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,8CAA8C,CAAC;AACtD,OAAO,KAAK,EAAC,YAAY,EAAe,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAEL,KAAK,QAAQ,EACd,MAAM,wCAAwC,CAAC;AAEhD,OAAO,KAAK,EAEV,uBAAuB,EAExB,MAAM,wBAAwB,CAAC;AAQhC,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAEV,cAAc,EACd,eAAe,IAAI,gBAAgB,EACpC,MAAM,yCAAyC,CAAC;AA0BjD;;;;GAIG;AACH,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,UAAU,EACd,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,kBAAkB,EAC/B,OAAO,EAAE,aAAa,EACtB,mBAAmB,SAAI,GACtB,OAAO,CAAC;IAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAC,CAAC,CAuC7E;AAuaD,qBAAa,KAAM,YAAW,QAAQ;;gBAIxB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;IAI9B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAgC3C,GAAG,CAAC,SAAS,EAAE,WAAW;CAoB3B;AAED,QAAA,MAAM,eAAe;;;;aAInB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD,qBAAa,WAAW;;IACtB,MAAM,CAAC,QAAQ,CAAC,cAAc,oBAAoB;IAGlD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBAqB7B,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,MAAM;IAiBvB,IAAI,SAAS,uBAEZ;IAEK,iBAAiB,CAAC,GAAG,UAAQ;;;IA0EnC;;;;;;;;;;;;;;;;;;OAkBG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS;IAcjE,IAAI;IA2CJ,uBAAuB,CAAC,GAAG,EAAE,cAAc,GAAG,uBAAuB;CAiDtE;AAonBD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,gBAAgB,WAwB3E"}
@@ -9,6 +9,7 @@ import { stringify } from "../../../../../shared/src/bigint-json.js";
9
9
  import { upstreamSchema } from "../../../types/shards.js";
10
10
  import { Database } from "../../../../../zqlite/src/db.js";
11
11
  import { UnsupportedColumnDefaultError, mapPostgresToLiteColumn } from "../../../db/pg-to-lite.js";
12
+ import { getOrCreateCounter } from "../../../observability/metrics.js";
12
13
  import { getSubscriptionStateAndContext } from "../../replicator/schema/replication-state.js";
13
14
  import { StatementRunner } from "../../../db/statements.js";
14
15
  import { connectPgClient, isPostgresError, pgClient } from "../../../types/pg.js";
@@ -314,6 +315,10 @@ var LagReporter = class LagReporter {
314
315
  messagePrefix;
315
316
  #db;
316
317
  #lagIntervalMs;
318
+ #lagReportRetries = getOrCreateCounter("replication", "lag_report_retries", {
319
+ description: "Number of replication lag reports retried because the expected report was not received before the next report interval.",
320
+ unit: "{report}"
321
+ });
317
322
  #pgVersion;
318
323
  #expectingLagReport = null;
319
324
  #timer;
@@ -347,33 +352,39 @@ var LagReporter = class LagReporter {
347
352
  this.#expectingLagReport = lagReport;
348
353
  let commitTimeMs;
349
354
  let lsn;
350
- if (pgVersion >= 17e4) [{commitTimeMs, lsn}] = await this.#db`
351
- WITH CTE AS (SELECT extract(epoch from now()) * 1000 AS "commitTimeMs")
352
- SELECT "commitTimeMs", pg_logical_emit_message(
353
- false,
354
- ${this.messagePrefix},
355
- json_build_object(
356
- 'id', ${id}::text,
357
- 'sendTimeMs', ${now}::int8,
358
- 'commitTimeMs', "commitTimeMs"
359
- )::text,
360
- true
361
- ) as lsn FROM CTE;
362
- `;
363
- else [{commitTimeMs, lsn}] = await this.#db`
364
- WITH CTE AS (SELECT extract(epoch from now()) * 1000 as "commitTimeMs")
365
- SELECT "commitTimeMs", pg_logical_emit_message(
366
- false,
367
- ${this.messagePrefix},
368
- json_build_object(
369
- 'id', ${id}::text,
370
- 'sendTimeMs', ${now}::int8,
371
- 'commitTimeMs', "commitTimeMs"
372
- )::text
373
- ) as lsn FROM CTE;
374
- `;
355
+ try {
356
+ if (pgVersion >= 17e4) [{commitTimeMs, lsn}] = await this.#db`
357
+ WITH CTE AS (SELECT extract(epoch from now()) * 1000 AS "commitTimeMs")
358
+ SELECT "commitTimeMs", pg_logical_emit_message(
359
+ false,
360
+ ${this.messagePrefix},
361
+ json_build_object(
362
+ 'id', ${id}::text,
363
+ 'sendTimeMs', ${now}::int8,
364
+ 'commitTimeMs', "commitTimeMs"
365
+ )::text,
366
+ true
367
+ ) as lsn FROM CTE;
368
+ `;
369
+ else [{commitTimeMs, lsn}] = await this.#db`
370
+ WITH CTE AS (SELECT extract(epoch from now()) * 1000 as "commitTimeMs")
371
+ SELECT "commitTimeMs", pg_logical_emit_message(
372
+ false,
373
+ ${this.messagePrefix},
374
+ json_build_object(
375
+ 'id', ${id}::text,
376
+ 'sendTimeMs', ${now}::int8,
377
+ 'commitTimeMs', "commitTimeMs"
378
+ )::text
379
+ ) as lsn FROM CTE;
380
+ `;
381
+ } catch (e) {
382
+ if (this.#expectingLagReport?.id === id) this.#expectingLagReport = null;
383
+ throw e;
384
+ }
375
385
  lagReport.lsn = toBigInt(lsn);
376
386
  lagReport.commitTimeMs = commitTimeMs;
387
+ if (this.#expectingLagReport?.id === id) this.#scheduleMissingReportRetry(id);
377
388
  if (log) this.#lc.info?.(`initiated lag report at lsn ${lsn}`, {
378
389
  id,
379
390
  lsn,
@@ -415,6 +426,7 @@ var LagReporter = class LagReporter {
415
426
  this.#expectingLagReport = null;
416
427
  clearTimeout(this.#timer);
417
428
  this.#timer = setTimeout(async () => {
429
+ this.#timer = void 0;
418
430
  try {
419
431
  await this.initiateLagReport();
420
432
  } catch (e) {
@@ -423,6 +435,26 @@ var LagReporter = class LagReporter {
423
435
  }
424
436
  }, delayMs);
425
437
  }
438
+ #scheduleMissingReportRetry(reportID) {
439
+ clearTimeout(this.#timer);
440
+ this.#timer = setTimeout(async () => {
441
+ this.#timer = void 0;
442
+ const missingReport = this.#expectingLagReport;
443
+ if (missingReport?.id !== reportID) return;
444
+ this.#lagReportRetries.add(1);
445
+ this.#lc.warn?.(`retrying missing lag report`, {
446
+ id: missingReport.id,
447
+ lsn: fromBigInt(missingReport.lsn),
448
+ sendTimeMs: missingReport.sendTimeMs
449
+ });
450
+ try {
451
+ await this.initiateLagReport();
452
+ } catch (e) {
453
+ this.#lc.warn?.(`error retrying lag report`, e);
454
+ this.#scheduleNextReport(this.#lagIntervalMs);
455
+ }
456
+ }, this.#lagIntervalMs);
457
+ }
426
458
  processLagReportMessage(msg) {
427
459
  assert(msg.prefix === this.messagePrefix, `unexpected message prefix: ${msg.prefix}`);
428
460
  const report = parseLogicalMessageContent(this.#lc, msg, lagReportSchema);
@@ -430,9 +462,15 @@ var LagReporter = class LagReporter {
430
462
  }
431
463
  #processLagReport(report, watermark) {
432
464
  const now = Date.now();
433
- const nextSendTimeMs = Math.max(now, report.sendTimeMs + this.#lagIntervalMs);
434
- if (report.id === this.#expectingLagReport?.id) this.#scheduleNextReport(nextSendTimeMs - now);
435
- else this.#lc.debug?.(`received extraneous lag report`, { report });
465
+ const expectedReport = this.#expectingLagReport;
466
+ let nextSendTimeMs;
467
+ if (report.id === expectedReport?.id) {
468
+ nextSendTimeMs = Math.max(now, report.sendTimeMs + this.#lagIntervalMs);
469
+ this.#scheduleNextReport(nextSendTimeMs - now);
470
+ } else {
471
+ this.#lc.debug?.(`received extraneous lag report`, { report });
472
+ nextSendTimeMs = expectedReport?.sendTimeMs ?? Math.max(now, report.sendTimeMs + this.#lagIntervalMs);
473
+ }
436
474
  const { sendTimeMs, commitTimeMs } = report;
437
475
  return [
438
476
  "status",
@@ -581,6 +619,11 @@ var ChangeMaker = class {
581
619
  }
582
620
  const prevEvent = this.#lastReplicationEvent;
583
621
  this.#lastReplicationEvent = event;
622
+ const { schema } = event;
623
+ if (schema === void 0) {
624
+ lc.debug?.(`received context-only ${msg.prefix}/${type} event`, { event: summarizeReplicationEventForLog(event) });
625
+ return [];
626
+ }
584
627
  const prevSchema = event.previousSchema === void 0 ? prevEvent?.schema : event.previousSchema;
585
628
  if (!prevSchema) {
586
629
  lc.info?.(`received ${msg.prefix}/${type} event`, { event: summarizeReplicationEventForLog(event) });
@@ -588,9 +631,9 @@ var ChangeMaker = class {
588
631
  }
589
632
  const effectiveTag = prevEvent?.type === "ddlStart" ? prevEvent.event.tag : event.type === "ddlUpdate" ? event.event.tag : "UNKNOWN";
590
633
  lc.info?.(`processing ${effectiveTag} command from ${msg.prefix}/${type}`, { event: summarizeReplicationEventForLog(event) });
591
- const changes = this.#makeSchemaChanges(lc, prevSchema, event, effectiveTag).map((change) => ["data", change]);
634
+ const changes = this.#makeSchemaChanges(lc, prevSchema, schema, event, effectiveTag).map((change) => ["data", change]);
592
635
  lc.info?.(`${changes.length} schema change(s)`, { changes });
593
- const replicaIdentities = replicaIdentitiesForTablesWithoutPrimaryKeys(event.schema);
636
+ const replicaIdentities = replicaIdentitiesForTablesWithoutPrimaryKeys(schema);
594
637
  if (replicaIdentities) this.#replicaIdentityTimer = setTimeout(async () => {
595
638
  try {
596
639
  await replicaIdentities.apply(lc, this.#db);
@@ -627,10 +670,10 @@ var ChangeMaker = class {
627
670
  * columns. This, for example, would be needed to properly support changing
628
671
  * the type of a column that's indexed.
629
672
  */
630
- #makeSchemaChanges(lc, preSchema, event, tag) {
673
+ #makeSchemaChanges(lc, preSchema, nextSchema, event, tag) {
631
674
  try {
632
675
  const [prevTbl, prevIdx] = specsByID(preSchema);
633
- const [nextTbl, nextIdx] = specsByID(event.schema);
676
+ const [nextTbl, nextIdx] = specsByID(nextSchema);
634
677
  const changes = [];
635
678
  for (const table of nextTbl.values()) validate(lc, table);
636
679
  const [droppedIdx, createdIdx] = symmetricDifferences(prevIdx, nextIdx);
@@ -903,7 +946,7 @@ function summarizeReplicationEventForLog(event) {
903
946
  const { previousSchema, ...eventWithoutSchemas } = rest;
904
947
  return {
905
948
  ...eventWithoutSchemas,
906
- schema: summarizeSchemaForLog(schema),
949
+ ...schema !== void 0 && { schema: summarizeSchemaForLog(schema) },
907
950
  ...previousSchema !== void 0 && { previousSchema: previousSchema === null ? null : summarizeSchemaForLog(previousSchema) }
908
951
  };
909
952
  }