@rocicorp/zero 1.10.0-canary.21 → 1.10.0-canary.23

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 (334) hide show
  1. package/out/analyze-query/src/bin-analyze.js +1 -1
  2. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  3. package/out/replicache/src/dag/gc.d.ts.map +1 -1
  4. package/out/replicache/src/dag/gc.js +2 -1
  5. package/out/replicache/src/dag/gc.js.map +1 -1
  6. package/out/replicache/src/dag/invalid-ref-count-error.d.ts +12 -0
  7. package/out/replicache/src/dag/invalid-ref-count-error.d.ts.map +1 -0
  8. package/out/replicache/src/dag/invalid-ref-count-error.js +19 -0
  9. package/out/replicache/src/dag/invalid-ref-count-error.js.map +1 -0
  10. package/out/replicache/src/dag/store-impl.d.ts +12 -2
  11. package/out/replicache/src/dag/store-impl.d.ts.map +1 -1
  12. package/out/replicache/src/dag/store-impl.js +22 -8
  13. package/out/replicache/src/dag/store-impl.js.map +1 -1
  14. package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  15. package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
  16. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  17. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +15 -5
  18. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +1 -1
  19. package/out/replicache/src/kv/idb-store-with-mem-fallback.js +25 -25
  20. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +1 -1
  21. package/out/replicache/src/kv/idb-store.d.ts +9 -0
  22. package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
  23. package/out/replicache/src/kv/idb-store.js +14 -2
  24. package/out/replicache/src/kv/idb-store.js.map +1 -1
  25. package/out/replicache/src/kv/mem-store.d.ts +1 -0
  26. package/out/replicache/src/kv/mem-store.d.ts.map +1 -1
  27. package/out/replicache/src/kv/mem-store.js +3 -0
  28. package/out/replicache/src/kv/mem-store.js.map +1 -1
  29. package/out/replicache/src/kv/op-sqlite/store.d.ts.map +1 -1
  30. package/out/replicache/src/kv/op-sqlite/store.js +1 -1
  31. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  32. package/out/replicache/src/kv/sqlite-store.d.ts +2 -1
  33. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  34. package/out/replicache/src/kv/sqlite-store.js +6 -1
  35. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  36. package/out/replicache/src/kv/store.d.ts +6 -0
  37. package/out/replicache/src/kv/store.d.ts.map +1 -1
  38. package/out/replicache/src/persist/collect-idb-databases.d.ts +10 -5
  39. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  40. package/out/replicache/src/persist/collect-idb-databases.js +16 -9
  41. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  42. package/out/replicache/src/replicache-impl.d.ts +14 -3
  43. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  44. package/out/replicache/src/replicache-impl.js +104 -9
  45. package/out/replicache/src/replicache-impl.js.map +1 -1
  46. package/out/shared/src/arrays.js +1 -15
  47. package/out/shared/src/arrays.js.map +1 -1
  48. package/out/shared/src/hash.d.ts +1 -0
  49. package/out/shared/src/hash.d.ts.map +1 -1
  50. package/out/shared/src/hash.js +10 -28
  51. package/out/shared/src/hash.js.map +1 -1
  52. package/out/shared/src/iterables.d.ts +2 -0
  53. package/out/shared/src/iterables.d.ts.map +1 -1
  54. package/out/shared/src/iterables.js +18 -1
  55. package/out/shared/src/iterables.js.map +1 -1
  56. package/out/shared/src/json.d.ts.map +1 -1
  57. package/out/shared/src/json.js +2 -2
  58. package/out/shared/src/json.js.map +1 -1
  59. package/out/shared/src/ring-buffer.d.ts +8 -0
  60. package/out/shared/src/ring-buffer.d.ts.map +1 -1
  61. package/out/shared/src/ring-buffer.js +22 -1
  62. package/out/shared/src/ring-buffer.js.map +1 -1
  63. package/out/shared/src/xxhash32.d.ts +36 -0
  64. package/out/shared/src/xxhash32.d.ts.map +1 -0
  65. package/out/shared/src/xxhash32.js +51 -0
  66. package/out/shared/src/xxhash32.js.map +1 -0
  67. package/out/z2s/src/compiler.js +1 -1
  68. package/out/zero/package.js +1 -1
  69. package/out/zero/package.js.map +1 -1
  70. package/out/zero-cache/src/auth/read-authorizer.js +1 -1
  71. package/out/zero-cache/src/config/zero-config.d.ts +8 -0
  72. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  73. package/out/zero-cache/src/config/zero-config.js +28 -1
  74. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  75. package/out/zero-cache/src/custom-queries/transform-query.js +1 -1
  76. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  77. package/out/zero-cache/src/server/change-streamer.js +11 -3
  78. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  79. package/out/zero-cache/src/server/inspector-delegate.d.ts +18 -2
  80. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  81. package/out/zero-cache/src/server/inspector-delegate.js +23 -9
  82. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  83. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  84. package/out/zero-cache/src/server/main.js +8 -1
  85. package/out/zero-cache/src/server/main.js.map +1 -1
  86. package/out/zero-cache/src/server/mutator.js +2 -0
  87. package/out/zero-cache/src/server/mutator.js.map +1 -1
  88. package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
  89. package/out/zero-cache/src/server/reaper.js +2 -0
  90. package/out/zero-cache/src/server/reaper.js.map +1 -1
  91. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  92. package/out/zero-cache/src/server/replicator.js +2 -0
  93. package/out/zero-cache/src/server/replicator.js.map +1 -1
  94. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  95. package/out/zero-cache/src/server/runner/zero-dispatcher.js +3 -0
  96. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  97. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  98. package/out/zero-cache/src/server/syncer.js +2 -0
  99. package/out/zero-cache/src/server/syncer.js.map +1 -1
  100. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  101. package/out/zero-cache/src/services/analyze.js +3 -2
  102. package/out/zero-cache/src/services/analyze.js.map +1 -1
  103. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  104. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +14 -1
  105. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  106. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
  107. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +15 -3
  108. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  109. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  110. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -0
  111. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  112. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -1
  113. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  114. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +5 -0
  115. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  116. package/out/zero-cache/src/services/mutagen/mutagen.js +7 -1
  117. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  118. package/out/zero-cache/src/services/mutagen/pusher.js +3 -2
  119. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  120. package/out/zero-cache/src/services/profz.d.ts +7 -0
  121. package/out/zero-cache/src/services/profz.d.ts.map +1 -0
  122. package/out/zero-cache/src/services/profz.js +103 -0
  123. package/out/zero-cache/src/services/profz.js.map +1 -0
  124. package/out/zero-cache/src/services/run-ast.d.ts +12 -0
  125. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  126. package/out/zero-cache/src/services/run-ast.js +27 -15
  127. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  128. package/out/zero-cache/src/services/runner.d.ts.map +1 -1
  129. package/out/zero-cache/src/services/runner.js +1 -1
  130. package/out/zero-cache/src/services/runner.js.map +1 -1
  131. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +45 -0
  132. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  133. package/out/zero-cache/src/services/view-syncer/cvr.js +133 -1
  134. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  135. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +14 -1
  136. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +1 -1
  137. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +30 -4
  138. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  139. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts +33 -0
  140. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts.map +1 -0
  141. package/out/zero-cache/src/services/view-syncer/hydration-budget.js +62 -0
  142. package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -0
  143. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +51 -0
  144. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
  145. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
  146. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
  147. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +1 -1
  148. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  149. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -2
  150. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  151. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +2 -1
  152. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  153. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +33 -18
  154. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  155. package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +1 -1
  156. package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
  157. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -1
  158. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  159. package/out/zero-cache/src/services/view-syncer/snapshotter.js +9 -3
  160. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  161. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
  162. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  163. package/out/zero-cache/src/services/view-syncer/view-syncer.js +470 -90
  164. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  165. package/out/zero-cache/src/types/processes.d.ts +32 -0
  166. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  167. package/out/zero-cache/src/types/processes.js +43 -4
  168. package/out/zero-cache/src/types/processes.js.map +1 -1
  169. package/out/zero-cache/src/types/profiler.d.ts +12 -0
  170. package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
  171. package/out/zero-cache/src/types/profiler.js +46 -1
  172. package/out/zero-cache/src/types/profiler.js.map +1 -1
  173. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  174. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +21 -16
  175. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  176. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  177. package/out/zero-cache/src/workers/syncer.js +10 -1
  178. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  179. package/out/zero-client/src/client/connection-manager.d.ts +20 -0
  180. package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
  181. package/out/zero-client/src/client/connection-manager.js +32 -14
  182. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  183. package/out/zero-client/src/client/connection-status-enum.d.ts +2 -0
  184. package/out/zero-client/src/client/connection-status-enum.d.ts.map +1 -1
  185. package/out/zero-client/src/client/connection-status-enum.js +3 -1
  186. package/out/zero-client/src/client/connection-status-enum.js.map +1 -1
  187. package/out/zero-client/src/client/connection.d.ts +6 -0
  188. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  189. package/out/zero-client/src/client/connection.js +2 -1
  190. package/out/zero-client/src/client/connection.js.map +1 -1
  191. package/out/zero-client/src/client/context.d.ts +22 -12
  192. package/out/zero-client/src/client/context.d.ts.map +1 -1
  193. package/out/zero-client/src/client/context.js +92 -25
  194. package/out/zero-client/src/client/context.js.map +1 -1
  195. package/out/zero-client/src/client/custom.js +2 -2
  196. package/out/zero-client/src/client/custom.js.map +1 -1
  197. package/out/zero-client/src/client/error.js +1 -1
  198. package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
  199. package/out/zero-client/src/client/ivm-branch.js +20 -3
  200. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  201. package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
  202. package/out/zero-client/src/client/mutator-proxy.js +2 -1
  203. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  204. package/out/zero-client/src/client/options.d.ts +3 -0
  205. package/out/zero-client/src/client/options.d.ts.map +1 -1
  206. package/out/zero-client/src/client/options.js.map +1 -1
  207. package/out/zero-client/src/client/query-manager.d.ts +2 -0
  208. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  209. package/out/zero-client/src/client/query-manager.js +48 -9
  210. package/out/zero-client/src/client/query-manager.js.map +1 -1
  211. package/out/zero-client/src/client/version.js +1 -1
  212. package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
  213. package/out/zero-client/src/client/zero-rep.js +1 -1
  214. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  215. package/out/zero-client/src/client/zero.d.ts +18 -3
  216. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  217. package/out/zero-client/src/client/zero.js +61 -14
  218. package/out/zero-client/src/client/zero.js.map +1 -1
  219. package/out/zero-client/src/types/query-result.d.ts +2 -0
  220. package/out/zero-client/src/types/query-result.d.ts.map +1 -1
  221. package/out/zero-protocol/src/ast.d.ts +64 -1
  222. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  223. package/out/zero-protocol/src/ast.js +223 -51
  224. package/out/zero-protocol/src/ast.js.map +1 -1
  225. package/out/zero-protocol/src/error.d.ts +1 -1
  226. package/out/zero-protocol/src/query-hash-visitor.d.ts +81 -0
  227. package/out/zero-protocol/src/query-hash-visitor.d.ts.map +1 -0
  228. package/out/zero-protocol/src/query-hash-visitor.js +324 -0
  229. package/out/zero-protocol/src/query-hash-visitor.js.map +1 -0
  230. package/out/zero-protocol/src/query-hash.d.ts +7 -2
  231. package/out/zero-protocol/src/query-hash.d.ts.map +1 -1
  232. package/out/zero-protocol/src/query-hash.js +12 -9
  233. package/out/zero-protocol/src/query-hash.js.map +1 -1
  234. package/out/zero-react/src/use-query.d.ts +5 -4
  235. package/out/zero-react/src/use-query.d.ts.map +1 -1
  236. package/out/zero-react/src/use-query.js +86 -9
  237. package/out/zero-react/src/use-query.js.map +1 -1
  238. package/out/zero-solid/src/solid-view.d.ts +17 -0
  239. package/out/zero-solid/src/solid-view.d.ts.map +1 -1
  240. package/out/zero-solid/src/solid-view.js +39 -1
  241. package/out/zero-solid/src/solid-view.js.map +1 -1
  242. package/out/zero-solid/src/use-query.js +5 -3
  243. package/out/zero-solid/src/use-query.js.map +1 -1
  244. package/out/zql/src/builder/debug-delegate.d.ts +6 -1
  245. package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
  246. package/out/zql/src/builder/debug-delegate.js +14 -2
  247. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  248. package/out/zql/src/ivm/array-view.d.ts +29 -0
  249. package/out/zql/src/ivm/array-view.d.ts.map +1 -1
  250. package/out/zql/src/ivm/array-view.js +58 -9
  251. package/out/zql/src/ivm/array-view.js.map +1 -1
  252. package/out/zql/src/ivm/cap.d.ts.map +1 -1
  253. package/out/zql/src/ivm/cap.js +5 -3
  254. package/out/zql/src/ivm/cap.js.map +1 -1
  255. package/out/zql/src/ivm/deferred-input.d.ts.map +1 -1
  256. package/out/zql/src/ivm/deferred-input.js +3 -2
  257. package/out/zql/src/ivm/deferred-input.js.map +1 -1
  258. package/out/zql/src/ivm/exists.d.ts +1 -1
  259. package/out/zql/src/ivm/exists.d.ts.map +1 -1
  260. package/out/zql/src/ivm/exists.js +1 -1
  261. package/out/zql/src/ivm/exists.js.map +1 -1
  262. package/out/zql/src/ivm/fan-in.d.ts +2 -2
  263. package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
  264. package/out/zql/src/ivm/fan-in.js +6 -5
  265. package/out/zql/src/ivm/fan-in.js.map +1 -1
  266. package/out/zql/src/ivm/fan-out.d.ts +2 -2
  267. package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
  268. package/out/zql/src/ivm/fan-out.js.map +1 -1
  269. package/out/zql/src/ivm/filter-operators.d.ts +4 -4
  270. package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
  271. package/out/zql/src/ivm/filter-operators.js +10 -8
  272. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  273. package/out/zql/src/ivm/filter.d.ts +2 -2
  274. package/out/zql/src/ivm/filter.d.ts.map +1 -1
  275. package/out/zql/src/ivm/filter.js +7 -4
  276. package/out/zql/src/ivm/filter.js.map +1 -1
  277. package/out/zql/src/ivm/flipped-join.js +2 -2
  278. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  279. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
  280. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
  281. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +6 -4
  282. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  283. package/out/zql/src/ivm/memory-source.d.ts +15 -3
  284. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  285. package/out/zql/src/ivm/memory-source.js +35 -9
  286. package/out/zql/src/ivm/memory-source.js.map +1 -1
  287. package/out/zql/src/ivm/take.d.ts.map +1 -1
  288. package/out/zql/src/ivm/take.js +3 -4
  289. package/out/zql/src/ivm/take.js.map +1 -1
  290. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  291. package/out/zql/src/ivm/union-fan-in.js +9 -7
  292. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  293. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  294. package/out/zql/src/ivm/view-apply-change.js +3 -3
  295. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  296. package/out/zql/src/ivm/view.d.ts +15 -0
  297. package/out/zql/src/ivm/view.d.ts.map +1 -1
  298. package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
  299. package/out/zql/src/mutate/mutator-registry.js +2 -2
  300. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  301. package/out/zql/src/query/query-delegate-base.d.ts +4 -2
  302. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  303. package/out/zql/src/query/query-delegate-base.js +69 -36
  304. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  305. package/out/zql/src/query/query-delegate.d.ts +33 -6
  306. package/out/zql/src/query/query-delegate.d.ts.map +1 -1
  307. package/out/zql/src/query/query-impl.d.ts +25 -14
  308. package/out/zql/src/query/query-impl.d.ts.map +1 -1
  309. package/out/zql/src/query/query-impl.js +414 -94
  310. package/out/zql/src/query/query-impl.js.map +1 -1
  311. package/out/zql/src/query/query-internals.d.ts +21 -11
  312. package/out/zql/src/query/query-internals.d.ts.map +1 -1
  313. package/out/zql/src/query/query-internals.js.map +1 -1
  314. package/out/zql/src/query/query-transitions.d.ts +171 -0
  315. package/out/zql/src/query/query-transitions.d.ts.map +1 -0
  316. package/out/zql/src/query/query-transitions.js +207 -0
  317. package/out/zql/src/query/query-transitions.js.map +1 -0
  318. package/out/zql/src/query/query.d.ts +11 -2
  319. package/out/zql/src/query/query.d.ts.map +1 -1
  320. package/out/zql/src/query/runnable-query-impl.d.ts +3 -2
  321. package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
  322. package/out/zql/src/query/runnable-query-impl.js +23 -2
  323. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  324. package/out/zql/src/query/typed-view.d.ts +1 -1
  325. package/out/zql/src/query/typed-view.d.ts.map +1 -1
  326. package/out/zqlite/src/db.d.ts +9 -1
  327. package/out/zqlite/src/db.d.ts.map +1 -1
  328. package/out/zqlite/src/db.js +8 -2
  329. package/out/zqlite/src/db.js.map +1 -1
  330. package/out/zqlite/src/table-source.d.ts +1 -0
  331. package/out/zqlite/src/table-source.d.ts.map +1 -1
  332. package/out/zqlite/src/table-source.js +10 -5
  333. package/out/zqlite/src/table-source.js.map +1 -1
  334. package/package.json +1 -1
