@rocicorp/zero 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/out/_virtual/{_@oxc-project_runtime@0.115.0 → _@oxc-project_runtime@0.122.0}/helpers/usingCtx.js +1 -1
  2. package/out/analyze-query/src/bin-analyze.js +19 -7
  3. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  4. package/out/replicache/src/mutation-recovery.js +0 -3
  5. package/out/zero/package.js +7 -6
  6. package/out/zero/package.js.map +1 -1
  7. package/out/zero-cache/src/config/zero-config.d.ts +6 -0
  8. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  9. package/out/zero-cache/src/config/zero-config.js +12 -0
  10. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  11. package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -1
  12. package/out/zero-cache/src/server/anonymous-otel-start.js +1 -14
  13. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  14. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  15. package/out/zero-cache/src/server/change-streamer.js +2 -2
  16. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  17. package/out/zero-cache/src/services/analyze.js +2 -2
  18. package/out/zero-cache/src/services/change-source/change-source.d.ts +7 -0
  19. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
  20. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
  21. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +1 -1
  22. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  23. package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
  24. package/out/zero-cache/src/services/change-source/custom/change-source.js +3 -0
  25. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  26. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +9 -1
  27. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  28. package/out/zero-cache/src/services/change-source/pg/change-source.js +172 -46
  29. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  30. package/out/zero-cache/src/services/change-source/pg/lsn.js +1 -1
  31. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +8 -0
  32. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  33. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +26 -1
  34. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
  35. package/out/zero-cache/src/services/change-source/protocol/current/status.js +7 -2
  36. package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
  37. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +8 -0
  38. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  39. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  40. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +10 -2
  41. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  42. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +25 -0
  43. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/change-streamer/change-streamer.js +8 -1
  45. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +2 -0
  47. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-streamer/forwarder.js +3 -0
  49. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +3 -2
  51. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  52. package/out/zero-cache/src/services/change-streamer/subscriber.js +17 -8
  53. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  54. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/life-cycle.js +6 -2
  56. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  57. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +2 -2
  58. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/replicator/incremental-sync.js +19 -4
  60. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  61. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  62. package/out/zero-cache/src/services/replicator/replicator.js +2 -2
  63. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  64. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +12 -0
  65. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -0
  66. package/out/zero-cache/src/services/replicator/reporter/recorder.js +58 -0
  67. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -0
  68. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +35 -0
  69. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts.map +1 -0
  70. package/out/zero-cache/src/services/replicator/reporter/report-schema.js +20 -0
  71. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -0
  72. package/out/zero-cache/src/services/run-ast.js +1 -1
  73. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  74. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  75. package/out/zero-cache/src/types/pg.js +2 -0
  76. package/out/zero-cache/src/types/pg.js.map +1 -1
  77. package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
  78. package/out/zero-cache/src/workers/replicator.js +1 -0
  79. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  80. package/out/zero-client/src/client/version.js +1 -1
  81. package/out/zql/src/builder/builder.d.ts.map +1 -1
  82. package/out/zql/src/builder/builder.js +15 -5
  83. package/out/zql/src/builder/builder.js.map +1 -1
  84. package/out/zql/src/ivm/cap.d.ts +32 -0
  85. package/out/zql/src/ivm/cap.d.ts.map +1 -0
  86. package/out/zql/src/ivm/cap.js +226 -0
  87. package/out/zql/src/ivm/cap.js.map +1 -0
  88. package/out/zql/src/ivm/join-utils.d.ts +2 -0
  89. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  90. package/out/zql/src/ivm/join-utils.js +35 -1
  91. package/out/zql/src/ivm/join-utils.js.map +1 -1
  92. package/out/zql/src/ivm/join.d.ts.map +1 -1
  93. package/out/zql/src/ivm/join.js +6 -2
  94. package/out/zql/src/ivm/join.js.map +1 -1
  95. package/out/zql/src/ivm/memory-source.d.ts +15 -2
  96. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  97. package/out/zql/src/ivm/memory-source.js +69 -8
  98. package/out/zql/src/ivm/memory-source.js.map +1 -1
  99. package/out/zql/src/ivm/schema.d.ts +1 -1
  100. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  101. package/out/zql/src/ivm/skip.d.ts.map +1 -1
  102. package/out/zql/src/ivm/skip.js +3 -0
  103. package/out/zql/src/ivm/skip.js.map +1 -1
  104. package/out/zql/src/ivm/source.d.ts +1 -1
  105. package/out/zql/src/ivm/source.d.ts.map +1 -1
  106. package/out/zql/src/ivm/take.d.ts +4 -1
  107. package/out/zql/src/ivm/take.d.ts.map +1 -1
  108. package/out/zql/src/ivm/take.js +4 -2
  109. package/out/zql/src/ivm/take.js.map +1 -1
  110. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  111. package/out/zql/src/ivm/union-fan-in.js +1 -0
  112. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  113. package/out/zqlite/src/query-builder.d.ts +1 -1
  114. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  115. package/out/zqlite/src/query-builder.js +7 -2
  116. package/out/zqlite/src/query-builder.js.map +1 -1
  117. package/out/zqlite/src/table-source.d.ts +1 -1
  118. package/out/zqlite/src/table-source.d.ts.map +1 -1
  119. package/out/zqlite/src/table-source.js +15 -10
  120. package/out/zqlite/src/table-source.js.map +1 -1
  121. package/package.json +7 -6
  122. package/out/analyze-query/src/run-ast.d.ts +0 -22
  123. package/out/analyze-query/src/run-ast.d.ts.map +0 -1
  124. package/out/analyze-query/src/run-ast.js +0 -75
  125. package/out/analyze-query/src/run-ast.js.map +0 -1
  126. package/out/replicache/src/mutation-recovery.js.map +0 -1
