@rocicorp/zero 1.11.3 → 1.11.5

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 (192) hide show
  1. package/out/replicache/src/sync/request-id.js +2 -2
  2. package/out/replicache/src/sync/request-id.js.map +1 -1
  3. package/out/shared/src/bigint-json.d.ts.map +1 -1
  4. package/out/shared/src/bigint-json.js +1 -0
  5. package/out/shared/src/bigint-json.js.map +1 -1
  6. package/out/shared/src/headers.d.ts +12 -0
  7. package/out/shared/src/headers.d.ts.map +1 -0
  8. package/out/shared/src/headers.js +20 -0
  9. package/out/shared/src/headers.js.map +1 -0
  10. package/out/shared/src/random-uint64.d.ts.map +1 -1
  11. package/out/shared/src/random-uint64.js +2 -2
  12. package/out/shared/src/random-uint64.js.map +1 -1
  13. package/out/shared/src/random-values.d.ts +14 -1
  14. package/out/shared/src/random-values.d.ts.map +1 -1
  15. package/out/shared/src/random-values.js +24 -4
  16. package/out/shared/src/random-values.js.map +1 -1
  17. package/out/zero/package.js +1 -1
  18. package/out/zero/package.js.map +1 -1
  19. package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
  20. package/out/zero-cache/src/auth/auth.js +5 -0
  21. package/out/zero-cache/src/auth/auth.js.map +1 -1
  22. package/out/zero-cache/src/config/zero-config.d.ts +21 -0
  23. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  24. package/out/zero-cache/src/config/zero-config.js +47 -9
  25. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  26. package/out/zero-cache/src/custom-queries/transform-query.js +2 -2
  27. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  28. package/out/zero-cache/src/db/pg-to-lite.d.ts.map +1 -1
  29. package/out/zero-cache/src/db/pg-to-lite.js +3 -2
  30. package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
  31. package/out/zero-cache/src/db/pg-type-parser.d.ts +13 -0
  32. package/out/zero-cache/src/db/pg-type-parser.d.ts.map +1 -1
  33. package/out/zero-cache/src/db/pg-type-parser.js +55 -2
  34. package/out/zero-cache/src/db/pg-type-parser.js.map +1 -1
  35. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  36. package/out/zero-cache/src/server/change-streamer.js +5 -2
  37. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  38. package/out/zero-cache/src/server/inspector-delegate.d.ts +2 -25
  39. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  40. package/out/zero-cache/src/server/inspector-delegate.js +6 -29
  41. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  42. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  43. package/out/zero-cache/src/server/main.js +5 -0
  44. package/out/zero-cache/src/server/main.js.map +1 -1
  45. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  46. package/out/zero-cache/src/server/replicator.js +3 -2
  47. package/out/zero-cache/src/server/replicator.js.map +1 -1
  48. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  49. package/out/zero-cache/src/server/syncer.js +5 -4
  50. package/out/zero-cache/src/server/syncer.js.map +1 -1
  51. package/out/zero-cache/src/server/worker-dispatcher.d.ts.map +1 -1
  52. package/out/zero-cache/src/server/worker-dispatcher.js +2 -1
  53. package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
  54. package/out/zero-cache/src/services/admin-auth.d.ts +22 -0
  55. package/out/zero-cache/src/services/admin-auth.d.ts.map +1 -0
  56. package/out/zero-cache/src/services/admin-auth.js +42 -0
  57. package/out/zero-cache/src/services/admin-auth.js.map +1 -0
  58. package/out/zero-cache/src/services/auth-attempt-counter.d.ts +33 -0
  59. package/out/zero-cache/src/services/auth-attempt-counter.d.ts.map +1 -0
  60. package/out/zero-cache/src/services/auth-attempt-counter.js +61 -0
  61. package/out/zero-cache/src/services/auth-attempt-counter.js.map +1 -0
  62. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  63. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +7 -1
  64. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  65. package/out/zero-cache/src/services/change-source/pg/change-source.js +110 -13
  66. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  67. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  68. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +1 -1
  69. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +15 -8
  70. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +38 -5
  72. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  73. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/change-source/pg/schema/init.js +10 -1
  75. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  76. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +4 -0
  77. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  78. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +10 -3
  79. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  80. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +7 -1
  81. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  82. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +23 -2
  83. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  84. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +2 -2
  85. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  86. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +30 -9
  87. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  88. package/out/zero-cache/src/services/change-streamer/change-streamer.js +10 -3
  89. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  90. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +1 -0
  91. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
  92. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +1 -1
  93. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  95. package/out/zero-cache/src/services/heapz.d.ts.map +1 -1
  96. package/out/zero-cache/src/services/heapz.js +4 -5
  97. package/out/zero-cache/src/services/heapz.js.map +1 -1
  98. package/out/zero-cache/src/services/mutagen/pusher.js +1 -1
  99. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  100. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -1
  101. package/out/zero-cache/src/services/plannerz.js +3 -7
  102. package/out/zero-cache/src/services/plannerz.js.map +1 -1
  103. package/out/zero-cache/src/services/profz.d.ts.map +1 -1
  104. package/out/zero-cache/src/services/profz.js +4 -13
  105. package/out/zero-cache/src/services/profz.js.map +1 -1
  106. package/out/zero-cache/src/services/replicator/change-processor.js +1 -2
  107. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  108. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  109. package/out/zero-cache/src/services/replicator/incremental-sync.js +3 -3
  110. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  111. package/out/zero-cache/src/services/statz.d.ts.map +1 -1
  112. package/out/zero-cache/src/services/statz.js +2 -7
  113. package/out/zero-cache/src/services/statz.js.map +1 -1
  114. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +16 -0
  115. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  116. package/out/zero-cache/src/services/view-syncer/client-handler.js +22 -1
  117. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  118. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +29 -4
  119. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -1
  120. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +40 -2
  121. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  122. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +10 -2
  123. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  124. package/out/zero-cache/src/services/view-syncer/cvr-store.js +54 -17
  125. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  126. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +8 -0
  127. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  128. package/out/zero-cache/src/services/view-syncer/cvr.js +11 -0
  129. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  130. package/out/zero-cache/src/services/view-syncer/inspect-auth-limiter.d.ts +35 -0
  131. package/out/zero-cache/src/services/view-syncer/inspect-auth-limiter.d.ts.map +1 -0
  132. package/out/zero-cache/src/services/view-syncer/inspect-auth-limiter.js +107 -0
  133. package/out/zero-cache/src/services/view-syncer/inspect-auth-limiter.js.map +1 -0
  134. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +2 -2
  135. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  136. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +11 -6
  137. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  138. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  139. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +2 -1
  140. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  141. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts +9 -0
  142. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts.map +1 -1
  143. package/out/zero-cache/src/services/view-syncer/schema/cvr.js +4 -1
  144. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  145. package/out/zero-cache/src/services/view-syncer/schema/init.d.ts.map +1 -1
  146. package/out/zero-cache/src/services/view-syncer/schema/init.js +3 -0
  147. package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
  148. package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
  149. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  150. package/out/zero-cache/src/services/view-syncer/view-syncer.js +96 -44
  151. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  152. package/out/zero-cache/src/types/client-group-log-id.d.ts +9 -0
  153. package/out/zero-cache/src/types/client-group-log-id.d.ts.map +1 -0
  154. package/out/zero-cache/src/types/client-group-log-id.js +16 -0
  155. package/out/zero-cache/src/types/client-group-log-id.js.map +1 -0
  156. package/out/zero-cache/src/types/lite.d.ts.map +1 -1
  157. package/out/zero-cache/src/types/lite.js +1 -0
  158. package/out/zero-cache/src/types/lite.js.map +1 -1
  159. package/out/zero-cache/src/types/pg.d.ts +13 -2
  160. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  161. package/out/zero-cache/src/types/pg.js +2 -2
  162. package/out/zero-cache/src/types/pg.js.map +1 -1
  163. package/out/zero-cache/src/workers/connection.d.ts +1 -0
  164. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  165. package/out/zero-cache/src/workers/connection.js +5 -1
  166. package/out/zero-cache/src/workers/connection.js.map +1 -1
  167. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  168. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +12 -3
  169. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  170. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  171. package/out/zero-cache/src/workers/syncer.js +15 -14
  172. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  173. package/out/zero-client/src/client/inspector/inspector.d.ts +4 -0
  174. package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
  175. package/out/zero-client/src/client/inspector/inspector.js +4 -0
  176. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  177. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts +10 -2
  178. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
  179. package/out/zero-client/src/client/inspector/lazy-inspector.js +38 -31
  180. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  181. package/out/zero-client/src/client/inspector/query.d.ts +2 -2
  182. package/out/zero-client/src/client/inspector/query.d.ts.map +1 -1
  183. package/out/zero-client/src/client/inspector/query.js +4 -4
  184. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  185. package/out/zero-client/src/client/version.js +1 -1
  186. package/out/zero-client/src/util/nanoid.js +2 -2
  187. package/out/zero-client/src/util/nanoid.js.map +1 -1
  188. package/out/zero-server/src/process-mutations.d.ts +10 -0
  189. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  190. package/out/zero-server/src/process-mutations.js +38 -5
  191. package/out/zero-server/src/process-mutations.js.map +1 -1
  192. package/package.json +1 -1
