@rocicorp/zero 1.11.0-canary.4 → 1.11.0-canary.6

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 (123) hide show
  1. package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  2. package/out/replicache/src/kv/expo-sqlite/store.js +27 -4
  3. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  4. package/out/replicache/src/kv/sqlite-store.d.ts +11 -0
  5. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  6. package/out/replicache/src/kv/sqlite-store.js +43 -3
  7. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  8. package/out/shared/src/error.d.ts +11 -0
  9. package/out/shared/src/error.d.ts.map +1 -1
  10. package/out/shared/src/error.js +35 -1
  11. package/out/shared/src/error.js.map +1 -1
  12. package/out/shared/src/map.d.ts +7 -2
  13. package/out/shared/src/map.d.ts.map +1 -1
  14. package/out/shared/src/map.js.map +1 -1
  15. package/out/zero/package.js +2 -2
  16. package/out/zero/package.js.map +1 -1
  17. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  18. package/out/zero-cache/src/config/normalize.js +7 -1
  19. package/out/zero-cache/src/config/normalize.js.map +1 -1
  20. package/out/zero-cache/src/config/zero-config.d.ts +9 -0
  21. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  22. package/out/zero-cache/src/config/zero-config.js +21 -0
  23. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  24. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  25. package/out/zero-cache/src/server/change-streamer.js +15 -6
  26. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  27. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  28. package/out/zero-cache/src/server/main.js +8 -1
  29. package/out/zero-cache/src/server/main.js.map +1 -1
  30. package/out/zero-cache/src/server/mutator.js +2 -0
  31. package/out/zero-cache/src/server/mutator.js.map +1 -1
  32. package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
  33. package/out/zero-cache/src/server/reaper.js +2 -0
  34. package/out/zero-cache/src/server/reaper.js.map +1 -1
  35. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  36. package/out/zero-cache/src/server/replicator.js +2 -0
  37. package/out/zero-cache/src/server/replicator.js.map +1 -1
  38. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  39. package/out/zero-cache/src/server/runner/zero-dispatcher.js +3 -0
  40. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  41. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  42. package/out/zero-cache/src/server/syncer.js +2 -0
  43. package/out/zero-cache/src/server/syncer.js.map +1 -1
  44. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  45. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -1
  46. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +68 -8
  47. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  48. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -4
  49. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/change-log-codec.js +28 -8
  51. package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
  52. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -0
  53. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  54. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -1
  55. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  56. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +6 -0
  57. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  58. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +86 -51
  59. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  60. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +1 -1
  61. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  62. package/out/zero-cache/src/services/change-streamer/schema/tables.js +4 -4
  63. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  64. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +6 -1
  65. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -1
  66. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +27 -9
  67. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
  68. package/out/zero-cache/src/services/change-streamer/storer.d.ts +2 -2
  69. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  70. package/out/zero-cache/src/services/change-streamer/storer.js +6 -5
  71. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  72. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +50 -23
  73. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
  74. package/out/zero-cache/src/services/profz.d.ts +7 -0
  75. package/out/zero-cache/src/services/profz.d.ts.map +1 -0
  76. package/out/zero-cache/src/services/profz.js +99 -0
  77. package/out/zero-cache/src/services/profz.js.map +1 -0
  78. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +5 -4
  79. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -1
  80. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +20 -22
  81. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  82. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +6 -6
  83. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
  84. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +19 -14
  85. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  86. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +17 -0
  87. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  88. package/out/zero-cache/src/services/view-syncer/cvr.js +59 -0
  89. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  90. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +51 -0
  91. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
  92. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
  93. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
  94. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  95. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +18 -6
  96. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  97. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/view-syncer/view-syncer.js +154 -6
  99. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  100. package/out/zero-cache/src/types/processes.d.ts +24 -0
  101. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  102. package/out/zero-cache/src/types/processes.js +25 -1
  103. package/out/zero-cache/src/types/processes.js.map +1 -1
  104. package/out/zero-cache/src/types/profiler.d.ts +12 -0
  105. package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
  106. package/out/zero-cache/src/types/profiler.js +46 -1
  107. package/out/zero-cache/src/types/profiler.js.map +1 -1
  108. package/out/zero-client/src/client/version.js +1 -1
  109. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  110. package/out/zero-client/src/client/zero.js +12 -8
  111. package/out/zero-client/src/client/zero.js.map +1 -1
  112. package/out/zero-protocol/src/down.d.ts +27 -27
  113. package/out/zero-react/src/use-query.d.ts.map +1 -1
  114. package/out/zero-react/src/use-query.js +50 -3
  115. package/out/zero-react/src/use-query.js.map +1 -1
  116. package/out/zero-solid/src/use-query.js +1 -2
  117. package/out/zero-solid/src/use-query.js.map +1 -1
  118. package/out/zql/src/ivm/data.d.ts.map +1 -1
  119. package/out/zql/src/ivm/data.js +8 -3
  120. package/out/zql/src/ivm/data.js.map +1 -1
  121. package/out/zql/src/ivm/view-apply-change.js +6 -1
  122. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  123. package/package.json +2 -2
