@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
@@ -3,6 +3,7 @@ import { getOrCreateCounter } from "../../observability/metrics.js";
3
3
  import { ReplicationStatusPublisher } from "./replication-status.js";
4
4
  import "../change-streamer/change-streamer.js";
5
5
  import { Notifier } from "./notifier.js";
6
+ import { ReplicationReportRecorder } from "./reporter/recorder.js";
6
7
  //#region ../zero-cache/src/services/replicator/incremental-sync.ts
7
8
  /**
8
9
  * The {@link IncrementalSyncer} manages a logical replication stream from upstream,
@@ -12,6 +13,7 @@ import { Notifier } from "./notifier.js";
12
13
  * in a worker thread via the {@link WriteWorkerClient}.
13
14
  */
14
15
  var IncrementalSyncer = class {
16
+ #lc;
15
17
  #taskID;
16
18
  #id;
17
19
  #changeStreamer;
@@ -20,9 +22,11 @@ var IncrementalSyncer = class {
20
22
  #mode;
21
23
  #publishReplicationStatus;
22
24
  #notifier;
25
+ #reporter;
23
26
  #state = new RunningState("IncrementalSyncer");
24
27
  #replicationEvents = getOrCreateCounter("replication", "events", "Number of replication events processed");
25
- constructor(taskID, id, changeStreamer, statusDb, worker, mode, publishReplicationStatus) {
28
+ constructor(lc, taskID, id, changeStreamer, statusDb, worker, mode, publishReplicationStatus) {
29
+ this.#lc = lc;
26
30
  this.#taskID = taskID;
27
31
  this.#id = id;
28
32
  this.#changeStreamer = changeStreamer;
@@ -31,8 +35,10 @@ var IncrementalSyncer = class {
31
35
  this.#mode = mode;
32
36
  this.#publishReplicationStatus = publishReplicationStatus;
33
37
  this.#notifier = new Notifier();
38
+ this.#reporter = new ReplicationReportRecorder(lc);
34
39
  }
35
- async run(lc) {
40
+ async run() {
41
+ const lc = this.#lc;
36
42
  this.#worker.onError((err) => this.#state.stop(lc, err));
37
43
  lc.info?.(`Starting IncrementalSyncer`);
38
44
  const { watermark: initialWatermark } = await this.#worker.getSubscriptionState();
@@ -60,9 +66,18 @@ var IncrementalSyncer = class {
60
66
  for await (const message of downstream) {
61
67
  this.#replicationEvents.add(1);
62
68
  switch (message[0]) {
63
- case "status":
64
- lc.debug?.(`Received initial status`, message[1]);
69
+ case "status": {
70
+ const { lagReport } = message[1];
71
+ if (lagReport) {
72
+ const report = { nextSendTimeMs: lagReport.nextSendTimeMs };
73
+ if (lagReport.lastTimings) report.lastTimings = {
74
+ ...lagReport.lastTimings,
75
+ replicateTimeMs: Date.now()
76
+ };
77
+ this.#reporter.record(report);
78
+ }
65
79
  break;
80
+ }
66
81
  case "error":
67
82
  this.stop(lc, message[1]);
68
83
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"incremental-sync.js","names":["#taskID","#id","#changeStreamer","#statusDb","#worker","#mode","#publishReplicationStatus","#notifier","#state","#replicationEvents","#handleResult"],"sources":["../../../../../../zero-cache/src/services/replicator/incremental-sync.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport type {Source} from '../../types/streams.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport type {DownloadStatus} from '../change-source/protocol/current.ts';\nimport {\n PROTOCOL_VERSION,\n type ChangeStreamer,\n type Downstream,\n} from '../change-streamer/change-streamer.ts';\nimport type {CommitResult} from './change-processor.ts';\nimport {RunningState} from '../running-state.ts';\nimport {Notifier} from './notifier.ts';\nimport {ReplicationStatusPublisher} from './replication-status.ts';\nimport type {ReplicaState, ReplicatorMode} from './replicator.ts';\nimport type {WriteWorkerClient} from './write-worker-client.ts';\n\n/**\n * The {@link IncrementalSyncer} manages a logical replication stream from upstream,\n * handling application lifecycle events (start, stop) and retrying the\n * connection with exponential backoff. The actual handling of the logical\n * replication messages is done by the {@link ChangeProcessor}, which runs\n * in a worker thread via the {@link WriteWorkerClient}.\n */\nexport class IncrementalSyncer {\n readonly #taskID: string;\n readonly #id: string;\n readonly #changeStreamer: ChangeStreamer;\n readonly #statusDb: Database;\n readonly #worker: WriteWorkerClient;\n readonly #mode: ReplicatorMode;\n readonly #publishReplicationStatus: boolean;\n readonly #notifier: Notifier;\n\n readonly #state = new RunningState('IncrementalSyncer');\n\n readonly #replicationEvents = getOrCreateCounter(\n 'replication',\n 'events',\n 'Number of replication events processed',\n );\n\n constructor(\n taskID: string,\n id: string,\n changeStreamer: ChangeStreamer,\n statusDb: Database,\n worker: WriteWorkerClient,\n mode: ReplicatorMode,\n publishReplicationStatus: boolean,\n ) {\n this.#taskID = taskID;\n this.#id = id;\n this.#changeStreamer = changeStreamer;\n this.#statusDb = statusDb;\n this.#worker = worker;\n this.#mode = mode;\n this.#publishReplicationStatus = publishReplicationStatus;\n this.#notifier = new Notifier();\n }\n\n async run(lc: LogContext) {\n this.#worker.onError(err => this.#state.stop(lc, err));\n lc.info?.(`Starting IncrementalSyncer`);\n const {watermark: initialWatermark} =\n await this.#worker.getSubscriptionState();\n\n // Notify any waiting subscribers that the replica is ready to be read.\n void this.#notifier.notifySubscribers();\n\n // Only the backup replicator publishes replication status events.\n const statusPublisher = this.#publishReplicationStatus\n ? new ReplicationStatusPublisher(this.#statusDb)\n : undefined;\n\n while (this.#state.shouldRun()) {\n const {replicaVersion, watermark} =\n await this.#worker.getSubscriptionState();\n\n let downstream: Source<Downstream> | undefined;\n let unregister = () => {};\n let err: unknown | undefined;\n\n try {\n downstream = await this.#changeStreamer.subscribe({\n protocolVersion: PROTOCOL_VERSION,\n taskID: this.#taskID,\n id: this.#id,\n mode: this.#mode,\n watermark,\n replicaVersion,\n initial: watermark === initialWatermark,\n });\n this.#state.resetBackoff();\n unregister = this.#state.cancelOnStop(downstream);\n statusPublisher?.publish(\n lc,\n 'Replicating',\n `Replicating from ${watermark}`,\n );\n\n let backfillStatus: DownloadStatus | undefined;\n\n for await (const message of downstream) {\n this.#replicationEvents.add(1);\n switch (message[0]) {\n case 'status':\n // Used for checking if a replica can be caught up. Not\n // relevant here.\n lc.debug?.(`Received initial status`, message[1]);\n break;\n case 'error':\n // Unrecoverable error. Stop the service.\n this.stop(lc, message[1]);\n break;\n default: {\n const msg = message[1];\n if (msg.tag === 'backfill' && msg.status) {\n const {status} = msg;\n if (!backfillStatus) {\n // Start publishing the status every 3 seconds.\n backfillStatus = status;\n statusPublisher?.publish(\n lc,\n 'Replicating',\n `Backfilling ${msg.relation.name} table`,\n 3000,\n () =>\n backfillStatus\n ? {\n downloadStatus: [\n {\n ...backfillStatus,\n table: msg.relation.name,\n columns: [\n ...msg.relation.rowKey.columns,\n ...msg.columns,\n ],\n },\n ],\n }\n : {},\n );\n }\n backfillStatus = status; // Update the current status\n }\n\n const result = await this.#worker.processMessage(\n message as ChangeStreamData,\n );\n\n this.#handleResult(lc, result, statusPublisher);\n if (result?.completedBackfill) {\n backfillStatus = undefined;\n }\n break;\n }\n }\n }\n this.#worker.abort();\n } catch (e) {\n err = e;\n this.#worker.abort();\n } finally {\n downstream?.cancel();\n unregister();\n statusPublisher?.stop();\n }\n await this.#state.backoff(lc, err);\n }\n lc.info?.('IncrementalSyncer stopped');\n }\n\n #handleResult(\n lc: LogContext,\n result: CommitResult | null,\n statusPublisher: ReplicationStatusPublisher | undefined,\n ) {\n if (!result) {\n return;\n }\n if (result.completedBackfill) {\n // Publish the final status\n const status = result.completedBackfill;\n statusPublisher?.publish(\n lc,\n 'Replicating',\n `Backfilled ${status.table} table`,\n 0,\n () => ({downloadStatus: [status]}),\n );\n } else if (result.schemaUpdated) {\n statusPublisher?.publish(lc, 'Replicating', 'Schema updated');\n }\n if (result.watermark && result.changeLogUpdated) {\n void this.#notifier.notifySubscribers({state: 'version-ready'});\n }\n }\n\n subscribe(): Source<ReplicaState> {\n return this.#notifier.subscribe();\n }\n\n stop(lc: LogContext, err?: unknown) {\n this.#state.stop(lc, err);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"incremental-sync.js","names":["#lc","#taskID","#id","#changeStreamer","#statusDb","#worker","#mode","#publishReplicationStatus","#notifier","#reporter","#state","#replicationEvents","#handleResult"],"sources":["../../../../../../zero-cache/src/services/replicator/incremental-sync.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport type {Source} from '../../types/streams.ts';\nimport type {DownloadStatus} from '../change-source/protocol/current.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport {\n PROTOCOL_VERSION,\n type ChangeStreamer,\n type Downstream,\n} from '../change-streamer/change-streamer.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {CommitResult} from './change-processor.ts';\nimport {Notifier} from './notifier.ts';\nimport {ReplicationStatusPublisher} from './replication-status.ts';\nimport type {ReplicaState, ReplicatorMode} from './replicator.ts';\nimport {ReplicationReportRecorder} from './reporter/recorder.ts';\nimport type {ReplicationReport} from './reporter/report-schema.ts';\nimport type {WriteWorkerClient} from './write-worker-client.ts';\n\n/**\n * The {@link IncrementalSyncer} manages a logical replication stream from upstream,\n * handling application lifecycle events (start, stop) and retrying the\n * connection with exponential backoff. The actual handling of the logical\n * replication messages is done by the {@link ChangeProcessor}, which runs\n * in a worker thread via the {@link WriteWorkerClient}.\n */\nexport class IncrementalSyncer {\n readonly #lc: LogContext;\n readonly #taskID: string;\n readonly #id: string;\n readonly #changeStreamer: ChangeStreamer;\n readonly #statusDb: Database;\n readonly #worker: WriteWorkerClient;\n readonly #mode: ReplicatorMode;\n readonly #publishReplicationStatus: boolean;\n readonly #notifier: Notifier;\n readonly #reporter: ReplicationReportRecorder;\n\n readonly #state = new RunningState('IncrementalSyncer');\n\n readonly #replicationEvents = getOrCreateCounter(\n 'replication',\n 'events',\n 'Number of replication events processed',\n );\n\n constructor(\n lc: LogContext,\n taskID: string,\n id: string,\n changeStreamer: ChangeStreamer,\n statusDb: Database,\n worker: WriteWorkerClient,\n mode: ReplicatorMode,\n publishReplicationStatus: boolean,\n ) {\n this.#lc = lc;\n this.#taskID = taskID;\n this.#id = id;\n this.#changeStreamer = changeStreamer;\n this.#statusDb = statusDb;\n this.#worker = worker;\n this.#mode = mode;\n this.#publishReplicationStatus = publishReplicationStatus;\n this.#notifier = new Notifier();\n this.#reporter = new ReplicationReportRecorder(lc);\n }\n\n async run() {\n const lc = this.#lc;\n this.#worker.onError(err => this.#state.stop(lc, err));\n lc.info?.(`Starting IncrementalSyncer`);\n const {watermark: initialWatermark} =\n await this.#worker.getSubscriptionState();\n\n // Notify any waiting subscribers that the replica is ready to be read.\n void this.#notifier.notifySubscribers();\n\n // Only the backup replicator publishes replication status events.\n const statusPublisher = this.#publishReplicationStatus\n ? new ReplicationStatusPublisher(this.#statusDb)\n : undefined;\n\n while (this.#state.shouldRun()) {\n const {replicaVersion, watermark} =\n await this.#worker.getSubscriptionState();\n\n let downstream: Source<Downstream> | undefined;\n let unregister = () => {};\n let err: unknown | undefined;\n\n try {\n downstream = await this.#changeStreamer.subscribe({\n protocolVersion: PROTOCOL_VERSION,\n taskID: this.#taskID,\n id: this.#id,\n mode: this.#mode,\n watermark,\n replicaVersion,\n initial: watermark === initialWatermark,\n });\n this.#state.resetBackoff();\n unregister = this.#state.cancelOnStop(downstream);\n statusPublisher?.publish(\n lc,\n 'Replicating',\n `Replicating from ${watermark}`,\n );\n\n let backfillStatus: DownloadStatus | undefined;\n\n for await (const message of downstream) {\n this.#replicationEvents.add(1);\n switch (message[0]) {\n case 'status': {\n const {lagReport} = message[1];\n if (lagReport) {\n const report: ReplicationReport = {\n nextSendTimeMs: lagReport.nextSendTimeMs,\n };\n if (lagReport.lastTimings) {\n report.lastTimings = {\n ...lagReport.lastTimings,\n replicateTimeMs: Date.now(),\n };\n }\n this.#reporter.record(report);\n }\n break;\n }\n case 'error':\n // Unrecoverable error. Stop the service.\n this.stop(lc, message[1]);\n break;\n default: {\n const msg = message[1];\n if (msg.tag === 'backfill' && msg.status) {\n const {status} = msg;\n if (!backfillStatus) {\n // Start publishing the status every 3 seconds.\n backfillStatus = status;\n statusPublisher?.publish(\n lc,\n 'Replicating',\n `Backfilling ${msg.relation.name} table`,\n 3000,\n () =>\n backfillStatus\n ? {\n downloadStatus: [\n {\n ...backfillStatus,\n table: msg.relation.name,\n columns: [\n ...msg.relation.rowKey.columns,\n ...msg.columns,\n ],\n },\n ],\n }\n : {},\n );\n }\n backfillStatus = status; // Update the current status\n }\n\n const result = await this.#worker.processMessage(\n message as ChangeStreamData,\n );\n\n this.#handleResult(lc, result, statusPublisher);\n if (result?.completedBackfill) {\n backfillStatus = undefined;\n }\n break;\n }\n }\n }\n this.#worker.abort();\n } catch (e) {\n err = e;\n this.#worker.abort();\n } finally {\n downstream?.cancel();\n unregister();\n statusPublisher?.stop();\n }\n await this.#state.backoff(lc, err);\n }\n lc.info?.('IncrementalSyncer stopped');\n }\n\n #handleResult(\n lc: LogContext,\n result: CommitResult | null,\n statusPublisher: ReplicationStatusPublisher | undefined,\n ) {\n if (!result) {\n return;\n }\n if (result.completedBackfill) {\n // Publish the final status\n const status = result.completedBackfill;\n statusPublisher?.publish(\n lc,\n 'Replicating',\n `Backfilled ${status.table} table`,\n 0,\n () => ({downloadStatus: [status]}),\n );\n } else if (result.schemaUpdated) {\n statusPublisher?.publish(lc, 'Replicating', 'Schema updated');\n }\n if (result.watermark && result.changeLogUpdated) {\n void this.#notifier.notifySubscribers({state: 'version-ready'});\n }\n }\n\n subscribe(): Source<ReplicaState> {\n return this.#notifier.subscribe();\n }\n\n stop(lc: LogContext, err?: unknown) {\n this.#state.stop(lc, err);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/replicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,oDAAoD;AACpD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAIhC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACvC;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElD,qBAAa,iBAAkB,YAAW,UAAU,EAAE,OAAO;;IAC3D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAOlB,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,cAAc,EACpB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,iBAAiB,EACzB,wBAAwB,EAAE,OAAO;IAmBnC,MAAM;;;IAIN,GAAG;IAKH,SAAS,IAAI,MAAM,CAAC,YAAY,CAAC;IAI3B,IAAI;CAQX"}
1
+ {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/replicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,oDAAoD;AACpD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAIhC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACvC;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElD,qBAAa,iBAAkB,YAAW,UAAU,EAAE,OAAO;;IAC3D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAOlB,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,cAAc,EACpB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,iBAAiB,EACzB,wBAAwB,EAAE,OAAO;IAoBnC,MAAM;;;IAIN,GAAG;IAKH,SAAS,IAAI,MAAM,CAAC,YAAY,CAAC;IAI3B,IAAI;CAQX"}
@@ -10,13 +10,13 @@ var ReplicatorService = class {
10
10
  this.id = id;
11
11
  this.#lc = lc.withContext("component", "replicator").withContext("serviceID", this.id);
12
12
  this.#worker = worker;
13
- this.#incrementalSyncer = new IncrementalSyncer(taskID, `${taskID}/${id}`, changeStreamer, statusDb, worker, mode, publishReplicationStatus);
13
+ this.#incrementalSyncer = new IncrementalSyncer(this.#lc, taskID, `${taskID}/${id}`, changeStreamer, statusDb, worker, mode, publishReplicationStatus);
14
14
  }
15
15
  status() {
16
16
  return Promise.resolve({ status: "ok" });
17
17
  }
18
18
  run() {
19
- this.#runPromise = this.#incrementalSyncer.run(this.#lc);
19
+ this.#runPromise = this.#incrementalSyncer.run();
20
20
  return this.#runPromise;
21
21
  }
22
22
  subscribe() {
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.js","names":["#lc","#incrementalSyncer","#worker","#runPromise"],"sources":["../../../../../../zero-cache/src/services/replicator/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {ReadonlyJSONObject} from '../../../../shared/src/json.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport type {Source} from '../../types/streams.ts';\nimport type {ChangeStreamer} from '../change-streamer/change-streamer.ts';\nimport type {Service} from '../service.ts';\nimport {IncrementalSyncer} from './incremental-sync.ts';\nimport type {WriteWorkerClient} from './write-worker-client.ts';\n\n/** See {@link ReplicaStateNotifier.subscribe()}. */\nexport type ReplicaState = {\n readonly state: 'version-ready';\n\n // Used in tests to verify behavior when additional information\n // is ferried in the future. Not set in production.\n readonly testSeqNum?: number;\n};\n\nexport interface ReplicaStateNotifier {\n /**\n * Creates a cancelable subscription of changes in the replica state.\n *\n * A `version-ready` message indicates that the replica is ready to be\n * read, and henceforth that a _new_ version is ready, i.e. whenever a\n * change is committed to the replica. The `version-ready` message itself\n * otherwise contains no other information; the subscriber queries the\n * replica for the current data.\n *\n * A `maintenance` state indicates that the replica should not be read from.\n * If a subscriber is holding any transaction locks, it should release them\n * until the next `version-ready` signal.\n *\n * Upon subscription, the current state of the replica is sent immediately\n * if known. If multiple notifications occur before the subscriber\n * can consume them, all but the last notification are discarded by the\n * Subscription object (i.e. not buffered). Thus, a subscriber only\n * ever consumes the current (i.e. known) state of the replica. This avoids\n * a buildup of \"work\" if a subscriber is too busy to consume all\n * notifications.\n */\n subscribe(): Source<ReplicaState>;\n}\n\nexport interface Replicator extends ReplicaStateNotifier {\n /**\n * Returns an opaque message for human-readable consumption. This is\n * purely for ensuring that the Replicator has started at least once to\n * bootstrap a new replica.\n */\n status(): Promise<ReadonlyJSONObject>;\n}\n\nexport type ReplicatorMode = 'backup' | 'serving';\n\nexport class ReplicatorService implements Replicator, Service {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #incrementalSyncer: IncrementalSyncer;\n readonly #worker: WriteWorkerClient;\n #runPromise: Promise<void> | undefined;\n\n constructor(\n lc: LogContext,\n taskID: string,\n id: string,\n mode: ReplicatorMode,\n changeStreamer: ChangeStreamer,\n statusDb: Database,\n worker: WriteWorkerClient,\n publishReplicationStatus: boolean,\n ) {\n this.id = id;\n this.#lc = lc\n .withContext('component', 'replicator')\n .withContext('serviceID', this.id);\n this.#worker = worker;\n\n this.#incrementalSyncer = new IncrementalSyncer(\n taskID,\n `${taskID}/${id}`,\n changeStreamer,\n statusDb,\n worker,\n mode,\n publishReplicationStatus,\n );\n }\n\n status() {\n return Promise.resolve({status: 'ok'});\n }\n\n run() {\n this.#runPromise = this.#incrementalSyncer.run(this.#lc);\n return this.#runPromise;\n }\n\n subscribe(): Source<ReplicaState> {\n return this.#incrementalSyncer.subscribe();\n }\n\n async stop() {\n this.#incrementalSyncer.stop(this.#lc);\n // Wait for the syncer's run loop to finish so that any in-flight\n // worker.processMessage() call completes and clears #pending\n // before we send the 'stop' message to the worker.\n await this.#runPromise;\n await this.#worker.stop();\n }\n}\n"],"mappings":";;AAsDA,IAAa,oBAAb,MAA8D;CAC5D;CACA;CACA;CACA;CACA;CAEA,YACE,IACA,QACA,IACA,MACA,gBACA,UACA,QACA,0BACA;AACA,OAAK,KAAK;AACV,QAAA,KAAW,GACR,YAAY,aAAa,aAAa,CACtC,YAAY,aAAa,KAAK,GAAG;AACpC,QAAA,SAAe;AAEf,QAAA,oBAA0B,IAAI,kBAC5B,QACA,GAAG,OAAO,GAAG,MACb,gBACA,UACA,QACA,MACA,yBACD;;CAGH,SAAS;AACP,SAAO,QAAQ,QAAQ,EAAC,QAAQ,MAAK,CAAC;;CAGxC,MAAM;AACJ,QAAA,aAAmB,MAAA,kBAAwB,IAAI,MAAA,GAAS;AACxD,SAAO,MAAA;;CAGT,YAAkC;AAChC,SAAO,MAAA,kBAAwB,WAAW;;CAG5C,MAAM,OAAO;AACX,QAAA,kBAAwB,KAAK,MAAA,GAAS;AAItC,QAAM,MAAA;AACN,QAAM,MAAA,OAAa,MAAM"}
1
+ {"version":3,"file":"replicator.js","names":["#lc","#incrementalSyncer","#worker","#runPromise"],"sources":["../../../../../../zero-cache/src/services/replicator/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {ReadonlyJSONObject} from '../../../../shared/src/json.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport type {Source} from '../../types/streams.ts';\nimport type {ChangeStreamer} from '../change-streamer/change-streamer.ts';\nimport type {Service} from '../service.ts';\nimport {IncrementalSyncer} from './incremental-sync.ts';\nimport type {WriteWorkerClient} from './write-worker-client.ts';\n\n/** See {@link ReplicaStateNotifier.subscribe()}. */\nexport type ReplicaState = {\n readonly state: 'version-ready';\n\n // Used in tests to verify behavior when additional information\n // is ferried in the future. Not set in production.\n readonly testSeqNum?: number;\n};\n\nexport interface ReplicaStateNotifier {\n /**\n * Creates a cancelable subscription of changes in the replica state.\n *\n * A `version-ready` message indicates that the replica is ready to be\n * read, and henceforth that a _new_ version is ready, i.e. whenever a\n * change is committed to the replica. The `version-ready` message itself\n * otherwise contains no other information; the subscriber queries the\n * replica for the current data.\n *\n * A `maintenance` state indicates that the replica should not be read from.\n * If a subscriber is holding any transaction locks, it should release them\n * until the next `version-ready` signal.\n *\n * Upon subscription, the current state of the replica is sent immediately\n * if known. If multiple notifications occur before the subscriber\n * can consume them, all but the last notification are discarded by the\n * Subscription object (i.e. not buffered). Thus, a subscriber only\n * ever consumes the current (i.e. known) state of the replica. This avoids\n * a buildup of \"work\" if a subscriber is too busy to consume all\n * notifications.\n */\n subscribe(): Source<ReplicaState>;\n}\n\nexport interface Replicator extends ReplicaStateNotifier {\n /**\n * Returns an opaque message for human-readable consumption. This is\n * purely for ensuring that the Replicator has started at least once to\n * bootstrap a new replica.\n */\n status(): Promise<ReadonlyJSONObject>;\n}\n\nexport type ReplicatorMode = 'backup' | 'serving';\n\nexport class ReplicatorService implements Replicator, Service {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #incrementalSyncer: IncrementalSyncer;\n readonly #worker: WriteWorkerClient;\n #runPromise: Promise<void> | undefined;\n\n constructor(\n lc: LogContext,\n taskID: string,\n id: string,\n mode: ReplicatorMode,\n changeStreamer: ChangeStreamer,\n statusDb: Database,\n worker: WriteWorkerClient,\n publishReplicationStatus: boolean,\n ) {\n this.id = id;\n this.#lc = lc\n .withContext('component', 'replicator')\n .withContext('serviceID', this.id);\n this.#worker = worker;\n\n this.#incrementalSyncer = new IncrementalSyncer(\n this.#lc,\n taskID,\n `${taskID}/${id}`,\n changeStreamer,\n statusDb,\n worker,\n mode,\n publishReplicationStatus,\n );\n }\n\n status() {\n return Promise.resolve({status: 'ok'});\n }\n\n run() {\n this.#runPromise = this.#incrementalSyncer.run();\n return this.#runPromise;\n }\n\n subscribe(): Source<ReplicaState> {\n return this.#incrementalSyncer.subscribe();\n }\n\n async stop() {\n this.#incrementalSyncer.stop(this.#lc);\n // Wait for the syncer's run loop to finish so that any in-flight\n // worker.processMessage() call completes and clears #pending\n // before we send the 'stop' message to the worker.\n await this.#runPromise;\n await this.#worker.stop();\n }\n}\n"],"mappings":";;AAsDA,IAAa,oBAAb,MAA8D;CAC5D;CACA;CACA;CACA;CACA;CAEA,YACE,IACA,QACA,IACA,MACA,gBACA,UACA,QACA,0BACA;AACA,OAAK,KAAK;AACV,QAAA,KAAW,GACR,YAAY,aAAa,aAAa,CACtC,YAAY,aAAa,KAAK,GAAG;AACpC,QAAA,SAAe;AAEf,QAAA,oBAA0B,IAAI,kBAC5B,MAAA,IACA,QACA,GAAG,OAAO,GAAG,MACb,gBACA,UACA,QACA,MACA,yBACD;;CAGH,SAAS;AACP,SAAO,QAAQ,QAAQ,EAAC,QAAQ,MAAK,CAAC;;CAGxC,MAAM;AACJ,QAAA,aAAmB,MAAA,kBAAwB,KAAK;AAChD,SAAO,MAAA;;CAGT,YAAkC;AAChC,SAAO,MAAA,kBAAwB,WAAW;;CAG5C,MAAM,OAAO;AACX,QAAA,kBAAwB,KAAK,MAAA,GAAS;AAItC,QAAM,MAAA;AACN,QAAM,MAAA,OAAa,MAAM"}
@@ -0,0 +1,12 @@
1
+ import type { ObservableResult } from '@opentelemetry/api';
2
+ import type { LogContext } from '@rocicorp/logger';
3
+ import type { ReplicationReport } from './report-schema.ts';
4
+ export declare class ReplicationReportRecorder {
5
+ #private;
6
+ constructor(lc: LogContext, now?: () => number);
7
+ record(report: ReplicationReport): void;
8
+ readonly reportUpstreamLag: (o: ObservableResult) => void;
9
+ readonly reportReplicaLag: (o: ObservableResult) => void;
10
+ readonly reportTotalLag: (o: ObservableResult) => void;
11
+ }
12
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/replicator/reporter/recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAM1D,qBAAa,yBAAyB;;gBAKxB,EAAE,EAAE,UAAU,EAAE,GAAG,eAAW;IAK1C,MAAM,CAAC,MAAM,EAAE,iBAAiB;IA2ChC,QAAQ,CAAC,iBAAiB,GAAI,GAAG,gBAAgB,UAK/C;IAEF,QAAQ,CAAC,gBAAgB,GAAI,GAAG,gBAAgB,UAK9C;IAEF,QAAQ,CAAC,cAAc,GAAI,GAAG,gBAAgB,UAU5C;CACH"}
@@ -0,0 +1,58 @@
1
+ import { getOrCreateGauge } from "../../../observability/metrics.js";
2
+ //#region ../zero-cache/src/services/replicator/reporter/recorder.ts
3
+ var LOG_ALL_REPLICATION_REPORTS_AT_DEBUG = process.env.ZERO_LOG_ALL_REPLICATION_REPORTS_AT_DEBUG === "1";
4
+ var ReplicationReportRecorder = class {
5
+ #lc;
6
+ #now;
7
+ #last = null;
8
+ constructor(lc, now = Date.now) {
9
+ this.#lc = lc;
10
+ this.#now = now;
11
+ }
12
+ record(report) {
13
+ const first = this.#last === null;
14
+ this.#last = report;
15
+ const { lastTimings } = report;
16
+ if (lastTimings) {
17
+ const total = lastTimings.replicateTimeMs - lastTimings.sendTimeMs;
18
+ if (total > 1e4) this.#lc.warn?.(`high replication lag: ${total} ms`, report);
19
+ else if (total > 1e3) this.#lc.info?.(`replication lag: ${total} ms`, report);
20
+ if (LOG_ALL_REPLICATION_REPORTS_AT_DEBUG) this.#lc.debug?.(`replication lag ${total} ms`, report);
21
+ }
22
+ if (first) {
23
+ getOrCreateGauge("replication", "upstream_lag", {
24
+ description: "Latency from sending an upstream replication report to receiving it in the replication stream",
25
+ unit: "millisecond"
26
+ }).addCallback(this.reportUpstreamLag);
27
+ getOrCreateGauge("replication", "replica_lag", {
28
+ description: "Latency from receiving an upstream replication report to its reaching the replica",
29
+ unit: "millisecond"
30
+ }).addCallback(this.reportReplicaLag);
31
+ getOrCreateGauge("replication", "total_lag", {
32
+ description: "Latency from sending an upstream replication report to its reaching the replica. This will be a (growing) estimate if the next expected report has yet to be received and the elapsed time has exceeded the previous report's total lag.",
33
+ unit: "millisecond"
34
+ }).addCallback(this.reportTotalLag);
35
+ }
36
+ }
37
+ reportUpstreamLag = (o) => {
38
+ const last = this.#last?.lastTimings;
39
+ if (last) o.observe(last.receiveTimeMs - last.sendTimeMs);
40
+ };
41
+ reportReplicaLag = (o) => {
42
+ const last = this.#last?.lastTimings;
43
+ if (last) o.observe(last.replicateTimeMs - last.receiveTimeMs);
44
+ };
45
+ reportTotalLag = (o) => {
46
+ const last = this.#last;
47
+ if (last) {
48
+ const nextLagEstimate = this.#now() - last.nextSendTimeMs;
49
+ const timings = last.lastTimings;
50
+ const lastLag = timings ? timings.replicateTimeMs - timings.sendTimeMs : 0;
51
+ o.observe(Math.max(lastLag, nextLagEstimate));
52
+ }
53
+ };
54
+ };
55
+ //#endregion
56
+ export { ReplicationReportRecorder };
57
+
58
+ //# sourceMappingURL=recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.js","names":["#lc","#now","#last"],"sources":["../../../../../../../zero-cache/src/services/replicator/reporter/recorder.ts"],"sourcesContent":["import type {ObservableResult} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {getOrCreateGauge} from '../../../observability/metrics.ts';\nimport type {ReplicationReport} from './report-schema.ts';\n\n// Hook for sanity checking lag reports in development.\nconst LOG_ALL_REPLICATION_REPORTS_AT_DEBUG =\n process.env.ZERO_LOG_ALL_REPLICATION_REPORTS_AT_DEBUG === '1';\n\nexport class ReplicationReportRecorder {\n readonly #lc: LogContext;\n readonly #now: () => number;\n #last: ReplicationReport | null = null;\n\n constructor(lc: LogContext, now = Date.now) {\n this.#lc = lc;\n this.#now = now;\n }\n\n record(report: ReplicationReport) {\n const first = this.#last === null;\n this.#last = report;\n\n const {lastTimings} = report;\n if (lastTimings) {\n const total = lastTimings.replicateTimeMs - lastTimings.sendTimeMs;\n if (total > 10_000) {\n this.#lc.warn?.(`high replication lag: ${total} ms`, report);\n } else if (total > 1_000) {\n this.#lc.info?.(`replication lag: ${total} ms`, report);\n }\n if (LOG_ALL_REPLICATION_REPORTS_AT_DEBUG) {\n this.#lc.debug?.(`replication lag ${total} ms`, report);\n }\n }\n\n if (first) {\n getOrCreateGauge('replication', 'upstream_lag', {\n description:\n 'Latency from sending an upstream replication report ' +\n 'to receiving it in the replication stream',\n unit: 'millisecond',\n }).addCallback(this.reportUpstreamLag);\n\n getOrCreateGauge('replication', 'replica_lag', {\n description:\n 'Latency from receiving an upstream replication report ' +\n 'to its reaching the replica',\n unit: 'millisecond',\n }).addCallback(this.reportReplicaLag);\n\n getOrCreateGauge('replication', 'total_lag', {\n description:\n 'Latency from sending an upstream replication report to its ' +\n 'reaching the replica. This will be a (growing) estimate if the ' +\n 'next expected report has yet to be received and the elapsed ' +\n `time has exceeded the previous report's total lag.`,\n unit: 'millisecond',\n }).addCallback(this.reportTotalLag);\n }\n }\n\n readonly reportUpstreamLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.receiveTimeMs - last.sendTimeMs);\n }\n };\n\n readonly reportReplicaLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.replicateTimeMs - last.receiveTimeMs);\n }\n };\n\n readonly reportTotalLag = (o: ObservableResult) => {\n const last = this.#last;\n if (last) {\n const nextLagEstimate = this.#now() - last.nextSendTimeMs;\n const timings = last.lastTimings;\n const lastLag = timings\n ? timings.replicateTimeMs - timings.sendTimeMs\n : 0;\n o.observe(Math.max(lastLag, nextLagEstimate));\n }\n };\n}\n"],"mappings":";;AAMA,IAAM,uCACJ,QAAQ,IAAI,8CAA8C;AAE5D,IAAa,4BAAb,MAAuC;CACrC;CACA;CACA,QAAkC;CAElC,YAAY,IAAgB,MAAM,KAAK,KAAK;AAC1C,QAAA,KAAW;AACX,QAAA,MAAY;;CAGd,OAAO,QAA2B;EAChC,MAAM,QAAQ,MAAA,SAAe;AAC7B,QAAA,OAAa;EAEb,MAAM,EAAC,gBAAe;AACtB,MAAI,aAAa;GACf,MAAM,QAAQ,YAAY,kBAAkB,YAAY;AACxD,OAAI,QAAQ,IACV,OAAA,GAAS,OAAO,yBAAyB,MAAM,MAAM,OAAO;YACnD,QAAQ,IACjB,OAAA,GAAS,OAAO,oBAAoB,MAAM,MAAM,OAAO;AAEzD,OAAI,qCACF,OAAA,GAAS,QAAQ,mBAAmB,MAAM,MAAM,OAAO;;AAI3D,MAAI,OAAO;AACT,oBAAiB,eAAe,gBAAgB;IAC9C,aACE;IAEF,MAAM;IACP,CAAC,CAAC,YAAY,KAAK,kBAAkB;AAEtC,oBAAiB,eAAe,eAAe;IAC7C,aACE;IAEF,MAAM;IACP,CAAC,CAAC,YAAY,KAAK,iBAAiB;AAErC,oBAAiB,eAAe,aAAa;IAC3C,aACE;IAIF,MAAM;IACP,CAAC,CAAC,YAAY,KAAK,eAAe;;;CAIvC,qBAA8B,MAAwB;EACpD,MAAM,OAAO,MAAA,MAAY;AACzB,MAAI,KACF,GAAE,QAAQ,KAAK,gBAAgB,KAAK,WAAW;;CAInD,oBAA6B,MAAwB;EACnD,MAAM,OAAO,MAAA,MAAY;AACzB,MAAI,KACF,GAAE,QAAQ,KAAK,kBAAkB,KAAK,cAAc;;CAIxD,kBAA2B,MAAwB;EACjD,MAAM,OAAO,MAAA;AACb,MAAI,MAAM;GACR,MAAM,kBAAkB,MAAA,KAAW,GAAG,KAAK;GAC3C,MAAM,UAAU,KAAK;GACrB,MAAM,UAAU,UACZ,QAAQ,kBAAkB,QAAQ,aAClC;AACJ,KAAE,QAAQ,KAAK,IAAI,SAAS,gBAAgB,CAAC"}
@@ -0,0 +1,35 @@
1
+ import * as v from '../../../../../shared/src/valita.ts';
2
+ export declare const changeSourceTimingsSchema: v.ObjectType<{
3
+ sendTimeMs: v.Type<number>;
4
+ commitTimeMs: v.Type<number>;
5
+ receiveTimeMs: v.Type<number>;
6
+ }, undefined>;
7
+ export declare const changeSourceReportSchema: v.ObjectType<{
8
+ lastTimings: v.ObjectType<{
9
+ sendTimeMs: v.Type<number>;
10
+ commitTimeMs: v.Type<number>;
11
+ receiveTimeMs: v.Type<number>;
12
+ }, undefined>;
13
+ nextSendTimeMs: v.Type<number>;
14
+ }, undefined>;
15
+ export declare const replicationTimingsSchema: v.ObjectType<Omit<{
16
+ sendTimeMs: v.Type<number>;
17
+ commitTimeMs: v.Type<number>;
18
+ receiveTimeMs: v.Type<number>;
19
+ }, "replicateTimeMs"> & {
20
+ replicateTimeMs: v.Type<number>;
21
+ }, undefined>;
22
+ export declare const replicationReportSchema: v.ObjectType<{
23
+ lastTimings: v.Optional<{
24
+ sendTimeMs: number;
25
+ commitTimeMs: number;
26
+ receiveTimeMs: number;
27
+ replicateTimeMs: number;
28
+ }>;
29
+ nextSendTimeMs: v.Type<number>;
30
+ }, undefined>;
31
+ export type ChangeSourceTimings = v.Infer<typeof changeSourceTimingsSchema>;
32
+ export type ChangeSourceReport = v.Infer<typeof changeSourceReportSchema>;
33
+ export type ReplicationTimings = v.Infer<typeof replicationTimingsSchema>;
34
+ export type ReplicationReport = v.Infer<typeof replicationReportSchema>;
35
+ //# sourceMappingURL=report-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-schema.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/replicator/reporter/report-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,qCAAqC,CAAC;AAEzD,eAAO,MAAM,yBAAyB;;;;aAIpC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;aAGnC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;aAEnC,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;aAGlC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { valita_exports } from "../../../../../shared/src/valita.js";
2
+ //#region ../zero-cache/src/services/replicator/reporter/report-schema.ts
3
+ var changeSourceTimingsSchema = valita_exports.object({
4
+ sendTimeMs: valita_exports.number(),
5
+ commitTimeMs: valita_exports.number(),
6
+ receiveTimeMs: valita_exports.number()
7
+ });
8
+ var changeSourceReportSchema = valita_exports.object({
9
+ lastTimings: changeSourceTimingsSchema,
10
+ nextSendTimeMs: valita_exports.number()
11
+ });
12
+ var replicationTimingsSchema = changeSourceTimingsSchema.extend({ replicateTimeMs: valita_exports.number() });
13
+ valita_exports.object({
14
+ lastTimings: replicationTimingsSchema.optional(),
15
+ nextSendTimeMs: valita_exports.number()
16
+ });
17
+ //#endregion
18
+ export { changeSourceReportSchema, changeSourceTimingsSchema };
19
+
20
+ //# sourceMappingURL=report-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-schema.js","names":[],"sources":["../../../../../../../zero-cache/src/services/replicator/reporter/report-schema.ts"],"sourcesContent":["import * as v from '../../../../../shared/src/valita.ts';\n\nexport const changeSourceTimingsSchema = v.object({\n sendTimeMs: v.number(),\n commitTimeMs: v.number(),\n receiveTimeMs: v.number(),\n});\n\nexport const changeSourceReportSchema = v.object({\n lastTimings: changeSourceTimingsSchema,\n nextSendTimeMs: v.number(),\n});\n\nexport const replicationTimingsSchema = changeSourceTimingsSchema.extend({\n replicateTimeMs: v.number(),\n});\n\nexport const replicationReportSchema = v.object({\n lastTimings: replicationTimingsSchema.optional(),\n nextSendTimeMs: v.number(),\n});\n\nexport type ChangeSourceTimings = v.Infer<typeof changeSourceTimingsSchema>;\nexport type ChangeSourceReport = v.Infer<typeof changeSourceReportSchema>;\n\nexport type ReplicationTimings = v.Infer<typeof replicationTimingsSchema>;\nexport type ReplicationReport = v.Infer<typeof replicationReportSchema>;\n"],"mappings":";;AAEA,IAAa,4BAA4B,eAAE,OAAO;CAChD,YAAY,eAAE,QAAQ;CACtB,cAAc,eAAE,QAAQ;CACxB,eAAe,eAAE,QAAQ;CAC1B,CAAC;AAEF,IAAa,2BAA2B,eAAE,OAAO;CAC/C,aAAa;CACb,gBAAgB,eAAE,QAAQ;CAC3B,CAAC;AAEF,IAAa,2BAA2B,0BAA0B,OAAO,EACvE,iBAAiB,eAAE,QAAQ,EAC5B,CAAC;AAEqC,eAAE,OAAO;CAC9C,aAAa,yBAAyB,UAAU;CAChD,gBAAgB,eAAE,QAAQ;CAC3B,CAAC"}
@@ -8,8 +8,8 @@ import { buildPipeline } from "../../../zql/src/builder/builder.js";
8
8
  import { computeZqlSpecs } from "../db/lite-tables.js";
9
9
  import { astToZQL } from "../../../ast-to-zql/src/ast-to-zql.js";
10
10
  import { formatOutput } from "../../../ast-to-zql/src/format.js";
11
- import { transformAndHashQuery } from "../auth/read-authorizer.js";
12
11
  import { resolveSimpleScalarSubqueries } from "../../../zqlite/src/resolve-scalar-subqueries.js";
12
+ import { transformAndHashQuery } from "../auth/read-authorizer.js";
13
13
  import { hydrate } from "./view-syncer/pipeline-driver.js";
14
14
  //#region ../zero-cache/src/services/run-ast.ts
15
15
  async function runAst(lc, clientSchema, ast, isTransformed, options, yieldProcess) {
@@ -4,7 +4,7 @@ import { Database } from "../../../../zqlite/src/db.js";
4
4
  import { getServerVersion, isAdminPasswordValid } from "../../config/zero-config.js";
5
5
  import { StatementRunner } from "../../db/statements.js";
6
6
  import { loadPermissions } from "../../auth/load-permissions.js";
7
- import { _usingCtx } from "../../../../_virtual/_@oxc-project_runtime@0.115.0/helpers/usingCtx.js";
7
+ import { _usingCtx } from "../../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/usingCtx.js";
8
8
  import { analyzeQuery } from "../analyze.js";
9
9
  //#region ../zero-cache/src/services/view-syncer/inspect-handler.ts
10
10
  async function handleInspect(lc, body, cvr, client, inspectorDelegate, clientGroupID, cvrStore, config, headerOptions, userQueryURL, auth) {
@@ -1 +1 @@
1
- {"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/pg.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,QAAQ,EAAE,EAAc,KAAK,YAAY,EAAC,MAAM,UAAU,CAAC;AAClE,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAc9E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAqC7D;AAED,iBAAS,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAuBhD;AAcD,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAoBvE;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAkFrE;AAED,iBAAS,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,uBAAoB,WAAgB;;;;;;;;;;;;;;;;;;2BAwBlD,OAAO;;;;;;2BAOP,OAAO;;;;;;2BAQP,MAAM,GAAG,IAAI;;;;;;2BAUb,MAAM;uBACV,MAAM,GAAG,MAAM;;;CAG9B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,cAAc,CAAC;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,wBAAgB,QAAQ,CACtB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;CAC/B,CAAC,EACF,IAAI,CAAC,EAAE,WAAW,GACjB,UAAU,CA0CZ;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,QAE/D;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOhD,CAAC"}
1
+ {"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/pg.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,QAAQ,EAAE,EAAc,KAAK,YAAY,EAAC,MAAM,UAAU,CAAC;AAClE,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAc9E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAqC7D;AAED,iBAAS,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAuBhD;AAcD,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAoBvE;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAkFrE;AAED,iBAAS,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,uBAAoB,WAAgB;;;;;;;;;;;;;;;;;;2BAwBlD,OAAO;;;;;;2BAOP,OAAO;;;;;;2BAQP,MAAM,GAAG,IAAI;;;;;;2BAUb,MAAM;uBACV,MAAM,GAAG,MAAM;;;CAG9B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,cAAc,CAAC;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,wBAAgB,QAAQ,CACtB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;CAC/B,CAAC,EACF,IAAI,CAAC,EAAE,WAAW,GACjB,UAAU,CA4CZ;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,QAE/D;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOhD,CAAC"}
@@ -137,6 +137,8 @@ function pgClient(lc, connectionURI, options, opts) {
137
137
  lc.debug?.(n);
138
138
  return;
139
139
  case "WARNING":
140
+ lc.warn?.(n);
141
+ return;
140
142
  case "EXCEPTION":
141
143
  lc.error?.(n);
142
144
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"pg.js","names":[],"sources":["../../../../../zero-cache/src/types/pg.ts"],"sourcesContent":["import {PreciseDate} from '@google-cloud/precise-date';\nimport {OID} from '@postgresql-typed/oids';\nimport type {LogContext} from '@rocicorp/logger';\nimport postgres, {type Notice, type PostgresType} from 'postgres';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {\n DATE,\n JSON,\n JSONB,\n NUMERIC,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n} from './pg-types.ts';\n\n// exported for testing.\nexport function timestampToFpMillis(timestamp: string): number {\n // Convert from PG's time string, e.g. \"1999-01-08 12:05:06+00\" to \"Z\"\n // format expected by PreciseDate.\n timestamp = timestamp.replace(' ', 'T');\n const positiveOffset = timestamp.includes('+');\n const tzSplitIndex = positiveOffset\n ? timestamp.lastIndexOf('+')\n : timestamp.indexOf('-', timestamp.indexOf('T'));\n const timezoneOffset =\n tzSplitIndex === -1 ? undefined : timestamp.substring(tzSplitIndex);\n const tsWithoutTimezone =\n (tzSplitIndex === -1 ? timestamp : timestamp.substring(0, tzSplitIndex)) +\n 'Z';\n\n try {\n // PreciseDate does not return microsecond precision unless the provided\n // timestamp is in UTC time so we need to add the timezone offset back in.\n const fullTime = new PreciseDate(tsWithoutTimezone).getFullTime();\n const millis = Number(fullTime / 1_000_000n);\n const nanos = Number(fullTime % 1_000_000n);\n const ret = millis + nanos * 1e-6; // floating point milliseconds\n\n // add back in the timezone offset\n if (timezoneOffset) {\n const [hours, minutes] = timezoneOffset.split(':');\n const offset =\n Math.abs(Number(hours)) * 60 + (minutes ? Number(minutes) : 0);\n const offsetMillis = offset * 60 * 1_000;\n // If it is a positive offset, we subtract the offset from the UTC\n // because we passed in the \"local time\" as if it was UTC.\n // The opposite is true for negative offsets.\n return positiveOffset ? ret - offsetMillis : ret + offsetMillis;\n }\n return ret;\n } catch (e) {\n throw new Error(`Error parsing ${timestamp}`, {cause: e});\n }\n}\n\nfunction serializeTimestamp(val: unknown): string {\n switch (typeof val) {\n case 'string':\n return val; // Let Postgres parse it\n case 'number': {\n if (Number.isInteger(val)) {\n return new PreciseDate(val).toISOString();\n }\n // Convert floating point to bigint nanoseconds.\n const nanoseconds =\n 1_000_000n * BigInt(Math.trunc(val)) +\n BigInt(Math.trunc((val % 1) * 1e6));\n return new PreciseDate(nanoseconds).toISOString();\n }\n // Note: Don't support bigint inputs until we decide what the semantics are (e.g. micros vs nanos)\n // case 'bigint':\n // return new PreciseDate(val).toISOString();\n default:\n if (val instanceof Date) {\n return val.toISOString();\n }\n }\n throw new Error(`Unsupported type \"${typeof val}\" for timestamp: ${val}`);\n}\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction serializeTime(x: unknown, type: 'time' | 'timetz'): string {\n switch (typeof x) {\n case 'string':\n return x; // Let Postgres parse it\n case 'number':\n return millisecondsToPostgresTime(x);\n }\n throw new Error(`Unsupported type \"${typeof x}\" for ${type}: ${x}`);\n}\n\nexport function millisecondsToPostgresTime(milliseconds: number): string {\n if (milliseconds < 0) {\n throw new Error('Milliseconds cannot be negative');\n }\n\n if (milliseconds >= MILLISECONDS_PER_DAY) {\n throw new Error(\n `Milliseconds cannot exceed 24 hours (${MILLISECONDS_PER_DAY}ms)`,\n );\n }\n\n milliseconds = Math.floor(milliseconds); // Ensure it's an integer\n\n const totalSeconds = Math.floor(milliseconds / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const ms = milliseconds % 1000;\n\n return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}+00`;\n}\n\nexport function postgresTimeToMilliseconds(timeString: string): number {\n // Validate basic format\n if (!timeString || typeof timeString !== 'string') {\n throw new Error('Invalid time string: must be a non-empty string');\n }\n\n // Regular expression to match HH:MM:SS, HH:MM:SS.mmm, or HH:MM:SS+00 / HH:MM:SS.mmm+00\n // Supports optional timezone offset\n const timeRegex =\n /^(\\d{1,2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?(?:([+-])(\\d{1,2})(?::(\\d{2}))?)?$/;\n const match = timeString.match(timeRegex);\n\n if (!match) {\n throw new Error(\n `Invalid time format: \"${timeString}\". Expected HH:MM:SS[.mmm][+|-HH[:MM]]`,\n );\n }\n\n // Extract components\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const seconds = parseInt(match[3], 10);\n // Handle optional milliseconds, pad right with zeros if needed\n let milliseconds = 0;\n if (match[4]) {\n // Pad microseconds to 6 digits\n const msString = match[4].padEnd(6, '0');\n // slice milliseconds out of the microseconds\n // e.g. 123456 -> 123, 1234 -> 123,\n milliseconds = parseInt(msString.slice(0, 3), 10);\n }\n\n // Validate ranges\n if (hours < 0 || hours > 24) {\n throw new Error(\n `Invalid hours: ${hours}. Must be between 0 and 24 (24 means end of day)`,\n );\n }\n\n if (minutes < 0 || minutes >= 60) {\n throw new Error(`Invalid minutes: ${minutes}. Must be between 0 and 59`);\n }\n\n if (seconds < 0 || seconds >= 60) {\n throw new Error(`Invalid seconds: ${seconds}. Must be between 0 and 59`);\n }\n\n if (milliseconds < 0 || milliseconds >= 1000) {\n throw new Error(\n `Invalid milliseconds: ${milliseconds}. Must be between 0 and 999`,\n );\n }\n\n // Special case: PostgreSQL allows 24:00:00 to represent end of day\n if (hours === 24 && (minutes !== 0 || seconds !== 0 || milliseconds !== 0)) {\n throw new Error(\n 'Invalid time: when hours is 24, minutes, seconds, and milliseconds must be 0',\n );\n }\n\n // Calculate total milliseconds\n let totalMs =\n hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;\n\n // Timezone Offset\n if (match[5]) {\n const sign = match[5] === '+' ? 1 : -1;\n const tzHours = parseInt(match[6], 10);\n const tzMinutes = match[7] ? parseInt(match[7], 10) : 0;\n const offsetMs = sign * (tzHours * 3600000 + tzMinutes * 60000);\n totalMs -= offsetMs;\n }\n\n // Normalize to 0-24h only if outside valid range\n if (totalMs > MILLISECONDS_PER_DAY || totalMs < 0) {\n return (\n ((totalMs % MILLISECONDS_PER_DAY) + MILLISECONDS_PER_DAY) %\n MILLISECONDS_PER_DAY\n );\n }\n\n return totalMs;\n}\n\nfunction dateToUTCMidnight(date: string): number {\n const d = new Date(date);\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n}\n\n/**\n * The (javascript) types of objects that can be returned by our configured\n * Postgres clients. For initial-sync, these comes from the postgres.js client:\n *\n * https://github.com/porsager/postgres/blob/master/src/types.js\n *\n * and for the replication stream these come from the the node-postgres client:\n *\n * https://github.com/brianc/node-pg-types/blob/master/lib/textParsers.js\n */\nexport type PostgresValueType = JSONValue | Uint8Array;\n\nexport type TypeOptions = {\n /**\n * Sends strings directly as JSON values (i.e. without JSON stringification).\n * The application is responsible for ensuring that string inputs for JSON\n * columns are already stringified. Other data types (e.g. objects) will\n * still be stringified by the pg client.\n */\n sendStringAsJson?: boolean;\n};\n\n/**\n * Configures types for the Postgres.js client library (`postgres`).\n *\n * @param jsonAsString Keep JSON / JSONB values as strings instead of parsing.\n */\nexport const postgresTypeConfig = ({sendStringAsJson}: TypeOptions = {}) => ({\n // Type the type IDs as `number` so that Typescript doesn't complain about\n // referencing external types during type inference.\n types: {\n bigint: postgres.BigInt,\n json: {\n to: JSON,\n from: [JSON, JSONB],\n serialize: sendStringAsJson\n ? (x: unknown) => (typeof x === 'string' ? x : BigIntJSON.stringify(x))\n : BigIntJSON.stringify,\n parse: BigIntJSON.parse,\n },\n // Timestamps are converted to PreciseDate objects.\n timestamp: {\n to: TIMESTAMP,\n from: [TIMESTAMP, TIMESTAMPTZ],\n serialize: serializeTimestamp,\n parse: timestampToFpMillis,\n },\n // Times are converted as strings\n time: {\n to: TIME,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'time'),\n parse: postgresTimeToMilliseconds,\n },\n\n timetz: {\n to: TIMETZ,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'timetz'),\n parse: postgresTimeToMilliseconds,\n },\n\n // The DATE type is stored directly as the PG normalized date string.\n date: {\n to: DATE,\n from: [DATE],\n serialize: (x: string | Date) =>\n (x instanceof Date ? x : new Date(x)).toISOString(),\n parse: dateToUTCMidnight,\n },\n // Returns a `js` number which can lose precision for large numbers.\n // JS number is 53 bits so this should generally not occur.\n // An API will be provided for users to override this type.\n numeric: {\n to: NUMERIC,\n from: [NUMERIC],\n serialize: (x: number) => String(x), // pg expects a string\n parse: (x: string | number) => Number(x),\n },\n },\n});\n\nexport type PostgresDB = postgres.Sql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport type PostgresTransaction = postgres.TransactionSql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport function pgClient(\n lc: LogContext,\n connectionURI: string,\n options?: postgres.Options<{\n bigint: PostgresType<bigint>;\n json: PostgresType<JSONValue>;\n }>,\n opts?: TypeOptions,\n): PostgresDB {\n const onnotice = (n: Notice) => {\n // https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html#PLPGSQL-STATEMENTS-RAISE\n switch (n.severity) {\n case 'NOTICE':\n return; // silenced\n case 'DEBUG':\n lc.debug?.(n);\n return;\n case 'WARNING':\n case 'EXCEPTION':\n lc.error?.(n);\n return;\n case 'LOG':\n case 'INFO':\n default:\n lc.info?.(n);\n }\n };\n const url = new URL(connectionURI);\n const sslFlag =\n url.searchParams.get('ssl') ?? url.searchParams.get('sslmode') ?? 'prefer';\n\n let ssl: boolean | 'prefer' | {rejectUnauthorized: boolean};\n if (sslFlag === 'disable' || sslFlag === 'false') {\n ssl = false;\n } else if (sslFlag === 'no-verify') {\n ssl = {rejectUnauthorized: false};\n } else {\n ssl = sslFlag as 'prefer';\n }\n\n // Set connections to expire between 5 and 10 minutes to free up state on PG.\n const maxLifetimeSeconds = randInt(5 * 60, 10 * 60);\n\n return postgres(connectionURI, {\n ...postgresTypeConfig(opts),\n onnotice,\n ['max_lifetime']: maxLifetimeSeconds,\n ssl,\n ...options,\n });\n}\n\n/**\n * Disables any statement_timeout for the current transaction. By default,\n * Postgres does not impose a statement timeout, but some users and providers\n * set one at the database level (even though it is explicitly discouraged by\n * the Postgres documentation).\n *\n * Zero logic in particular often does not fit into the category of general\n * application logic; for potentially long-running operations like migrations\n * and background cleanup, the statement timeout should be disabled to prevent\n * these operations from timing out.\n */\nexport function disableStatementTimeout(sql: PostgresTransaction) {\n void sql`SET LOCAL statement_timeout = 0;`.execute();\n}\n\nexport const typeNameByOID: Record<number, string> = Object.freeze(\n Object.fromEntries(\n Object.entries(OID).map(([name, oid]) => [\n oid,\n name.startsWith('_') ? `${name.substring(1)}[]` : name,\n ]),\n ),\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"pg.js","names":[],"sources":["../../../../../zero-cache/src/types/pg.ts"],"sourcesContent":["import {PreciseDate} from '@google-cloud/precise-date';\nimport {OID} from '@postgresql-typed/oids';\nimport type {LogContext} from '@rocicorp/logger';\nimport postgres, {type Notice, type PostgresType} from 'postgres';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {\n DATE,\n JSON,\n JSONB,\n NUMERIC,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n} from './pg-types.ts';\n\n// exported for testing.\nexport function timestampToFpMillis(timestamp: string): number {\n // Convert from PG's time string, e.g. \"1999-01-08 12:05:06+00\" to \"Z\"\n // format expected by PreciseDate.\n timestamp = timestamp.replace(' ', 'T');\n const positiveOffset = timestamp.includes('+');\n const tzSplitIndex = positiveOffset\n ? timestamp.lastIndexOf('+')\n : timestamp.indexOf('-', timestamp.indexOf('T'));\n const timezoneOffset =\n tzSplitIndex === -1 ? undefined : timestamp.substring(tzSplitIndex);\n const tsWithoutTimezone =\n (tzSplitIndex === -1 ? timestamp : timestamp.substring(0, tzSplitIndex)) +\n 'Z';\n\n try {\n // PreciseDate does not return microsecond precision unless the provided\n // timestamp is in UTC time so we need to add the timezone offset back in.\n const fullTime = new PreciseDate(tsWithoutTimezone).getFullTime();\n const millis = Number(fullTime / 1_000_000n);\n const nanos = Number(fullTime % 1_000_000n);\n const ret = millis + nanos * 1e-6; // floating point milliseconds\n\n // add back in the timezone offset\n if (timezoneOffset) {\n const [hours, minutes] = timezoneOffset.split(':');\n const offset =\n Math.abs(Number(hours)) * 60 + (minutes ? Number(minutes) : 0);\n const offsetMillis = offset * 60 * 1_000;\n // If it is a positive offset, we subtract the offset from the UTC\n // because we passed in the \"local time\" as if it was UTC.\n // The opposite is true for negative offsets.\n return positiveOffset ? ret - offsetMillis : ret + offsetMillis;\n }\n return ret;\n } catch (e) {\n throw new Error(`Error parsing ${timestamp}`, {cause: e});\n }\n}\n\nfunction serializeTimestamp(val: unknown): string {\n switch (typeof val) {\n case 'string':\n return val; // Let Postgres parse it\n case 'number': {\n if (Number.isInteger(val)) {\n return new PreciseDate(val).toISOString();\n }\n // Convert floating point to bigint nanoseconds.\n const nanoseconds =\n 1_000_000n * BigInt(Math.trunc(val)) +\n BigInt(Math.trunc((val % 1) * 1e6));\n return new PreciseDate(nanoseconds).toISOString();\n }\n // Note: Don't support bigint inputs until we decide what the semantics are (e.g. micros vs nanos)\n // case 'bigint':\n // return new PreciseDate(val).toISOString();\n default:\n if (val instanceof Date) {\n return val.toISOString();\n }\n }\n throw new Error(`Unsupported type \"${typeof val}\" for timestamp: ${val}`);\n}\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction serializeTime(x: unknown, type: 'time' | 'timetz'): string {\n switch (typeof x) {\n case 'string':\n return x; // Let Postgres parse it\n case 'number':\n return millisecondsToPostgresTime(x);\n }\n throw new Error(`Unsupported type \"${typeof x}\" for ${type}: ${x}`);\n}\n\nexport function millisecondsToPostgresTime(milliseconds: number): string {\n if (milliseconds < 0) {\n throw new Error('Milliseconds cannot be negative');\n }\n\n if (milliseconds >= MILLISECONDS_PER_DAY) {\n throw new Error(\n `Milliseconds cannot exceed 24 hours (${MILLISECONDS_PER_DAY}ms)`,\n );\n }\n\n milliseconds = Math.floor(milliseconds); // Ensure it's an integer\n\n const totalSeconds = Math.floor(milliseconds / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const ms = milliseconds % 1000;\n\n return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}+00`;\n}\n\nexport function postgresTimeToMilliseconds(timeString: string): number {\n // Validate basic format\n if (!timeString || typeof timeString !== 'string') {\n throw new Error('Invalid time string: must be a non-empty string');\n }\n\n // Regular expression to match HH:MM:SS, HH:MM:SS.mmm, or HH:MM:SS+00 / HH:MM:SS.mmm+00\n // Supports optional timezone offset\n const timeRegex =\n /^(\\d{1,2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?(?:([+-])(\\d{1,2})(?::(\\d{2}))?)?$/;\n const match = timeString.match(timeRegex);\n\n if (!match) {\n throw new Error(\n `Invalid time format: \"${timeString}\". Expected HH:MM:SS[.mmm][+|-HH[:MM]]`,\n );\n }\n\n // Extract components\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const seconds = parseInt(match[3], 10);\n // Handle optional milliseconds, pad right with zeros if needed\n let milliseconds = 0;\n if (match[4]) {\n // Pad microseconds to 6 digits\n const msString = match[4].padEnd(6, '0');\n // slice milliseconds out of the microseconds\n // e.g. 123456 -> 123, 1234 -> 123,\n milliseconds = parseInt(msString.slice(0, 3), 10);\n }\n\n // Validate ranges\n if (hours < 0 || hours > 24) {\n throw new Error(\n `Invalid hours: ${hours}. Must be between 0 and 24 (24 means end of day)`,\n );\n }\n\n if (minutes < 0 || minutes >= 60) {\n throw new Error(`Invalid minutes: ${minutes}. Must be between 0 and 59`);\n }\n\n if (seconds < 0 || seconds >= 60) {\n throw new Error(`Invalid seconds: ${seconds}. Must be between 0 and 59`);\n }\n\n if (milliseconds < 0 || milliseconds >= 1000) {\n throw new Error(\n `Invalid milliseconds: ${milliseconds}. Must be between 0 and 999`,\n );\n }\n\n // Special case: PostgreSQL allows 24:00:00 to represent end of day\n if (hours === 24 && (minutes !== 0 || seconds !== 0 || milliseconds !== 0)) {\n throw new Error(\n 'Invalid time: when hours is 24, minutes, seconds, and milliseconds must be 0',\n );\n }\n\n // Calculate total milliseconds\n let totalMs =\n hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;\n\n // Timezone Offset\n if (match[5]) {\n const sign = match[5] === '+' ? 1 : -1;\n const tzHours = parseInt(match[6], 10);\n const tzMinutes = match[7] ? parseInt(match[7], 10) : 0;\n const offsetMs = sign * (tzHours * 3600000 + tzMinutes * 60000);\n totalMs -= offsetMs;\n }\n\n // Normalize to 0-24h only if outside valid range\n if (totalMs > MILLISECONDS_PER_DAY || totalMs < 0) {\n return (\n ((totalMs % MILLISECONDS_PER_DAY) + MILLISECONDS_PER_DAY) %\n MILLISECONDS_PER_DAY\n );\n }\n\n return totalMs;\n}\n\nfunction dateToUTCMidnight(date: string): number {\n const d = new Date(date);\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n}\n\n/**\n * The (javascript) types of objects that can be returned by our configured\n * Postgres clients. For initial-sync, these comes from the postgres.js client:\n *\n * https://github.com/porsager/postgres/blob/master/src/types.js\n *\n * and for the replication stream these come from the the node-postgres client:\n *\n * https://github.com/brianc/node-pg-types/blob/master/lib/textParsers.js\n */\nexport type PostgresValueType = JSONValue | Uint8Array;\n\nexport type TypeOptions = {\n /**\n * Sends strings directly as JSON values (i.e. without JSON stringification).\n * The application is responsible for ensuring that string inputs for JSON\n * columns are already stringified. Other data types (e.g. objects) will\n * still be stringified by the pg client.\n */\n sendStringAsJson?: boolean;\n};\n\n/**\n * Configures types for the Postgres.js client library (`postgres`).\n *\n * @param jsonAsString Keep JSON / JSONB values as strings instead of parsing.\n */\nexport const postgresTypeConfig = ({sendStringAsJson}: TypeOptions = {}) => ({\n // Type the type IDs as `number` so that Typescript doesn't complain about\n // referencing external types during type inference.\n types: {\n bigint: postgres.BigInt,\n json: {\n to: JSON,\n from: [JSON, JSONB],\n serialize: sendStringAsJson\n ? (x: unknown) => (typeof x === 'string' ? x : BigIntJSON.stringify(x))\n : BigIntJSON.stringify,\n parse: BigIntJSON.parse,\n },\n // Timestamps are converted to PreciseDate objects.\n timestamp: {\n to: TIMESTAMP,\n from: [TIMESTAMP, TIMESTAMPTZ],\n serialize: serializeTimestamp,\n parse: timestampToFpMillis,\n },\n // Times are converted as strings\n time: {\n to: TIME,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'time'),\n parse: postgresTimeToMilliseconds,\n },\n\n timetz: {\n to: TIMETZ,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'timetz'),\n parse: postgresTimeToMilliseconds,\n },\n\n // The DATE type is stored directly as the PG normalized date string.\n date: {\n to: DATE,\n from: [DATE],\n serialize: (x: string | Date) =>\n (x instanceof Date ? x : new Date(x)).toISOString(),\n parse: dateToUTCMidnight,\n },\n // Returns a `js` number which can lose precision for large numbers.\n // JS number is 53 bits so this should generally not occur.\n // An API will be provided for users to override this type.\n numeric: {\n to: NUMERIC,\n from: [NUMERIC],\n serialize: (x: number) => String(x), // pg expects a string\n parse: (x: string | number) => Number(x),\n },\n },\n});\n\nexport type PostgresDB = postgres.Sql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport type PostgresTransaction = postgres.TransactionSql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport function pgClient(\n lc: LogContext,\n connectionURI: string,\n options?: postgres.Options<{\n bigint: PostgresType<bigint>;\n json: PostgresType<JSONValue>;\n }>,\n opts?: TypeOptions,\n): PostgresDB {\n const onnotice = (n: Notice) => {\n // https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html#PLPGSQL-STATEMENTS-RAISE\n switch (n.severity) {\n case 'NOTICE':\n return; // silenced\n case 'DEBUG':\n lc.debug?.(n);\n return;\n case 'WARNING':\n lc.warn?.(n);\n return;\n case 'EXCEPTION':\n lc.error?.(n);\n return;\n case 'LOG':\n case 'INFO':\n default:\n lc.info?.(n);\n }\n };\n const url = new URL(connectionURI);\n const sslFlag =\n url.searchParams.get('ssl') ?? url.searchParams.get('sslmode') ?? 'prefer';\n\n let ssl: boolean | 'prefer' | {rejectUnauthorized: boolean};\n if (sslFlag === 'disable' || sslFlag === 'false') {\n ssl = false;\n } else if (sslFlag === 'no-verify') {\n ssl = {rejectUnauthorized: false};\n } else {\n ssl = sslFlag as 'prefer';\n }\n\n // Set connections to expire between 5 and 10 minutes to free up state on PG.\n const maxLifetimeSeconds = randInt(5 * 60, 10 * 60);\n\n return postgres(connectionURI, {\n ...postgresTypeConfig(opts),\n onnotice,\n ['max_lifetime']: maxLifetimeSeconds,\n ssl,\n ...options,\n });\n}\n\n/**\n * Disables any statement_timeout for the current transaction. By default,\n * Postgres does not impose a statement timeout, but some users and providers\n * set one at the database level (even though it is explicitly discouraged by\n * the Postgres documentation).\n *\n * Zero logic in particular often does not fit into the category of general\n * application logic; for potentially long-running operations like migrations\n * and background cleanup, the statement timeout should be disabled to prevent\n * these operations from timing out.\n */\nexport function disableStatementTimeout(sql: PostgresTransaction) {\n void sql`SET LOCAL statement_timeout = 0;`.execute();\n}\n\nexport const typeNameByOID: Record<number, string> = Object.freeze(\n Object.fromEntries(\n Object.entries(OID).map(([name, oid]) => [\n oid,\n name.startsWith('_') ? `${name.substring(1)}[]` : name,\n ]),\n ),\n);\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/replicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAEV,oBAAoB,EACpB,UAAU,EACX,MAAM,sCAAsC,CAAC;AAK9C,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAElD,eAAO,MAAM,qBAAqB,+CAIjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,UAEzE;AAkED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,YAAY,CAMnE;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,eAAe,EACrB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,QAGf;AAID,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,oBAAoB,QA8B/B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAM5E;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,QAE1D"}
1
+ {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/replicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAEV,oBAAoB,EACpB,UAAU,EACX,MAAM,sCAAsC,CAAC;AAK9C,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAElD,eAAO,MAAM,qBAAqB,+CAIjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,UAEzE;AAuED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,YAAY,CAMnE;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,eAAe,EACrB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,QAGf;AAID,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,oBAAoB,QA8B/B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAM5E;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,QAE1D"}
@@ -14,6 +14,7 @@ var MILLIS_PER_HOUR = 1e3 * 60 * 60;
14
14
  var MB = 1024 * 1024;
15
15
  async function connect(lc, { file, vacuumIntervalHours }, walMode, mode) {
16
16
  const replica = new Database(lc, file);
17
+ replica.pragma("busy_timeout = 1000");
17
18
  await upgradeReplica(lc, `${mode}-replica`, file);
18
19
  replica.pragma("journal_mode = delete");
19
20
  const [{ page_size: pageSize }] = replica.pragma("page_size");
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function connect(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: 'wal' | 'wal2',\n mode: ReplicaFileMode,\n): Promise<Database> {\n const replica = new Database(lc, file);\n\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n // Start by folding any (e.g. restored) WAL(2) files into the main db.\n replica.pragma('journal_mode = delete');\n\n const [{page_size: pageSize}] = replica.pragma<{page_size: number}>(\n 'page_size',\n );\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n replica.unsafeMode(false);\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n }\n }\n\n lc.info?.(`setting ${file} to ${walMode} mode`);\n replica.pragma(`journal_mode = ${walMode}`);\n\n const pragmas = getPragmaConfig(mode);\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n return replica;\n}\n\n/**\n * Returns the PragmaConfig for a given replica file mode.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n */\nexport function getPragmaConfig(mode: ReplicaFileMode): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: mode === 'backup' ? 0 : undefined,\n };\n}\n\nexport async function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n): Promise<Database> {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return await connect(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return connect(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return connect(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(_lc: LogContext, source: Worker): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg =>\n notifier.notifySubscribers(msg),\n );\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function connect(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: 'wal' | 'wal2',\n mode: ReplicaFileMode,\n): Promise<Database> {\n const replica = new Database(lc, file);\n\n // To allow other readers (e.g. the change-streamer) to access the replica\n // for stats / event publishing, allow a short busy_timeout for performing\n // locking operations.\n replica.pragma('busy_timeout = 1000');\n\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n // Start by folding any (e.g. restored) WAL(2) files into the main db.\n replica.pragma('journal_mode = delete');\n\n const [{page_size: pageSize}] = replica.pragma<{page_size: number}>(\n 'page_size',\n );\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n replica.unsafeMode(false);\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n }\n }\n\n lc.info?.(`setting ${file} to ${walMode} mode`);\n replica.pragma(`journal_mode = ${walMode}`);\n\n const pragmas = getPragmaConfig(mode);\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n return replica;\n}\n\n/**\n * Returns the PragmaConfig for a given replica file mode.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n */\nexport function getPragmaConfig(mode: ReplicaFileMode): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: mode === 'backup' ? 0 : undefined,\n };\n}\n\nexport async function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n): Promise<Database> {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return await connect(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return connect(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return connect(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(_lc: LogContext, source: Worker): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg =>\n notifier.notifySubscribers(msg),\n );\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * The current version of Zero.
4
4
  */
5
- var version = "1.0.0";
5
+ var version = "1.1.0";
6
6
  //#endregion
7
7
  export { version };
8
8
 
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/builder/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAE3D,OAAO,KAAK,EACV,GAAG,EAGH,SAAS,EAIT,WAAW,EAEX,QAAQ,EAIT,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAI1E,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAElE,OAAO,KAAK,EAAC,MAAM,EAAE,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAK1D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,kCAAkC,CAAC;AAE1E,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAkB,KAAK,mBAAmB,EAAC,MAAM,aAAa,CAAC;AAEtE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC,cAAc,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClD,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAElC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAE/C;;;;OAIG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAEjD;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAErC,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IAEjD,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IAElD,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAEnE,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;IAEhE;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,mBAAmB,EAC/B,EAAE,CAAC,EAAE,UAAU,EACf,YAAY,CAAC,EAAE,YAAY,GAC1B,KAAK,CAWP;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,GAAG,EACR,qBAAqB,EAAE,qBAAqB,GAAG,SAAS,OAsDzD;AAyBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAsB5D;AAqQD,wBAAgB,OAAO,CACrB,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,WAAW,EACtB,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,GACX,WAAW,CAsCb;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,WAAW,6EAa7D;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,SAAS,GACnB,SAAS,IAAI,mBAAmB,CAQlC;AAsHD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,EAAE,EAAE,UAAU,GACb,IAAI,CAgBN;AA8CD,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,SAAS,GACd,OAAO,CAWT;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,SAAS,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,OAAO,uCAYrC"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/builder/builder.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ import { Join } from "../ivm/join.js";
10
10
  import { Skip } from "../ivm/skip.js";
11
11
  import { completeOrdering } from "../query/complete-ordering.js";
12
12
  import { Take } from "../ivm/take.js";
13
+ import { Cap } from "../ivm/cap.js";
13
14
  import { UnionFanIn } from "../ivm/union-fan-in.js";
14
15
  import { UnionFanOut } from "../ivm/union-fan-out.js";
15
16
  import { planQuery } from "../planner/planner-builder.js";
@@ -112,7 +113,7 @@ function assertNoNotExists(condition) {
112
113
  default: unreachable(condition);
113
114
  }
114
115
  }
115
- function buildPipelineInternal(ast, delegate, queryID, name, partitionKey) {
116
+ function buildPipelineInternal(ast, delegate, queryID, name, partitionKey, isNonFlippedExistsChild) {
116
117
  const source = delegate.getSource(ast.table);
117
118
  if (!source) throw new Error(`Source not found: ${ast.table}`);
118
119
  ast = uniquifyCorrelatedSubqueryConditionAliases(ast);
@@ -125,7 +126,11 @@ function buildPipelineInternal(ast, delegate, queryID, name, partitionKey) {
125
126
  for (const key of csq.related.correlation.parentField) splitEditKeys.add(key);
126
127
  }
127
128
  if (ast.related) for (const csq of ast.related) for (const key of csq.correlation.parentField) splitEditKeys.add(key);
128
- const conn = source.connect(must(ast.orderBy), ast.where, splitEditKeys, delegate.debug);
129
+ if (isNonFlippedExistsChild) {
130
+ assert(ast.start === void 0, "EXISTS subqueries must not have start");
131
+ assert(ast.related === void 0, "EXISTS subqueries must not have related");
132
+ }
133
+ const conn = source.connect(isNonFlippedExistsChild ? void 0 : must(ast.orderBy), ast.where, splitEditKeys, delegate.debug);
129
134
  let end = delegate.decorateSourceInput(conn, queryID);
130
135
  end = delegate.decorateInput(end, `${name}:source(${ast.table})`);
131
136
  const { fullyAppliedFilters } = conn;
@@ -142,7 +147,12 @@ function buildPipelineInternal(ast, delegate, queryID, name, partitionKey) {
142
147
  }
143
148
  }, delegate, queryID, end, name, true);
144
149
  if (ast.where && (!fullyAppliedFilters || delegate.applyFiltersAnyway)) end = applyWhere(end, ast.where, delegate, name);
145
- if (ast.limit !== void 0) {
150
+ if (ast.limit !== void 0) if (isNonFlippedExistsChild) {
151
+ const capName = `${name}:cap`;
152
+ const cap = new Cap(end, delegate.createStorage(capName), ast.limit, partitionKey);
153
+ delegate.addEdge(end, cap);
154
+ end = delegate.decorateInput(cap, capName);
155
+ } else {
146
156
  const takeName = `${name}:take`;
147
157
  const take = new Take(end, delegate.createStorage(takeName), ast.limit, partitionKey);
148
158
  delegate.addEdge(end, take);
@@ -192,7 +202,7 @@ function applyFilterWithFlips(input, condition, delegate, name) {
192
202
  }
193
203
  case "correlatedSubquery": {
194
204
  const sq = condition.related;
195
- const child = buildPipelineInternal(sq.subquery, delegate, "", `${name}.${sq.subquery.alias}`, sq.correlation.childField);
205
+ const child = buildPipelineInternal(sq.subquery, delegate, "", `${name}.${sq.subquery.alias}`, sq.correlation.childField, false);
196
206
  const flippedJoin = new FlippedJoin({
197
207
  parent: end,
198
208
  child,
@@ -275,7 +285,7 @@ function valuePosName(left) {
275
285
  function applyCorrelatedSubQuery(sq, delegate, queryID, end, name, fromCondition) {
276
286
  if (sq.subquery.limit === 0 && fromCondition) return end;
277
287
  assert(sq.subquery.alias, "Subquery must have an alias");
278
- const child = buildPipelineInternal(sq.subquery, delegate, queryID, `${name}.${sq.subquery.alias}`, sq.correlation.childField);
288
+ const child = buildPipelineInternal(sq.subquery, delegate, queryID, `${name}.${sq.subquery.alias}`, sq.correlation.childField, fromCondition);
279
289
  const joinName = `${name}:join(${sq.subquery.alias})`;
280
290
  const join = new Join({
281
291
  parent: end,