@@ -14,6 +14,7 @@ import { transformAndHashQuery } from "../../auth/read-authorizer.js";
14
14
  import { randInt } from "../../../../shared/src/rand.js";
15
15
  import { LogThrottle } from "../../observability/log-throttle.js";
16
16
  import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter, getOrCreateValueHistogram } from "../../observability/metrics.js";
17
+ import { clientGroupLogID } from "../../types/client-group-log-id.js";
17
18
  import { rowIDString } from "../../types/row-key.js";
18
19
  import "../replicator/schema/replication-state.js";
19
20
  import { planWarningMessage } from "./plan-warnings.js";
@@ -29,8 +30,8 @@ import { isAuthErrorBody } from "../../auth/auth.js";
29
30
  import { AdvancementMetrics } from "./advancement-metrics.js";
30
31
  import { ClientHandler, startPoke } from "./client-handler.js";
31
32
  import { tracer } from "./tracer.js";
32
- import { CVRStore, ClientNotFoundError } from "./cvr-store.js";
33
- import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, classifyQueriesForHydration, nextEvictionTime } from "./cvr.js";
33
+ import { CVRStore, ClientNotFoundError, PinnedUserMismatchError } from "./cvr-store.js";
34
+ import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, CVRUpdater, classifyQueriesForHydration, nextEvictionTime } from "./cvr.js";
34
35
  import { E2EServingLagTracker } from "./e2e-serving-lag.js";