@@ -11,12 +11,11 @@ import { manualSpan, startAsyncSpan, startSpan } from "../../../../otel/src/span
11
11
  import { ZERO_VERSION_COLUMN_NAME } from "../replicator/schema/constants.js";
12
12
  import { transformAndHashQuery } from "../../auth/read-authorizer.js";
13
13
  import { randInt } from "../../../../shared/src/rand.js";
14
- import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter } from "../../observability/metrics.js";
14
+ import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter, getOrCreateValueHistogram } from "../../observability/metrics.js";
15
15
  import { rowIDString } from "../../types/row-key.js";
16
16
  import "../replicator/schema/replication-state.js";
17
17
  import { parseSignature } from "./row-set-signature.js";
18
18
  import { ResetPipelinesSignal } from "./snapshotter.js";
19
- import "./pipeline-driver.js";
20
19
  import { Subscription } from "../../types/subscription.js";
21
20
  import { CustomKeyMap } from "../../../../shared/src/custom-key-map.js";
22
21
  import { ttlClockAsNumber, ttlClockFromNumber } from "./ttl-clock.js";
@@ -26,8 +25,10 @@ import { isAuthErrorBody } from "../../auth/auth.js";
26
25
  import { ClientHandler, startPoke } from "./client-handler.js";