@@ -1,8 +1,8 @@
1
+ import { assert } from "../../../../../shared/src/asserts.js";
1
2
  import { deepEqual } from "../../../../../shared/src/json.js";
2
- import { promiseVoid } from "../../../../../shared/src/resolved-promises.js";
3
3
  import { AbortError } from "../../../../../shared/src/abort-error.js";
4
4
  import { sleep } from "../../../../../shared/src/sleep.js";
5
- import { parse } from "../../../../../shared/src/valita.js";
5
+ import { parse, valita_exports } from "../../../../../shared/src/valita.js";
6
6
  import { must } from "../../../../../shared/src/must.js";
7
7
  import { mapValues } from "../../../../../shared/src/objects.js";
8
8
  import { equals, intersection, symmetricDifferences } from "../../../../../shared/src/set-utils.js";
@@ -12,7 +12,7 @@ import { upstreamSchema } from "../../../types/shards.js";
12
12
  import { StatementRunner } from "../../../db/statements.js";
13
13
  import { pgClient } from "../../../types/pg.js";
14
14
  import { majorVersionFromString, majorVersionToString } from "../../../types/state-version.js";
15
- import { fromBigInt, toStateVersionString } from "./lsn.js";
15
+ import { fromBigInt, toBigInt, toStateVersionString } from "./lsn.js";
16
16
  import { UnsupportedColumnDefaultError, mapPostgresToLiteColumn } from "../../../db/pg-to-lite.js";
17
17
  import { getSubscriptionStateAndContext } from "../../replicator/schema/replication-state.js";
18
18
  import { runTx } from "../../../db/run-transaction.js";
@@ -29,6 +29,7 @@ import { initialSync } from "./initial-sync.js";
29
29
  import { streamBackfill } from "./backfill-stream.js";
30
30
  import { subscribe } from "./logical-replication/stream.js";
31
31
  import postgres from "postgres";
32
+ import { nanoid } from "nanoid";
32
33
  import { PG_ADMIN_SHUTDOWN, PG_OBJECT_IN_USE } from "@drdgvhbh/postgres-error-codes";
33
34
  //#region ../zero-cache/src/services/change-source/pg/change-source.ts
34
35
  /**
@@ -36,7 +37,7 @@ import { PG_ADMIN_SHUTDOWN, PG_OBJECT_IN_USE } from "@drdgvhbh/postgres-error-co
36
37
  * replica, before streaming changes from the corresponding logical replication
37
38
  * stream.
38
39
  */
