@rocicorp/zero 1.4.0-canary.5 → 1.5.0-canary.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 (200) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/zero/package.js +1 -1
  4. package/out/zero/package.js.map +1 -1
  5. package/out/zero-cache/src/auth/auth.d.ts +1 -1
  6. package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
  7. package/out/zero-cache/src/auth/auth.js +1 -1
  8. package/out/zero-cache/src/auth/auth.js.map +1 -1
  9. package/out/zero-cache/src/auth/write-authorizer.d.ts +1 -1
  10. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  11. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  12. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  13. package/out/zero-cache/src/config/normalize.js +8 -0
  14. package/out/zero-cache/src/config/normalize.js.map +1 -1
  15. package/out/zero-cache/src/config/zero-config.d.ts +8 -4
  16. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  17. package/out/zero-cache/src/config/zero-config.js +28 -6
  18. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  19. package/out/zero-cache/src/custom/fetch.d.ts +1 -1
  20. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  21. package/out/zero-cache/src/custom/fetch.js +2 -2
  22. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  23. package/out/zero-cache/src/custom-queries/transform-query.d.ts +21 -7
  24. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  25. package/out/zero-cache/src/custom-queries/transform-query.js +26 -9
  26. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  27. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  28. package/out/zero-cache/src/server/change-streamer.js +2 -1
  29. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  30. package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
  31. package/out/zero-cache/src/server/runner/run-worker.js +5 -2
  32. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  33. package/out/zero-cache/src/server/syncer.js +3 -3
  34. package/out/zero-cache/src/server/syncer.js.map +1 -1
  35. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  36. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  37. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/change-source.js +24 -20
  39. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  40. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +258 -45
  41. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +119 -83
  43. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  44. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  45. package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
  46. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  47. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +11 -2
  49. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +1 -0
  51. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  52. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -3
  53. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  54. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-streamer/storer.js +3 -3
  56. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  57. package/out/zero-cache/src/services/http-service.d.ts +1 -0
  58. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/http-service.js +5 -4
  60. package/out/zero-cache/src/services/http-service.js.map +1 -1
  61. package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
  62. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/life-cycle.js +1 -2
  64. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  65. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +1 -1
  66. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  67. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  68. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  69. package/out/zero-cache/src/services/mutagen/pusher.d.ts +4 -3
  70. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/mutagen/pusher.js +57 -38
  72. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  73. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  75. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js +2 -1
  76. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  77. package/out/zero-cache/src/services/view-syncer/client-handler.js +1 -1
  78. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  79. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +39 -27
  80. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -1
  81. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +138 -102
  82. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  83. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  84. package/out/zero-cache/src/services/view-syncer/cvr-store.js +22 -2
  85. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  86. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +6 -0
  87. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  88. package/out/zero-cache/src/services/view-syncer/cvr.js +8 -0
  89. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  90. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +27 -3
  92. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  93. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +3 -3
  94. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  95. package/out/zero-cache/src/services/view-syncer/view-syncer.js +115 -86
  96. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  97. package/out/zero-cache/src/workers/connection.js +2 -2
  98. package/out/zero-cache/src/workers/connection.js.map +1 -1
  99. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts +1 -1
  100. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  101. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +7 -7
  102. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  103. package/out/zero-cache/src/workers/syncer.d.ts +1 -1
  104. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  105. package/out/zero-cache/src/workers/syncer.js +11 -10
  106. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  107. package/out/zero-client/src/client/connection.d.ts +15 -7
  108. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  109. package/out/zero-client/src/client/connection.js.map +1 -1
  110. package/out/zero-client/src/client/crud-impl.d.ts +1 -1
  111. package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
  112. package/out/zero-client/src/client/crud-impl.js +1 -1
  113. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  114. package/out/zero-client/src/client/crud.d.ts +1 -1
  115. package/out/zero-client/src/client/crud.d.ts.map +1 -1
  116. package/out/zero-client/src/client/crud.js +1 -1
  117. package/out/zero-client/src/client/crud.js.map +1 -1
  118. package/out/zero-client/src/client/keys.d.ts +1 -1
  119. package/out/zero-client/src/client/keys.d.ts.map +1 -1
  120. package/out/zero-client/src/client/keys.js.map +1 -1
  121. package/out/zero-client/src/client/make-replicache-mutators.js +1 -1
  122. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  123. package/out/zero-client/src/client/mutation-tracker.d.ts +2 -1
  124. package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
  125. package/out/zero-client/src/client/mutation-tracker.js +3 -3
  126. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  127. package/out/zero-client/src/client/version.js +1 -1
  128. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  129. package/out/zero-client/src/client/zero.js +2 -2
  130. package/out/zero-client/src/client/zero.js.map +1 -1
  131. package/out/zero-client/src/types/client-state.d.ts +1 -1
  132. package/out/zero-client/src/types/client-state.d.ts.map +1 -1
  133. package/out/zero-protocol/src/custom-queries.js +1 -1
  134. package/out/zero-protocol/src/down.js +1 -1
  135. package/out/zero-protocol/src/error-kind-enum.d.ts +1 -2
  136. package/out/zero-protocol/src/error-kind-enum.d.ts.map +1 -1
  137. package/out/zero-protocol/src/error-kind-enum.js.map +1 -1
  138. package/out/zero-protocol/src/mutate-server.d.ts +165 -0
  139. package/out/zero-protocol/src/mutate-server.d.ts.map +1 -0
  140. package/out/zero-protocol/src/mutate-server.js +24 -0
  141. package/out/zero-protocol/src/mutate-server.js.map +1 -0
  142. package/out/zero-protocol/src/mutation.d.ts +229 -0
  143. package/out/zero-protocol/src/mutation.d.ts.map +1 -0
  144. package/out/zero-protocol/src/mutation.js +112 -0
  145. package/out/zero-protocol/src/mutation.js.map +1 -0
  146. package/out/zero-protocol/src/mutations-patch.js +1 -1
  147. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  148. package/out/zero-protocol/src/push.d.ts +3 -234
  149. package/out/zero-protocol/src/push.d.ts.map +1 -1
  150. package/out/zero-protocol/src/push.js +3 -114
  151. package/out/zero-protocol/src/push.js.map +1 -1
  152. package/out/zero-protocol/src/query-server.d.ts +150 -0
  153. package/out/zero-protocol/src/query-server.d.ts.map +1 -0
  154. package/out/zero-protocol/src/query-server.js +16 -0
  155. package/out/zero-protocol/src/query-server.js.map +1 -0
  156. package/out/zero-protocol/src/up.js +1 -1
  157. package/out/zero-server/src/mod.d.ts +4 -2
  158. package/out/zero-server/src/mod.d.ts.map +1 -1
  159. package/out/zero-server/src/process-mutations.d.ts +41 -4
  160. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  161. package/out/zero-server/src/process-mutations.js +52 -35
  162. package/out/zero-server/src/process-mutations.js.map +1 -1
  163. package/out/zero-server/src/push-processor.d.ts +3 -3
  164. package/out/zero-server/src/push-processor.d.ts.map +1 -1
  165. package/out/zero-server/src/push-processor.js.map +1 -1
  166. package/out/zero-server/src/queries/process-queries.d.ts +22 -52
  167. package/out/zero-server/src/queries/process-queries.d.ts.map +1 -1
  168. package/out/zero-server/src/queries/process-queries.js +50 -49
  169. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  170. package/out/zero-server/src/zql-database.js.map +1 -1
  171. package/out/zero-types/src/default-types.d.ts +1 -0
  172. package/out/zero-types/src/default-types.d.ts.map +1 -1
  173. package/out/zql/src/builder/builder.d.ts.map +1 -1
  174. package/out/zql/src/builder/builder.js +17 -7
  175. package/out/zql/src/builder/builder.js.map +1 -1
  176. package/out/zql/src/ivm/cap.d.ts +32 -0
  177. package/out/zql/src/ivm/cap.d.ts.map +1 -0
  178. package/out/zql/src/ivm/cap.js +205 -0
  179. package/out/zql/src/ivm/cap.js.map +1 -0
  180. package/out/zql/src/ivm/constraint.js +1 -1
  181. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  182. package/out/zql/src/ivm/flipped-join.js +61 -15
  183. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  184. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  185. package/out/zql/src/ivm/memory-source.js +3 -4
  186. package/out/zql/src/ivm/memory-source.js.map +1 -1
  187. package/out/zql/src/ivm/schema.d.ts +8 -0
  188. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  189. package/out/zql/src/ivm/take.js +2 -2
  190. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  191. package/out/zql/src/mutate/mutator.d.ts +11 -2
  192. package/out/zql/src/mutate/mutator.d.ts.map +1 -1
  193. package/out/zql/src/mutate/mutator.js.map +1 -1
  194. package/out/zql/src/query/query-registry.d.ts +9 -2
  195. package/out/zql/src/query/query-registry.d.ts.map +1 -1
  196. package/out/zql/src/query/query-registry.js.map +1 -1
  197. package/out/zqlite/src/table-source.d.ts.map +1 -1
  198. package/out/zqlite/src/table-source.js +4 -1
  199. package/out/zqlite/src/table-source.js.map +1 -1
  200. package/package.json +1 -1