27
26
  import { tracer } from "./tracer.js";
28
27
  import { CVRStore, ClientNotFoundError } from "./cvr-store.js";
29
- import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, nextEvictionTime } from "./cvr.js";
28
+ import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, classifyQueriesForHydration, nextEvictionTime } from "./cvr.js";
30
29
  import { E2EServingLagTracker } from "./e2e-serving-lag.js";
30
+ import { HydrationBudget } from "./hydration-budget.js";
31
+ import { HydrationCircuitBreaker } from "./hydration-circuit-breaker.js";
31
32
  import { handleInspect } from "./inspect-handler.js";
32
33
  import { QueryCoveringIndex } from "./query-covering.js";
33
34
  import { resolver } from "@rocicorp/resolver";
@@ -62,6 +63,7 @@ var ViewSyncerService = class {
62
63
  #drainCoordinator;
63
64
  #keepaliveMs;
64
65
  #slowHydrateThreshold;
66
+ #hydrationCircuitBreaker;
65
67
  #lastConnectTime = Date.now();
66
68
  /**
67
69
  * The TTL clock is used to determine the time at which queries are considered
@@ -89,14 +91,36 @@ var ViewSyncerService = class {
89
91
  #lock = new Lock();
90
92
  #cvrStore;
91
93
  #stopped = resolver();
94
+ /**
95
+ * Set when {@link #cleanup} begins. Lock tasks that were in flight when the
96
+ * view-syncer was stopped may still complete after the timers have been
97
+ * cleared; this flag prevents them from scheduling new timers, which would
98
+ * otherwise outlive the service (and retain everything it references).
99
+ */
100
+ #shuttingDown = false;
92
101
  #initialized = resolver();
93
102
  #cvr;
94
- #pipelinesSynced = false;
103
+ /**
104
+ * Indicates whether the query pipelines have completed initial catch-up and
105
+ * hydration with the CVR snapshot, and are ready for steady-state operation.
106
+ *
107
+ * - When `false`: The syncer is in its initial catch-up phase. The replica
108
+ * advances without diffs until it reaches `cvr.version.stateVersion`,
109
+ * after which `#maybeHydratePipelines` hydrates queries.
110
+ * - When `true`: Pipelines are fully populated and in steady-state; replica
111
+ * changes advance incrementally via `#advancePipelines`, and client query
112
+ * updates are processed via `#syncQueryPipelineSet`.
113
+ *
114
+ * Resets to `false` if `#advancePipelines` returns a `ResetPipelinesSignal`
115
+ * and pipelines must be reset and rehydrated.
116
+ */
117
+ #pipelinesHydrated = false;
95
118
  #servedVersion = null;
96
119
  #e2eServingLagTracker = new E2EServingLagTracker();
97
120
  #expiredQueriesTimer = 0;
98
121
  #authMaintenanceTimer = 0;
99
122
  #setTimeout;
123
+ #now;
100
124
  #customQueryTransformer;
101
125
  #queryReplacements = /* @__PURE__ */ new Map();
102
126
  #activeClients = getOrCreateUpDownCounter("sync", "active-clients", "Number of active sync clients");
@@ -123,10 +147,64 @@ var ViewSyncerService = class {
123
147
  #pipelineResets = getOrCreateCounter("sync", "pipeline-resets", "Number of pipeline resets");
124
148
  #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).");
125
149
  #sameHashRehydrationVersionBumps = getOrCreateCounter("sync", "query.same-hash-rehydrations-forced-bump", "Number of times query-set reconciliation forced a configVersion bump for already-gotten same-transformation-hash query rehydration because trackQueries would not otherwise bump. Expected to be near-zero; non-zero values indicate pipeline/CVR row-set drift reached query-set reconciliation.");
150
+ #hydrationBudgetExhaustions = getOrCreateCounter("sync", "hydration_budget_exhaustions", {
151
+ description: "Number of hydration passes that exhaust their budget.",
152
+ unit: "{pass}"
153
+ });
154
+ #hydrationBudgetEvictions = getOrCreateCounter("sync", "hydration_budget_evictions", {
155
+ description: "Number of inactive queries removed after hydration budget exhaustion.",
156
+ unit: "{query}"
157
+ });
158
+ #hydrationBudgetElapsed = getOrCreateValueHistogram("sync", "hydration_budget_elapsed", {
159
+ description: "Elapsed milliseconds when optional query hydration stopped.",
160
+ unit: "ms",
161
+ bucketBoundaries: [
162
+ 1,
163
+ 2,
164
+ 5,
165
+ 10,
166
+ 20,
167
+ 50,
168
+ 100,
169
+ 200,
170
+ 500,
171
+ 1e3,
172
+ 5e3
173
+ ]
174
+ });
175
+ #hydrationBudgetOvershoot = getOrCreateValueHistogram("sync", "hydration_budget_overshoot", {
176
+ description: "Milliseconds elapsed beyond the configured hydration budget.",
177
+ unit: "ms",
178
+ bucketBoundaries: [
179
+ 0,
180
+ 1,
181
+ 2,
182
+ 5,
183
+ 10,
184
+ 20,
185
+ 50,
186
+ 100,
187
+ 200,
188
+ 500,
189
+ 1e3
190
+ ]
191
+ });
192
+ #queryEvictions = getOrCreateCounter("sync", "query_evictions", {
193
+ description: "Number of queries evicted from the CVR ahead of their removal by the client, grouped by reason. Inactive queries are evicted by ttl and hydration-budget; hydration-timeout and hydration-circuit-breaker evict active queries as well.",
194
+ unit: "{query}"
195
+ });
196
+ #hydrationTimeouts = getOrCreateCounter("sync", "hydration_timeouts", {
197
+ description: "Number of query hydrations aborted for exceeding the query hydration timeout.",
198
+ unit: "{query}"
199
+ });
200
+ #hydrationCircuitBreakerRejections = getOrCreateCounter("sync", "hydration_circuit_breaker_rejections", {
201
+ description: "Number of queries rejected without hydration because their hydration circuit breaker was open.",
202
+ unit: "{query}"
203
+ });
126
204
  #inspectorDelegate;
127
205
  #config;
128
206
  #runPriorityOp;
129
- constructor(config, lc, shard, taskID, clientGroupID, cvrDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
207
+ constructor(config, lc, shard, taskID, clientGroupID, cvrDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis), now = performance.now.bind(performance)) {
130
208
  this.#config = config;
131
209
  this.id = clientGroupID;
132
210
  this.connContextManager = connContextManager;
@@ -141,6 +219,8 @@ var ViewSyncerService = class {
141
219
  this.#customQueryTransformer = customQueryTransformer;
142
220
  this.#cvrStore = new CVRStore(lc, cvrDb, shard, taskID, clientGroupID, () => this.#stateChanges.cancel());
143
221
  this.#setTimeout = setTimeoutFn;
222
+ this.#now = now;
223
+ this.#hydrationCircuitBreaker = new HydrationCircuitBreaker(config.viewSyncerQueryHydrationTimeoutMs ?? 0, void 0, now);
144
224
  this.#runPriorityOp = runPriorityOp;
145
225
  this.keepalive();
146
226
  }
@@ -190,10 +270,20 @@ var ViewSyncerService = class {
190
270
  });
191
271
  }
192
272
  readyState() {
193
- return Promise.race([this.#initialized.promise, this.#drainCoordinator.draining]);
273
+ return new Promise((resolve, reject) => {
274
+ const unsubscribe = this.#drainCoordinator.onDraining(() => resolve("draining"));
275
+ this.#initialized.promise.then((state) => {
276
+ unsubscribe();
277
+ resolve(state);
278
+ }, (err) => {
279
+ unsubscribe();
280
+ reject(err);
281
+ });
282
+ });
194
283
  }
