@rocicorp/zero 1.2.0-canary.0 → 1.2.0-canary.10

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 (163) hide show
  1. package/out/ast-to-zql/src/format.d.ts.map +1 -1
  2. package/out/ast-to-zql/src/format.js +6 -6
  3. package/out/ast-to-zql/src/format.js.map +1 -1
  4. package/out/shared/src/bigint-json.d.ts.map +1 -1
  5. package/out/shared/src/bigint-json.js.map +1 -1
  6. package/out/shared/src/btree-set.d.ts.map +1 -1
  7. package/out/shared/src/btree-set.js +73 -41
  8. package/out/shared/src/btree-set.js.map +1 -1
  9. package/out/z2s/src/sql.d.ts +2 -2
  10. package/out/z2s/src/sql.d.ts.map +1 -1
  11. package/out/z2s/src/sql.js +2 -3
  12. package/out/z2s/src/sql.js.map +1 -1
  13. package/out/zero/package.js +6 -6
  14. package/out/zero/package.js.map +1 -1
  15. package/out/zero-cache/src/auth/load-permissions.d.ts +2 -2
  16. package/out/zero-cache/src/auth/load-permissions.d.ts.map +1 -1
  17. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  18. package/out/zero-cache/src/config/zero-config.d.ts +9 -1
  19. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  20. package/out/zero-cache/src/config/zero-config.js +21 -3
  21. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  22. package/out/zero-cache/src/custom/fetch.d.ts +1 -1
  23. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  24. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  25. package/out/zero-cache/src/custom-queries/transform-query.js +4 -1
  26. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  27. package/out/zero-cache/src/db/run-transaction.d.ts.map +1 -1
  28. package/out/zero-cache/src/db/run-transaction.js +2 -2
  29. package/out/zero-cache/src/db/run-transaction.js.map +1 -1
  30. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  31. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  32. package/out/zero-cache/src/observability/metrics.d.ts +1 -1
  33. package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
  34. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  35. package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -1
  36. package/out/zero-cache/src/server/anonymous-otel-start.js +6 -1
  37. package/out/zero-cache/src/server/anonymous-otel-start.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 +3 -1
  40. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  41. package/out/zero-cache/src/server/logging.d.ts.map +1 -1
  42. package/out/zero-cache/src/server/logging.js +9 -1
  43. package/out/zero-cache/src/server/logging.js.map +1 -1
  44. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  45. package/out/zero-cache/src/server/replicator.js +28 -1
  46. package/out/zero-cache/src/server/replicator.js.map +1 -1
  47. package/out/zero-cache/src/services/change-source/change-source.d.ts +1 -1
  48. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
  49. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/change-source/common/replica-schema.js +13 -1
  51. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  52. package/out/zero-cache/src/services/change-source/custom/change-source.js +4 -4
  53. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  54. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-source/pg/change-source.js +65 -22
  56. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  57. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +1 -1
  58. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +1 -1
  59. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  60. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +31 -1
  61. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  62. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +4 -4
  63. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  64. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +2 -3
  65. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  66. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +4 -0
  67. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  68. package/out/zero-cache/src/services/change-streamer/change-streamer.js +9 -1
  69. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  70. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  72. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  73. package/out/zero-cache/src/services/life-cycle.d.ts +1 -0
  74. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  75. package/out/zero-cache/src/services/life-cycle.js +2 -2
  76. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  77. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  78. package/out/zero-cache/src/services/litestream/commands.js +5 -5
  79. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  80. package/out/zero-cache/src/services/mutagen/pusher.d.ts +2 -2
  81. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  82. package/out/zero-cache/src/services/mutagen/pusher.js +7 -4
  83. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  84. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  85. package/out/zero-cache/src/services/replicator/incremental-sync.js +6 -3
  86. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  87. package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts +1 -1
  88. package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts.map +1 -1
  89. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  90. package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/replicator/schema/replication-state.js +6 -3
  92. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  93. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/view-syncer/cvr.js +8 -6
  95. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  96. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +1 -1
  97. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  99. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  100. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +13 -7
  101. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  102. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +1 -1
  103. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  104. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  105. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  106. package/out/zero-cache/src/services/view-syncer/view-syncer.js +32 -13
  107. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  108. package/out/zero-cache/src/types/pg.d.ts +1 -0
  109. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  110. package/out/zero-cache/src/types/pg.js +20 -9
  111. package/out/zero-cache/src/types/pg.js.map +1 -1
  112. package/out/zero-cache/src/workers/connection.js +2 -2
  113. package/out/zero-cache/src/workers/connection.js.map +1 -1
  114. package/out/zero-cache/src/workers/replicator.d.ts +5 -2
  115. package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
  116. package/out/zero-cache/src/workers/replicator.js +10 -6
  117. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  118. package/out/zero-client/src/client/version.js +1 -1
  119. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  120. package/out/zero-client/src/client/zero-poke-handler.js +6 -2
  121. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  122. package/out/zero-protocol/src/application-error.d.ts +1 -1
  123. package/out/zero-protocol/src/application-error.d.ts.map +1 -1
  124. package/out/zero-protocol/src/application-error.js.map +1 -1
  125. package/out/zero-types/src/name-mapper.d.ts +1 -0
  126. package/out/zero-types/src/name-mapper.d.ts.map +1 -1
  127. package/out/zero-types/src/name-mapper.js +3 -0
  128. package/out/zero-types/src/name-mapper.js.map +1 -1
  129. package/out/zql/src/builder/builder.d.ts.map +1 -1
  130. package/out/zql/src/builder/builder.js +5 -15
  131. package/out/zql/src/builder/builder.js.map +1 -1
  132. package/out/zql/src/ivm/memory-source.d.ts +1 -1
  133. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  134. package/out/zql/src/ivm/memory-source.js +2 -2
  135. package/out/zql/src/ivm/memory-source.js.map +1 -1
  136. package/out/zql/src/ivm/take.d.ts.map +1 -1
  137. package/out/zql/src/ivm/take.js +2 -2
  138. package/out/zql/src/ivm/take.js.map +1 -1
  139. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  140. package/out/zql/src/ivm/view-apply-change.js +34 -26
  141. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  142. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  143. package/out/zql/src/planner/planner-debug.js.map +1 -1
  144. package/out/zql/src/query/expression.d.ts +1 -1
  145. package/out/zql/src/query/expression.d.ts.map +1 -1
  146. package/out/zql/src/query/expression.js.map +1 -1
  147. package/out/zql/src/query/query.d.ts +1 -2
  148. package/out/zql/src/query/query.d.ts.map +1 -1
  149. package/out/zqlite/src/db.d.ts.map +1 -1
  150. package/out/zqlite/src/db.js +1 -1
  151. package/out/zqlite/src/db.js.map +1 -1
  152. package/out/zqlite/src/internal/sql.d.ts +2 -2
  153. package/out/zqlite/src/internal/sql.d.ts.map +1 -1
  154. package/out/zqlite/src/internal/sql.js +1 -2
  155. package/out/zqlite/src/internal/sql.js.map +1 -1
  156. package/out/zqlite/src/table-source.d.ts.map +1 -1
  157. package/out/zqlite/src/table-source.js +7 -11
  158. package/out/zqlite/src/table-source.js.map +1 -1
  159. package/package.json +6 -6
  160. package/out/zql/src/ivm/cap.d.ts +0 -32
  161. package/out/zql/src/ivm/cap.d.ts.map +0 -1
  162. package/out/zql/src/ivm/cap.js +0 -226
  163. package/out/zql/src/ivm/cap.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { populateFromExistingTables } from "../../replicator/schema/column-metadata.js";