@@ -11,6 +11,7 @@ import { Subscription } from "../../types/subscription.js";
11
11
  import { AutoResetSignal, ensureReplicationConfig, markResetRequired } from "./schema/tables.js";
12
12
  import { publishReplicationError, replicationStatusError } from "../replicator/replication-status.js";
13
13
  import "./change-streamer.js";
14
+ import { serializeChangeStreamDataWithChange } from "./change-log-codec.js";
14
15
  import { SQLiteChangeLogReader } from "./sqlite-change-log-reader.js";
15
16
  import { ChangeLogInitializer, replicaInitializationSource } from "./change-log-initializer.js";
16
17
  import { Forwarder } from "./forwarder.js";
@@ -32,7 +33,7 @@ import { getDefaultHighWaterMark } from "node:stream";
32
33
  * Performs initialization and schema migrations to initialize a ChangeStreamerImpl.
33
34
  */
34
35
  async function initializeStreamer(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, changeSource, replicationStatusPublisher, subscriptionState, backupConfig, purgeLock, autoReset, opts, setTimeoutFn = setTimeout) {
35
- await ensureReplicationConfig(lc, changeDB, subscriptionState, shard, autoReset, purgeLock ?? void 0, setTimeoutFn);
36
+ await ensureReplicationConfig(lc, changeDB, subscriptionState, shard, autoReset, purgeLock ?? void 0, setTimeoutFn, opts.pgChangeLogEnabled);
36
37
  const changeDBProvider = (applicationName, max) => postgres(defu({
37
38
  max,
38
39
  connection: { ["application_name"]: applicationName }
@@ -188,6 +189,7 @@ var ChangeStreamerImpl = class {
188
189
  #replicaVersion;
189
190
  #source;
190
191
  #storer;
192
+ #pgChangeLogEnabled;
191
193
  #forwarder;
192
194
  #reservations;
193
195
  #replicationStatusPublisher;
@@ -237,6 +239,11 @@ var ChangeStreamerImpl = class {
237
239
  this.#changeDBProvider = changeDBProvider;
238
240
  this.#replicaVersion = replicaVersion;
239
241
  this.#source = source;
242
+ this.#pgChangeLogEnabled = opts.pgChangeLogEnabled;
243
+ if (!this.#pgChangeLogEnabled) {
244
+ assert(opts.sqliteChangeLogWriter && opts.sqliteChangeLogPurge && opts.sqliteCatchup && opts.sqliteChangeLogServe?.readPercent === 100 && opts.sqliteChangeLogServe.coldReadPercent === 100 && backupConfig?.litestreamVersion === "v5", "disabling the PG change log requires SQLite serving at 100 percent and a v5 backup");
245
+ assert(opts.sqliteChangeLogCompare === void 0, "SQLite change-log comparison requires the PG change log");
246
+ }
240
247
  this.#storer = new Storer(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDBProvider, replicaVersion, (consumed) => this.#acker.trackPgChangeLog(consumed[2].watermark), (err) => this.stop(err), opts);
241
248
  this.#forwarder = new Forwarder(lc, {
242
249
  flowControlConsensusTimeoutProportion: opts.flowControlConsensusTimeoutProportion,
@@ -277,12 +284,13 @@ var ChangeStreamerImpl = class {
277
284
  }) : void 0;
278
285
  this.#purgeScheduler = opts.sqliteChangeLogPurge && this.#changeLogWriter ? new SQLiteChangeLogPurgeScheduler(lc, () => this.#changeLogWriter?.connection, () => this.#forwarder.getAcks(), opts.sqliteChangeLogPurge) : void 0;
279
286
  this.#acker = new UpstreamAcker({
280
- trackPgChangeLog: true,
287
+ trackPgChangeLog: this.#pgChangeLogEnabled,
281
288
  trackBackup: backupConfig?.litestreamVersion === "v5"
282
289
  });
283
- const replicaSource = opts.sqliteChangeLogCompare ? replicaInitializationSource(lc, opts.sqliteChangeLogCompare.replicaFile) : void 0;
290
+ const replicaFileForInitialization = opts.sqliteChangeLogCompare?.replicaFile ?? (!this.#pgChangeLogEnabled ? opts.sqliteChangeLogWriter?.replicaFile : void 0);
291
+ const replicaSource = replicaFileForInitialization ? replicaInitializationSource(lc, replicaFileForInitialization) : void 0;
284
292
  this.#initializer = new ChangeLogInitializer(lc, {
285
- initFromPgChangeLog: true,
293
+ initFromPgChangeLog: this.#pgChangeLogEnabled,
286
294
  initFromReplica: replicaSource !== void 0
287
295
  }, {
288
296
  pgChangeLog: () => this.#storer.getStartStreamInitializationParameters(),
@@ -294,7 +302,7 @@ var ChangeStreamerImpl = class {
294
302
  ...opts.sqliteCatchup,
295
303
  cleanupGuard: opts.sqliteCatchup.cleanupGuard ?? this.#purgeScheduler?.cleanupGuard
296
304
  } : void 0;
297
- this.#comparator = opts.sqliteChangeLogCompare && opts.sqliteChangeLogWriter && opts.sqliteCatchup ? new SQLiteChangeLogComparator(lc, shard, opts.sqliteCatchup.changeLogFile, opts.sqliteChangeLogWriter.identity, this.#storer, {
305
+ this.#comparator = this.#pgChangeLogEnabled && opts.sqliteChangeLogCompare && opts.sqliteChangeLogWriter && opts.sqliteCatchup ? new SQLiteChangeLogComparator(lc, shard, opts.sqliteCatchup.changeLogFile, opts.sqliteChangeLogWriter.identity, this.#storer, {
298
306
  setTimeoutFn,
299
307
  ...opts.sqliteChangeLogCompare
300
308
  }) : void 0;
@@ -322,7 +330,7 @@ var ChangeStreamerImpl = class {
322
330
  const { lastWatermark, backfillRequests } = await this.#initializer.initialize();
323
331
  this.#lastForwardedCommitWatermark = lastWatermark;
324
332
  const stream = await this.#source.startStream(lastWatermark, backfillRequests);
325
- this.#storer.run().catch((e) => stream.changes.cancel(e));
333
+ if (this.#pgChangeLogEnabled) this.#storer.run().catch((e) => stream.changes.cancel(e));
326
334
  this.#stream = stream;
327
335
  if (this.#state.resetBackoff() > REPLICATION_STATUS_ERROR_DELAY_THRESHOLD_MS) this.#replicationStatusPublisher.publish(this.#lc, "Replicating", `Replicating from ${lastWatermark}`);
328
336
  watermark = null;
@@ -353,8 +361,10 @@ var ChangeStreamerImpl = class {
353
361
  if (watermark === null) throw new UnrecoverableError(`${type} change (${msg.tag}) received before 'begin' message`);
354
362
  break;
355
363
  }
356
- const json = this.#storer.store(watermark, change);
357
- this.#changeLogWriter?.write(change, json);
364
+ const serialized = serializeChangeStreamDataWithChange(change);
365
+ const { json } = serialized;
366
+ if (this.#pgChangeLogEnabled) this.#storer.store(watermark, change, serialized);
367
+ this.#changeLogWriter?.write(change, json, serialized.change);
358
368
  const entry = [
359
369
  watermark,
360
370
  change[1].tag,
@@ -371,7 +381,7 @@ var ChangeStreamerImpl = class {
371
381
  unflushedBytes = 0;
372
382
  }
373
383
  if (type === "commit" || type === "rollback") watermark = null;
374
- const readyForMore = this.#storer.readyForMore();
384
+ const readyForMore = this.#pgChangeLogEnabled ? this.#storer.readyForMore() : void 0;
375
385
  if (readyForMore) await promiseOrAbort(readyForMore, stream.changes.signal, this.#state.signal);
376
386
  }
377
387
  } catch (e) {
@@ -382,7 +392,7 @@ var ChangeStreamerImpl = class {
382
392
  }
383
393
  if (watermark) {
384
394
  this.#lc.warn?.(`aborting interrupted transaction ${watermark}`);
385
- this.#storer.abort();
395
+ if (this.#pgChangeLogEnabled) this.#storer.abort();
386
396
  this.#changeLogWriter?.abort();
387
397
  this.#purgeScheduler?.onWriterIdle();
388
398
  this.#forwarder.forward([
@@ -434,14 +444,19 @@ var ChangeStreamerImpl = class {
434
444
  } else {
435
445
  lc.info?.(`adding subscriber ${subscriber.id}`);
436
446
  const catchupFromPG = () => {
447
+ assert(this.#pgChangeLogEnabled, "cannot catch up from a disabled PG change log");
437
448
  cleanupSubscriber = removeFromForwarder;
438
449
  this.#forwarder.add(subscriber);
439
450
  this.#storer.catchup(subscriber, mode);
440
451
  };
441
- const sqliteSelection = this.#selectSQLiteCatchup(lc, ctx);
442
- if (!sqliteSelection) catchupFromPG();
443
- else {
444
- const { catchup, reason, coverage, logWarm } = sqliteSelection;
452
+ const sqliteDecision = this.#selectSQLiteCatchup(lc, ctx);
453
+ if (!sqliteDecision) catchupFromPG();
454
+ else if (sqliteDecision.kind === "rejected") {
455
+ lc.warn?.(`${sqliteDecision.terminal ? "rejecting" : "ending"} subscription for ${ctx.id}: ${sqliteDecision.message}`);
456
+ if (sqliteDecision.terminal) subscriber.close(2, sqliteDecision.message);
457
+ else subscriber.close();
458
+ } else {
459
+ const { catchup, reason, coverage, logWarm } = sqliteDecision;
445
460
  cleanupSubscriber = () => catchup.remove(subscriber);
446
461
  const registration = await catchup.catchup(subscriber, () => this.#captureRequiredHead(), { logWarm });
447
462
  switch (registration.kind) {
@@ -450,14 +465,27 @@ var ChangeStreamerImpl = class {
450
465
  this.#recordCatchupRoute("sqlite", reason);
451
466
  break;
452
467
  case "uncovered":
453
- lc.info?.(`serving ${ctx.id} from PG catchup: subscriber watermark ${ctx.watermark} is below the SQLite change-log minimum ` + registration.minWatermark);
454
- this.#recordCatchupRoute("pg", "watermark-uncovered");
455
- catchupFromPG();
468
+ if (this.#pgChangeLogEnabled) {
469
+ lc.info?.(`serving ${ctx.id} from PG catchup: subscriber watermark ${ctx.watermark} is below the SQLite change-log minimum ` + registration.minWatermark);
470
+ this.#recordCatchupRoute("pg", "watermark-uncovered");
471
+ catchupFromPG();
472
+ } else {
473
+ const message = `earliest supported watermark is ${registration.minWatermark} (requested ${ctx.watermark})`;
474
+ lc.warn?.(`rejecting subscriber at watermark ${ctx.watermark}: the SQLite change log starts at ${registration.minWatermark} and the PG change log is disabled`);
475
+ this.#recordCatchupRoute("none", "watermark-uncovered");
476
+ subscriber.close(2, message);
477
+ }
456
478
  break;
457
479
  case "declined":
458
- lc.error?.(`serving ${ctx.id} from PG catchup: SQLite catchup registration failed`, registration.error);
459
- this.#recordCatchupRoute("pg", "registration-failed");
460
- catchupFromPG();
480
+ if (this.#pgChangeLogEnabled) {
481
+ lc.error?.(`serving ${ctx.id} from PG catchup: SQLite catchup registration failed`, registration.error);
482
+ this.#recordCatchupRoute("pg", "registration-failed");
483
+ catchupFromPG();
484
+ } else {
485
+ lc.error?.(`ending subscription for ${ctx.id} to retry SQLite catchup: registration failed and the PG change log is disabled`, registration.error);
486
+ this.#recordCatchupRoute("none", "registration-failed");
487
+ subscriber.fail(registration.error);
488
+ }
461
489
  break;
462
490
  case "handled":
463
491
  this.#recordCatchupRoute("sqlite", "registration-handled");
@@ -495,13 +523,22 @@ var ChangeStreamerImpl = class {
495
523
  const backupWatermark = this.#backupWatermark;
496
524
  const reservations = this.#reservations;
497
525
  if (backupWatermark === void 0 || !reservations?.confirmationsRequired()) return;
498
- const pgState = await this.#getChangeLogState();
526
+ const pgState = this.#pgChangeLogEnabled ? await this.#getChangeLogState() : void 0;
499
527
  for (const taskID of reservations.unconfirmedTaskIDs()) {
500
528
  let route = this.#readRouter?.peek(taskID);
501
529
  if (this.#readRouter && route === void 0) continue;
530
+ if (!this.#pgChangeLogEnabled && route?.source === "pg") {
531
+ this.#readRouter?.release(taskID);
532
+ route = this.#readRouter?.pin(taskID);
533
+ if (route?.source === "pg") {
534
+ if (reservations.noteConfirmationDelayed(taskID)) this.#reservationConfirmDelays.add(1);
535
+ this.#lc.warn?.(`delaying snapshot reservation for ${taskID}: SQLite change log is unavailable and the PG change log is disabled`);
536
+ continue;
537
+ }
538
+ }
502
539
  if (route?.source === "sqlite") {
503
540
  const coverage = must(route.coverage, "a pinned SQLite route must carry its covered range");
504
- if (coverage.minWatermark > backupWatermark) {
541
+ if (this.#pgChangeLogEnabled && coverage.minWatermark > backupWatermark) {
505
542
  this.#lc.info?.(`demoting ${taskID} to PG catchup: SQLite change-log minimum ${coverage.minWatermark} is later than backupWatermark ` + backupWatermark);
506
543
  this.#reservationDemotions.add(1);
507
544
  route = must(this.#readRouter).demote(taskID);
@@ -510,17 +547,17 @@ var ChangeStreamerImpl = class {
510
547
  const source = route?.source ?? "pg";
511
548
  let minWatermark;
512
549
  if (route?.source === "sqlite") minWatermark = must(route.coverage, "a pinned SQLite route must carry its covered range").minWatermark;
513
- else ({minWatermark} = pgState);
514
- if (minWatermark <= backupWatermark) reservations.confirmFor(taskID, pgState.replicaVersion, backupWatermark, source);
550
+ else minWatermark = must(pgState, "a PG reservation route requires the PG change log").minWatermark;
551
+ if (minWatermark <= backupWatermark) reservations.confirmFor(taskID, this.#replicaVersion, backupWatermark, source);
515
552
  else {
516
553
  if (reservations.noteConfirmationDelayed(taskID)) this.#reservationConfirmDelays.add(1);
517
- this.#lc.error?.(`pg change-log minWatermark ${minWatermark} is later than backupWatermark ${backupWatermark}. Delaying confirmation of snapshot reservation until next backup.`);
554
+ this.#lc.error?.(`${source} change-log minWatermark ${minWatermark} is later than backupWatermark ${backupWatermark}. Delaying confirmation of snapshot reservation until next backup.`);
518
555
  }
519
556
  }
520
557
  }
521
558
  #maybeSchedulePGPurge() {
522
559
  const backupWatermark = this.#backupWatermark;
523
- if (this.#pgPurgeScheduled || this.#pgPurgeRunning || backupWatermark === void 0 || this.#pgPurgedWatermark >= backupWatermark) return;
560
+ if (!this.#pgChangeLogEnabled || this.#pgPurgeScheduled || this.#pgPurgeRunning || backupWatermark === void 0 || this.#pgPurgedWatermark >= backupWatermark) return;
524
561
  this.#pgPurgeScheduled = true;
525
562
  this.#state.setTimeout(() => {
526
563
  this.#pgPurgeScheduled = false;
@@ -664,14 +701,10 @@ var ChangeStreamerImpl = class {
664
701
  }
665
702
  #selectSQLiteCatchup(lc, ctx) {
666
703
  const opts = this.#sqliteCatchupOptions;
667
- if (this.#lastForwardedCommitWatermark === void 0 || !opts) {
668
- this.#recordCatchupRoute("pg", "not-ready");
669
- return;
670
- }
671
- if (ctx.mode !== "serving") {
704
+ if (this.#lastForwardedCommitWatermark === void 0 || !opts) return this.#fallbackOrReject("not-ready", "SQLite catchup is not ready and the PG change log is disabled");
705
+ if (ctx.mode === "backup" && this.#pgChangeLogEnabled) {
672
706
  lc.info?.(`not serving backup subscriber ${ctx.id} from SQLite catchup`);
673
- this.#recordCatchupRoute("pg", "ineligible-mode");
674
- return;
707
+ return this.#fallbackOrReject("ineligible-mode", "backup subscribers retain the PG change-log recovery policy");
675
708
  }
676
709
  let route;
677
710
  if (this.#readRouter) {
@@ -680,33 +713,34 @@ var ChangeStreamerImpl = class {
680
713
  if (route.reason === "cold-log") lc.info?.(`serving ${ctx.id} from PG catchup: SQLite change log is still warming`, { sqliteChangeLogCoverage: route.coverage });
681
714
  else if (route.reason === "log-unavailable") this.#declineSQLiteCatchup(lc, ctx, opts, `${opts.changeLogFile} is unavailable or incompatible`);
682
715
  else lc.debug?.(`serving ${ctx.id} from PG catchup: SQLite route ${route.reason}`, ...route.coverage ? [{ sqliteChangeLogCoverage: route.coverage }] : []);
683
- this.#recordCatchupRoute("pg", route.reason);
684
- return;
716
+ return this.#fallbackOrReject(route.reason, `SQLite catchup route ${route.reason} is unavailable and the PG change log is disabled`);
685
717
  }
686
718
  const coverage = route.coverage;
687
719
  assert(coverage, "a SQLite route must carry its covered range");
688
720
  if (ctx.watermark < coverage.minWatermark) {
689
721
  lc.info?.(`serving ${ctx.id} from PG catchup: subscriber watermark ${ctx.watermark} is below the SQLite change-log minimum ` + coverage.minWatermark, { sqliteChangeLogCoverage: coverage });
690
- this.#recordCatchupRoute("pg", "watermark-uncovered");
691
- return;
722
+ return this.#fallbackOrReject("watermark-uncovered", `earliest supported watermark is ${coverage.minWatermark} (requested ${ctx.watermark})`, true);
692
723
  }
693
724
  }
694
- if (!this.#readRouter && !opts.shouldUse?.(ctx)) {
695
- this.#recordCatchupRoute("pg", "selector");
696
- return;
697
- }
698
- if (this.#readRouter && opts.shouldUse && !opts.shouldUse(ctx)) {
699
- this.#recordCatchupRoute("pg", "selector");
700
- return;
701
- }
725
+ if (!this.#readRouter && !opts.shouldUse?.(ctx)) return this.#fallbackOrReject("selector", "SQLite catchup was not selected and the PG change log is disabled");
726
+ if (this.#readRouter && opts.shouldUse && !opts.shouldUse(ctx)) return this.#fallbackOrReject("selector", "SQLite catchup was not selected and the PG change log is disabled");
702
727
  const catchup = this.#sqliteCatchup ?? this.#openSQLiteCatchup(lc, opts, ctx);
703
728
  if (catchup) return {
729
+ kind: "selected",
704
730
  catchup,
705
731
  reason: route?.reason ?? "selector",
706
732
  coverage: route?.coverage,
707
733
  logWarm: route === void 0 ? void 0 : route.reason !== "selected-cold"
708
734
  };
709
- else this.#recordCatchupRoute("pg", "log-unavailable");
735
+ else return this.#fallbackOrReject("log-unavailable", "SQLite change log is unavailable and the PG change log is disabled");
736
+ }
737
+ #fallbackOrReject(reason, message, terminal = false) {
738
+ this.#recordCatchupRoute(this.#pgChangeLogEnabled ? "pg" : "none", reason);
739
+ return this.#pgChangeLogEnabled ? void 0 : {
740
+ kind: "rejected",
741
+ message,
742
+ terminal
743
+ };
710
744
  }
711
745
  #recordCatchupRoute(source, reason) {
712
746
  this.#catchupRoutes.add(1, {
@@ -722,10 +756,11 @@ var ChangeStreamerImpl = class {
722
756
  * first reconcile, and deletes it when it fails soft -- or may exist without
723
757
  * content, or may be unreadable. None of those can be allowed to fail a
724
758
  * subscription:
725
- * this is the last point at which PG catchup is still available -- past
726
- * `Forwarder.add()` the subscriber is committed to SQLite -- so each declines
727
- * here instead. Neither the failure nor the reader is retained, so a later
728
- * subscription retries from scratch.
759
+ * this is the last point at which PG catchup can be selected when it is
760
+ * enabled -- past `Forwarder.add()` the subscriber is committed to SQLite --
761
+ * so each declines here instead. With PG disabled, the caller ends the
762
+ * subscription so that it retries. Neither the failure nor the reader is
763
+ * retained, so a later subscription retries from scratch.
729
764
  */
730
765
  #openSQLiteCatchup(lc, opts, ctx) {
731
766
  let reader;
@@ -763,7 +798,7 @@ var ChangeStreamerImpl = class {
763
798
  const now = Date.now();
764
799
  this.#changeLogUnavailableSince ??= now;
765
800
  const unavailableMs = now - this.#changeLogUnavailableSince;
766
- lc[unavailableMs >= (opts.notReadyWarnThresholdMs ?? DEFAULT_CHANGE_LOG_UNAVAILABLE_WARN_THRESHOLD_MS) ? "warn" : "debug"]?.(`serving ${ctx.id} from PG catchup: ${reason} (unavailable for ${unavailableMs} ms)`, ...error === void 0 ? [] : [error]);
801
+ lc[unavailableMs >= (opts.notReadyWarnThresholdMs ?? DEFAULT_CHANGE_LOG_UNAVAILABLE_WARN_THRESHOLD_MS) ? "warn" : "debug"]?.((this.#pgChangeLogEnabled ? `serving ${ctx.id} from PG catchup: ` : `cannot serve ${ctx.id} from SQLite catchup: `) + `${reason} (unavailable for ${unavailableMs} ms)`, ...error === void 0 ? [] : [error]);
767
802
  }
768
803
  };
769
804
  var CLEANUP_DELAY_MS = DEFAULT_MAX_RETRY_DELAY_MS * 3;