195
284
  async run() {
196
285
  try {
286
+ this.#scheduleShutdown(this.#keepaliveMs);
197
287
  if (await this.readyState() === "draining") {
198
288
  this.#lc.debug?.(`draining view-syncer ${this.id} before running`);
199
289
  this.stop();
@@ -214,26 +304,29 @@ var ViewSyncerService = class {
214
304
  lc.info?.(`resetting CVR: ${message}`);
215
305
  throw new ClientNotFoundError(message);
216
306
  }
217
- if (this.#pipelinesSynced) {
307
+ let previousQueries;
308
+ if (this.#pipelinesHydrated) {
218
309
  const result = await this.#advancePipelines(lc, cvr);
219
310
  if (result === "success") return;
220
311
  lc.info?.(`resetting pipelines: ${result.message}`);
221
312
  this.#pipelineResets.add(1, { reason: result.reason });
313
+ switch (result.reason) {
314
+ case "advancement-timeout":
315
+ case "scalar-subquery":
316
+ case "truncation":
317
+ case "schema-change":
318
+ if (!Object.values(cvr.queries).some((q) => q.type === "client")) previousQueries = new Map(this.#pipelines.queries());
319
+ break;
320
+ case "permissions-change": break;
321
+ default: unreachable(result.reason);
322
+ }
222
323
  this.#pipelines.reset(clientSchema);
223
- this.#pipelinesSynced = false;
324
+ this.#pipelinesHydrated = false;
224
325
  this.connContextManager.setSharedRetransformReady(false);
225
326
  }
226
- const version = this.#pipelines.advanceWithoutDiff();
227
- const cvrVer = versionString(cvr.version);
228
- if (version < cvr.version.stateVersion) {
229
- lc.debug?.(`replica@${version} is behind cvr@${cvrVer}`);
230
- return;
231
- }
232
- lc.info?.(`init pipelines@${version} (cvr@${cvrVer})`);
233
- const driftedQueryIDs = await this.#hydrateUnchangedQueries(lc, cvr);
234
- await this.#syncQueryPipelineSet(lc, cvr, "missing", void 0, driftedQueryIDs);
235
- this.#pipelinesSynced = true;
236
- this.connContextManager.setSharedRetransformReady(true);
327
+ const backgroundConnCtx = this.connContextManager.getBackgroundConnectionContext();
328
+ if (backgroundConnCtx) await this.#maybeHydratePipelines(lc, cvr, backgroundConnCtx, previousQueries);
329
+ else lc.info?.("No validated background connection; deferring pipeline init");
237
330
  });
238
331
  }
239
332
  if (this.#drainCoordinator.shouldDrain()) this.#drainCoordinator.drainNextIn(this.#totalHydrationTimeMs());
@@ -247,11 +340,57 @@ var ViewSyncerService = class {
247
340
  this.#stopped.resolve();
248
341
  }
249
342
  }
343
+ /**
344
+ * Ensures that query pipelines are hydrated and ready for steady-state
345
+ * operation.
346
+ *
347
+ * Preconditions:
348
+ * 1. Must be called from within the `#lock`.
349
+ * 2. The caller must provide an active, validated `ConnectionContext` for
350
+ * evaluating custom query transforms and permissions.
351
+ *
352
+ * Readiness prerequisites:
353
+ * - The underlying `QueryPipelineDriver` must be initialized with the schema
354
+ * (i.e. `this.#pipelines.initialized()` is true).
355
+ * - The SQLite replica must have caught up to at least the CVR's
356
+ * `stateVersion` (`version >= cvr.version.stateVersion`).
357
+ *
358
+ * If either prerequisite is not met, this method returns early as a no-op,
359
+ * waiting for the replica to catch up or the driver to initialize.
360
+ *
361
+ * Once ready:
362
+ * - Hydrates unchanged queries from the CVR snapshot without full row diffing.
363
+ * - Syncs missing, errored, or drifted queries via `#syncQueryPipelineSet`.
364
+ * - Sets `#pipelinesHydrated = true` and enables shared retransformations.
365
+ */
366
+ async #maybeHydratePipelines(lc, cvr, connCtx, previousQueries) {
367
+ if (!this.#pipelines.initialized()) return;
368
+ const version = this.#pipelines.advanceWithoutDiff();
369
+ const cvrVer = versionString(cvr.version);
370
+ if (version < cvr.version.stateVersion) {
371
+ lc.debug?.(`replica@${version} is behind cvr@${cvrVer}`);
372
+ return;
373
+ }
374
+ lc.info?.(`init pipelines@${version} (cvr@${cvrVer})`);
375
+ const hydrationBudget = new HydrationBudget(this.#config.viewSyncerHydrationBudgetMs ?? 0, this.#now);
376
+ const hydrationPassStats = {
377
+ activeHydratedQueries: 0,
378
+ inactiveHydratedQueries: 0
379
+ };
380
+ const driftedQueryIDs = await this.#hydrateUnchangedQueries(lc, cvr, connCtx, hydrationPassStats, hydrationBudget, previousQueries);
381
+ await this.#syncQueryPipelineSet(lc, cvr, "missing", connCtx, driftedQueryIDs, hydrationBudget, hydrationPassStats, previousQueries);
382
+ this.#pipelinesHydrated = true;
383
+ this.connContextManager.setSharedRetransformReady(true);
384
+ }
250
385
  #removeExpiredQueries = async (lc, cvr) => {
251
386
  if (hasExpiredQueries(cvr)) {
252
387
  lc = lc.withContext("method", "#removeExpiredQueries");
253
388
  lc.debug?.("Queries have expired");
254
- if (this.#pipelinesSynced) await this.#syncQueryPipelineSet(lc, cvr, "missing", void 0);
389
+ if (this.#pipelinesHydrated) {
390
+ const connCtx = this.connContextManager.getBackgroundConnectionContext();
391
+ if (connCtx) await this.#syncQueryPipelineSet(lc, cvr, "missing", connCtx);
392
+ else lc.info?.("No validated background connection to remove expired queries; deferring");
393
+ }
255
394
  }
256
395
  this.#scheduleExpireEviction(lc, cvr);
257
396
  };
@@ -333,7 +472,14 @@ var ViewSyncerService = class {
333
472
  return true;
334
473
  }
335
474
  #shutdownTimer = null;
475
+ #stopShutdownTimer() {
476
+ if (this.#shutdownTimer !== null) {
477
+ clearTimeout(this.#shutdownTimer);
478
+ this.#shutdownTimer = null;
479
+ }
480
+ }
336
481
  #scheduleShutdown(delayMs = 0) {
482
+ if (this.#shuttingDown) return;
337
483
  this.#shutdownTimer ??= this.#setTimeout(() => {
338
484
  this.#shutdownTimer = null;
339
485
  this.#runInLockWithCVR(() => {}).catch((e) => this.#stateChanges.fail(e));
@@ -380,6 +526,7 @@ var ViewSyncerService = class {
380
526
  */
381
527
  #scheduleAuthMaintenance(lc) {
382
528
  this.#stopAuthMaintenanceTimer();
529
+ if (this.#shuttingDown) return;
383
530
  const plan = this.connContextManager.planMaintenance();
384
531
  if (plan.earliestDeadlineAt === void 0) {
385
532
  lc.debug?.("No auth maintenance wakeup scheduled");
@@ -467,7 +614,7 @@ var ViewSyncerService = class {
467
614
  }
468
615
  lc.debug?.("Auth changed, re-validating and re-transforming queries");
469
616
  const connCtx = this.connContextManager.mustGetConnectionContext(selector);
470
- if (!this.#pipelinesSynced) {
617
+ if (!this.#pipelinesHydrated) {
471
618
  if (!await this.#validateConnection(connCtx)) return;
472
619
  }
473
620
  return await this.#handleConfigUpdate(lc, clientID, {}, cvr, "all", void 0, connCtx);
@@ -496,6 +643,7 @@ var ViewSyncerService = class {
496
643
  }
497
644
  #startTTLClockInterval(lc) {
498
645
  this.#stopTTLClockInterval();
646
+ if (this.#shuttingDown) return;
499
647
  this.#ttlClockInterval = this.#setTimeout(() => {
500
648
  this.#updateTTLClockInCVRWithoutLock(lc);
501
649
  this.#startTTLClockInterval(lc);
@@ -529,7 +677,8 @@ var ViewSyncerService = class {
529
677
  await pokers.end(newCVR.version);
530
678
  });
531
679
  }
532
- if (this.#pipelinesSynced) await this.#syncQueryPipelineSet(lc, this.#cvr, customQueryTransformMode, connCtx);
680
+ if (!this.#pipelinesHydrated) await this.#maybeHydratePipelines(lc, this.#cvr, connCtx);
681
+ else await this.#syncQueryPipelineSet(lc, this.#cvr, customQueryTransformMode, connCtx);
533
682
  return this.#cvr;
534
683
  }
535
684
  /**
@@ -625,6 +774,7 @@ var ViewSyncerService = class {
625
774
  #scheduleExpireEviction(lc, cvr) {
626
775
  const { ttlClock } = cvr;
627
776
  this.#stopExpireTimer();
777
+ if (this.#shuttingDown) return;
628
778
  const next = nextEvictionTime(cvr);
629
779
  if (next === void 0) {
630
780
  lc.debug?.("no inactive queries with ttl");
@@ -652,7 +802,7 @@ var ViewSyncerService = class {
652
802
  *
653
803
  * This must be called from within the #lock.
654
804
  */
655
- async #hydrateUnchangedQueries(lc, cvr) {
805
+ async #hydrateUnchangedQueries(lc, cvr, connCtx, hydrationPassStats, hydrationBudget, previousQueries) {
656
806
  assert(this.#pipelines.initialized(), "pipelines must be initialized");
657
807
  const dbVersion = this.#pipelines.currentVersion();
658
808
  const cvrVersion = cvr.version;
@@ -661,33 +811,34 @@ var ViewSyncerService = class {
661
811
  return /* @__PURE__ */ new Set();
662
812
  }
663
813
  const gotQueries = Object.entries(cvr.queries).filter(([_, state]) => state.transformationHash !== void 0);
814
+ const { required: requiredGotQueries } = classifyQueriesForHydration(gotQueries.map(([, query]) => query));
664
815
  const customQueries = /* @__PURE__ */ new Map();
665
816
  const otherQueries = [];
666
- let inactivatedCount = 0;
667
- for (const [, query] of gotQueries) {
668
- if (query.type !== "internal" && Object.values(query.clientState).every(({ inactivatedAt }) => inactivatedAt !== void 0)) {
669
- inactivatedCount++;
670
- continue;
671
- }
672
- if (query.type === "custom") customQueries.set(query.id, query);
673
- else otherQueries.push(query);
674
- }
817
+ const inactivatedCount = gotQueries.length - requiredGotQueries.length;
675
818
  const transformedQueries = [];
819
+ for (const query of requiredGotQueries) if (query.type === "custom") {
820
+ const previous = previousQueries?.get(query.id);
821
+ if (previous && previous.transformationHash === query.transformationHash) transformedQueries.push({
822
+ id: query.id,
823
+ transformationHash: previous.transformationHash,
824
+ transformedAst: previous.originalAst ?? previous.transformedAst
825
+ });
826
+ else customQueries.set(query.id, query);
827
+ } else otherQueries.push(query);
676
828
  let customErrorCount = 0;
677
829
  let customHashMismatchCount = 0;
678
830
  let otherHashMismatchCount = 0;
679
831
  if (customQueries.size > 0 && !this.#customQueryTransformer) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
680
- const backgroundContext = this.connContextManager.mustGetBackgroundConnectionContext();
681
832
  const customQueryTransformer = this.#customQueryTransformer;
682
833
  if (customQueryTransformer && customQueries.size > 0) {
683
- const transformedCustomQueries = await this.#runPriorityOp(lc, "#hydrateUnchangedQueries transforming custom queries", () => customQueryTransformer.transform(backgroundContext, customQueries.values()));
684
- if (transformedCustomQueries.kind === "success" && !transformedCustomQueries.cached) this.connContextManager.validateConnection(backgroundContext, backgroundContext.revision, transformedCustomQueries.validation);
834
+ const transformedCustomQueries = await hydrationBudget.excluding(() => this.#runPriorityOp(lc, "#hydrateUnchangedQueries transforming custom queries", () => customQueryTransformer.transform(connCtx, customQueries.values())));
835
+ if (transformedCustomQueries.kind === "success" && !transformedCustomQueries.cached) this.connContextManager.validateConnection(connCtx, connCtx.revision, transformedCustomQueries.validation);
685
836
  if (Array.isArray(transformedCustomQueries.result)) for (const q of transformedCustomQueries.result) if ("error" in q) customErrorCount++;
686
837
  else if (q.transformationHash !== customQueries.get(q.id)?.transformationHash) customHashMismatchCount++;
687
838
  else transformedQueries.push(q);
688
839
  }
689
840
  for (const q of otherQueries) {
690
- const transformed = transformAndHashQuery(lc, q.id, q.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, backgroundContext.auth?.type === "jwt" ? backgroundContext.auth : void 0, q.type === "internal");
841
+ const transformed = transformAndHashQuery(lc, q.id, q.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, connCtx.auth?.type === "jwt" ? connCtx.auth : void 0, q.type === "internal");
691
842
  if (transformed.transformationHash === q.transformationHash) transformedQueries.push(transformed);
692
843
  else otherHashMismatchCount++;
693
844
  }
@@ -700,6 +851,10 @@ var ViewSyncerService = class {
700
851
  for (const { id: queryID, transformationHash, transformedAst } of transformedQueries) {
701
852
  const query = cvr.queries[queryID];
702
853
  const queryName = query.type === "custom" ? query.name : void 0;
854
+ if (query.type !== "internal" && this.#hydrationCircuitBreaker.isOpen(transformationHash)) {
855
+ lc.info?.(`skipping hydration of ${queryID}: hydration circuit breaker open`);
856
+ continue;
857
+ }
703
858
  const covered = queryCoveringIndex ? this.#findQueryCoverageShadowHit(queryCoveringIndex, queryID, transformationHash, transformedAst, queryName) : void 0;
704
859
  totalHydratedQueries++;
705
860
  if (covered) {
@@ -708,19 +863,35 @@ var ViewSyncerService = class {
708
863
  }
709
864
  const timer = new TimeSliceTimer(lc);
710
865
  let count = 0;
866
+ let timedOut = false;
711
867
  await startAsyncSpan(tracer, "vs.#hydrateUnchangedQueries.addQuery", async (span) => {
712
868
  span.setAttribute("queryHash", queryID);
713
869
  span.setAttribute("transformationHash", transformationHash);
714
870
  span.setAttribute("table", transformedAst.table);
715
871
  if (queryName !== void 0) span.setAttribute("queryName", queryName);
716
- for (const change of this.#pipelines.addQuery(transformationHash, queryID, transformedAst, await timer.start(), queryName, "unchanged-query-rehydrate")) if (change === "yield") await timer.yieldProcess("yield in hydrateUnchangedQueries");
717
- else count++;
872
+ for (const change of this.#pipelines.addQuery(transformationHash, queryID, transformedAst, await timer.start(), queryName, "unchanged-query-rehydrate")) if (change === "yield") {
873
+ if (query.type !== "internal" && this.#hydrationCircuitBreaker.exceeded(timer.totalElapsed())) {
874
+ timedOut = true;
875
+ await timer.yieldProcess("yield in hydrateUnchangedQueries");
876
+ break;
877
+ }
878
+ await timer.yieldProcess("yield in hydrateUnchangedQueries");
879
+ } else count++;
718
880
  });
719
881
  const elapsed = timer.totalElapsed();
882
+ if (timedOut) {
883
+ this.#recordHydrationTimeout(lc, {
884
+ id: queryID,
885
+ transformationHash,
886
+ name: queryName
887
+ }, elapsed, count);
888
+ continue;
889
+ }
890
+ hydrationPassStats.activeHydratedQueries++;
720
891
  this.#hydrations.add(1);
721
892
  this.#hydrationTime.recordMs(elapsed);
722
- this.#addQueryMaterializationServerMetric(transformationHash, elapsed);
723
- this.#inspectorDelegate.addQuery(transformationHash, transformedAst);
893
+ this.#addQueryMaterializationServerMetric(queryID, elapsed);
894
+ this.#inspectorDelegate.addQuery(queryID, transformedAst);
724
895
  lc.debug?.(`hydrated ${count} rows for ${queryID} (${elapsed} ms)`);
725
896
  let drifted = false;
726
897
  const storedSigHex = cvr.queries[queryID]?.rowSetSignature;
@@ -821,7 +992,10 @@ var ViewSyncerService = class {
821
992
  *
822
993
  * This must be called from within the #lock.
823
994
  */
824
- #syncQueryPipelineSet(lc, cvr, customQueryTransformMode, connCtx, driftedQueryIDs = /* @__PURE__ */ new Set()) {
995
+ #syncQueryPipelineSet(lc, cvr, customQueryTransformMode, connCtx, driftedQueryIDs = /* @__PURE__ */ new Set(), hydrationBudget = new HydrationBudget(this.#config.viewSyncerHydrationBudgetMs ?? 0, this.#now), hydrationPassStats = {
996
+ activeHydratedQueries: 0,
997
+ inactiveHydratedQueries: 0
998
+ }, previousQueries) {
825
999
  return startAsyncSpan(tracer, "vs.#syncQueryPipelineSet", async (span) => {
826
1000
  const start = performance.now();
827
1001
  span.setAttribute("clientGroupID", this.id);
@@ -830,38 +1004,45 @@ var ViewSyncerService = class {
830
1004
  const now = Date.now();
831
1005
  const ttlClock = this.#getTTLClock(now);
832
1006
  const cvrQueryEntires = Object.entries(cvr.queries);
833
- const customQueries = /* @__PURE__ */ new Map();
834
- const otherQueries = [];
835
1007
  const transformedQueries = [];
836
- const resolvedConnCtx = connCtx ?? this.connContextManager.mustGetBackgroundConnectionContext();
837
- for (const [id, query] of cvrQueryEntires) if (query.type === "custom") {
838
- assert(id === query.id, "custom query id mismatch");
839
- customQueries.set(id, query);
840
- } else otherQueries.push({
841
- id,
842
- query
843
- });
844
- for (const { id, query: origQuery } of otherQueries) {
845
- assert(id === origQuery.id, "query id mismatch");
846
- 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");
847
- transformedQueries.push({
848
- id,
849
- origQuery,
850
- transformed
851
- });
852
- }
853
- if (customQueries.size > 0 && !this.#customQueryTransformer) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
854
- let erroredQueryIDs;
855
- const customQueriesToTransform = customQueryTransformMode === "all" ? [...customQueries.values()] : customQueryTransformMode && [...customQueries.values()].filter((q) => !this.#pipelines.queries().has(q.id));
1008
+ const naturallyExpiredQueryIDs = new Set(cvrQueryEntires.map(([, query]) => query).filter((query) => expired(ttlClock, query)).map((query) => query.id));
1009
+ if (naturallyExpiredQueryIDs.size > 0) this.#queryEvictions.add(naturallyExpiredQueryIDs.size, { reason: "ttl" });
1010
+ const hydrationCandidates = cvrQueryEntires.map(([, query]) => query).filter((query) => !naturallyExpiredQueryIDs.has(query.id));
1011
+ const { required, optional } = classifyQueriesForHydration(hydrationCandidates);
1012
+ const requiredQueryIDs = new Set(required.map((query) => query.id));
1013
+ const optionalToHydrate = hydrationBudget.limitMs === 0 ? optional : optional.filter((query) => query.patchVersion !== void 0);
1014
+ if (!this.#customQueryTransformer && hydrationCandidates.some((query) => query.type === "custom")) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
856
1015
  const customQueryTransformer = this.#customQueryTransformer;
857
- if (customQueryTransformer && customQueriesToTransform.length > 0) {
1016
+ const erroredQueryIDs = [];
1017
+ let transformedCustomQueryCount = 0;
1018
+ const transformOtherQueries = (queries) => {
1019
+ for (const origQuery of queries) {
1020
+ if (origQuery.type === "custom") continue;
1021
+ const transformed = transformAndHashQuery(lc, origQuery.id, origQuery.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, connCtx.auth?.type === "jwt" ? connCtx.auth : void 0, origQuery.type === "internal");
1022
+ transformedQueries.push({
1023
+ id: origQuery.id,
1024
+ origQuery,
1025
+ transformed
1026
+ });
1027
+ }
1028
+ };
1029
+ const shouldTransformCustomQuery = (query) => {
1030
+ if (customQueryTransformMode === "all") return true;
1031
+ if (this.#pipelines.queries().has(query.id)) return false;
1032
+ const previous = previousQueries?.get(query.id);
1033
+ if (previous && previous.transformationHash === query.transformationHash) return false;
1034
+ return true;
1035
+ };
1036
+ const transformCustomQueries = async (queries) => {
1037
+ if (!customQueryTransformer || queries.length === 0) return;
1038
+ transformedCustomQueryCount += queries.length;
858
1039
  const transformStart = performance.now();
859
1040
  let transformedCustomQueries;
860
1041
  try {
861
- transformedCustomQueries = await this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(resolvedConnCtx, customQueriesToTransform));
1042
+ transformedCustomQueries = await hydrationBudget.excluding(() => this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(connCtx, queries)));
862
1043
  if (transformedCustomQueries.kind === "failed") throw new ProtocolErrorWithLevel(transformedCustomQueries.result, "warn");
863
1044
  else {
864
- if (!transformedCustomQueries.cached) this.connContextManager.validateConnection(resolvedConnCtx, resolvedConnCtx.revision, transformedCustomQueries.validation);
1045
+ if (!transformedCustomQueries.cached) this.connContextManager.validateConnection(connCtx, connCtx.revision, transformedCustomQueries.validation);
865
1046
  this.#queryTransformations.add(1, { result: "success" });
866
1047
  }
867
1048
  } catch (e) {
@@ -872,8 +1053,9 @@ var ViewSyncerService = class {
872
1053
  this.#queryTransformationTime.recordMs(transformDuration);
873
1054
  }
874
1055
  const successfullyTransformedCustomQueries = /* @__PURE__ */ new Map();
875
- erroredQueryIDs = this.#processTransformedCustomQueries(lc, transformedCustomQueries.result, (q) => {
876
- const origQuery = customQueries.get(q.id);
1056
+ const customQueryMap = new Map(queries.map((query) => [query.id, query]));
1057
+ erroredQueryIDs.push(...this.#processTransformedCustomQueries(lc, transformedCustomQueries.result, (q) => {
1058
+ const origQuery = customQueryMap.get(q.id);
877
1059
  if (origQuery) {
878
1060
  successfullyTransformedCustomQueries.set(q.id, q);
879
1061
  transformedQueries.push({
@@ -882,7 +1064,7 @@ var ViewSyncerService = class {
882
1064
  transformed: q
883
1065
  });
884
1066
  }
885
- }, customQueries);
1067
+ }, customQueryMap));
886
1068
  for (const [queryID, newTransform] of successfullyTransformedCustomQueries) {
887
1069
  const existingTransformHash = cvr.queries[queryID]?.transformationHash;
888
1070
  if (existingTransformHash) {
@@ -895,21 +1077,55 @@ var ViewSyncerService = class {
895
1077
  } else this.#queryTransformationNoOps.add(1);
896
1078
  }
897
1079
  }
1080
+ };
1081
+ const queriesToTransform = [...required, ...optionalToHydrate];
1082
+ transformOtherQueries(queriesToTransform);
1083
+ if (customQueryTransformMode === "missing" && previousQueries) {
1084
+ for (const query of queriesToTransform) if (query.type === "custom" && !this.#pipelines.queries().has(query.id)) {
1085
+ const previous = previousQueries.get(query.id);
1086
+ if (previous && previous.transformationHash === query.transformationHash) {
1087
+ this.#queryTransformationNoOps.add(1);
1088
+ transformedQueries.push({
1089
+ id: query.id,
1090
+ origQuery: query,
1091
+ transformed: {
1092
+ id: query.id,
1093
+ transformationHash: previous.transformationHash,
1094
+ transformedAst: previous.originalAst ?? previous.transformedAst
1095
+ }
1096
+ });
1097
+ }
1098
+ }
898
1099
  }
899
- const removeQueriesQueryIds = new Set([...Object.values(cvr.queries).filter((q) => expired(ttlClock, q)).map((q) => q.id), ...erroredQueryIDs || []]);
1100
+ await transformCustomQueries(queriesToTransform.filter((query) => query.type === "custom" && shouldTransformCustomQuery(query)));
1101
+ const circuitBrokenQueries = transformedQueries.filter(({ id, origQuery, transformed }) => origQuery.type !== "internal" && this.#pipelines.queries().get(id)?.transformationHash !== transformed.transformationHash && this.#hydrationCircuitBreaker.isOpen(transformed.transformationHash)).map(({ id, origQuery, transformed }) => ({
1102
+ id,
1103
+ transformationHash: transformed.transformationHash,
1104
+ name: origQuery.type === "custom" ? origQuery.name : void 0
1105
+ }));
1106
+ if (circuitBrokenQueries.length > 0) this.#rejectCircuitBrokenQueries(lc, cvr, circuitBrokenQueries);
1107
+ const removeQueriesQueryIds = new Set([
1108
+ ...naturallyExpiredQueryIDs,
1109
+ ...erroredQueryIDs,
1110
+ ...circuitBrokenQueries.map(({ id }) => id)
1111
+ ]);
900
1112
  const addQueries = transformedQueries.map(({ id, origQuery, transformed }) => ({
901
1113
  id,
902
1114
  ast: transformed.transformedAst,
903
1115
  transformationHash: transformed.transformationHash,
904
1116
  name: origQuery.type === "custom" ? origQuery.name : void 0
905
1117
  })).filter((q) => !removeQueriesQueryIds.has(q.id) && this.#pipelines.queries().get(q.id)?.transformationHash !== q.transformationHash);
906
- lc.info?.(`syncQueryPipelineSet: ${cvrQueryEntires.length} CVR queries, ${customQueriesToTransform.length} custom re-transformed, ${erroredQueryIDs?.length ?? 0} errored, ${removeQueriesQueryIds.size} to remove, ${addQueries.length} to add`);
1118
+ const hydrationOrder = new Map([...required, ...optional].map((query, index) => [query.id, index]));
1119
+ addQueries.sort((a, b) => must(hydrationOrder.get(a.id)) - must(hydrationOrder.get(b.id)));
1120
+ const requiredAddQueries = addQueries.filter((query) => requiredQueryIDs.has(query.id));
1121
+ const optionalAddQueries = addQueries.filter((query) => !requiredQueryIDs.has(query.id));
1122
+ lc.info?.(`syncQueryPipelineSet: ${cvrQueryEntires.length} CVR queries, ${transformedCustomQueryCount} custom re-transformed, ${erroredQueryIDs.length} errored, ${circuitBrokenQueries.length} circuit-broken, ${removeQueriesQueryIds.size} to remove, ${addQueries.length} to add`);
907
1123
  for (const q of addQueries) {
908
1124
  const orig = cvr.queries[q.id];
909
1125
  lc.debug?.("ViewSyncer adding query", q.ast, "transformed from", orig.type === "custom" ? orig.name : orig.ast);
910
1126
  }
911
1127
  if (addQueries.length > 0 || removeQueriesQueryIds.size > 0) {
912
- await this.#addAndRemoveQueries(lc, cvr, addQueries, Array.from(removeQueriesQueryIds, (id) => ({ id })), driftedQueryIDs);
1128
+ await this.#addAndRemoveQueries(lc, cvr, requiredAddQueries, optionalAddQueries, Array.from(removeQueriesQueryIds, (id) => ({ id })), hydrationBudget, hydrationPassStats, driftedQueryIDs);
913
1129
  if (addQueries.length > 0) this.#viewSyncerHydration.recordMs(performance.now() - start);
914
1130
  } else await this.#catchupClients(lc, cvr);
915
1131
  });
@@ -942,8 +1158,100 @@ var ViewSyncerService = class {
942
1158
  record.count++;
943
1159
  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.`);
944
1160
  }
945
- #addAndRemoveQueries(lc, cvr, addQueries, removeQueries, driftedQueryIDs = /* @__PURE__ */ new Set()) {
1161
+ /**
1162
+ * Records that hydrating `query` was aborted for exceeding the query
1163
+ * hydration timeout, and opens its circuit breaker.
1164
+ */
1165
+ #recordHydrationTimeout(lc, query, elapsedMs, rowCount) {
1166
+ this.#hydrationCircuitBreaker.trip(query.transformationHash);
1167
+ this.#hydrationTimeouts.add(1);
1168
+ lc.warn?.("Query hydration aborted for exceeding the hydration timeout", {
1169
+ clientGroupID: this.id,
1170
+ queryHash: query.id,
1171
+ transformationHash: query.transformationHash,
1172
+ ...query.name !== void 0 && { queryName: query.name },
1173
+ hydrationTimeoutMs: this.#hydrationCircuitBreaker.timeoutMs,
1174
+ hydrationElapsedMs: elapsedMs,
1175
+ ...rowCount !== void 0 && { hydrationRowCount: rowCount },
1176
+ circuitBreakerOpenMs: this.#hydrationCircuitBreaker.openMs
1177
+ });
1178
+ }
1179
+ /** Records that `query` was rejected without hydration by its open breaker. */
1180
+ #recordCircuitBreakerRejection(lc, query) {
1181
+ this.#hydrationCircuitBreakerRejections.add(1);
1182
+ lc.warn?.("Query rejected by its open hydration circuit breaker", {
1183
+ clientGroupID: this.id,
1184
+ queryHash: query.id,
1185
+ transformationHash: query.transformationHash,
1186
+ ...query.name !== void 0 && { queryName: query.name },
1187
+ hydrationTimeoutMs: this.#hydrationCircuitBreaker.timeoutMs,
1188
+ circuitBreakerOpenMs: this.#hydrationCircuitBreaker.openMs
1189
+ });
1190
+ }
1191
+ /**
1192
+ * Handles queries whose hydration circuit breaker is open: they are counted,
1193
+ * logged, and errored to the affected clients. The caller removes them from
1194
+ * the CVR.
1195
+ */
1196
+ #rejectCircuitBrokenQueries(lc, cvr, queries) {
1197
+ for (const query of queries) this.#recordCircuitBreakerRejection(lc, query);
1198
+ this.#queryEvictions.add(queries.length, { reason: "hydration-circuit-breaker" });
1199
+ this.#sendHydrationTimeoutErrors(cvr, queries);
1200
+ }
1201
+ /**
1202
+ * Sends a per-query error to every client that desires one of `queries`.
1203
+ * The error goes out as a `transformError` application error, which every
1204
+ * client understands as "this query errored" without affecting the
1205
+ * connection or the client's other queries.
1206
+ */
1207
+ #sendHydrationTimeoutErrors(cvr, queries) {
1208
+ const timeoutMs = this.#hydrationCircuitBreaker.timeoutMs;
1209
+ const errorsByClient = /* @__PURE__ */ new Map();
1210
+ for (const { id, name } of queries) {
1211
+ const query = cvr.queries[id];
1212
+ if (query === void 0 || query.type === "internal") continue;
1213
+ const error = {
1214
+ error: "app",
1215
+ id,
1216
+ name: name ?? (query.type === "custom" ? query.name : "legacy"),
1217
+ message: `Query hydration exceeded the ${timeoutMs}ms limit (ZERO_VIEW_SYNCER_QUERY_HYDRATION_TIMEOUT_MS) and was aborted`,
1218
+ details: {
1219
+ kind: "HydrationTimeout",
1220
+ timeoutMs
1221
+ }
1222
+ };
1223
+ for (const clientID of Object.keys(query.clientState)) {
1224
+ let errors = errorsByClient.get(clientID);
1225
+ if (!errors) {
1226
+ errors = [];
1227
+ errorsByClient.set(clientID, errors);
1228
+ }
1229
+ errors.push(error);
1230
+ }
1231
+ }
1232
+ for (const [clientID, errors] of errorsByClient) this.#clients.get(clientID)?.sendQueryTransformApplicationErrors(errors);
1233
+ }
1234
+ #recordHydrationBudgetExhaustion(lc, hydrationBudget, activeHydratedQueries, inactiveHydratedQueries, inactiveEvictedQueryIDs) {
1235
+ const elapsedMs = hydrationBudget.exhaustedAtMs;
1236
+ if (elapsedMs === void 0) return;
1237
+ this.#hydrationBudgetExhaustions.add(1);
1238
+ this.#hydrationBudgetEvictions.add(inactiveEvictedQueryIDs.length);
1239
+ this.#hydrationBudgetElapsed.record(elapsedMs);
1240
+ this.#hydrationBudgetOvershoot.record(Math.max(0, elapsedMs - hydrationBudget.limitMs));
1241
+ if (inactiveEvictedQueryIDs.length > 0) this.#queryEvictions.add(inactiveEvictedQueryIDs.length, { reason: "hydration-budget" });
1242
+ lc.info?.("view-syncer hydration budget exhausted", {
1243
+ clientGroupID: this.id,
1244
+ hydrationBudgetMs: hydrationBudget.limitMs,
1245
+ hydrationElapsedMs: elapsedMs,
1246
+ activeHydratedQueries,
1247
+ inactiveHydratedQueries,
1248
+ inactiveEvictedQueries: inactiveEvictedQueryIDs.length,
1249
+ firstEvictedQueryHash: inactiveEvictedQueryIDs[0] ?? null
1250
+ });
1251
+ }
1252
+ #addAndRemoveQueries(lc, cvr, requiredQueries, optionalQueries, removeQueries, hydrationBudget, hydrationPassStats, driftedQueryIDs = /* @__PURE__ */ new Set()) {
946
1253
  return startAsyncSpan(tracer, "vs.#addAndRemoveQueries", async () => {
1254
+ const addQueries = [...requiredQueries, ...optionalQueries];
947
1255
  assert(addQueries.length > 0 || removeQueries.length > 0, "Must have queries to add or remove");
948
1256
  const start = performance.now();
949
1257
  const stateVersion = this.#pipelines.currentVersion();
@@ -960,6 +1268,7 @@ var ViewSyncerService = class {
960
1268
  updater.ensureNewVersion();
961
1269
  }
962
1270
  const { queryPatches, newVersion } = updater.trackQueries(lc, addQueries, removeQueries);
1271
+ if (hydrationBudget.limitMs !== 0 && optionalQueries.length > 0) assert(cmpVersions(cvr.version, newVersion) < 0, "Optional hydration requires a final poke version before row processing so a removal is actually poked");
963
1272
  const pokers = startPoke(this.#getClients(), newVersion);
964
1273
  for (const patch of queryPatches) await pokers.addPatch(patch);
965
1274
  for (const q of removeQueries) {
@@ -976,22 +1285,54 @@ var ViewSyncerService = class {
976
1285
  let totalHydratedQueries = 0;
977
1286
  let coveredHydratedQueries = 0;
978
1287
  let firstCoveredQuery;
1288
+ const hydratedQueryIDs = [];
1289
+ const budgetEvictedQueryIDs = [];
1290
+ const timedOutQueries = [];
1291
+ const rejectedQueries = [];
1292
+ const circuitBreaker = this.#hydrationCircuitBreaker;
979
1293
  const self = this;
980
1294
  await yieldProcess(lc);
981
- function* generateRowChanges(slowHydrateThreshold) {
982
- for (const q of addQueries) {
1295
+ function* hydrateQueries(queries, optional, slowHydrateThreshold) {
1296
+ for (let i = 0; i < queries.length; i++) {
1297
+ if (optional && hydrationBudget.exhausted()) {
1298
+ budgetEvictedQueryIDs.push(...queries.slice(i).map((query) => query.id));
1299
+ return;
1300
+ }
1301
+ const q = must(queries[i]);
983
1302
  let queryLC = lc.withContext("hash", q.id).withContext("queryHash", q.id).withContext("transformationHash", q.transformationHash);
984
1303
  if (q.name !== void 0) queryLC = queryLC.withContext("queryName", q.name);
985
1304
  queryLC.debug?.(`adding pipeline for query`, q.ast);
1305
+ const breakable = cvr.queries[q.id]?.type !== "internal";
1306
+ if (breakable && circuitBreaker.isOpen(q.transformationHash)) {
1307
+ rejectedQueries.push(q);
1308
+ self.#recordCircuitBreakerRejection(queryLC, q);
1309
+ continue;
1310
+ }
986
1311
  const covered = queryCoveringIndex ? self.#findQueryCoverageShadowHit(queryCoveringIndex, q.id, q.transformationHash, q.ast, q.name) : void 0;
987
1312
  totalHydratedQueries++;
988
1313
  if (covered) {
989
1314
  coveredHydratedQueries++;
990
1315
  firstCoveredQuery ??= covered;
991
1316
  }
992
- yield* pipelines.addQuery(q.transformationHash, q.id, q.ast, timer.startWithoutYielding(), q.name, "query-set-sync");
1317
+ let timedOut = false;
1318
+ for (const change of pipelines.addQuery(q.transformationHash, q.id, q.ast, timer.startWithoutYielding(), q.name, "query-set-sync")) {
1319
+ if (change === "yield" && breakable && circuitBreaker.exceeded(timer.totalElapsed())) {
1320
+ timedOut = true;
1321
+ yield change;
1322
+ break;
1323
+ }
1324
+ yield change;
1325
+ }
993
1326
  const elapsed = timer.stop();
994
1327
  totalProcessTime += elapsed;
1328
+ if (timedOut) {
1329
+ timedOutQueries.push(q);
1330
+ self.#recordHydrationTimeout(queryLC, q, elapsed);
1331
+ continue;
1332
+ }
1333
+ hydratedQueryIDs.push(q.id);
1334
+ if (optional) hydrationPassStats.inactiveHydratedQueries++;
1335
+ else hydrationPassStats.activeHydratedQueries++;
995
1336
  self.#addQueryMaterializationServerMetric(q.id, elapsed);
996
1337
  self.#inspectorDelegate.addQuery(q.id, q.ast);
997
1338
  queryCoveringIndex?.add(q.id, {
@@ -1005,18 +1346,43 @@ var ViewSyncerService = class {
1005
1346
  transformationHash: q.transformationHash,
1006
1347
  ...q.name !== void 0 && { name: q.name }
1007
1348
  });
1349
+ hydrations.add(1);
1350
+ hydrationTime.recordMs(elapsed);
1008
1351
  }
1009
- hydrations.add(1);
1010
- hydrationTime.recordMs(totalProcessTime);
1352
+ }
1353
+ function* generateRowChanges(slowHydrateThreshold) {
1354
+ yield* hydrateQueries(requiredQueries, false, slowHydrateThreshold);
1355
+ yield* hydrateQueries(optionalQueries, true, slowHydrateThreshold);
1011
1356
  }
1012
1357
  await this.#processChanges(lc, timer, generateRowChanges(this.#slowHydrateThreshold), updater, pokers);
1358
+ const abortedQueries = [...timedOutQueries, ...rejectedQueries];
1359
+ if (abortedQueries.length > 0) {
1360
+ const abortedQueryIDs = abortedQueries.map(({ id }) => id);
1361
+ for (const patch of await updater.abortExecutedQueries(lc, abortedQueryIDs)) await pokers.addPatch(patch);
1362
+ for (const queryID of abortedQueryIDs) {
1363
+ this.#pipelines.removeQuery(queryID);
1364
+ this.#inspectorDelegate.removeQuery(queryID);
1365
+ this.#queryReplacements.delete(queryID);
1366
+ }
1367
+ if (timedOutQueries.length > 0) this.#queryEvictions.add(timedOutQueries.length, { reason: "hydration-timeout" });
1368
+ if (rejectedQueries.length > 0) this.#queryEvictions.add(rejectedQueries.length, { reason: "hydration-circuit-breaker" });
1369
+ this.#sendHydrationTimeoutErrors(cvr, abortedQueries);
1370
+ }
1371
+ for (const patch of updater.removeTrackedQueries(budgetEvictedQueryIDs)) await pokers.addPatch(patch);
1372
+ for (const queryID of budgetEvictedQueryIDs) {
1373
+ this.#pipelines.removeQuery(queryID);
1374
+ this.#inspectorDelegate.removeQuery(queryID);
1375
+ this.#queryReplacements.delete(queryID);
1376
+ }
1377
+ this.#recordHydrationBudgetExhaustion(lc, hydrationBudget, hydrationPassStats.activeHydratedQueries, hydrationPassStats.inactiveHydratedQueries, budgetEvictedQueryIDs);
1013
1378
  this.#logQueryCoverageShadowSummary(lc, "add", totalHydratedQueries, coveredHydratedQueries, firstCoveredQuery);
1014
1379
  await startAsyncSpan(tracer, "vs.#syncQueryPipelineSet.deleteUnreferencedRows", async () => {
1015
1380
  for (const patch of await updater.deleteUnreferencedRows(lc)) await pokers.addPatch(patch);
1016
1381
  });
1017
1382
  this.#cvr = await this.#flushUpdater(lc, updater);
1383
+ if (budgetEvictedQueryIDs.length > 0) this.#scheduleExpireEviction(lc, this.#cvr);
1018
1384
  const finalVersion = this.#cvr.version;
1019
- await this.#catchupClients(lc, cvr, finalVersion, addQueries.map((q) => q.id), pokers);
1385
+ await this.#catchupClients(lc, cvr, finalVersion, hydratedQueryIDs, pokers);
1020
1386
  await startAsyncSpan(tracer, "vs.#syncQueryPipelineSet.pokeEnd", () => pokers.end(finalVersion));
1021
1387
  this.#markVersionServed(stateVersion);
1022
1388
  const wallTime = performance.now() - start;
@@ -1157,7 +1523,9 @@ var ViewSyncerService = class {
1157
1523
  *
1158
1524
  * Must be called from within the #lock.
1159
1525
  *
1160
- * Returns false if the advancement failed due to a schema change.
1526
+ * Returns 'success' if changes were successfully processed and poked,
1527
+ * or a `ResetPipelinesSignal` if advancement aborted (e.g. schema change,
1528
+ * timeout) and pipelines need to be reset and rehydrated.
1161
1529
  */
1162
1530
  #advancePipelines(lc, cvr) {
1163
1531
  return startAsyncSpan(tracer, "vs.#advancePipelines", async (span) => {
@@ -1165,20 +1533,27 @@ var ViewSyncerService = class {
1165
1533
  assert(this.#pipelines.initialized(), "pipelines must be initialized (advancePipelines");
1166
1534
  const start = performance.now();
1167
1535
  const timer = new TimeSliceTimer(lc);
1168
- const { version, numChanges, changes } = this.#pipelines.advance(timer);
1169
- lc = lc.withContext("newVersion", version);
1170
- const updater = new CVRQueryDrivenUpdater(this.#cvrStore, cvr, version, this.#pipelines.replicaVersion, (queryID) => this.#pipelines.rowSetSignature(queryID));
1171
- const pokers = startPoke(this.#getClients(cvr.version), updater.updatedVersion());
1172
- lc.debug?.(`applying ${numChanges} to advance to ${version}`);
1536
+ let pokers;
1537
+ let updater;
1538
+ let version;
1539
+ let numChanges = 0;
1173
1540
  try {
1174
- await this.#processChanges(lc, await timer.start(), changes, updater, pokers);
1541
+ const advancement = this.#pipelines.advance(timer);
1542
+ version = advancement.version;
1543
+ numChanges = advancement.numChanges;
1544
+ lc = lc.withContext("newVersion", version);
1545
+ updater = new CVRQueryDrivenUpdater(this.#cvrStore, cvr, version, this.#pipelines.replicaVersion, (queryID) => this.#pipelines.rowSetSignature(queryID));
1546
+ pokers = startPoke(this.#getClients(cvr.version), updater.updatedVersion());
1547
+ lc.debug?.(`applying ${numChanges} to advance to ${version}`);
1548
+ await this.#processChanges(lc, await timer.start(), advancement.changes, updater, pokers);
1175
1549
  } catch (e) {
1176
1550
  if (e instanceof ResetPipelinesSignal) {
1177
- await pokers.cancel();
1551
+ await pokers?.cancel();
1178
1552
  return e;
1179
1553
  }
1180
1554
  throw e;
1181
1555
  }
1556
+ assert(updater && pokers && version !== void 0, "advancement state missing");
1182
1557
  this.#cvr = await this.#flushUpdater(lc, updater);
1183
1558
  const finalVersion = this.#cvr.version;
1184
1559
  await startAsyncSpan(tracer, "vs.#advancePipelines.pokeEnd", () => pokers.end(finalVersion));
@@ -1199,7 +1574,7 @@ var ViewSyncerService = class {
1199
1574
  clientID,
1200
1575
  wsID: client.wsID
1201
1576
  });
1202
- return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this.#cvrStore, this.#config, connCtx);
1577
+ return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this, this.#cvrStore, this.#config, connCtx);
1203
1578
  };
1204
1579
  async #runBackgroundRetransform(lc) {
1205
1580
  const attemptRetransform = async (connCtx) => {
@@ -1286,15 +1661,18 @@ var ViewSyncerService = class {
1286
1661
  return this.#stopped.promise;
1287
1662
  }
1288
1663
  async #cleanup(err) {
1664
+ this.#shuttingDown = true;
1289
1665
  this.connContextManager.setSharedRetransformReady(false);
1290
1666
  this.#stopTTLClockInterval();
1291
1667
  this.#stopExpireTimer();
1292
1668
  this.#stopAuthMaintenanceTimer();
1669
+ this.#stopShutdownTimer();
1293
1670
  this.#customQueryTransformer?.destroy();
1294
1671
  for (const client of this.#clients.values()) if (err) client.fail(err);
1295
1672
  else client.close(`closed clientGroupID=${this.id}`);
1296
1673
  await this.#lock.withLock(() => {});
1297
1674
  this.#pipelines.destroy();
1675
+ this.#inspectorDelegate.clearAuthenticated(this.id, this);
1298
1676
  }
1299
1677
  /**
1300
1678
  * Test helper: Manually mark initialization as complete.
@@ -1330,8 +1708,10 @@ function isTransformFailedError(error) {
1330
1708
  return error.errorBody.kind === "TransformFailed" && !isAuthErrorBody(error.errorBody);
1331
1709
  }
1332
1710
  /**
1333
- * A query must be expired for all clients in order to be considered
1334
- * expired.
1711
+ * Whether a query's TTL has elapsed. A query must be expired for all clients
1712
+ * in order to be considered expired.
1713
+ *
1714
+ * Exported for testing.
1335
1715
  */
1336
1716
  function expired(ttlClock, q) {
1337
1717
  if (q.type === "internal") return false;