2
2
  import { CREATE_RUNTIME_EVENTS_TABLE, recordEvent } from "../../replicator/schema/replication-state.js";
3
3
  import { listTables } from "../../../db/lite-tables.js";
4
- import { AutoResetSignal } from "../../change-streamer/schema/tables.js";
5
4
  import { runSchemaMigrations } from "../../../db/migration-lite.js";
5
+ import { AutoResetSignal } from "../../change-streamer/schema/tables.js";
6
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) {
@@ -123,6 +123,18 @@ var schemaVersionMigrationMap = {
123
123
  "metadata" = NULL;
124
124
  `);
125
125
  }
126
+ },
127
+ 13: {
128
+ migrateSchema: (_, db) => {
129
+ db.exec(`
130
+ ALTER TABLE "_zero.replicationState" ADD COLUMN writeTimeMs INTEGER;
131
+ `);
132
+ },
133
+ migrateData: (_, db) => {
134
+ db.exec(`
135
+ UPDATE "_zero.replicationState"
136
+ SET writeTimeMs = COALESCE(writeTimeMs, unixepoch('subsec') * 1000)`);
137
+ }
126
138
  }
127
139
  };
128
140
  Object.keys(schemaVersionMigrationMap).reduce((prev, curr) => Math.max(prev, parseInt(curr)), 0);
@@ -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 if (e instanceof SqliteError && e.code === 'SQLITE_CORRUPT') {\n throw new AutoResetSignal(e.message);\n }\n throw e;\n }\n}\n\nexport async function upgradeReplica(\n log: LogContext,\n debugName: string,\n dbPath: string,\n) {\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}\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\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,GAAG;EAChD,gBAAgB;EACjB;AAED,KAAI;AACF,QAAM,oBACJ,KACA,WACA,QACA,gBACA,0BACD;UACM,GAAG;AACV,MAAI,aAAa,eAAe,EAAE,SAAS,iBACzC,OAAM,IAAI,gBAAgB,EAAE,QAAQ;AAEtC,QAAM;;;AAIV,eAAsB,eACpB,KACA,WACA,QACA;AACA,OAAM,oBACJ,KACA,WACA,QAEA,EACE,qBAAqB;AACnB,QAAM,IAAI,MACR,yDACD;IAEJ,EACD,0BACD;;AAGH,IAAa,kCAA0C;;;;;;;;;;;;AAavD,IAAa,uBAA+B;;;;;;;;;;;AAY5C,IAAa,iCAAyC;;;;;;;;AAStD,IAAa,4BAAqD;CAEhE,GAAG;EACD,qBAAqB;AACnB,SAAM,IAAI,gBAAgB,kCAAkC;;EAE9D,gBAAgB;EACjB;CAED,GAAG;EACD,gBAAgB,GAAG,OAAO;AACxB,MAAG,KAAK,4BAA4B;;EAEtC,cAAc,GAAG,OAAO;AACtB,eAAY,IAAI,UAAU;;EAE7B;CAGD,GAAG,EAAE;CAEL,GAAG,EACD,gBAAgB,GAAG,OAAO;AAIxB,KAAG,KAAK,gCAAgC;AAExC,KAAG,KAAK,qBAAqB;IAEhC;CAED,GAAG;EACD,gBAAgB,GAAG,OAAO;AAOxB,OAAI,CANgB,GACjB,QACC,sFACD,CACA,KAAK,CAGN,IAAG,KAAK,gCAAgC;;EAG5C,cAAc,GAAG,OAAO;AAItB,MAAG,KAAa,sCAAsC;AAGtD,8BAA2B,IADZ,WAAW,IAAI,OAAO,MAAM,CACL;;EAEzC;CAED,GAAG,EACD,gBAAgB,GAAG,OAAO;AACxB,KAAG,KACO;;;;;UAKN,+BACH;IAEJ;CAED,IAAI,EACF,gBAAgB,GAAG,OAAO;AACxB,KAAG,KAAa;;;QAGd;IAEL;CAED,IAAI,EACF,gBAAgB,GAAG,OAAO;AACxB,KAAG,KAAa;;;;;;;;;;QAUd;IAEL;CAED,IAAI;EACF,gBAAgB,GAAG,OAAO;AAGxB,MAAG,KAAa;;;QAGd;;EAGJ,cAAc,GAAG,OAAO;AAEtB,MAAG,KAAa;;;;QAId;;EAEL;CACF;AAGqC,OAAO,KAC3C,0BACD,CAAC,QAAQ,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,CAAC,EAAE,EAAE"}
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 if (e instanceof SqliteError && e.code === 'SQLITE_CORRUPT') {\n throw new AutoResetSignal(e.message);\n }\n throw e;\n }\n}\n\nexport async function upgradeReplica(\n log: LogContext,\n debugName: string,\n dbPath: string,\n) {\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}\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,GAAG;EAChD,gBAAgB;EACjB;AAED,KAAI;AACF,QAAM,oBACJ,KACA,WACA,QACA,gBACA,0BACD;UACM,GAAG;AACV,MAAI,aAAa,eAAe,EAAE,SAAS,iBACzC,OAAM,IAAI,gBAAgB,EAAE,QAAQ;AAEtC,QAAM;;;AAIV,eAAsB,eACpB,KACA,WACA,QACA;AACA,OAAM,oBACJ,KACA,WACA,QAEA,EACE,qBAAqB;AACnB,QAAM,IAAI,MACR,yDACD;IAEJ,EACD,0BACD;;AAGH,IAAa,kCAA0C;;;;;;;;;;;;AAavD,IAAa,uBAA+B;;;;;;;;;;;AAY5C,IAAa,iCAAyC;;;;;;;;AAStD,IAAa,4BAAqD;CAEhE,GAAG;EACD,qBAAqB;AACnB,SAAM,IAAI,gBAAgB,kCAAkC;;EAE9D,gBAAgB;EACjB;CAED,GAAG;EACD,gBAAgB,GAAG,OAAO;AACxB,MAAG,KAAK,4BAA4B;;EAEtC,cAAc,GAAG,OAAO;AACtB,eAAY,IAAI,UAAU;;EAE7B;CAGD,GAAG,EAAE;CAEL,GAAG,EACD,gBAAgB,GAAG,OAAO;AAIxB,KAAG,KAAK,gCAAgC;AAExC,KAAG,KAAK,qBAAqB;IAEhC;CAED,GAAG;EACD,gBAAgB,GAAG,OAAO;AAOxB,OAAI,CANgB,GACjB,QACC,sFACD,CACA,KAAK,CAGN,IAAG,KAAK,gCAAgC;;EAG5C,cAAc,GAAG,OAAO;AAItB,MAAG,KAAa,sCAAsC;AAGtD,8BAA2B,IADZ,WAAW,IAAI,OAAO,MAAM,CACL;;EAEzC;CAED,GAAG,EACD,gBAAgB,GAAG,OAAO;AACxB,KAAG,KACO;;;;;UAKN,+BACH;IAEJ;CAED,IAAI,EACF,gBAAgB,GAAG,OAAO;AACxB,KAAG,KAAa;;;QAGd;IAEL;CAED,IAAI,EACF,gBAAgB,GAAG,OAAO;AACxB,KAAG,KAAa;;;;;;;;;;QAUd;IAEL;CAED,IAAI;EACF,gBAAgB,GAAG,OAAO;AAGxB,MAAG,KAAa;;;QAGd;;EAGJ,cAAc,GAAG,OAAO;AAEtB,MAAG,KAAa;;;;QAId;;EAEL;CAED,IAAI;EACF,gBAAgB,GAAG,OAAO;AACxB,MAAG,KAAa;;QAEd;;EAGJ,cAAc,GAAG,OAAO;AACtB,MAAG,KAAa;;+EAEyD;;EAE5E;CACF;AAGqC,OAAO,KAC3C,0BACD,CAAC,QAAQ,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,CAAC,EAAE,EAAE"}
@@ -7,12 +7,12 @@ import { Database } from "../../../../../zqlite/src/db.js";
7
7
  import { StatementRunner } from "../../../db/statements.js";
8
8
  import { createReplicationStateTables, getSubscriptionState, initReplicationState } from "../../replicator/schema/replication-state.js";
9
9
  import { computeZqlSpecs } from "../../../db/lite-tables.js";
10
- import { stream } from "../../../types/streams.js";
11
10
  import { AutoResetSignal } from "../../change-streamer/schema/tables.js";
11
+ import { initReplica } from "../common/replica-schema.js";
12
+ import { stream } from "../../../types/streams.js";
12
13
  import { ChangeProcessor } from "../../replicator/change-processor.js";
13
14
  import { ReplicationStatusPublisher } from "../../replicator/replication-status.js";
14
- import { initReplica } from "../common/replica-schema.js";
15
- import { WebSocket as WebSocket$1 } from "ws";
15
+ import { WebSocket } from "ws";
16
16
  //#region ../zero-cache/src/services/change-source/custom/change-source.ts
17
17
  /**
18
18
  * Initializes a Custom change source before streaming changes from the
@@ -66,7 +66,7 @@ var CustomChangeSource = class {
66
66
  url.searchParams.set("lastWatermark", clientWatermark);
67
67
  url.searchParams.set("replicaVersion", replicaVersion);
68
68
  }
69
- const ws = new WebSocket$1(url);
69
+ const ws = new WebSocket(url);
70
70
  const { instream, outstream } = stream(this.#lc, ws, changeStreamMessageSchema, { coalesce: (curr) => curr });
71
71
  return {
72
72
  changes: instream,
@@ -1 +1 @@
1
- {"version":3,"file":"change-source.js","names":["#lc","#upstreamUri","#shard","#replicationConfig","#startStream"],"sources":["../../../../../../../zero-cache/src/services/change-source/custom/change-source.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {WebSocket} from 'ws';\nimport {assert, unreachable} from '../../../../../shared/src/asserts.ts';\nimport {\n stringify,\n type JSONObject,\n} from '../../../../../shared/src/bigint-json.ts';\nimport {deepEqual} from '../../../../../shared/src/json.ts';\nimport type {SchemaValue} from '../../../../../zero-schema/src/table-schema.ts';\nimport {Database} from '../../../../../zqlite/src/db.ts';\nimport {computeZqlSpecs} from '../../../db/lite-tables.ts';\nimport {StatementRunner} from '../../../db/statements.ts';\nimport type {ShardConfig, ShardID} from '../../../types/shards.ts';\nimport {stream} from '../../../types/streams.ts';\nimport {\n AutoResetSignal,\n type ReplicationConfig,\n} from '../../change-streamer/schema/tables.ts';\nimport {ChangeProcessor} from '../../replicator/change-processor.ts';\nimport {ReplicationStatusPublisher} from '../../replicator/replication-status.ts';\nimport {\n createReplicationStateTables,\n getSubscriptionState,\n initReplicationState,\n type SubscriptionState,\n} from '../../replicator/schema/replication-state.ts';\nimport type {ChangeSource, ChangeStream} from '../change-source.ts';\nimport {initReplica} from '../common/replica-schema.ts';\nimport {changeStreamMessageSchema} from '../protocol/current/downstream.ts';\nimport {\n type BackfillRequest,\n type ChangeSourceUpstream,\n} from '../protocol/current/upstream.ts';\n\n/** Server context to store with the initial sync metadata for debugging. */\nexport type ServerContext = JSONObject;\n\n/**\n * Initializes a Custom change source before streaming changes from the\n * corresponding logical replication stream.\n */\nexport async function initializeCustomChangeSource(\n lc: LogContext,\n upstreamURI: string,\n shard: ShardConfig,\n replicaDbFile: string,\n context: ServerContext,\n): Promise<{subscriptionState: SubscriptionState; changeSource: ChangeSource}> {\n await initReplica(\n lc,\n `replica-${shard.appID}-${shard.shardNum}`,\n replicaDbFile,\n (log, tx) => initialSync(log, shard, tx, upstreamURI, context),\n );\n\n const replica = new Database(lc, replicaDbFile);\n const subscriptionState = getSubscriptionState(new StatementRunner(replica));\n replica.close();\n\n if (shard.publications.length) {\n // Verify that the publications match what has been synced.\n const requested = [...shard.publications].sort();\n const replicated = subscriptionState.publications.sort();\n if (!deepEqual(requested, replicated)) {\n throw new Error(\n `Invalid ShardConfig. Requested publications [${requested}] do not match synced publications: [${replicated}]`,\n );\n }\n }\n\n const changeSource = new CustomChangeSource(\n lc,\n upstreamURI,\n shard,\n subscriptionState,\n );\n\n return {subscriptionState, changeSource};\n}\n\nclass CustomChangeSource implements ChangeSource {\n readonly #lc: LogContext;\n readonly #upstreamUri: string;\n readonly #shard: ShardID;\n readonly #replicationConfig: ReplicationConfig;\n\n constructor(\n lc: LogContext,\n upstreamUri: string,\n shard: ShardID,\n replicationConfig: ReplicationConfig,\n ) {\n this.#lc = lc.withContext('component', 'change-source');\n this.#upstreamUri = upstreamUri;\n this.#shard = shard;\n this.#replicationConfig = replicationConfig;\n }\n\n initialSync(): ChangeStream {\n return this.#startStream();\n }\n\n startLagReporter() {\n return null; // Not supported for custom sources\n }\n\n startStream(\n clientWatermark: string,\n backfillRequests: BackfillRequest[] = [],\n ): Promise<ChangeStream> {\n if (backfillRequests?.length) {\n throw new Error(\n 'backfill is yet not supported for custom change sources',\n );\n }\n return Promise.resolve(this.#startStream(clientWatermark));\n }\n\n #startStream(clientWatermark?: string): ChangeStream {\n const {publications, replicaVersion} = this.#replicationConfig;\n const {appID, shardNum} = this.#shard;\n const url = new URL(this.#upstreamUri);\n url.searchParams.set('appID', appID);\n url.searchParams.set('shardNum', String(shardNum));\n for (const pub of publications) {\n url.searchParams.append('publications', pub);\n }\n if (clientWatermark) {\n assert(\n replicaVersion.length,\n 'replicaVersion is required when clientWatermark is set',\n );\n url.searchParams.set('lastWatermark', clientWatermark);\n url.searchParams.set('replicaVersion', replicaVersion);\n }\n\n const ws = new WebSocket(url);\n const {instream, outstream} = stream(\n this.#lc,\n ws,\n changeStreamMessageSchema,\n // Upstream acks coalesce. If upstream exhibits back-pressure,\n // only the last ACK is kept / buffered.\n {coalesce: (curr: ChangeSourceUpstream) => curr},\n );\n return {changes: instream, acks: outstream};\n }\n}\n\n/**\n * Initial sync for a custom change source makes a request to the\n * change source endpoint with no `replicaVersion` or `lastWatermark`.\n * The initial transaction returned by the endpoint is treated as\n * the initial sync, and the commit watermark of that transaction\n * becomes the `replicaVersion` of the initialized replica.\n *\n * Note that this is equivalent to how the LSN of the Postgres WAL\n * at initial sync time is the `replicaVersion` (and starting\n * version for all initially-synced rows).\n */\nexport async function initialSync(\n lc: LogContext,\n shard: ShardConfig,\n tx: Database,\n upstreamURI: string,\n context: ServerContext,\n) {\n const {appID: id, publications} = shard;\n const changeSource = new CustomChangeSource(lc, upstreamURI, shard, {\n replicaVersion: '', // ignored for initialSync()\n publications,\n });\n const {changes} = changeSource.initialSync();\n\n createReplicationStateTables(tx);\n const processor = new ChangeProcessor(\n new StatementRunner(tx),\n 'initial-sync',\n (_, err) => {\n throw err;\n },\n );\n\n const statusPublisher = ReplicationStatusPublisher.forRunningTransaction(tx);\n try {\n let num = 0;\n for await (const change of changes) {\n const [tag] = change;\n switch (tag) {\n case 'begin': {\n const {commitWatermark} = change[2];\n lc.info?.(\n `initial sync of shard ${id} at replicaVersion ${commitWatermark}`,\n );\n statusPublisher.publish(\n lc,\n 'Initializing',\n `Copying upstream tables at version ${commitWatermark}`,\n 5000,\n );\n initReplicationState(\n tx,\n [...publications].sort(),\n commitWatermark,\n context,\n false,\n );\n processor.processMessage(lc, change);\n break;\n }\n case 'data':\n processor.processMessage(lc, change);\n if (++num % 1000 === 0) {\n lc.debug?.(`processed ${num} changes`);\n }\n break;\n case 'commit':\n processor.processMessage(lc, change);\n validateInitiallySyncedData(lc, tx, shard);\n lc.info?.(`finished initial-sync of ${num} changes`);\n return;\n\n case 'status':\n break; // Ignored\n // @ts-expect-error: falls through if the tag is not 'reset-required\n case 'control': {\n const {tag, message} = change[1];\n if (tag === 'reset-required') {\n throw new AutoResetSignal(\n message ?? 'auto-reset signaled by change source',\n );\n }\n }\n // falls through\n case 'rollback':\n throw new Error(\n `unexpected message during initial-sync: ${stringify(change)}`,\n );\n default:\n unreachable(change);\n }\n }\n throw new Error(\n `change source ${upstreamURI} closed before initial-sync completed`,\n );\n } catch (e) {\n await statusPublisher.publishAndThrowError(lc, 'Initializing', e);\n } finally {\n statusPublisher.stop();\n }\n}\n\n// Verify that the upstream tables expected by the sync logic\n// have been properly initialized.\nfunction getRequiredTables({\n appID,\n shardNum,\n}: ShardID): Record<string, Record<string, SchemaValue>> {\n return {\n [`${appID}_${shardNum}.clients`]: {\n clientGroupID: {type: 'string'},\n clientID: {type: 'string'},\n lastMutationID: {type: 'number'},\n userID: {type: 'string'},\n },\n [`${appID}_${shardNum}.mutations`]: {\n clientGroupID: {type: 'string'},\n clientID: {type: 'string'},\n mutationID: {type: 'number'},\n mutation: {type: 'json'},\n },\n [`${appID}.permissions`]: {\n permissions: {type: 'json'},\n hash: {type: 'string'},\n },\n };\n}\n\nfunction validateInitiallySyncedData(\n lc: LogContext,\n db: Database,\n shard: ShardID,\n) {\n const tables = computeZqlSpecs(lc, db, {includeBackfillingColumns: true});\n const required = getRequiredTables(shard);\n for (const [name, columns] of Object.entries(required)) {\n const table = tables.get(name)?.zqlSpec;\n if (!table) {\n throw new Error(\n `Upstream is missing the \"${name}\" table. (Found ${[\n ...tables.keys(),\n ]})` +\n `Please ensure that each table has a unique index over one ` +\n `or more non-null columns.`,\n );\n }\n for (const [col, {type}] of Object.entries(columns)) {\n const found = table[col];\n if (!found) {\n throw new Error(\n `Upstream \"${table}\" table is missing the \"${col}\" column`,\n );\n }\n if (found.type !== type) {\n throw new Error(\n `Upstream \"${table}.${col}\" column is a ${found.type} type but must be a ${type} type.`,\n );\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"change-source.js","names":["#lc","#upstreamUri","#shard","#replicationConfig","#startStream"],"sources":["../../../../../../../zero-cache/src/services/change-source/custom/change-source.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {WebSocket} from 'ws';\nimport {assert, unreachable} from '../../../../../shared/src/asserts.ts';\nimport {\n stringify,\n type JSONObject,\n} from '../../../../../shared/src/bigint-json.ts';\nimport {deepEqual} from '../../../../../shared/src/json.ts';\nimport type {SchemaValue} from '../../../../../zero-schema/src/table-schema.ts';\nimport {Database} from '../../../../../zqlite/src/db.ts';\nimport {computeZqlSpecs} from '../../../db/lite-tables.ts';\nimport {StatementRunner} from '../../../db/statements.ts';\nimport type {ShardConfig, ShardID} from '../../../types/shards.ts';\nimport {stream} from '../../../types/streams.ts';\nimport {\n AutoResetSignal,\n type ReplicationConfig,\n} from '../../change-streamer/schema/tables.ts';\nimport {ChangeProcessor} from '../../replicator/change-processor.ts';\nimport {ReplicationStatusPublisher} from '../../replicator/replication-status.ts';\nimport {\n createReplicationStateTables,\n getSubscriptionState,\n initReplicationState,\n type SubscriptionState,\n} from '../../replicator/schema/replication-state.ts';\nimport type {ChangeSource, ChangeStream} from '../change-source.ts';\nimport {initReplica} from '../common/replica-schema.ts';\nimport {changeStreamMessageSchema} from '../protocol/current/downstream.ts';\nimport {\n type BackfillRequest,\n type ChangeSourceUpstream,\n} from '../protocol/current/upstream.ts';\n\n/** Server context to store with the initial sync metadata for debugging. */\nexport type ServerContext = JSONObject;\n\n/**\n * Initializes a Custom change source before streaming changes from the\n * corresponding logical replication stream.\n */\nexport async function initializeCustomChangeSource(\n lc: LogContext,\n upstreamURI: string,\n shard: ShardConfig,\n replicaDbFile: string,\n context: ServerContext,\n): Promise<{subscriptionState: SubscriptionState; changeSource: ChangeSource}> {\n await initReplica(\n lc,\n `replica-${shard.appID}-${shard.shardNum}`,\n replicaDbFile,\n (log, tx) => initialSync(log, shard, tx, upstreamURI, context),\n );\n\n const replica = new Database(lc, replicaDbFile);\n const subscriptionState = getSubscriptionState(new StatementRunner(replica));\n replica.close();\n\n if (shard.publications.length) {\n // Verify that the publications match what has been synced.\n const requested = [...shard.publications].sort();\n const replicated = subscriptionState.publications.sort();\n if (!deepEqual(requested, replicated)) {\n throw new Error(\n `Invalid ShardConfig. Requested publications [${requested}] do not match synced publications: [${replicated}]`,\n );\n }\n }\n\n const changeSource = new CustomChangeSource(\n lc,\n upstreamURI,\n shard,\n subscriptionState,\n );\n\n return {subscriptionState, changeSource};\n}\n\nclass CustomChangeSource implements ChangeSource {\n readonly #lc: LogContext;\n readonly #upstreamUri: string;\n readonly #shard: ShardID;\n readonly #replicationConfig: ReplicationConfig;\n\n constructor(\n lc: LogContext,\n upstreamUri: string,\n shard: ShardID,\n replicationConfig: ReplicationConfig,\n ) {\n this.#lc = lc.withContext('component', 'change-source');\n this.#upstreamUri = upstreamUri;\n this.#shard = shard;\n this.#replicationConfig = replicationConfig;\n }\n\n initialSync(): ChangeStream {\n return this.#startStream();\n }\n\n startLagReporter() {\n return null; // Not supported for custom sources\n }\n\n startStream(\n clientWatermark: string,\n backfillRequests: BackfillRequest[] = [],\n ): Promise<ChangeStream> {\n if (backfillRequests?.length) {\n throw new Error(\n 'backfill is yet not supported for custom change sources',\n );\n }\n return Promise.resolve(this.#startStream(clientWatermark));\n }\n\n #startStream(clientWatermark?: string): ChangeStream {\n const {publications, replicaVersion} = this.#replicationConfig;\n const {appID, shardNum} = this.#shard;\n const url = new URL(this.#upstreamUri);\n url.searchParams.set('appID', appID);\n url.searchParams.set('shardNum', String(shardNum));\n for (const pub of publications) {\n url.searchParams.append('publications', pub);\n }\n if (clientWatermark) {\n assert(\n replicaVersion.length,\n 'replicaVersion is required when clientWatermark is set',\n );\n url.searchParams.set('lastWatermark', clientWatermark);\n url.searchParams.set('replicaVersion', replicaVersion);\n }\n\n const ws = new WebSocket(url);\n const {instream, outstream} = stream(\n this.#lc,\n ws,\n changeStreamMessageSchema,\n // Upstream acks coalesce. If upstream exhibits back-pressure,\n // only the last ACK is kept / buffered.\n {coalesce: (curr: ChangeSourceUpstream) => curr},\n );\n return {changes: instream, acks: outstream};\n }\n}\n\n/**\n * Initial sync for a custom change source makes a request to the\n * change source endpoint with no `replicaVersion` or `lastWatermark`.\n * The initial transaction returned by the endpoint is treated as\n * the initial sync, and the commit watermark of that transaction\n * becomes the `replicaVersion` of the initialized replica.\n *\n * Note that this is equivalent to how the LSN of the Postgres WAL\n * at initial sync time is the `replicaVersion` (and starting\n * version for all initially-synced rows).\n */\nexport async function initialSync(\n lc: LogContext,\n shard: ShardConfig,\n tx: Database,\n upstreamURI: string,\n context: ServerContext,\n) {\n const {appID: id, publications} = shard;\n const changeSource = new CustomChangeSource(lc, upstreamURI, shard, {\n replicaVersion: '', // ignored for initialSync()\n publications,\n });\n const {changes} = changeSource.initialSync();\n\n createReplicationStateTables(tx);\n const processor = new ChangeProcessor(\n new StatementRunner(tx),\n 'initial-sync',\n (_, err) => {\n throw err;\n },\n );\n\n const statusPublisher = ReplicationStatusPublisher.forRunningTransaction(tx);\n try {\n let num = 0;\n for await (const change of changes) {\n const [tag] = change;\n switch (tag) {\n case 'begin': {\n const {commitWatermark} = change[2];\n lc.info?.(\n `initial sync of shard ${id} at replicaVersion ${commitWatermark}`,\n );\n statusPublisher.publish(\n lc,\n 'Initializing',\n `Copying upstream tables at version ${commitWatermark}`,\n 5000,\n );\n initReplicationState(\n tx,\n [...publications].sort(),\n commitWatermark,\n context,\n false,\n );\n processor.processMessage(lc, change);\n break;\n }\n case 'data':\n processor.processMessage(lc, change);\n if (++num % 1000 === 0) {\n lc.debug?.(`processed ${num} changes`);\n }\n break;\n case 'commit':\n processor.processMessage(lc, change);\n validateInitiallySyncedData(lc, tx, shard);\n lc.info?.(`finished initial-sync of ${num} changes`);\n return;\n\n case 'status':\n break; // Ignored\n // @ts-expect-error: falls through if the tag is not 'reset-required\n case 'control': {\n const {tag, message} = change[1];\n if (tag === 'reset-required') {\n throw new AutoResetSignal(\n message ?? 'auto-reset signaled by change source',\n );\n }\n }\n // falls through\n case 'rollback':\n throw new Error(\n `unexpected message during initial-sync: ${stringify(change)}`,\n );\n default:\n unreachable(change);\n }\n }\n throw new Error(\n `change source ${upstreamURI} closed before initial-sync completed`,\n );\n } catch (e) {\n await statusPublisher.publishAndThrowError(lc, 'Initializing', e);\n } finally {\n statusPublisher.stop();\n }\n}\n\n// Verify that the upstream tables expected by the sync logic\n// have been properly initialized.\nfunction getRequiredTables({\n appID,\n shardNum,\n}: ShardID): Record<string, Record<string, SchemaValue>> {\n return {\n [`${appID}_${shardNum}.clients`]: {\n clientGroupID: {type: 'string'},\n clientID: {type: 'string'},\n lastMutationID: {type: 'number'},\n userID: {type: 'string'},\n },\n [`${appID}_${shardNum}.mutations`]: {\n clientGroupID: {type: 'string'},\n clientID: {type: 'string'},\n mutationID: {type: 'number'},\n mutation: {type: 'json'},\n },\n [`${appID}.permissions`]: {\n permissions: {type: 'json'},\n hash: {type: 'string'},\n },\n };\n}\n\nfunction validateInitiallySyncedData(\n lc: LogContext,\n db: Database,\n shard: ShardID,\n) {\n const tables = computeZqlSpecs(lc, db, {includeBackfillingColumns: true});\n const required = getRequiredTables(shard);\n for (const [name, columns] of Object.entries(required)) {\n const table = tables.get(name)?.zqlSpec;\n if (!table) {\n throw new Error(\n `Upstream is missing the \"${name}\" table. (Found ${[\n ...tables.keys(),\n ]})` +\n `Please ensure that each table has a unique index over one ` +\n `or more non-null columns.`,\n );\n }\n for (const [col, {type}] of Object.entries(columns)) {\n const found = table[col];\n if (!found) {\n throw new Error(\n `Upstream \"${table}\" table is missing the \"${col}\" column`,\n );\n }\n if (found.type !== type) {\n throw new Error(\n `Upstream \"${table}.${col}\" column is a ${found.type} type but must be a ${type} type.`,\n );\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -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;AAejD,OAAO,KAAK,CAAC,MAAM,qCAAqC,CAAC;AAOzD,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAEhE,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;AA2BjD;;;;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,CAAC,EAAE,MAAM,GAC3B,OAAO,CAAC;IAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAC,CAAC,CAsC7E;AAqaD,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;AAitBxD,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":"AAKA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAejD,OAAO,KAAK,CAAC,MAAM,qCAAqC,CAAC;AAOzD,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAEhE,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;AA6BjD;;;;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,CAsC7E;AA+bD,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;AA8vBxD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,gBAAgB,WAwB3E"}
@@ -10,7 +10,7 @@ import { stringify } from "../../../../../shared/src/bigint-json.js";
10
10
  import { Database } from "../../../../../zqlite/src/db.js";
11
11
  import { upstreamSchema } from "../../../types/shards.js";
12
12
  import { StatementRunner } from "../../../db/statements.js";
13
- import { pgClient } from "../../../types/pg.js";
13
+ import { isPostgresError, pgClient } from "../../../types/pg.js";
14
14
  import { majorVersionFromString, majorVersionToString } from "../../../types/state-version.js";
15
15
  import { fromBigInt, toBigInt, toStateVersionString } from "./lsn.js";
16
16
  import { UnsupportedColumnDefaultError, mapPostgresToLiteColumn } from "../../../db/pg-to-lite.js";
@@ -30,14 +30,15 @@ import { streamBackfill } from "./backfill-stream.js";
30
30
  import { subscribe } from "./logical-replication/stream.js";
31
31
  import postgres from "postgres";
32
32
  import { nanoid } from "nanoid";
33
- import { PG_ADMIN_SHUTDOWN, PG_OBJECT_IN_USE } from "@drdgvhbh/postgres-error-codes";
33
+ import { PG_ADMIN_SHUTDOWN, PG_INSUFFICIENT_PRIVILEGE, PG_OBJECT_IN_USE } from "@drdgvhbh/postgres-error-codes";
34
34
  //#region ../zero-cache/src/services/change-source/pg/change-source.ts
35
+ var PG_17 = 17e4;
35
36
  /**
36
37
  * Initializes a Postgres change source, including the initial sync of the
37
38
  * replica, before streaming changes from the corresponding logical replication
38
39
  * stream.
39
40
  */
40
- async function initializePostgresChangeSource(lc, upstreamURI, shard, replicaDbFile, syncOptions, context, lagReportIntervalMs) {
41
+ async function initializePostgresChangeSource(lc, upstreamURI, shard, replicaDbFile, syncOptions, context, lagReportIntervalMs = 0) {
41
42
  await initReplica(lc, `replica-${shard.appID}-${shard.shardNum}`, replicaDbFile, (log, tx) => initialSync(log, shard, tx, upstreamURI, syncOptions, context));
42
43
  const replica = new Database(lc, replicaDbFile);
43
44
  const subscriptionState = getSubscriptionStateAndContext(new StatementRunner(replica));
@@ -46,7 +47,7 @@ async function initializePostgresChangeSource(lc, upstreamURI, shard, replicaDbF
46
47
  try {
47
48
  return {
48
49
  subscriptionState,
49
- changeSource: new PostgresChangeSource(lc, upstreamURI, shard, await checkAndUpdateUpstream(lc, db, shard, subscriptionState), context, lagReportIntervalMs ?? null)
50
+ changeSource: new PostgresChangeSource(lc, upstreamURI, shard, await checkAndUpdateUpstream(lc, db, shard, subscriptionState), context, lagReportIntervalMs)
50
51
  };
51
52
  } finally {
52
53
  await db.end();
@@ -100,10 +101,18 @@ var PostgresChangeSource = class {
100
101
  this.#shard = shard;
101
102
  this.#replica = replica;
102
103
  this.#context = context;
103
- this.#lagReporter = lagReportIntervalMs ? new LagReporter(lc.withContext("component", "lag-reporter"), shard, this.#db, lagReportIntervalMs) : null;
104
+ this.#lagReporter = lagReportIntervalMs > 0 ? new LagReporter(lc.withContext("component", "lag-reporter"), shard, this.#db, lagReportIntervalMs) : null;
104
105
  }
105
- startLagReporter() {
106
- return this.#lagReporter ? this.#lagReporter.initiateLagReport(true) : null;
106
+ async startLagReporter() {
107
+ if (this.#lagReporter) try {
108
+ return await this.#lagReporter.initiateLagReport(true);
109
+ } catch (e) {
110
+ if (isPostgresError(e, PG_INSUFFICIENT_PRIVILEGE)) {
111
+ const functionName = (this.#lagReporter.pgVersion ?? 0) >= PG_17 ? "pg_logical_emit_message(boolean, text, text, boolean)" : "pg_logical_emit_message(boolean, text, text)";
112
+ this.#lc.warn?.("\n\nUnable to initiate replication lag reports due to insufficient privileges.\nTo enable replication lag reporting, run:", `\n\tGRANT EXECUTE ON FUNCTION ${functionName} TO <your_db_user>;\n\n`, e);
113
+ } else this.#lc.error?.(`Unexpected error while initiating lag reports. Lag reports will be disabled.`, e);
114
+ }
115
+ return null;
107
116
  }
108
117
  async startStream(clientWatermark, backfillRequests = []) {
109
118
  const { slot } = this.#replica;
@@ -127,10 +136,11 @@ var PostgresChangeSource = class {
127
136
  */
128
137
  const isTransactionalMessage = (lsn, msg) => {
129
138
  if (msg.tag === "message" && msg.prefix === this.#lagReporter?.messagePrefix) {
130
- changes.pushStatus(this.#lagReporter.processLagReport(msg));
139
+ changes.pushStatus(this.#lagReporter.processLagReportMessage(msg));
131
140
  return false;
132
141
  }
133
- this.#lagReporter?.checkCurrentLSN(lsn);
142
+ const status = this.#lagReporter?.checkCurrentLSN(lsn);
143
+ if (status) changes.pushStatus(status);
134
144
  if (msg.tag === "keepalive") {
135
145
  changes.pushStatus([
136
146
  "status",
@@ -328,50 +338,80 @@ var LagReporter = class LagReporter {
328
338
  }
329
339
  return this.#pgVersion;
330
340
  }
341
+ get pgVersion() {
342
+ return this.#pgVersion;
343
+ }
331
344
  async initiateLagReport(log = false) {
332
345
  const pgVersion = this.#pgVersion ?? await this.#getPgVersion();
333
346
  const now = Date.now();
334
347
  const id = nanoid();
335
348
  const lagReport = {
336
349
  id,
350
+ sendTimeMs: now,
351
+ commitTimeMs: now,
337
352
  lsn: 0n
338
353
  };
339
354
  this.#expectingLagReport = lagReport;
355
+ let commitTimeMs;
340
356
  let lsn;
341
- if (pgVersion >= 17e4) [{lsn}] = await this.#db`
342
- SELECT pg_logical_emit_message(
357
+ if (pgVersion >= PG_17) [{commitTimeMs, lsn}] = await this.#db`
358
+ WITH CTE AS (SELECT extract(epoch from now()) * 1000 AS "commitTimeMs")
359
+ SELECT "commitTimeMs", pg_logical_emit_message(
343
360
  false,
344
361
  ${this.messagePrefix},
345
362
  json_build_object(
346
363
  'id', ${id}::text,
347
364
  'sendTimeMs', ${now}::int8,
348
- 'commitTimeMs', extract(epoch from now()) * 1000
365
+ 'commitTimeMs', "commitTimeMs"
349
366
  )::text,
350
367
  true
351
- ) as lsn;
368
+ ) as lsn FROM CTE;
352
369
  `;
353
- else [{lsn}] = await this.#db`
354
- SELECT pg_logical_emit_message(
370
+ else [{commitTimeMs, lsn}] = await this.#db`
371
+ WITH CTE AS (SELECT extract(epoch from now()) * 1000 as "commitTimeMs")
372
+ SELECT "commitTimeMs", pg_logical_emit_message(
355
373
  false,
356
374
  ${this.messagePrefix},
357
375
  json_build_object(
358
376
  'id', ${id}::text,
359
377
  'sendTimeMs', ${now}::int8,
360
- 'commitTimeMs', extract(epoch from now()) * 1000
378
+ 'commitTimeMs', "commitTimeMs"
361
379
  )::text
362
- ) as lsn;
380
+ ) as lsn FROM CTE;
363
381
  `;
364
382
  lagReport.lsn = toBigInt(lsn);
383
+ lagReport.commitTimeMs = commitTimeMs;
365
384
  if (log) this.#lc.info?.(`initiated lag report at lsn ${lsn}`, {
366
385
  id,
367
- lsn
386
+ lsn,
387
+ sendTimeMs: now,
388
+ commitTimeMs
368
389
  });
369
390
  return { nextSendTimeMs: now };
370
391
  }
392
+ /**
393
+ * In Postgres < 17, the pg_logical_emit_message lacks an immediate "flush"
394
+ * option, which can cause messages to be missed when the replication stream
395
+ * starts up:
396
+ *
397
+ * ```
398
+ * * emit message → WAL write (buffered, not flushed)
399
+ * * walsender reads up to current flush LSN
400
+ * * emitted message's LSN is beyond flush LSN → not yet visible
401
+ * * stream feedback/acknowledgment advances slot
402
+ * * WAL eventually flushes → but slot has already moved past it
403
+ * ```
404
+ *
405
+ * This has been seen to happen for the initial `wal_writer_delay` interval
406
+ * of a replication session.
407
+ *
408
+ * To account for this, the last emitted lag report is considered "received"
409
+ * if the stream has advanced beyond the LSN of the report.
410
+ */
371
411
  checkCurrentLSN(lsn) {
372
412
  if (this.#expectingLagReport?.lsn && lsn > this.#expectingLagReport.lsn) {
373
- this.#lc.warn?.(`LSN ${fromBigInt(lsn)} is passed expected lag report ${fromBigInt(this.#expectingLagReport.lsn)}. Initiating new report.`);
374
- this.#scheduleNextReport(0);
413
+ this.#lc.info?.(`LSN ${fromBigInt(lsn)} is passed expected lag report ${fromBigInt(this.#expectingLagReport.lsn)}. Processing it as received.`);
414
+ return this.#processLagReport(this.#expectingLagReport, majorVersionToString(lsn));
375
415
  }
376
416
  }
377
417
  #scheduleNextReport(delayMs) {
@@ -386,9 +426,12 @@ var LagReporter = class LagReporter {
386
426
  }
387
427
  }, delayMs);
388
428
  }
389
- processLagReport(msg) {
429
+ processLagReportMessage(msg) {
390
430
  assert(msg.prefix === this.messagePrefix, `unexpected message prefix: ${msg.prefix}`);
391
431
  const report = parseLogicalMessageContent(msg, lagReportSchema);
432
+ return this.#processLagReport(report, toStateVersionString(msg.messageLsn ?? "0/0"));
433
+ }
434
+ #processLagReport(report, watermark) {
392
435
  const now = Date.now();
393
436
  const nextSendTimeMs = Math.max(now, report.sendTimeMs + this.#lagIntervalMs);
394
437
  if (report.id === this.#expectingLagReport?.id) this.#scheduleNextReport(nextSendTimeMs - now);
@@ -407,7 +450,7 @@ var LagReporter = class LagReporter {
407
450
  nextSendTimeMs
408
451
  }
409
452
  },
410
- { watermark: toStateVersionString(msg.messageLsn ?? "0/0") }
453
+ { watermark }
411
454
  ];
412
455
  }
413
456
  };