@@ -11,6 +11,7 @@ import { transformAndHashQuery } from "../../auth/read-authorizer.js";
11
11
  import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateUpDownCounter } from "../../observability/metrics.js";
12
12
  import { rowIDString } from "../../types/row-key.js";
13
13
  import "../replicator/schema/replication-state.js";
14
+ import { parseSignature } from "./row-set-signature.js";
14
15
  import { ResetPipelinesSignal } from "./snapshotter.js";
15
16
  import "./pipeline-driver.js";
16
17
  import { randInt } from "../../../../shared/src/rand.js";
@@ -41,7 +42,7 @@ function randomID() {
41
42
  var TTL_CLOCK_INTERVAL = 6e4;
42
43
  var ViewSyncerService = class {
43
44
  id;
44
- contextManager;
45
+ connContextManager;
45
46
  #shard;
46
47
  #lc;
47
48
  #pipelines;
@@ -94,13 +95,14 @@ var ViewSyncerService = class {
94
95
  #queryTransformationNoOps = getOrCreateCounter("sync", "query.transformation-no-ops", "Number of times query transformation resulted in no-op (hash unchanged)");
95
96
  #lockWaitTime = getOrCreateLatencyHistogram("sync", "lock-wait-time", "Time spent waiting to acquire the ViewSyncer lock.");
96
97
  #pipelineResets = getOrCreateCounter("sync", "pipeline-resets", "Number of pipeline resets");
98
+ #rowSetSignatureDrifts = getOrCreateCounter("sync", "query.row-set-signature-drifts", "Number of times re-hydration of an unchanged query produced a different row-set signature than what is stored in the CVR (forcing a configVersion bump and full re-execution). Expected to be near-zero in steady state; persistent non-zero values indicate non-deterministic query execution (e.g. Cap operator picking different N-row subsets).");
97
99
  #inspectorDelegate;
98
100
  #config;
99
101
  #runPriorityOp;
100
- constructor(config, lc, shard, taskID, clientGroupID, cvrDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, contextManager, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
102
+ constructor(config, lc, shard, taskID, clientGroupID, cvrDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
101
103
  this.#config = config;
102
104
  this.id = clientGroupID;
103
- this.contextManager = contextManager;
105
+ this.connContextManager = connContextManager;
104
106
  this.#shard = shard;
105
107
  this.#lc = lc;
106
108
  this.#pipelines = pipelineDriver;
@@ -197,8 +199,8 @@ var ViewSyncerService = class {
197
199
  return;
198
200
  }
199
201
  lc.info?.(`init pipelines@${version} (cvr@${cvrVer})`);
200
- await this.#hydrateUnchangedQueries(lc, cvr);
201
- await this.#syncQueryPipelineSet(lc, cvr, "missing", void 0);
202
+ const driftedQueryIDs = await this.#hydrateUnchangedQueries(lc, cvr);
203
+ await this.#syncQueryPipelineSet(lc, cvr, "missing", void 0, driftedQueryIDs);
202
204
  this.#pipelinesSynced = true;
203
205
  });
204
206
  }
@@ -262,7 +264,7 @@ var ViewSyncerService = class {
262
264
  return this.#clients.size === 0;
263
265
  }
264
266
  #deleteClientDueToDisconnect(clientID, client) {
265
- this.contextManager.closeConnection({
267
+ this.connContextManager.closeConnection({
266
268
  clientID,
267
269
  wsID: client.wsID
268
270
  });
@@ -290,13 +292,10 @@ var ViewSyncerService = class {
290
292
  * deadlines. The timer plumbing is intentionally separate from the actual
291
293
  * revalidation/retransform work so future policy changes only need to update
292
294
  * the maintenance workers, not the wakeup logic.
293
- *
294
- * This is intentionally cheap & idempotent, so it can be called frequently
295
- * when upstream state might have changed.
296
295
  */
297
296
  #scheduleAuthMaintenance(lc) {
298
297
  this.#stopAuthMaintenanceTimer();
299
- const plan = this.contextManager.planMaintenance();
298
+ const plan = this.connContextManager.planMaintenance();
300
299
  if (plan.earliestDeadlineAt === void 0) {
301
300
  lc.debug?.("No auth maintenance wakeup scheduled");
302
301
  return;
@@ -306,13 +305,17 @@ var ViewSyncerService = class {
306
305
  delay,
307
306
  earliestDeadlineAt: plan.earliestDeadlineAt
308
307
  });
309
- this.#authMaintenanceTimer = this.#setTimeout(() => {
310
- this.#authMaintenanceTimer = 0;
311
- this.#runInLockWithCVR((lc, cvr) => this.#runAuthMaintenance(lc, cvr)).catch((e) => this.#stateChanges.fail(e));
308
+ this.#authMaintenanceTimer = this.#setTimeout(async () => {
309
+ try {
310
+ this.#authMaintenanceTimer = 0;
311
+ await this.#runInLockWithCVR((lc, cvr) => this.#runAuthMaintenance(lc, cvr));
312
+ } catch (e) {
313
+ this.#stateChanges.fail(e instanceof Error ? e : new Error(String(e)));
314
+ }
312
315
  }, delay);
313
316
  }
314
317
  async #runAuthMaintenance(lc, _cvr) {
315
- const plan = this.contextManager.planMaintenance();
318
+ const plan = this.connContextManager.planMaintenance();
316
319
  if (plan.dueRevalidations.length === 0 && !plan.dueRetransform) {
317
320
  lc.debug?.("Auth maintenance woke up with no due work");
318
321
  return;
@@ -321,52 +324,52 @@ var ViewSyncerService = class {
321
324
  dueRevalidations: plan.dueRevalidations.length,
322
325
  dueRetransform: plan.dueRetransform
323
326
  });
324
- for (const connection of plan.dueRevalidations) try {
325
- await this.#validateConnection(connection);
327
+ for (const connCtx of plan.dueRevalidations) try {
328
+ await this.#validateConnection(connCtx);
326
329
  } catch (e) {
327
330
  if (isProtocolError(e) && isTransformFailedError(e)) {
328
331
  lc.warn?.("Scheduled auth revalidation failed; deferring auth maintenance", {
329
- clientID: connection.clientID,
330
- wsID: connection.wsID,
332
+ clientID: connCtx.clientID,
333
+ wsID: connCtx.wsID,
331
334
  message: e.message
332
335
  });
333
- this.contextManager.deferMaintenance("revalidate");
336
+ this.connContextManager.deferMaintenance("revalidate");
334
337
  return;
335
338
  }
336
339
  throw e;
337
340
  }
338
- if (this.contextManager.planMaintenance().dueRetransform) await this.#runBackgroundRetransform(lc);
341
+ if (this.connContextManager.planMaintenance().dueRetransform) await this.#runBackgroundRetransform(lc);
339
342
  }
340
343
  initConnection(selector, initConnectionMessage) {
341
344
  this.#lc.debug?.("viewSyncer.initConnection");
342
345
  return startSpan(tracer, "vs.initConnection", () => {
343
- const ctx = this.contextManager.mustGetConnectionContext(selector);
344
- const lc = this.#lc.withContext("clientID", ctx.clientID).withContext("wsID", ctx.wsID);
346
+ const connCtx = this.connContextManager.mustGetConnectionContext(selector);
347
+ const lc = this.#lc.withContext("clientID", connCtx.clientID).withContext("wsID", connCtx.wsID);
345
348
  const downstream = Subscription.create({ cleanup: (_, err) => {
346
349
  err ? lc[getLogLevel(err)]?.(`client closed with error`, err) : lc.info?.("client closed");
347
- this.#deleteClientDueToDisconnect(ctx.clientID, newClient);
348
- this.#activeClients.add(-1, { [PROTOCOL_VERSION_ATTR]: ctx.protocolVersion });
350
+ this.#deleteClientDueToDisconnect(connCtx.clientID, newClient);
351
+ this.#activeClients.add(-1, { [PROTOCOL_VERSION_ATTR]: connCtx.protocolVersion });
349
352
  } });
350
- this.#activeClients.add(1, { [PROTOCOL_VERSION_ATTR]: ctx.protocolVersion });
353
+ this.#activeClients.add(1, { [PROTOCOL_VERSION_ATTR]: connCtx.protocolVersion });
351
354
  if (this.#clients.size === 0) this.#ttlClockBase = Date.now();
352
- const newClient = new ClientHandler(lc, this.id, ctx.clientID, ctx.wsID, this.#shard, ctx.baseCookie, downstream);
353
- this.#clients.get(ctx.clientID)?.close(`replaced by wsID: ${ctx.wsID}`);
354
- this.#clients.set(ctx.clientID, newClient);
355
- startAsyncSpan(tracer, "vs.initConnection.async", () => this.#runInLockForClient(ctx, initConnectionMessage, async (lc, clientID, msg, cvr) => {
355
+ const newClient = new ClientHandler(lc, this.id, connCtx.clientID, connCtx.wsID, this.#shard, connCtx.baseCookie, downstream);
356
+ this.#clients.get(connCtx.clientID)?.close(`replaced by wsID: ${connCtx.wsID}`);
357
+ this.#clients.set(connCtx.clientID, newClient);
358
+ startAsyncSpan(tracer, "vs.initConnection.async", () => this.#runInLockForClient(connCtx, initConnectionMessage, async (lc, clientID, msg, cvr) => {
356
359
  if (cvr.clientSchema === null && !msg.clientSchema) throw new ProtocolErrorWithLevel({
357
360
  kind: InvalidConnectionRequest,
358
361
  message: "The initConnection message for a new client group must include client schema.",
359
362
  origin: ZeroCache
360
363
  }, "warn");
361
- if (!await this.#validateConnection(ctx)) return;
362
- await this.#handleConfigUpdate(lc, clientID, msg, cvr, "all", ctx.profileID ?? `cg${this.id}`, ctx);
364
+ if (!await this.#validateConnection(connCtx)) return;
365
+ await this.#handleConfigUpdate(lc, clientID, msg, cvr, "all", connCtx.profileID ?? `cg${this.id}`, connCtx);
363
366
  this.#initialized.resolve("initialized");
364
367
  }, newClient)).catch((e) => newClient.fail(e));
365
368
  return downstream;
366
369
  });
367
370
  }
368
371
  async changeDesiredQueries(selector, msg) {
369
- await this.#runInLockForClient(selector, msg, (lc, clientID, msg, cvr) => this.#handleConfigUpdate(lc, clientID, msg, cvr, "missing", void 0, this.contextManager.mustGetConnectionContext(selector)));
372
+ await this.#runInLockForClient(selector, msg, (lc, clientID, msg, cvr) => this.#handleConfigUpdate(lc, clientID, msg, cvr, "missing", void 0, this.connContextManager.mustGetConnectionContext(selector)));
370
373
  }
371
374
  async updateAuth(selector, msg, authRevisionChanged) {
372
375
  await this.#runInLockForClient(selector, msg, async (lc, clientID, _, cvr) => {
@@ -375,15 +378,15 @@ var ViewSyncerService = class {
375
378
  return;
376
379
  }
377
380
  lc.debug?.("Auth changed, re-validating and re-transforming queries");
378
- const connection = this.contextManager.mustGetConnectionContext(selector);
381
+ const connCtx = this.connContextManager.mustGetConnectionContext(selector);
379
382
  if (!this.#pipelinesSynced) {
380
- if (!await this.#validateConnection(connection)) return;
383
+ if (!await this.#validateConnection(connCtx)) return;
381
384
  }
382
- return await this.#handleConfigUpdate(lc, clientID, {}, cvr, "all", void 0, connection);
385
+ return await this.#handleConfigUpdate(lc, clientID, {}, cvr, "all", void 0, connCtx);
383
386
  });
384
387
  }
385
388
  async deleteClients(selector, msg) {
386
- return await this.#runInLockForClient(selector, [msg[0], { deleted: msg[1] }], (lc, clientID, msg, cvr) => this.#handleConfigUpdate(lc, clientID, msg, cvr, "missing", void 0, this.contextManager.mustGetConnectionContext(selector))) ?? [];
389
+ return await this.#runInLockForClient(selector, [msg[0], { deleted: msg[1] }], (lc, clientID, msg, cvr) => this.#handleConfigUpdate(lc, clientID, msg, cvr, "missing", void 0, this.connContextManager.mustGetConnectionContext(selector))) ?? [];
387
390
  }
388
391
  #getTTLClock(now) {
389
392
  const delta = now - this.#ttlClockBase;
@@ -425,7 +428,7 @@ var ViewSyncerService = class {
425
428
  lc.warn?.("failed to update TTL clock", rid, `after ${Date.now() - start} ms`, e);
426
429
  });
427
430
  }
428
- async #updateCVRConfig(lc, cvr, clientID, customQueryTransformMode, ctx, fn) {
431
+ async #updateCVRConfig(lc, cvr, clientID, customQueryTransformMode, connCtx, fn) {
429
432
  const updater = new CVRConfigDrivenUpdater(this.#cvrStore, cvr, this.#shard);
430
433
  updater.ensureClient(clientID);
431
434
  const patches = fn(updater);
@@ -438,7 +441,7 @@ var ViewSyncerService = class {
438
441
  await pokers.end(newCVR.version);
439
442
  });
440
443
  }
441
- if (this.#pipelinesSynced) await this.#syncQueryPipelineSet(lc, this.#cvr, customQueryTransformMode, ctx);
444
+ if (this.#pipelinesSynced) await this.#syncQueryPipelineSet(lc, this.#cvr, customQueryTransformMode, connCtx);
442
445
  return this.#cvr;
443
446
  }
444
447
  /**
@@ -455,7 +458,7 @@ var ViewSyncerService = class {
455
458
  span.setAttribute("clientID", clientID);
456
459
  let client;
457
460
  let result;
458
- let ctx;
461
+ let connCtx;
459
462
  try {
460
463
  await this.#runInLockWithCVR(async (lc, cvr) => {
461
464
  lc = lc.withContext("clientID", clientID).withContext("wsID", wsID).withContext("cmd", cmd);
@@ -465,7 +468,7 @@ var ViewSyncerService = class {
465
468
  lc.debug?.("mismatched wsID", client?.wsID, wsID);
466
469
  return;
467
470
  }
468
- ctx = this.contextManager.getConnectionContext(selector);
471
+ connCtx = this.connContextManager.getConnectionContext(selector);
469
472
  if (newClient) {
470
473
  assert(newClient === client, "newClient must match existing client");
471
474
  checkClientAndCVRVersions(client.version(), cvr.version);
@@ -475,7 +478,7 @@ var ViewSyncerService = class {
475
478
  });
476
479
  } catch (e) {
477
480
  this.#lc.withContext("clientID", clientID).withContext("wsID", wsID).withContext("cmd", cmd)[getLogLevel(e)]?.(`closing connection with error`, e);
478
- if (ctx) this.contextManager.failConnection(selector, ctx.revision);
481
+ if (connCtx) this.connContextManager.failConnection(selector, connCtx.revision);
479
482
  if (client) client.fail(e);
480
483
  else throw e;
481
484
  }
@@ -486,10 +489,10 @@ var ViewSyncerService = class {
486
489
  const clients = [...this.#clients.values()];
487
490
  return atVersion ? clients.filter((c) => cmpVersions(c.version() ?? EMPTY_CVR_VERSION, atVersion) === 0) : clients;
488
491
  }
489
- #handleConfigUpdate = (lc, clientID, { clientSchema, deleted, desiredQueriesPatch, activeClients }, cvr, customQueryTransformMode, profileID, ctx) => startAsyncSpan(tracer, "vs.#handleConfigUpdate", async () => {
492
+ #handleConfigUpdate = (lc, clientID, { clientSchema, deleted, desiredQueriesPatch, activeClients }, cvr, customQueryTransformMode, profileID, connCtx) => startAsyncSpan(tracer, "vs.#handleConfigUpdate", async () => {
490
493
  const deletedClientIDs = [];
491
494
  const deletedClientGroupIDs = [];
492
- cvr = await this.#updateCVRConfig(lc, cvr, clientID, customQueryTransformMode, ctx, (updater) => {
495
+ cvr = await this.#updateCVRConfig(lc, cvr, clientID, customQueryTransformMode, connCtx, (updater) => {
493
496
  const { ttlClock } = cvr;
494
497
  const patches = [];
495
498
  if (clientSchema) updater.setClientSchema(lc, clientSchema);
@@ -567,7 +570,7 @@ var ViewSyncerService = class {
567
570
  const cvrVersion = cvr.version;
568
571
  if (cvrVersion.stateVersion !== dbVersion) {
569
572
  lc.info?.(`CVR (${versionToCookie(cvrVersion)}) is behind db ${dbVersion}`);
570
- return;
573
+ return /* @__PURE__ */ new Set();
571
574
  }
572
575
  const gotQueries = Object.entries(cvr.queries).filter(([_, state]) => state.transformationHash !== void 0);
573
576
  const customQueries = /* @__PURE__ */ new Map();
@@ -586,11 +589,11 @@ var ViewSyncerService = class {
586
589
  let customHashMismatchCount = 0;
587
590
  let otherHashMismatchCount = 0;
588
591
  if (customQueries.size > 0 && !this.#customQueryTransformer) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
589
- const backgroundContext = this.contextManager.mustGetBackgroundConnectionContext();
592
+ const backgroundContext = this.connContextManager.mustGetBackgroundConnectionContext();
590
593
  const customQueryTransformer = this.#customQueryTransformer;
591
594
  if (customQueryTransformer && customQueries.size > 0) {
592
595
  const transformedCustomQueries = await this.#runPriorityOp(lc, "#hydrateUnchangedQueries transforming custom queries", () => customQueryTransformer.transform(backgroundContext, customQueries.values()));
593
- if (!transformedCustomQueries.cached) this.contextManager.validateConnection(backgroundContext, backgroundContext.revision);
596
+ if (transformedCustomQueries.kind === "success" && !transformedCustomQueries.cached) this.connContextManager.validateConnection(backgroundContext, backgroundContext.revision, transformedCustomQueries.validation);
594
597
  if (Array.isArray(transformedCustomQueries.result)) for (const q of transformedCustomQueries.result) if ("error" in q) customErrorCount++;
595
598
  else if (q.transformationHash !== customQueries.get(q.id)?.transformationHash) customHashMismatchCount++;
596
599
  else transformedQueries.push(q);
@@ -601,6 +604,7 @@ var ViewSyncerService = class {
601
604
  else otherHashMismatchCount++;
602
605
  }
603
606
  lc.info?.(`hydrateUnchangedQueries: ${gotQueries.length} got queries, ${inactivatedCount} inactivated, ${customErrorCount} custom transform errors, ${customHashMismatchCount} custom hash mismatches, ${otherHashMismatchCount} other hash mismatches, ${transformedQueries.length} hydrated`);
607
+ const driftedQueryIDs = /* @__PURE__ */ new Set();
604
608
  for (const { id: queryID, transformationHash, transformedAst } of transformedQueries) {
605
609
  const timer = new TimeSliceTimer(lc);
606
610
  let count = 0;
@@ -616,7 +620,19 @@ var ViewSyncerService = class {
616
620
  this.#hydrationTime.recordMs(elapsed);
617
621
  this.#addQueryMaterializationServerMetric(transformationHash, elapsed);
618
622
  lc.debug?.(`hydrated ${count} rows for ${queryID} (${elapsed} ms)`);
623
+ const storedSigHex = cvr.queries[queryID]?.rowSetSignature;
624
+ if (storedSigHex !== void 0 && storedSigHex !== null) {
625
+ const priorSig = parseSignature(storedSigHex);
626
+ const candidateSig = this.#pipelines.rowSetSignature(queryID) ?? 0n;
627
+ if (priorSig !== candidateSig) {
628
+ lc.warn?.(`rowSetSignature drift for query ${queryID}: prior=${priorSig.toString(16)} new=${candidateSig.toString(16)} (${count} rows). Removing from pipelines for full re-execution.`);
629
+ this.#rowSetSignatureDrifts.add(1);
630
+ this.#pipelines.removeQuery(queryID);
631
+ driftedQueryIDs.add(queryID);
632
+ }
633
+ }
619
634
  }
635
+ return driftedQueryIDs;
620
636
  }
621
637
  #processTransformedCustomQueries(lc, transformedCustomQueries, cb, customQueryMap) {
622
638
  if ("kind" in transformedCustomQueries) {
@@ -669,7 +685,7 @@ var ViewSyncerService = class {
669
685
  *
670
686
  * This must be called from within the #lock.
671
687
  */
672
- #syncQueryPipelineSet(lc, cvr, customQueryTransformMode, ctx) {
688
+ #syncQueryPipelineSet(lc, cvr, customQueryTransformMode, connCtx, driftedQueryIDs = /* @__PURE__ */ new Set()) {
673
689
  return startAsyncSpan(tracer, "vs.#syncQueryPipelineSet", async (span) => {
674
690
  span.setAttribute("clientGroupID", this.id);
675
691
  assert(this.#pipelines.initialized(), "pipelines must be initialized (syncQueryPipelineSet)");
@@ -680,7 +696,7 @@ var ViewSyncerService = class {
680
696
  const customQueries = /* @__PURE__ */ new Map();
681
697
  const otherQueries = [];
682
698
  const transformedQueries = [];
683
- const resolvedContext = ctx ?? this.contextManager.mustGetBackgroundConnectionContext();
699
+ const resolvedConnCtx = connCtx ?? this.connContextManager.mustGetBackgroundConnectionContext();
684
700
  for (const [id, query] of cvrQueryEntires) if (query.type === "custom") {
685
701
  assert(id === query.id, "custom query id mismatch");
686
702
  customQueries.set(id, query);
@@ -690,7 +706,7 @@ var ViewSyncerService = class {
690
706
  });
691
707
  for (const { id, query: origQuery } of otherQueries) {
692
708
  assert(id === origQuery.id, "query id mismatch");
693
- const transformed = transformAndHashQuery(lc, origQuery.id, origQuery.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, resolvedContext.auth?.type === "jwt" ? resolvedContext.auth : void 0, origQuery.type === "internal");
709
+ const transformed = transformAndHashQuery(lc, origQuery.id, origQuery.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, resolvedConnCtx.auth?.type === "jwt" ? resolvedConnCtx.auth : void 0, origQuery.type === "internal");
694
710
  transformedQueries.push({
695
711
  id,
696
712
  origQuery,
@@ -705,10 +721,10 @@ var ViewSyncerService = class {
705
721
  const transformStart = performance.now();
706
722
  let transformedCustomQueries;
707
723
  try {
708
- transformedCustomQueries = await this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(resolvedContext, customQueriesToTransform));
709
- if ("kind" in transformedCustomQueries.result) throw new ProtocolErrorWithLevel(transformedCustomQueries.result, "warn");
724
+ transformedCustomQueries = await this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(resolvedConnCtx, customQueriesToTransform));
725
+ if (transformedCustomQueries.kind === "failed") throw new ProtocolErrorWithLevel(transformedCustomQueries.result, "warn");
710
726
  else {
711
- if (!transformedCustomQueries.cached) this.contextManager.validateConnection(resolvedContext, resolvedContext.revision);
727
+ if (!transformedCustomQueries.cached) this.connContextManager.validateConnection(resolvedConnCtx, resolvedConnCtx.revision, transformedCustomQueries.validation);
712
728
  this.#queryTransformations.add(1, { result: "success" });
713
729
  }
714
730
  } catch (e) {
@@ -755,7 +771,7 @@ var ViewSyncerService = class {
755
771
  const orig = cvr.queries[q.id];
756
772
  lc.debug?.("ViewSyncer adding query", q.ast, "transformed from", orig.type === "custom" ? orig.name : orig.ast);
757
773
  }
758
- if (addQueries.length > 0 || removeQueriesQueryIds.size > 0) await this.#addAndRemoveQueries(lc, cvr, addQueries, Array.from(removeQueriesQueryIds, (id) => ({ id })));
774
+ if (addQueries.length > 0 || removeQueriesQueryIds.size > 0) await this.#addAndRemoveQueries(lc, cvr, addQueries, Array.from(removeQueriesQueryIds, (id) => ({ id })), driftedQueryIDs);
759
775
  else await this.#catchupClients(lc, cvr);
760
776
  });
761
777
  }
@@ -787,7 +803,7 @@ var ViewSyncerService = class {
787
803
  record.count++;
788
804
  if (record.count >= THRASH_THRESHOLD) this.#lc.warn?.(`Query thrashing detected for query ${queryID}. ${record.count} replacements in 60s. This may indicate clients with different auth contexts connecting to the same client group.`);
789
805
  }
790
- #addAndRemoveQueries(lc, cvr, addQueries, removeQueries) {
806
+ #addAndRemoveQueries(lc, cvr, addQueries, removeQueries, driftedQueryIDs = /* @__PURE__ */ new Set()) {
791
807
  return startAsyncSpan(tracer, "vs.#addAndRemoveQueries", async () => {
792
808
  assert(addQueries.length > 0 || removeQueries.length > 0, "Must have queries to add or remove");
793
809
  const start = performance.now();
@@ -795,9 +811,14 @@ var ViewSyncerService = class {
795
811
  lc = lc.withContext("stateVersion", stateVersion);
796
812
  lc.info?.(`hydrating ${addQueries.length} queries`);
797
813
  const updater = new CVRQueryDrivenUpdater(this.#cvrStore, cvr, stateVersion, this.#pipelines.replicaVersion, (queryID) => this.#pipelines.rowSetSignature(queryID));
798
- const { newVersion, queryPatches } = updater.trackQueries(lc, addQueries, removeQueries);
814
+ const { queryPatches } = updater.trackQueries(lc, addQueries, removeQueries);
815
+ if (addQueries.some((q) => driftedQueryIDs.has(q.id))) updater.ensureNewVersion();
816
+ const newVersion = updater.updatedVersion();
799
817
  const pokers = startPoke(this.#getClients(), newVersion);
800
- for (const patch of queryPatches) await pokers.addPatch(patch);
818
+ for (const patch of queryPatches) await pokers.addPatch({
819
+ ...patch,
820
+ toVersion: newVersion
821
+ });
801
822
  for (const q of removeQueries) {
802
823
  this.#pipelines.removeQuery(q.id);
803
824
  this.#inspectorDelegate.removeQuery(q.id);
@@ -1008,80 +1029,88 @@ var ViewSyncerService = class {
1008
1029
  }
1009
1030
  #handleInspect = async (lc, clientID, body, cvr) => {
1010
1031
  const client = must(this.#clients.get(clientID));
1011
- const ctx = this.contextManager.mustGetConnectionContext({
1032
+ const connCtx = this.connContextManager.mustGetConnectionContext({
1012
1033
  clientID,
1013
1034
  wsID: client.wsID
1014
1035
  });
1015
- return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this.#cvrStore, this.#config, ctx);
1036
+ return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this.#cvrStore, this.#config, connCtx);
1016
1037
  };
1017
1038
  async #runBackgroundRetransform(lc) {
1018
- const attemptRetransform = async (connection) => {
1019
- await this.#syncQueryPipelineSet(lc, must(this.#cvr, "cvr missing during auth maintenance retransform"), "all", connection);
1020
- this.contextManager.markBackgroundRetransformSuccess({
1021
- clientID: connection.clientID,
1022
- wsID: connection.wsID
1023
- }, connection.revision);
1039
+ const attemptRetransform = async (connCtx) => {
1040
+ await this.#syncQueryPipelineSet(lc, must(this.#cvr, "cvr missing during auth maintenance retransform"), "all", connCtx);
1041
+ this.connContextManager.markBackgroundRetransformSuccess({
1042
+ clientID: connCtx.clientID,
1043
+ wsID: connCtx.wsID
1044
+ }, connCtx.revision);
1024
1045
  };
1025
- let backgroundConnection = this.contextManager.getBackgroundConnectionContext();
1026
- if (!backgroundConnection) {
1046
+ let backgroundConnCtx = this.connContextManager.getBackgroundConnectionContext();
1047
+ if (!backgroundConnCtx) {
1027
1048
  lc.debug?.("Skipping background retransform with no selected connection");
1028
1049
  return;
1029
1050
  }
1030
1051
  for (;;) {
1031
1052
  try {
1032
- await attemptRetransform(backgroundConnection);
1053
+ await attemptRetransform(backgroundConnCtx);
1033
1054
  return;
1034
1055
  } catch (e) {
1035
1056
  if (isProtocolError(e)) {
1036
1057
  if (isAuthErrorBody(e.errorBody)) {
1037
1058
  lc.warn?.("Background retransform auth failed; failing connection and searching for replacement", {
1038
- clientID: backgroundConnection.clientID,
1059
+ clientID: backgroundConnCtx.clientID,
1039
1060
  message: e.message
1040
1061
  });
1041
- this.#failMaintenanceConnection(backgroundConnection, e);
1062
+ this.#failMaintenanceConnection(backgroundConnCtx, e);
1042
1063
  } else if (isTransformFailedError(e)) {
1043
1064
  lc.warn?.("Background retransform failed; deferring auth maintenance", {
1044
- clientID: backgroundConnection.clientID,
1065
+ clientID: backgroundConnCtx.clientID,
1045
1066
  message: e.message
1046
1067
  });
1047
- this.contextManager.deferMaintenance("retransform");
1068
+ this.connContextManager.deferMaintenance("retransform");
1048
1069
  return;
1049
1070
  }
1050
1071
  } else throw e;
1051
1072
  }
1052
- const replacement = this.contextManager.getBackgroundConnectionContext();
1053
- if (!replacement) {
1073
+ const replacementConnCtx = this.connContextManager.getBackgroundConnectionContext();
1074
+ if (!replacementConnCtx) {
1054
1075
  lc.debug?.("No replacement connection available for background retransform");
1055
1076
  return;
1056
1077
  }
1057
1078
  lc.debug?.("Retrying background retransform with replacement connection", {
1058
- clientID: replacement.clientID,
1059
- wsID: replacement.wsID
1079
+ clientID: replacementConnCtx.clientID,
1080
+ wsID: replacementConnCtx.wsID
1060
1081
  });
1061
- backgroundConnection = replacement;
1082
+ backgroundConnCtx = replacementConnCtx;
1062
1083
  }
1063
1084
  }
1064
- async #validateConnection(ctx) {
1085
+ async #validateConnection(connCtx) {
1065
1086
  try {
1087
+ let validation = void 0;
1066
1088
  if (this.#customQueryTransformer) {
1067
- const validation = await this.#customQueryTransformer.validate(ctx);
1068
- if (validation !== void 0) throw new ProtocolErrorWithLevel(validation, "warn");
1069
- }
1070
- this.contextManager.validateConnection(ctx, ctx.revision);
1089
+ const response = await this.#customQueryTransformer.validate(connCtx);
1090
+ if (response.kind === "TransformFailed") throw new ProtocolErrorWithLevel(response, "warn");
1091
+ validation = response.validation;
1092
+ } else validation = { kind: "client-fallback" };
1093
+ this.connContextManager.validateConnection(connCtx, connCtx.revision, validation);
1071
1094
  return true;
1072
1095
  } catch (e) {
1073
1096
  if (isProtocolError(e) && isAuthErrorBody(e.errorBody)) {
1074
- this.#failMaintenanceConnection(ctx, e);
1097
+ this.#lc.warn?.("Connection auth validation failed; invalidating connection", {
1098
+ clientID: connCtx.clientID,
1099
+ wsID: connCtx.wsID,
1100
+ revision: connCtx.revision,
1101
+ message: e.message
1102
+ });
1103
+ this.#failMaintenanceConnection(connCtx, e);
1075
1104
  return false;
1076
1105
  }
1077
1106
  throw e;
1078
1107
  }
1079
1108
  }
1080
- #failMaintenanceConnection(ctx, error) {
1081
- if (!this.contextManager.failConnection(ctx, ctx.revision)) return;
1109
+ #failMaintenanceConnection(connCtx, error) {
1110
+ if (!this.connContextManager.failConnection(connCtx, connCtx.revision)) return;
1082
1111
  const wrapped = wrapWithProtocolError(error);
1083
- const client = this.#clients.get(ctx.clientID);
1084
- if (client?.wsID === ctx.wsID) client.fail(wrapped);
1112
+ const client = this.#clients.get(connCtx.clientID);
1113
+ if (client?.wsID === connCtx.wsID) client.fail(wrapped);
1085
1114
  }
1086
1115
  stop() {
1087
1116
  this.#lc.info?.("stopping view syncer");