39
- async function initializePostgresChangeSource(lc, upstreamURI, shard, replicaDbFile, syncOptions, context) {
40
+ async function initializePostgresChangeSource(lc, upstreamURI, shard, replicaDbFile, syncOptions, context, lagReportIntervalMs) {
40
41
  await initReplica(lc, `replica-${shard.appID}-${shard.shardNum}`, replicaDbFile, (log, tx) => initialSync(log, shard, tx, upstreamURI, syncOptions, context));
41
42
  const replica = new Database(lc, replicaDbFile);
42
43
  const subscriptionState = getSubscriptionStateAndContext(new StatementRunner(replica));
@@ -45,7 +46,7 @@ async function initializePostgresChangeSource(lc, upstreamURI, shard, replicaDbF
45
46
  try {
46
47
  return {
47
48
  subscriptionState,
48
- changeSource: new PostgresChangeSource(lc, upstreamURI, shard, await checkAndUpdateUpstream(lc, db, shard, subscriptionState), context)
49
+ changeSource: new PostgresChangeSource(lc, upstreamURI, shard, await checkAndUpdateUpstream(lc, db, shard, subscriptionState), context, lagReportIntervalMs ?? null)
49
50
  };
50
51
  } finally {
51
52
  await db.end();
@@ -83,50 +84,69 @@ var MAX_LOW_PRIORITY_DELAY_MS = 1e3;
83
84
  */
84
85
  var PostgresChangeSource = class {
85
86
  #lc;
87
+ #db;
86
88
  #upstreamUri;
87
89
  #shard;
88
90
  #replica;
89
91
  #context;
90
- constructor(lc, upstreamUri, shard, replica, context) {
92
+ #lagReporter;
93
+ constructor(lc, upstreamUri, shard, replica, context, lagReportIntervalMs) {
91
94
  this.#lc = lc.withContext("component", "change-source");
95
+ this.#db = pgClient(lc, upstreamUri, {
96
+ ["idle_timeout"]: 60,
97
+ connection: { ["application_name"]: "zero-replication-monitor" }
98
+ });
92
99
  this.#upstreamUri = upstreamUri;
93
100
  this.#shard = shard;
94
101
  this.#replica = replica;
95
102
  this.#context = context;
103
+ this.#lagReporter = lagReportIntervalMs ? new LagReporter(lc.withContext("component", "lag-reporter"), shard, this.#db, lagReportIntervalMs) : null;
104
+ }
105
+ startLagReporter() {
106
+ return this.#lagReporter ? this.#lagReporter.initiateLagReport(true) : null;
96
107
  }
97
108
  async startStream(clientWatermark, backfillRequests = []) {
98
- const db = pgClient(this.#lc, this.#upstreamUri);
99
109
  const { slot } = this.#replica;
100
- let cleanup = promiseVoid;
101
- try {
102
- ({cleanup} = await this.#stopExistingReplicationSlotSubscribers(db, slot));
103
- const config = await getInternalShardConfig(db, this.#shard);
104
- this.#lc.info?.(`starting replication stream@${slot}`);
105
- return await this.#startStream(db, slot, clientWatermark, config, backfillRequests);
106
- } finally {
107
- cleanup.then(() => db.end());
108
- }
110
+ await this.#stopExistingReplicationSlotSubscribers(slot);
111
+ const config = await getInternalShardConfig(this.#db, this.#shard);
112
+ this.#lc.info?.(`starting replication stream@${slot}`);
113
+ return this.#startStream(slot, clientWatermark, config, backfillRequests);
109
114
  }
110
- async #startStream(db, slot, clientWatermark, shardConfig, backfillRequests) {
115
+ async #startStream(slot, clientWatermark, shardConfig, backfillRequests) {
111
116
  const clientStart = majorVersionFromString(clientWatermark) + 1n;
112
- const { messages, acks } = await subscribe(this.#lc, db, slot, [...shardConfig.publications], clientStart);
117
+ const { messages, acks } = await subscribe(this.#lc, this.#db, slot, [...shardConfig.publications], clientStart);
113
118
  const acker = new Acker(acks);
114
119
  const changes = new ChangeStreamMultiplexer(this.#lc, clientWatermark);
115
120
  const backfillManager = new BackfillManager(this.#lc, changes, (req) => streamBackfill(this.#lc, this.#upstreamUri, this.#replica, req));
116
121
  changes.addProducers(messages, backfillManager).addListeners(backfillManager, acker);
117
122
  backfillManager.run(clientWatermark, backfillRequests);
118
- const changeMaker = new ChangeMaker(this.#lc, this.#shard, shardConfig, this.#replica.initialSchema, this.#upstreamUri);
123
+ const changeMaker = new ChangeMaker(this.#lc, this.#shard, shardConfig, this.#db, this.#replica.initialSchema);
124
+ /**
125
+ * Determines if the incoming message is transactional, otherwise handling
126
+ * non-transactional messages with a downstream status message.
127
+ */
128
+ const isTransactionalMessage = (lsn, msg) => {
129
+ if (msg.tag === "message" && msg.prefix === this.#lagReporter?.messagePrefix) {
130
+ changes.pushStatus(this.#lagReporter.processLagReport(msg));
131
+ return false;
132
+ }
133
+ this.#lagReporter?.checkCurrentLSN(lsn);
134
+ if (msg.tag === "keepalive") {
135
+ changes.pushStatus([
136
+ "status",
137
+ { ack: msg.shouldRespond },
138
+ { watermark: majorVersionToString(lsn) }
139
+ ]);
140
+ return false;
141
+ }
142
+ return true;
143
+ };
119
144
  (async () => {
120
145
  try {
121
146
  let reservation = null;
122
147
  let inTransaction = false;
123
148
  for await (const [lsn, msg] of messages) {
124
- if (msg.tag === "keepalive") {
125
- changes.pushStatus([
126
- "status",
127
- { ack: msg.shouldRespond },
128
- { watermark: majorVersionToString(lsn) }
129
- ]);
149
+ if (!isTransactionalMessage(lsn, msg)) {
130
150
  if (!inTransaction && reservation?.lastWatermark) {
131
151
  changes.release(reservation.lastWatermark);
132
152
  reservation = null;
@@ -170,14 +190,11 @@ var PostgresChangeSource = class {
170
190
  };
171
191
  }
172
192
  async #logCurrentReplicaInfo() {
173
- const db = pgClient(this.#lc, this.#upstreamUri);
174
193
  try {
175
- const replica = await getReplicaAtVersion(this.#lc, db, this.#shard, this.#replica.version);
194
+ const replica = await getReplicaAtVersion(this.#lc, this.#db, this.#shard, this.#replica.version);
176
195
  if (replica) this.#lc.info?.(`Shutdown signal from replica@${this.#replica.version}: ${stringify(replica.subscriberContext)}`);
177
196
  } catch (e) {
178
197
  this.#lc.warn?.(`error logging replica info`, e);
179
- } finally {
180
- await db.end();
181
198
  }
182
199
  }
183
200
  /**
@@ -189,10 +206,10 @@ var PostgresChangeSource = class {
189
206
  * the timestamp suffix) are preserved, as those are newly syncing replicas
190
207
  * that will soon take over the slot.
191
208
  */
192
- async #stopExistingReplicationSlotSubscribers(db, slotToKeep) {
209
+ async #stopExistingReplicationSlotSubscribers(slotToKeep) {
193
210
  const slotExpression = replicationSlotExpression(this.#shard);
194
211
  const legacySlotName = legacyReplicationSlot(this.#shard);
195
- const result = await runTx(db, async (sql) => {
212
+ const result = await runTx(this.#db, async (sql) => {
196
213
  const result = await sql`
197
214
  SELECT slot_name as slot, pg_terminate_backend(active_pid) as terminated, active_pid as pid
198
215
  FROM pg_replication_slots
@@ -230,10 +247,11 @@ var PostgresChangeSource = class {
230
247
  const pids = result.filter(({ pid }) => pid !== null).map(({ pid }) => pid);
231
248
  if (pids.length) this.#lc.info?.(`signaled subscriber ${pids} to shut down`);
232
249
  const otherSlots = result.filter(({ slot }) => slot !== slotToKeep).map(({ slot }) => slot);
233
- return { cleanup: otherSlots.length ? this.#dropReplicationSlots(db, otherSlots) : promiseVoid };
250
+ if (otherSlots.length) this.#dropReplicationSlots(otherSlots).catch((e) => this.#lc.warn?.(`error dropping replication slots`, e));
234
251
  }
235
- async #dropReplicationSlots(sql, slots) {
252
+ async #dropReplicationSlots(slots) {
236
253
  this.#lc.info?.(`dropping other replication slot(s) ${slots}`);
254
+ const sql = this.#db;
237
255
  for (let i = 0; i < 5; i++) try {
238
256
  await sql`
239
257
  SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots
@@ -282,24 +300,132 @@ var Acker = class {
282
300
  this.#acks.push(lsn);
283
301
  }
284
302
  };
303
+ var lagReportSchema = valita_exports.object({
304
+ id: valita_exports.string(),
305
+ sendTimeMs: valita_exports.number(),
306
+ commitTimeMs: valita_exports.number()
307
+ });
308
+ var LagReporter = class LagReporter {
309
+ static MESSAGE_SUFFIX = "/lag-report/v1";
310
+ #lc;
311
+ messagePrefix;
312
+ #db;
313
+ #lagIntervalMs;
314
+ #pgVersion;
315
+ #expectingLagReport = null;
316
+ #timer;
317
+ constructor(lc, shard, db, lagIntervalMs) {
318
+ this.#lc = lc;
319
+ this.messagePrefix = `${shard.appID}/${shard.shardNum}${LagReporter.MESSAGE_SUFFIX}`;
320
+ this.#db = db;
321
+ this.#lagIntervalMs = lagIntervalMs;
322
+ }
323
+ async #getPgVersion() {
324
+ if (this.#pgVersion === void 0) {
325
+ const [{ pgVersion }] = await this.#db`
326
+ SELECT current_setting('server_version_num')::int as "pgVersion"`;
327
+ this.#pgVersion = pgVersion;
328
+ }
329
+ return this.#pgVersion;
330
+ }
331
+ async initiateLagReport(log = false) {
332
+ const pgVersion = this.#pgVersion ?? await this.#getPgVersion();
333
+ const now = Date.now();
334
+ const id = nanoid();
335
+ const lagReport = {
336
+ id,
337
+ lsn: 0n
338
+ };
339
+ this.#expectingLagReport = lagReport;
340
+ let lsn;
341
+ if (pgVersion >= 17e4) [{lsn}] = await this.#db`
342
+ SELECT pg_logical_emit_message(
343
+ false,
344
+ ${this.messagePrefix},
345
+ json_build_object(
346
+ 'id', ${id}::text,
347
+ 'sendTimeMs', ${now}::int8,
348
+ 'commitTimeMs', extract(epoch from now()) * 1000
349
+ )::text,
350
+ true
351
+ ) as lsn;
352
+ `;
353
+ else [{lsn}] = await this.#db`
354
+ SELECT pg_logical_emit_message(
355
+ false,
356
+ ${this.messagePrefix},
357
+ json_build_object(
358
+ 'id', ${id}::text,
359
+ 'sendTimeMs', ${now}::int8,
360
+ 'commitTimeMs', extract(epoch from now()) * 1000
361
+ )::text
362
+ ) as lsn;
363
+ `;
364
+ lagReport.lsn = toBigInt(lsn);
365
+ if (log) this.#lc.info?.(`initiated lag report at lsn ${lsn}`, {
366
+ id,
367
+ lsn
368
+ });
369
+ return { nextSendTimeMs: now };
370
+ }
371
+ checkCurrentLSN(lsn) {
372
+ 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);
375
+ }
376
+ }
377
+ #scheduleNextReport(delayMs) {
378
+ this.#expectingLagReport = null;
379
+ clearTimeout(this.#timer);
380
+ this.#timer = setTimeout(async () => {
381
+ try {
382
+ await this.initiateLagReport();
383
+ } catch (e) {
384
+ this.#lc.warn?.(`error initiating lag report`, e);
385
+ this.#scheduleNextReport(this.#lagIntervalMs);
386
+ }
387
+ }, delayMs);
388
+ }
389
+ processLagReport(msg) {
390
+ assert(msg.prefix === this.messagePrefix, `unexpected message prefix: ${msg.prefix}`);
391
+ const report = parseLogicalMessageContent(msg, lagReportSchema);
392
+ const now = Date.now();
393
+ const nextSendTimeMs = Math.max(now, report.sendTimeMs + this.#lagIntervalMs);
394
+ if (report.id === this.#expectingLagReport?.id) this.#scheduleNextReport(nextSendTimeMs - now);
395
+ else this.#lc.debug?.(`received extraneous lag report`, { report });
396
+ const { sendTimeMs, commitTimeMs } = report;
397
+ return [
398
+ "status",
399
+ {
400
+ ack: false,
401
+ lagReport: {
402
+ lastTimings: {
403
+ sendTimeMs,
404
+ commitTimeMs,
405
+ receiveTimeMs: now
406
+ },
407
+ nextSendTimeMs
408
+ }
409
+ },
410
+ { watermark: toStateVersionString(msg.messageLsn ?? "0/0") }
411
+ ];
412
+ }
413
+ };
285
414
  var SET_REPLICA_IDENTITY_DELAY_MS = 50;
286
415
  var ChangeMaker = class {
287
416
  #lc;
288
417
  #shardPrefix;
289
418
  #shardConfig;
290
419
  #initialSchema;
291
- #upstreamDB;
420
+ #db;
292
421
  #replicaIdentityTimer;
293
422
  #error;
294
- constructor(lc, { appID, shardNum }, shardConfig, initialSchema, upstreamURI) {
423
+ constructor(lc, { appID, shardNum }, shardConfig, db, initialSchema) {
295
424
  this.#lc = lc;
296
425
  this.#shardPrefix = `${appID}/${shardNum}`;
297
426
  this.#shardConfig = shardConfig;
298
427
  this.#initialSchema = initialSchema;
299
- this.#upstreamDB = pgClient(lc, upstreamURI, {
300
- ["idle_timeout"]: 10,
301
- connection: { ["application_name"]: "zero-schema-change-detector" }
302
- });
428
+ this.#db = db;
303
429
  }
304
430
  async makeChanges(lsn, msg) {
305
431
  if (this.#error) {
@@ -398,7 +524,7 @@ var ChangeMaker = class {
398
524
  #preSchema;
399
525
  #lastSnapshotInTx;
400
526
  #handleDdlMessage(msg) {
401
- const event = this.#parseReplicationEvent(msg.content);
527
+ const event = parseLogicalMessageContent(msg, replicationEventSchema);
402
528
  clearTimeout(this.#replicaIdentityTimer);
403
529
  let previousSchema;
404
530
  const { type } = event;
@@ -427,7 +553,7 @@ var ChangeMaker = class {
427
553
  const replicaIdentities = replicaIdentitiesForTablesWithoutPrimaryKeys(event.schema);
428
554
  if (replicaIdentities) this.#replicaIdentityTimer = setTimeout(async () => {
429
555
  try {
430
- await replicaIdentities.apply(this.#lc, this.#upstreamDB);
556
+ await replicaIdentities.apply(this.#lc, this.#db);
431
557
  } catch (err) {
432
558
  this.#lc.warn?.(`error setting replica identities`, err);
433
559
  }
@@ -602,10 +728,6 @@ var ChangeMaker = class {
602
728
  }
603
729
  return changes;
604
730
  }
605
- #parseReplicationEvent(content) {
606
- const str = content instanceof Buffer ? content.toString("utf-8") : new TextDecoder().decode(content);
607
- return parse(JSON.parse(str), replicationEventSchema, "passthrough");
608
- }
609
731
  /**
610
732
  * If `ddlDetection === true`, relation messages are irrelevant,
611
733
  * as schema changes are detected by event triggers that
@@ -627,7 +749,7 @@ var ChangeMaker = class {
627
749
  async #handleRelation(rel) {
628
750
  const { publications, ddlDetection } = this.#shardConfig;
629
751
  if (ddlDetection) return [];
630
- const currentSchema = await getPublicationInfo(this.#upstreamDB, publications);
752
+ const currentSchema = await getPublicationInfo(this.#db, publications);
631
753
  const difference = getSchemaDifference(this.#initialSchema, currentSchema);
632
754
  if (difference !== null) throw new MissingEventTriggerSupport(difference);
633
755
  const orel = this.#initialSchema.tables.find((t) => t.oid === rel.relationOid);
@@ -752,6 +874,10 @@ var ShutdownSignal = class extends AbortError {
752
874
  super("shutdown signal received (e.g. another zero-cache taking over the replication stream)", { cause });
753
875
  }
754
876
  };
877
+ function parseLogicalMessageContent({ content }, schema) {
878
+ const str = content instanceof Buffer ? content.toString("utf-8") : new TextDecoder().decode(content);
879
+ return parse(JSON.parse(str), schema, "passthrough");
880
+ }
755
881
  //#endregion
756
882
  export { initializePostgresChangeSource };
757
883