35
36
  import { HydrationBudget } from "./hydration-budget.js";
36
37
  import { HydrationCircuitBreaker } from "./hydration-circuit-breaker.js";
@@ -335,7 +336,7 @@ var ViewSyncerService = class {
335
336
  }, "info");
336
337
  }
337
338
  if (await this.#checkForShutdownConditionsInLock()) {
338
- this.#lc.info?.(`closing clientGroupID=${this.id}`);
339
+ this.#lc.info?.(`closing clientGroupID=${clientGroupLogID(this.id)}`);
339
340
  this.#initialized.reject(shutdownBeforeInitializationError());
340
341
  this.#stateChanges.cancel();
341
342
  return;
@@ -355,6 +356,12 @@ var ViewSyncerService = class {
355
356
  };
356
357
  }
357
358
  try {
359
+ if (reloaded && this.#cvr.pinnedUser !== null) try {
360
+ this.connContextManager.restorePinnedUser(this.#cvr.pinnedUser);
361
+ } catch (e) {
362
+ this.#stateChanges.fail(e instanceof Error ? e : new Error(String(e)));
363
+ throw e;
364
+ }
358
365
  if (reloaded && this.#resetPipelinesIfBehindCVR(lc, this.#cvr)) {
359
366
  const connCtx = this.connContextManager.getBackgroundConnectionContext();
360
367
  if (connCtx) await this.#maybeHydratePipelines(lc, this.#cvr, connCtx);
@@ -362,6 +369,7 @@ var ViewSyncerService = class {
362
369
  await fn(lc, this.#cvr);
363
370
  } catch (e) {
364
371
  this.#cvr = void 0;
372
+ if (e instanceof PinnedUserMismatchError) this.#stateChanges.fail(e);
365
373
  throw e;
366
374
  } finally {
367
375
  this.#scheduleAuthMaintenance(lc);
@@ -410,13 +418,13 @@ var ViewSyncerService = class {
410
418
  try {
411
419
  this.#scheduleShutdown(this.#keepaliveMs);
412
420
  if (await this.readyState() === "draining") {
413
- this.#lc.debug?.(`draining view-syncer ${this.id} before running`);
421
+ this.#lc.debug?.(`draining view-syncer ${clientGroupLogID(this.id)} before running`);
414
422
  this.stop();
415
423
  }
416
424
  for await (const replicaState of this.#stateChanges) {
417
425
  const { state } = replicaState;
418
426
  if (this.#drainCoordinator.shouldDrain()) {
419
- this.#lc.debug?.(`draining view-syncer ${this.id} (elective)`);
427
+ this.#lc.debug?.(`draining view-syncer ${clientGroupLogID(this.id)} (elective)`);
420
428
  break;
421
429
  }
422
430
  assert(state === "version-ready", "state should be version-ready");
@@ -460,11 +468,11 @@ var ViewSyncerService = class {
460
468
  if (this.#drainCoordinator.shouldDrain()) this.#drainCoordinator.drainNextIn(this.#totalHydrationTimeMs());
461
469
  await this.#cleanup();
462
470
  } catch (e) {
463
- this.#lc[getLogLevel(e)]?.(`stopping view-syncer ${this.id}: ${String(e)}`, e);
471
+ this.#lc[getLogLevel(e)]?.(`stopping view-syncer ${clientGroupLogID(this.id)}: ${String(e)}`, e);
464
472
  await this.#cleanup(e);
465
473
  } finally {
466
474
  await this.#cvrStore.flushed(this.#lc).catch((e) => this.#lc[getLogLevel(e)]?.(e));
467
- this.#lc.info?.(`view-syncer ${this.id} finished`);
475
+ this.#lc.info?.(`view-syncer ${clientGroupLogID(this.id)} finished`);
468
476
  this.#stopped.resolve();
469
477
  }
470
478
  }
@@ -632,11 +640,12 @@ var ViewSyncerService = class {
632
640
  return this.#clients.size === 0;
633
641
  }
634
642
  #deleteClientDueToDisconnect(clientID, client) {
635
- this.connContextManager.closeConnection({
643
+ const c = this.#clients.get(clientID);
644
+ if (c === client || c?.wsID !== client.wsID) this.connContextManager.closeConnection({
636
645
  clientID,
637
646
  wsID: client.wsID
638
647
  });
639
- if (this.#clients.get(clientID) === client) {
648
+ if (c === client) {
640
649
  this.#clients.delete(clientID);
641
650
  if (this.#clients.size === 0) {
642
651
  if (this.#ttlClock !== void 0) this.#updateTTLClockInCVRWithoutLock(this.#lc);
@@ -725,16 +734,19 @@ var ViewSyncerService = class {
725
734
  this.#ttlClockBase = now;
726
735
  }
727
736
  const newClient = new ClientHandler(lc, this.id, connCtx.clientID, connCtx.wsID, this.#shard, connCtx.baseCookie, downstream);
728
- this.#clients.get(connCtx.clientID)?.close(`replaced by wsID: ${connCtx.wsID}`);
737
+ const replaced = this.#clients.get(connCtx.clientID);
729
738
  this.#clients.set(connCtx.clientID, newClient);
739
+ replaced?.close(`replaced by wsID: ${connCtx.wsID}`);
730
740
  startAsyncSpan(tracer, "vs.initConnection.async", () => this.#runInLockForClient(connCtx, initConnectionMessage, async (lc, clientID, msg, cvr) => {
731
741
  if (cvr.clientSchema === null && !msg.clientSchema) throw new ProtocolErrorWithLevel({
732
742
  kind: InvalidConnectionRequest,
733
743
  message: "The initConnection message for a new client group must include client schema.",
734
744
  origin: ZeroCache
735
745
  }, "warn");
736
- if (!await this.#validateConnection(connCtx)) return;
737
- await this.#handleConfigUpdate(lc, clientID, msg, cvr, "all", connCtx.profileID ?? `cg${this.id}`, connCtx);
746
+ const validatedConnCtx = await this.#validateCurrentConnection(connCtx);
747
+ if (!validatedConnCtx) return;
748
+ newClient.markValidated();
749
+ await this.#handleConfigUpdate(lc, clientID, msg, cvr, "all", validatedConnCtx.profileID ?? `cg${this.id}`, validatedConnCtx);
738
750
  this.#initialized.resolve("initialized");
739
751
  }, newClient)).catch((e) => newClient.fail(e));
740
752
  return downstream;
@@ -750,11 +762,9 @@ var ViewSyncerService = class {
750
762
  return;
751
763
  }
752
764
  lc.debug?.("Auth changed, re-validating and re-transforming queries");
753
- const connCtx = this.connContextManager.mustGetConnectionContext(selector);
754
- if (!this.#pipelinesHydrated) {
755
- if (!await this.#validateConnection(connCtx)) return;
756
- }
757
- return await this.#handleConfigUpdate(lc, clientID, {}, cvr, "all", void 0, connCtx);
765
+ const validatedConnCtx = await this.#validateCurrentConnection(selector);
766
+ if (!validatedConnCtx) return;
767
+ return await this.#handleConfigUpdate(lc, clientID, {}, cvr, "all", void 0, validatedConnCtx);
758
768
  });
759
769
  }
760
770
  async deleteClients(selector, msg) {
@@ -779,6 +789,8 @@ var ViewSyncerService = class {
779
789
  */
780
790
  #flushUpdater(lc, updater, patchesPoked = false) {
781
791
  return startAsyncSpan(tracer, "vs.#flushUpdater", () => this.#runPriorityOp(lc, "flushing cvr", async () => {
792
+ const { pinnedUser } = this.connContextManager.getGroupState();
793
+ if (pinnedUser !== void 0) updater.setPinnedUser(pinnedUser);
782
794
  const now = Date.now();
783
795
  const ttlClock = this.#getTTLClock(now);
784
796
  const { cvr, flushed } = await updater.flush(lc, this.#lastConnectTime, now, ttlClock, patchesPoked);
@@ -851,7 +863,7 @@ var ViewSyncerService = class {
851
863
  const [cmd, body] = msg;
852
864
  if (newClient || !this.#clients.has(clientID)) this.#lastConnectTime = Date.now();
853
865
  return startAsyncSpan(tracer, `vs.#runInLockForClient(${cmd})`, async (span) => {
854
- span.setAttribute("clientGroupID", this.id);
866
+ span.setAttribute("clientGroupID", clientGroupLogID(this.id));
855
867
  span.setAttribute("clientID", clientID);
856
868
  let client;
857
869
  let result;
@@ -861,31 +873,44 @@ var ViewSyncerService = class {
861
873
  lc = lc.withContext("clientID", clientID).withContext("wsID", wsID).withContext("cmd", cmd);
862
874
  lc.debug?.("acquired lock for cvr");
863
875
  client = this.#clients.get(clientID);
864
- if (client?.wsID !== wsID) {
865
- lc.debug?.("mismatched wsID", client?.wsID, wsID);
876
+ if (client?.wsID !== wsID || newClient && client !== newClient) {
877
+ lc.debug?.("mismatched or superseded client", client?.wsID, wsID);
866
878
  return;
867
879
  }
868
880
  connCtx = this.connContextManager.getConnectionContext(selector);
869
- if (newClient) {
870
- assert(newClient === client, "newClient must match existing client");
871
- checkClientAndCVRVersions(client.version(), cvr.version);
872
- } else if (!this.#clients.has(clientID)) lc.warn?.(`Processing ${cmd} before initConnection was received`);
881
+ if (newClient) checkClientAndCVRVersions(client.version(), cvr.version);
882
+ else if (!this.#clients.has(clientID)) lc.warn?.(`Processing ${cmd} before initConnection was received`);
873
883
  lc.debug?.(cmd, body);
874
884
  result = await fn(lc, clientID, body, cvr);
875
885
  });
876
886
  } catch (e) {
877
887
  this.#lc.withContext("clientID", clientID).withContext("wsID", wsID).withContext("cmd", cmd)[getLogLevel(e)]?.(`closing connection with error`, e);
878
- if (connCtx) this.connContextManager.failConnection(selector, connCtx.revision);
888
+ const current = connCtx && this.#currentRegistration(connCtx);
889
+ if (current) this.connContextManager.failConnection(selector, current.revision);
879
890
  if (client) client.fail(e);
880
891
  else throw e;
881
892
  }
882
893
  return result;
883
894
  });
884
895
  }
896
+ /**
897
+ * The clients that pokes and catch-up are sent to. A client whose
898
+ * connection has not been validated yet is left out: its `initConnection`
899
+ * validates it and then catches it up from its baseCookie, so lock-held
900
+ * work for other clients that runs in the meantime must not send it data.
901
+ */
885
902
  #getClients(atVersion) {
886
- const clients = [...this.#clients.values()];
903
+ const clients = [...this.#clients.values()].filter((c) => c.validated());
887
904
  return atVersion ? clients.filter((c) => cmpVersions(c.version() ?? EMPTY_CVR_VERSION, atVersion) === 0) : clients;
888
905
  }
906
+ /**
907
+ * The client to send a targeted message (e.g. a query error) to, or
908
+ * `undefined` while its connection is still unvalidated; see #getClients().
909
+ */
910
+ #getValidatedClient(clientID) {
911
+ const client = this.#clients.get(clientID);
912
+ return client?.validated() ? client : void 0;
913
+ }
889
914
  #handleConfigUpdate = (lc, clientID, { clientSchema, deleted, desiredQueriesPatch, activeClients }, cvr, customQueryTransformMode, profileID, connCtx) => startAsyncSpan(tracer, "vs.#handleConfigUpdate", async () => {
890
915
  const deletedClientIDs = [];
891
916
  const deletedClientGroupIDs = [];
@@ -1112,12 +1137,12 @@ var ViewSyncerService = class {
1112
1137
  return clientIds;
1113
1138
  };
1114
1139
  if ("queryIDs" in errorOrErrors) {
1115
- for (const clientId of getAffectedClientIDs(errorOrErrors.queryIDs)) this.#clients.get(clientId)?.sendQueryTransformFailedError(errorOrErrors);
1140
+ for (const clientId of getAffectedClientIDs(errorOrErrors.queryIDs)) this.#getValidatedClient(clientId)?.sendQueryTransformFailedError(errorOrErrors);
1116
1141
  return;
1117
1142
  }
1118
1143
  const appErrorGroups = /* @__PURE__ */ new Map();
1119
1144
  for (const err of errorOrErrors) for (const clientId of getAffectedClientIDs([err.id])) getOrInsertComputed(appErrorGroups, clientId, newArray).push(err);
1120
- for (const [clientId, errors] of appErrorGroups) this.#clients.get(clientId)?.sendQueryTransformApplicationErrors(errors);
1145
+ for (const [clientId, errors] of appErrorGroups) this.#getValidatedClient(clientId)?.sendQueryTransformApplicationErrors(errors);
1121
1146
  }
1122
1147
  #addQueryMaterializationServerMetric(queryID, elapsed) {
1123
1148
  this.#inspectorDelegate.addMetric("query-materialization-server", elapsed, queryID);
@@ -1136,7 +1161,7 @@ var ViewSyncerService = class {
1136
1161
  }
1137
1162
  #logQueryCoverageShadowSummary(lc, hydrationPath, totalHydratedQueries, coveredHydratedQueries, firstCoveredQuery) {
1138
1163
  if (!this.#config.enableQueryCovering || totalHydratedQueries === 0) return;
1139
- let coverageLC = lc.withContext("appID", this.#shard.appID).withContext("shardNum", this.#shard.shardNum).withContext("clientGroupID", this.id).withContext("queryCoverageMode", "shadow").withContext("hydrationPath", hydrationPath).withContext("totalHydratedQueries", totalHydratedQueries).withContext("coveredHydratedQueries", coveredHydratedQueries).withContext("uncoveredHydratedQueries", totalHydratedQueries - coveredHydratedQueries);
1164
+ let coverageLC = lc.withContext("appID", this.#shard.appID).withContext("shardNum", this.#shard.shardNum).withContext("clientGroupID", clientGroupLogID(this.id)).withContext("queryCoverageMode", "shadow").withContext("hydrationPath", hydrationPath).withContext("totalHydratedQueries", totalHydratedQueries).withContext("coveredHydratedQueries", coveredHydratedQueries).withContext("uncoveredHydratedQueries", totalHydratedQueries - coveredHydratedQueries);
1140
1165
  if (firstCoveredQuery) {
1141
1166
  coverageLC = coverageLC.withContext("firstCoveredQueryHash", firstCoveredQuery.coveredQueryHash).withContext("firstCoveredTransformationHash", firstCoveredQuery.coveredTransformationHash).withContext("firstCoveringQueryHash", firstCoveredQuery.coveringQueryHash).withContext("firstCoveringTransformationHash", firstCoveredQuery.coveringTransformationHash);
1142
1167
  if (firstCoveredQuery.coveredQueryName !== void 0) coverageLC = coverageLC.withContext("firstCoveredQueryName", firstCoveredQuery.coveredQueryName);
@@ -1158,7 +1183,7 @@ var ViewSyncerService = class {
1158
1183
  }, previousQueries) {
1159
1184
  return startAsyncSpan(tracer, "vs.#syncQueryPipelineSet", async (span) => {
1160
1185
  const start = performance.now();
1161
- span.setAttribute("clientGroupID", this.id);
1186
+ span.setAttribute("clientGroupID", clientGroupLogID(this.id));
1162
1187
  assert(this.#pipelines.initialized(), "pipelines must be initialized (syncQueryPipelineSet)");
1163
1188
  if (this.#ttlClock === void 0) this.#ttlClock = cvr.ttlClock;
1164
1189
  const now = Date.now();
@@ -1328,7 +1353,7 @@ var ViewSyncerService = class {
1328
1353
  if (!lc.warn) return;
1329
1354
  const shape = queryShape(query.ast);
1330
1355
  lc.warn("Query hydration aborted for exceeding the hydration timeout", {
1331
- clientGroupID: this.id,
1356
+ clientGroupID: clientGroupLogID(this.id),
1332
1357
  queryHash: query.id,
1333
1358
  transformationHash: query.transformationHash,
1334
1359
  ...query.name !== void 0 && { queryName: query.name },
@@ -1356,7 +1381,7 @@ var ViewSyncerService = class {
1356
1381
  if (suppressed === void 0) return;
1357
1382
  lc.warn("Slow query materialization", {
1358
1383
  zeroEvent: "query-slow-hydration",
1359
- clientGroupID: this.id,
1384
+ clientGroupID: clientGroupLogID(this.id),
1360
1385
  queryHash: query.id,
1361
1386
  transformationHash: query.transformationHash,
1362
1387
  ...query.name !== void 0 && { queryName: query.name },
@@ -1375,7 +1400,7 @@ var ViewSyncerService = class {
1375
1400
  #recordCircuitBreakerRejection(lc, query) {
1376
1401
  this.#hydrationCircuitBreakerRejections.add(1);
1377
1402
  lc.warn?.("Query rejected by its open hydration circuit breaker", {
1378
- clientGroupID: this.id,
1403
+ clientGroupID: clientGroupLogID(this.id),
1379
1404
  queryHash: query.id,
1380
1405
  transformationHash: query.transformationHash,
1381
1406
  ...query.name !== void 0 && { queryName: query.name },
@@ -1417,7 +1442,7 @@ var ViewSyncerService = class {
1417
1442
  };
1418
1443
  for (const clientID of Object.keys(query.clientState)) getOrInsertComputed(errorsByClient, clientID, newArray).push(error);
1419
1444
  }
1420
- for (const [clientID, errors] of errorsByClient) this.#clients.get(clientID)?.sendQueryTransformApplicationErrors(errors);
1445
+ for (const [clientID, errors] of errorsByClient) this.#getValidatedClient(clientID)?.sendQueryTransformApplicationErrors(errors);
1421
1446
  }
1422
1447
  #recordHydrationBudgetExhaustion(lc, hydrationBudget, activeHydratedQueries, inactiveHydratedQueries, inactiveEvictedQueryIDs) {
1423
1448
  const elapsedMs = hydrationBudget.exhaustedAtMs;
@@ -1428,7 +1453,7 @@ var ViewSyncerService = class {
1428
1453
  this.#hydrationBudgetOvershoot.record(Math.max(0, elapsedMs - hydrationBudget.limitMs));
1429
1454
  if (inactiveEvictedQueryIDs.length > 0) this.#queryEvictions.add(inactiveEvictedQueryIDs.length, { reason: "hydration-budget" });
1430
1455
  lc.info?.("view-syncer hydration budget exhausted", {
1431
- clientGroupID: this.id,
1456
+ clientGroupID: clientGroupLogID(this.id),
1432
1457
  hydrationBudgetMs: hydrationBudget.limitMs,
1433
1458
  hydrationElapsedMs: elapsedMs,
1434
1459
  activeHydratedQueries,
@@ -1620,7 +1645,8 @@ var ViewSyncerService = class {
1620
1645
  if (!row.refCounts) patch = {
1621
1646
  type: "row",
1622
1647
  op: "del",
1623
- id
1648
+ id,
1649
+ catchup: true
1624
1650
  };
1625
1651
  else {
1626
1652
  const { contents } = contentsAndVersion(must(this.#pipelines.getRow(table, rowKey), `Missing row in ${table} keyed by ${Object.keys(rowKey).join()}`));
@@ -1718,7 +1744,7 @@ var ViewSyncerService = class {
1718
1744
  */
1719
1745
  #advancePipelines(lc, cvr) {
1720
1746
  return startAsyncSpan(tracer, "vs.#advancePipelines", async (span) => {
1721
- span.setAttribute("clientGroupID", this.id);
1747
+ span.setAttribute("clientGroupID", clientGroupLogID(this.id));
1722
1748
  assert(this.#pipelines.initialized(), "pipelines must be initialized (advancePipelines");
1723
1749
  const start = performance.now();
1724
1750
  const timer = this.#timer(lc, "batch");
@@ -1938,7 +1964,7 @@ var ViewSyncerService = class {
1938
1964
  const now = this.#now();
1939
1965
  if (log.hydrationTimeMs < REBUILD_LOG_MIN_MS || now - this.#rebuildsLoggedAt < REBUILD_LOG_INTERVAL_MS) return;
1940
1966
  lc.info?.("Client group hydrated queries again in its batches of changes instead of pushing the changes to them", {
1941
- clientGroupID: this.id,
1967
+ clientGroupID: clientGroupLogID(this.id),
1942
1968
  ...log
1943
1969
  });
1944
1970
  this.#rebuildsLoggedAt = now;
@@ -2006,7 +2032,7 @@ var ViewSyncerService = class {
2006
2032
  const now = this.#now();
2007
2033
  if (log.waitedMs < BATCH_WAIT_LOG_MIN_MS || now - log.loggedAt < BATCH_WAIT_LOG_INTERVAL_MS) return;
2008
2034
  this.#lc.info?.("Client group is waiting between batches: its sync worker is over its share target", {
2009
- clientGroupID: this.id,
2035
+ clientGroupID: clientGroupLogID(this.id),
2010
2036
  waits: log.waits,
2011
2037
  waitedMs: log.waitedMs,
2012
2038
  lastWaitMs: waitedMs,
@@ -2028,7 +2054,7 @@ var ViewSyncerService = class {
2028
2054
  clientID,
2029
2055
  wsID: client.wsID
2030
2056
  });
2031
- return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this, this.#cvrStore, this.#config, connCtx);
2057
+ return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this.#cvrStore, this.#config, connCtx);
2032
2058
  };
2033
2059
  async #runBackgroundRetransform(lc) {
2034
2060
  const attemptRetransform = async (connCtx) => {
@@ -2085,7 +2111,8 @@ var ViewSyncerService = class {
2085
2111
  if (response.kind === "TransformFailed") throw new ProtocolErrorWithLevel(response, "warn");
2086
2112
  validation = response.validation;
2087
2113
  } else validation = { kind: "client-fallback" };
2088
- this.connContextManager.validateConnection(connCtx, connCtx.revision, validation);
2114
+ if (!this.#currentRegistration(connCtx)) return false;
2115
+ if (this.connContextManager.validateConnection(connCtx, connCtx.revision, validation) && this.#cvr?.pinnedUser === null) this.#cvr = await this.#flushUpdater(this.#lc, new CVRUpdater(this.#cvrStore, this.#cvr, this.#cvr.replicaVersion));
2089
2116
  return true;
2090
2117
  } catch (e) {
2091
2118
  if (isProtocolError(e) && isAuthErrorBody(e.errorBody)) {
@@ -2095,12 +2122,38 @@ var ViewSyncerService = class {
2095
2122
  revision: connCtx.revision,
2096
2123
  message: e.message
2097
2124
  });
2098
- this.#failMaintenanceConnection(connCtx, e);
2125
+ if (this.#currentRegistration(connCtx)) this.#failMaintenanceConnection(connCtx, e);
2099
2126
  return false;
2100
2127
  }
2101
2128
  throw e;
2102
2129
  }
2103
2130
  }
2131
+ /**
2132
+ * The live context registered for `connCtx`'s selector, or `undefined` if
2133
+ * that socket is gone or has been replaced by one reusing the selector
2134
+ * (which starts over at revision 0, so revisions alone cannot tell).
2135
+ */
2136
+ #currentRegistration(connCtx) {
2137
+ const current = this.connContextManager.getConnectionContext(connCtx);
2138
+ return current?.insertionOrder === connCtx.insertionOrder ? current : void 0;
2139
+ }
2140
+ /**
2141
+ * Validates the connection's credential as it stands once validation ends.
2142
+ * An `updateAuth` handled while a validation is in flight moves the
2143
+ * connection to a newer revision, which makes a failure of the older one
2144
+ * stale, as is its success; the newer credential is then validated in its
2145
+ * place, so the `initConnection` waiting on it is not dropped.
2146
+ */
2147
+ async #validateCurrentConnection(selector) {
2148
+ let connCtx = this.connContextManager.getConnectionContext(selector);
2149
+ while (connCtx) {
2150
+ const valid = await this.#validateConnection(connCtx);
2151
+ const current = this.#currentRegistration(connCtx);
2152
+ if (!current) return;
2153
+ if (current.revision === connCtx.revision) return valid ? connCtx : void 0;
2154
+ connCtx = current;
2155
+ }
2156
+ }
2104
2157
  #failMaintenanceConnection(connCtx, error) {
2105
2158
  if (!this.connContextManager.failConnection(connCtx, connCtx.revision)) return;
2106
2159
  const wrapped = wrapWithProtocolError(error);
@@ -2124,10 +2177,9 @@ var ViewSyncerService = class {
2124
2177
  this.#stopShutdownTimer();
2125
2178
  this.#customQueryTransformer?.destroy();
2126
2179
  for (const client of this.#clients.values()) if (err) client.fail(err);
2127
- else client.close(`closed clientGroupID=${this.id}`);
2180
+ else client.close(`closed clientGroupID=${clientGroupLogID(this.id)}`);
2128
2181
  await this.#lock.withLock(() => {});
2129
2182
  this.#pipelines.destroy();
2130
- this.#inspectorDelegate.clearAuthenticated(this.id, this);
2131
2183
  }
2132
2184
  /**
2133
2185
  * Test helper: Manually mark initialization as complete.