@rocicorp/zero 1.11.0-canary.13 → 1.11.0-canary.15

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 (252) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
  4. package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
  5. package/out/ast-to-zql/src/ast-to-zql.js +25 -25
  6. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  7. package/out/otel/src/log-options.d.ts +16 -0
  8. package/out/otel/src/log-options.d.ts.map +1 -1
  9. package/out/otel/src/log-options.js +54 -1
  10. package/out/otel/src/log-options.js.map +1 -1
  11. package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
  12. package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
  13. package/out/replicache/src/get-kv-store-provider.js +4 -4
  14. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  15. package/out/replicache/src/kv/idb-store.d.ts +10 -1
  16. package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
  17. package/out/replicache/src/kv/idb-store.js +18 -2
  18. package/out/replicache/src/kv/idb-store.js.map +1 -1
  19. package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
  20. package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
  21. package/out/replicache/src/kv/mem-fallback-store.js +177 -0
  22. package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
  23. package/out/replicache/src/kv/op-sqlite/store.js +3 -3
  24. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  25. package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
  26. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  27. package/out/replicache/src/kv/sqlite-store.js +86 -6
  28. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  29. package/out/replicache/src/new-client-channel.js.map +1 -1
  30. package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
  31. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  32. package/out/replicache/src/persist/collect-idb-databases.js +81 -11
  33. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  34. package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
  35. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  36. package/out/replicache/src/persist/persist.d.ts.map +1 -1
  37. package/out/replicache/src/persist/persist.js +6 -3
  38. package/out/replicache/src/persist/persist.js.map +1 -1
  39. package/out/replicache/src/replicache-impl.d.ts +43 -3
  40. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  41. package/out/replicache/src/replicache-impl.js +135 -17
  42. package/out/replicache/src/replicache-impl.js.map +1 -1
  43. package/out/replicache/src/storage-failure.d.ts +37 -0
  44. package/out/replicache/src/storage-failure.d.ts.map +1 -0
  45. package/out/replicache/src/storage-failure.js +31 -0
  46. package/out/replicache/src/storage-failure.js.map +1 -0
  47. package/out/shared/src/random-values.js +12 -1
  48. package/out/shared/src/random-values.js.map +1 -1
  49. package/out/zero/package.js +1 -1
  50. package/out/zero/package.js.map +1 -1
  51. package/out/zero/src/zero.js +2 -1
  52. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  53. package/out/zero-cache/src/config/normalize.js +1 -1
  54. package/out/zero-cache/src/config/normalize.js.map +1 -1
  55. package/out/zero-cache/src/config/zero-config.d.ts +26 -0
  56. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  57. package/out/zero-cache/src/config/zero-config.js +36 -0
  58. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  59. package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
  60. package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
  61. package/out/zero-cache/src/observability/log-throttle.js +76 -0
  62. package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
  63. package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
  64. package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
  65. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  66. package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -0
  67. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  68. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  69. package/out/zero-cache/src/server/syncer.js +20 -2
  70. package/out/zero-cache/src/server/syncer.js.map +1 -1
  71. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  72. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +27 -15
  73. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  74. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
  75. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  76. package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
  77. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  78. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  79. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  80. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
  81. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  82. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
  83. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  84. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
  85. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  86. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +11 -2
  87. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  88. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
  89. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
  90. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
  91. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
  92. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
  93. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
  95. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  96. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
  97. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
  99. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  100. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
  101. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  102. package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
  103. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
  104. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
  105. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
  106. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
  107. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
  108. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
  109. package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
  110. package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
  111. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +7 -6
  112. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  113. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  114. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
  115. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  116. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +26 -1
  117. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  118. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  119. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  120. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  121. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  122. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -0
  123. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
  124. package/out/zero-cache/src/services/change-streamer/subscribe.js +9 -1
  125. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  126. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
  127. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  128. package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
  129. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  130. package/out/zero-cache/src/services/plannerz.d.ts +159 -0
  131. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
  132. package/out/zero-cache/src/services/plannerz.js +263 -0
  133. package/out/zero-cache/src/services/plannerz.js.map +1 -0
  134. package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
  135. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
  136. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  137. package/out/zero-cache/src/services/replicator/change-processor.js +44 -12
  138. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  139. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  140. package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -3
  141. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  142. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
  143. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
  144. package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
  145. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
  146. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
  147. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
  148. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  149. package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
  150. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  151. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
  152. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
  153. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
  154. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
  155. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  156. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -1
  157. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  158. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +362 -20
  159. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  160. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
  161. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
  162. package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
  163. package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
  164. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
  165. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
  166. package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
  167. package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
  168. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
  169. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
  170. package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
  171. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
  172. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -1
  173. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  174. package/out/zero-cache/src/services/view-syncer/snapshotter.js +7 -1
  175. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  176. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  177. package/out/zero-cache/src/services/view-syncer/view-syncer.js +49 -3
  178. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  179. package/out/zero-cache/src/workers/connection.js +3 -3
  180. package/out/zero-cache/src/workers/connection.js.map +1 -1
  181. package/out/zero-cache/src/workers/syncer.js +2 -2
  182. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  183. package/out/zero-client/src/client/options.d.ts +29 -0
  184. package/out/zero-client/src/client/options.d.ts.map +1 -1
  185. package/out/zero-client/src/client/options.js.map +1 -1
  186. package/out/zero-client/src/client/version.js +1 -1
  187. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  188. package/out/zero-client/src/client/zero.js +8 -23
  189. package/out/zero-client/src/client/zero.js.map +1 -1
  190. package/out/zero-client/src/mod.d.ts +1 -0
  191. package/out/zero-client/src/mod.d.ts.map +1 -1
  192. package/out/zero-client/src/mod.js +1 -0
  193. package/out/zero-protocol/src/down.d.ts +2 -2
  194. package/out/zero-react/src/zero.js +1 -0
  195. package/out/zero-solid/src/zero.js +1 -0
  196. package/out/zql/src/builder/builder.d.ts +6 -0
  197. package/out/zql/src/builder/builder.d.ts.map +1 -1
  198. package/out/zql/src/builder/builder.js +4 -3
  199. package/out/zql/src/builder/builder.js.map +1 -1
  200. package/out/zql/src/ivm/memory-source.d.ts +13 -0
  201. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  202. package/out/zql/src/ivm/memory-source.js +1 -1
  203. package/out/zql/src/ivm/memory-source.js.map +1 -1
  204. package/out/zql/src/planner/planner-builder.d.ts +12 -1
  205. package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
  206. package/out/zql/src/planner/planner-builder.js +8 -1
  207. package/out/zql/src/planner/planner-builder.js.map +1 -1
  208. package/out/zql/src/planner/planner-connection.d.ts +43 -0
  209. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  210. package/out/zql/src/planner/planner-connection.js +31 -4
  211. package/out/zql/src/planner/planner-connection.js.map +1 -1
  212. package/out/zql/src/planner/planner-graph.d.ts +10 -0
  213. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  214. package/out/zql/src/planner/planner-graph.js +14 -0
  215. package/out/zql/src/planner/planner-graph.js.map +1 -1
  216. package/out/zql/src/planner/planner-warnings.d.ts +83 -0
  217. package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
  218. package/out/zql/src/planner/planner-warnings.js +78 -0
  219. package/out/zql/src/planner/planner-warnings.js.map +1 -0
  220. package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
  221. package/out/zql/src/query/measure-push-operator.js +30 -8
  222. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  223. package/out/zqlite/src/pending-delta.d.ts +110 -0
  224. package/out/zqlite/src/pending-delta.d.ts.map +1 -0
  225. package/out/zqlite/src/pending-delta.js +278 -0
  226. package/out/zqlite/src/pending-delta.js.map +1 -0
  227. package/out/zqlite/src/query-delegate.d.ts.map +1 -1
  228. package/out/zqlite/src/query-delegate.js +5 -1
  229. package/out/zqlite/src/query-delegate.js.map +1 -1
  230. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  231. package/out/zqlite/src/sqlite-cost-model.js +52 -1
  232. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  233. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
  234. package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
  235. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  236. package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
  237. package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
  238. package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
  239. package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
  240. package/out/zqlite/src/table-source.d.ts +68 -0
  241. package/out/zqlite/src/table-source.d.ts.map +1 -1
  242. package/out/zqlite/src/table-source.js +206 -7
  243. package/out/zqlite/src/table-source.js.map +1 -1
  244. package/package.json +1 -1
  245. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
  246. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
  247. package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
  248. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
  249. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
  250. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
  251. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
  252. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +0 -1
@@ -15,13 +15,18 @@ import { resolveSimpleScalarSubqueries } from "../../../../zqlite/src/resolve-sc
15
15
  import { randInt } from "../../../../shared/src/rand.js";
16
16
  import { createSQLiteCostModel } from "../../../../zqlite/src/sqlite-cost-model.js";
17
17
  import { reloadPermissionsIfChanged } from "../../auth/load-permissions.js";
18
+ import { LogThrottle } from "../../observability/log-throttle.js";
18
19
  import { getOrCreateCounter, getOrCreateLatencyHistogram } from "../../observability/metrics.js";
19
20
  import "../../types/row-key.js";
20
21
  import { getSubscriptionState } from "../replicator/schema/replication-state.js";
21
22
  import { checkClientSchema } from "./client-schema.js";
23
+ import { planWarningMessage } from "./plan-warnings.js";
24
+ import { queryShape } from "./query-shape.js";
22
25
  import { rowIDSignatureUnit } from "./row-set-signature.js";
23
26
  import { ResetPipelinesSignal } from "./snapshotter.js";
24
27
  //#region ../zero-cache/src/services/view-syncer/pipeline-driver.ts
28
+ /** The most queries a log of an advancement timeout lists. */
29
+ var ADVANCE_TIMEOUT_LOG_MAX_QUERIES = 3;
25
30
  /**
26
31
  * No matter how fast hydration is, advancement is given at least this long to
27
32
  * complete before doing a pipeline reset.
@@ -34,6 +39,8 @@ var MIN_PROJECTED_ADVANCEMENT_SAMPLE_MS = 5;
34
39
  var MIN_PROJECTED_ADVANCEMENT_CHANGES = 16;
35
40
  var PROJECTED_ADVANCEMENT_RESET_MULTIPLIER = 1.5;
36
41
  var LATE_ADVANCEMENT_FINISH_PROGRESS = .8;
42
+ var planWarningLogThrottle = new LogThrottle({ windowMs: 60 * 6e4 });
43
+ var slowAdvanceLogThrottle = new LogThrottle({ windowMs: 5 * 6e4 });
37
44
  function randomID() {
38
45
  return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);
39
46
  }
@@ -78,9 +85,11 @@ var PipelineDriver = class {
78
85
  #shardID;
79
86
  #logConfig;
80
87
  #config;
88
+ #deferredWrites;
81
89
  #tableSpecs = /* @__PURE__ */ new Map();
82
90
  #allTableNames = /* @__PURE__ */ new Set();
83
91
  #costModels;
92
+ #planWarningThresholds;
84
93
  #yieldThresholdMs;
85
94
  #streamer = null;
86
95
  #hydrateContext = null;
@@ -90,17 +99,34 @@ var PipelineDriver = class {
90
99
  #permissions = null;
91
100
  #advanceTime = getOrCreateLatencyHistogram("sync", "ivm.advance-time", "Time to advance all queries for a given client group in response to a single change.");
92
101
  #conflictRowsDeleted = getOrCreateCounter("sync", "ivm.conflict-rows-deleted", "Number of rows deleted because they conflicted with added row");
102
+ #deferredWritesFallbacks = getOrCreateCounter("sync", "ivm.deferred-writes-fallbacks", "Number of advancements written through to the replica snapshot because their changes did not fit in the deferred IVM writes budget, from the start or partway (because of the bytes held, a change that needed more rows than were left, or more rows held than reserved)");
93
103
  #inspectorDelegate;
94
- constructor(lc, logConfig, snapshotter, shardID, storage, clientGroupID, inspectorDelegate, yieldThresholdMs, enablePlanner, config) {
104
+ #queryStats;
105
+ /**
106
+ * Passes the pipelines' metrics on to the inspector, and accounts the time
107
+ * spent pushing to each query to the advancement in progress.
108
+ */
109
+ #metricsDelegate = { addMetric: (metric, value, ...args) => {
110
+ this.#inspectorDelegate.addMetric(metric, value, ...args);
111
+ if (metric === "query-update-server") this.#recordAdvancePush(args[0], value);
112
+ } };
113
+ constructor(lc, logConfig, snapshotter, shardID, storage, clientGroupID, inspectorDelegate, yieldThresholdMs, enablePlanner, config, deferredWrites, queryStats) {
95
114
  this.#lc = lc.withContext("clientGroupID", clientGroupID);
96
115
  this.#snapshotter = snapshotter;
97
116
  this.#storage = storage;
98
117
  this.#shardID = shardID;
99
118
  this.#logConfig = logConfig;
100
119
  this.#config = config;
120
+ this.#deferredWrites = deferredWrites;
101
121
  this.#inspectorDelegate = inspectorDelegate;
102
122
  this.#costModels = enablePlanner ? /* @__PURE__ */ new WeakMap() : void 0;
123
+ const planWarningThresholds = {
124
+ rows: logConfig.planWarningRowThreshold,
125
+ cost: logConfig.planWarningCostThreshold
126
+ };
127
+ this.#planWarningThresholds = planWarningThresholds.rows > 0 || planWarningThresholds.cost > 0 ? planWarningThresholds : void 0;
103
128
  this.#yieldThresholdMs = yieldThresholdMs;
129
+ this.#queryStats = queryStats;
104
130
  }
105
131
  /**
106
132
  * Initializes the PipelineDriver to the current head of the database.
@@ -219,18 +245,76 @@ var PipelineDriver = class {
219
245
  queries() {
220
246
  return this.#pipelines;
221
247
  }
248
+ /**
249
+ * Stats from the hydration of the pipeline for `queryID`, or `undefined` if
250
+ * the query has no pipeline.
251
+ */
252
+ hydrationStats(queryID) {
253
+ const pipeline = this.#pipelines.get(queryID);
254
+ return pipeline ? {
255
+ rowCount: pipeline.hydrationRowCount,
256
+ rowsRead: pipeline.hydrationRowsRead,
257
+ planWarnings: pipeline.planWarnings,
258
+ shape: pipeline.shape
259
+ } : void 0;
260
+ }
261
+ /**
262
+ * Logs what the planner warned about the plan it chose for `query`, at
263
+ * most once per query shape per {@link PLAN_WARNING_LOG_WINDOW_MS}.
264
+ */
265
+ #logPlanWarnings(shape, { queryHash, transformationHash, queryName }, warnings) {
266
+ if (!this.#lc.warn) return;
267
+ const suppressed = planWarningLogThrottle.admit(`${queryName ?? ""}:${shape.hash}`);
268
+ if (suppressed === void 0) return;
269
+ const messages = warnings.map(planWarningMessage);
270
+ this.#lc.warn(`Query plan warning${queryName === void 0 ? "" : ` for ${queryName}`}: ` + messages.join(" "), {
271
+ zeroEvent: "query-plan-warning",
272
+ queryHash,
273
+ transformationHash,
274
+ ...queryName !== void 0 && { queryName },
275
+ queryShape: shape.hash,
276
+ warnings,
277
+ ...suppressed > 0 && { suppressedSinceLastLog: suppressed },
278
+ zql: shape.zql
279
+ });
280
+ }
281
+ #totalRowsRead() {
282
+ let total = 0;
283
+ for (const table of this.#tables.values()) total += table.rowsRead;
284
+ return total;
285
+ }
222
286
  totalHydrationTimeMs() {
223
287
  let total = 0;
224
288
  for (const pipeline of this.#pipelines.values()) total += pipeline.hydrationTimeMs;
225
289
  return total;
226
290
  }
227
- #logQueryPipelineLifecycle({ zeroEvent, pipelineRunID, queryHash, transformationHash, queryName, hydrationReason, stopReason, hydrationTimeMs, hydrationRowCount, pipelineLifetimeMs }) {
291
+ /**
292
+ * The rows the sources hold in memory for the advancement in progress.
293
+ * The advancement's reservation from the {@link DeferredWritesBudget}
294
+ * bounds this.
295
+ */
296
+ get pendingRows() {
297
+ let total = 0;
298
+ for (const source of this.#tables.values()) total += source.pendingRows;
299
+ return total;
300
+ }
301
+ /**
302
+ * The estimated bytes of {@link pendingRows}, which are added to the
303
+ * {@link DeferredWritesBudget}'s held bytes as the advancement goes.
304
+ */
305
+ get pendingBytes() {
306
+ let total = 0;
307
+ for (const source of this.#tables.values()) total += source.pendingBytes;
308
+ return total;
309
+ }
310
+ #logQueryPipelineLifecycle({ zeroEvent, pipelineRunID, queryHash, transformationHash, queryName, hydrationReason, stopReason, hydrationTimeMs, hydrationRowCount, hydrationRowsRead, pipelineLifetimeMs }) {
228
311
  let lc = this.#lc.withContext("zeroEvent", zeroEvent).withContext("pipelineRunID", pipelineRunID).withContext("queryHash", queryHash).withContext("transformationHash", transformationHash);
229
312
  if (queryName !== void 0) lc = lc.withContext("queryName", queryName);
230
313
  if (hydrationReason !== void 0) lc = lc.withContext("hydrationReason", hydrationReason);
231
314
  if (stopReason !== void 0) lc = lc.withContext("stopReason", stopReason);
232
315
  if (hydrationTimeMs !== void 0) lc = lc.withContext("hydrationTimeMs", hydrationTimeMs);
233
316
  if (hydrationRowCount !== void 0) lc = lc.withContext("hydrationRowCount", hydrationRowCount);
317
+ if (hydrationRowsRead !== void 0) lc = lc.withContext("hydrationRowsRead", hydrationRowsRead);
234
318
  if (pipelineLifetimeMs !== void 0) lc = lc.withContext("pipelineLifetimeMs", pipelineLifetimeMs);
235
319
  lc.info?.("query pipeline lifecycle");
236
320
  }
@@ -312,6 +396,7 @@ var PipelineDriver = class {
312
396
  assert(this.initialized(), "Pipeline driver must be initialized before adding queries");
313
397
  this.removeQuery(queryID, "replace-query");
314
398
  const pipelineRunID = randomID();
399
+ const shape = queryShape(query);
315
400
  this.#logQueryPipelineLifecycle({
316
401
  zeroEvent: "query-pipeline-hydrate-start",
317
402
  pipelineRunID,
@@ -324,9 +409,12 @@ var PipelineDriver = class {
324
409
  const costModel = this.#ensureCostModelExistsIfEnabled(this.#snapshotter.current().db.db);
325
410
  assert(this.#advanceContext === null, "Cannot hydrate while advance is in progress");
326
411
  this.#hydrateContext = { timer };
412
+ const rowsReadAtStart = this.#totalRowsRead();
327
413
  let hydrationFinished = false;
328
414
  let hydrationFailed = false;
329
415
  let hydrationRowCount = 0;
416
+ let planWarnings = [];
417
+ const planWarningThresholds = this.#planWarningThresholds;
330
418
  let builtInputs = [];
331
419
  try {
332
420
  const { ast: resolvedQuery, companionRows, companions: companionMeta, companionInputs, ignoredScalarHints } = this.#resolveScalarSubqueries(query);
@@ -339,12 +427,23 @@ var PipelineDriver = class {
339
427
  enablePlannerAwarePushdown: this.#config?.enablePlannerAwarePushdown !== false,
340
428
  getSource: (name) => this.#getSource(name),
341
429
  createStorage: () => this.#createStorage(),
342
- decorateSourceInput: (input, _queryID) => new MeasurePushOperator(new QueryFailureLoggingOperator(this.#lc, input, queryID, transformationHash, queryName), queryID, this.#inspectorDelegate, "query-update-server"),
430
+ decorateSourceInput: (input, _queryID) => new MeasurePushOperator(new QueryFailureLoggingOperator(this.#lc, input, queryID, transformationHash, queryName), queryID, this.#metricsDelegate, "query-update-server"),
343
431
  decorateInput: (input) => input,
344
432
  addEdge() {},
345
- decorateFilterInput: (input) => input
433
+ decorateFilterInput: (input) => input,
434
+ planWarnings: planWarningThresholds && {
435
+ thresholds: planWarningThresholds,
436
+ report: (warnings) => {
437
+ planWarnings = warnings;
438
+ }
439
+ }
346
440
  }, queryID, costModel);
347
441
  builtInputs.push(input);
442
+ if (planWarnings.length > 0) this.#logPlanWarnings(shape, {
443
+ queryHash: queryID,
444
+ transformationHash,
445
+ queryName
446
+ }, planWarnings);
348
447
  const schema = input.getSchema();
349
448
  input.setOutput({ push: (change) => this.#streamPushed(queryID, schema, change) });
350
449
  for (const change of hydrateInternal(input, queryID, must(this.#primaryKeys), this.#tableSpecs)) {
@@ -363,6 +462,7 @@ var PipelineDriver = class {
363
462
  };
364
463
  }
365
464
  const hydrationTimeMs = timer.totalElapsed();
465
+ const hydrationRowsRead = this.#totalRowsRead() - rowsReadAtStart;
366
466
  if (runtimeDebugFlags.trackRowCountsVended) {
367
467
  if (hydrationTimeMs > this.#logConfig.slowHydrateThreshold) {
368
468
  let totalRowsConsidered = 0;
@@ -408,16 +508,29 @@ var PipelineDriver = class {
408
508
  input,
409
509
  hydrationTimeMs,
410
510
  hydrationRowCount,
511
+ hydrationRowsRead,
512
+ planWarnings,
411
513
  hydrationReason,
412
514
  pipelineRunID,
413
515
  pipelineReadyAtMs,
414
516
  transformedAst: resolvedQuery,
415
517
  originalAst: query,
518
+ shape,
416
519
  transformationHash,
417
520
  ...queryName !== void 0 && { queryName },
418
521
  companions: liveCompanions
419
522
  });
420
523
  hydrationFinished = true;
524
+ this.#queryStats?.recordHydration({
525
+ queryName,
526
+ shape
527
+ }, {
528
+ outcome: "finished",
529
+ timeMs: hydrationTimeMs,
530
+ rowCount: hydrationRowCount,
531
+ rowsRead: hydrationRowsRead,
532
+ planWarnings
533
+ });
421
534
  this.#logQueryPipelineLifecycle({
422
535
  zeroEvent: "query-pipeline-hydrate-finish",
423
536
  pipelineRunID,
@@ -426,10 +539,18 @@ var PipelineDriver = class {
426
539
  queryName,
427
540
  hydrationReason,
428
541
  hydrationTimeMs,
429
- hydrationRowCount
542
+ hydrationRowCount,
543
+ hydrationRowsRead
430
544
  });
431
545
  } catch (e) {
432
546
  hydrationFailed = true;
547
+ this.#queryStats?.recordHydration({
548
+ queryName,
549
+ shape
550
+ }, {
551
+ outcome: "failed",
552
+ timeMs: timer.totalElapsed()
553
+ });
433
554
  this.#logQueryPipelineLifecycle({
434
555
  zeroEvent: "query-pipeline-hydrate-failed",
435
556
  pipelineRunID,
@@ -447,16 +568,25 @@ var PipelineDriver = class {
447
568
  }, "query hydration failed", e);
448
569
  throw e;
449
570
  } finally {
450
- if (!hydrationFinished && !hydrationFailed) this.#logQueryPipelineLifecycle({
451
- zeroEvent: "query-pipeline-hydrate-aborted",
452
- pipelineRunID,
453
- queryHash: queryID,
454
- transformationHash,
455
- queryName,
456
- hydrationReason,
457
- hydrationTimeMs: timer.totalElapsed(),
458
- hydrationRowCount
459
- });
571
+ if (!hydrationFinished && !hydrationFailed) {
572
+ this.#queryStats?.recordHydration({
573
+ queryName,
574
+ shape
575
+ }, {
576
+ outcome: "aborted",
577
+ timeMs: timer.totalElapsed()
578
+ });
579
+ this.#logQueryPipelineLifecycle({
580
+ zeroEvent: "query-pipeline-hydrate-aborted",
581
+ pipelineRunID,
582
+ queryHash: queryID,
583
+ transformationHash,
584
+ queryName,
585
+ hydrationReason,
586
+ hydrationTimeMs: timer.totalElapsed(),
587
+ hydrationRowCount
588
+ });
589
+ }
460
590
  if (!hydrationFinished) {
461
591
  for (const input of builtInputs) input.destroy();
462
592
  this.#pruneUnusedTables();
@@ -559,25 +689,38 @@ var PipelineDriver = class {
559
689
  *#advance(diff, timer, numChanges) {
560
690
  assert(this.#hydrateContext === null, "Cannot advance while hydration is in progress");
561
691
  const totalHydrationTimeMs = this.totalHydrationTimeMs();
562
- this.#advanceContext = {
692
+ const advanceContext = {
563
693
  timer,
564
694
  totalHydrationTimeMs,
565
695
  numChanges,
696
+ reservedRows: void 0,
697
+ heldBytes: 0,
566
698
  currentChangeStartMs: void 0,
567
- pos: 0
699
+ pos: 0,
700
+ currentTable: void 0,
701
+ queryStats: /* @__PURE__ */ new Map()
568
702
  };
569
- this.#lc.debug?.(`starting pipeline advancement of ${numChanges} changes with an advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`);
703
+ this.#advanceContext = advanceContext;
704
+ let timedOut = false;
570
705
  try {
571
- for (const { table, prevValues, nextValue } of diff) {
706
+ advanceContext.reservedRows = this.#reserveDeferredWrites(numChanges);
707
+ const deferWrites = advanceContext.reservedRows !== void 0;
708
+ if (deferWrites) diff.setPrevWrites("none");
709
+ for (const table of this.#tables.values()) table.setDeferWrites(deferWrites);
710
+ this.#lc.debug?.(`starting pipeline advancement of ${numChanges} changes with an advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms (${deferWrites ? "deferred" : "write-through"}).`);
711
+ for (const { table, prevValues: probedPrevValues, nextValue, rowKey } of diff) {
572
712
  if (this.#shouldAdvanceYieldMaybeAbortAdvance()) yield "yield";
573
713
  const start = timer.totalElapsed();
574
- const advanceContext = must(this.#advanceContext);
575
714
  advanceContext.currentChangeStartMs = start;
715
+ advanceContext.currentTable = table;
716
+ let holds = "fits";
576
717
  try {
577
718
  try {
578
719
  const tableSource = this.#tables.get(table);
579
720
  if (!tableSource) continue;
580
721
  const primaryKey = mustGetPrimaryKey(this.#primaryKeys, table);
722
+ if (nextValue !== null && !this.#reserveSecondRow(advanceContext, rowKey, primaryKey)) holds = "rows";
723
+ const prevValues = tableSource.reconcilePendingConflicts(probedPrevValues, nextValue, rowKey, this.#tableSpecs.get(table)?.tableSpec.uniqueKeys ?? []);
581
724
  let editOldRow = void 0;
582
725
  for (const prevValue of prevValues) if (nextValue && deepEqual(getRowKey(primaryKey, prevValue), getRowKey(primaryKey, nextValue))) editOldRow = prevValue;
583
726
  else {
@@ -590,20 +733,188 @@ var PipelineDriver = class {
590
733
  advanceContext.pos++;
591
734
  }
592
735
  this.#shouldAdvanceYieldMaybeAbortAdvance(false);
736
+ if (holds === "fits") holds = this.#holdPendingRows(advanceContext);
593
737
  } finally {
594
738
  advanceContext.currentChangeStartMs = void 0;
595
739
  }
596
740
  const elapsed = timer.totalElapsed() - start;
597
741
  this.#advanceTime.recordMs(elapsed, { table });
742
+ if (holds !== "fits") yield* this.#writeThrough(advanceContext, diff, holds);
598
743
  }
599
744
  const { curr } = diff;
600
745
  for (const table of this.#tables.values()) table.setDB(curr.db.db);
601
746
  this.#ensureCostModelExistsIfEnabled(curr.db.db);
602
747
  this.#lc.debug?.(`Advanced to ${curr.version}`);
748
+ this.#logSlowAdvances(advanceContext);
749
+ } catch (e) {
750
+ if (e instanceof ResetPipelinesSignal && e.reason === "advancement-timeout") {
751
+ timedOut = true;
752
+ this.#logAdvanceTimeout(advanceContext, e);
753
+ }
754
+ throw e;
603
755
  } finally {
756
+ if (advanceContext.reservedRows !== void 0) for (const table of this.#tables.values()) table.discardPendingChanges();
757
+ this.#releaseDeferredWrites(advanceContext);
604
758
  this.#advanceContext = null;
759
+ this.#recordAdvanceStats(advanceContext, timedOut);
760
+ }
761
+ }
762
+ /** Accounts the time each query took in an advancement to its shape. */
763
+ #recordAdvanceStats({ queryStats }, timedOut) {
764
+ if (this.#queryStats === void 0) return;
765
+ for (const [queryID, { timeMs, changes }] of queryStats) {
766
+ const pipeline = this.#pipelines.get(queryID);
767
+ if (pipeline !== void 0) this.#queryStats.recordAdvance(pipeline, {
768
+ timeMs,
769
+ changes,
770
+ timedOut
771
+ });
772
+ }
773
+ }
774
+ /**
775
+ * Decides how the sources apply an advancement's changes: in memory if
776
+ * they fit in the worker's {@link DeferredWritesBudget}, and otherwise
777
+ * written through to the `prev` snapshot. Returns the number of rows
778
+ * reserved if they are held in memory.
779
+ */
780
+ #reserveDeferredWrites(numChanges) {
781
+ const budget = this.#deferredWrites;
782
+ if (!budget) return;
783
+ if (budget.tryReserve(numChanges)) return numChanges;
784
+ this.#deferredWritesFallbacks.add(1, { stage: "start" });
785
+ this.#lc.debug?.(`writing through ${numChanges} changes: ${budget.reservedRows} rows of the deferred writes budget are reserved`);
786
+ }
787
+ /**
788
+ * If the change log identifies the rows of a table by a key other than its
789
+ * primary key here, an entry that sets a row can change its primary key,
790
+ * which is two rows to a source: the old and the new. (An entry that removes
791
+ * a row removes the one row its key identifies.) Reserves the second row for
792
+ * such an entry, if the advancement holds its changes in memory, and
793
+ * returns whether it fit.
794
+ */
795
+ #reserveSecondRow(advanceContext, rowKey, primaryKey) {
796
+ if (advanceContext.reservedRows === void 0 || isKeyedBy(rowKey, primaryKey)) return true;
797
+ if (!must(this.#deferredWrites).tryReserveMore(1)) return false;
798
+ advanceContext.reservedRows++;
799
+ return true;
800
+ }
801
+ /**
802
+ * Writes the changes that an advancement holds in memory through to the
803
+ * `prev` snapshot, and the rest of its changes after them, when they no
804
+ * longer fit (see {@link HeldRows}). The advancement then continues as if
805
+ * it had written through from the start.
806
+ */
807
+ *#writeThrough(advanceContext, diff, reason) {
808
+ const budget = must(this.#deferredWrites);
809
+ this.#deferredWritesFallbacks.add(1, {
810
+ stage: "partway",
811
+ reason
812
+ });
813
+ this.#lc.debug?.(`writing through (${reason}) at ${advanceContext.pos} of ${advanceContext.numChanges} changes, holding ${advanceContext.heldBytes} of the ${budget.heldBytes} estimated bytes held on this worker (budget: ${budget.maxBytes})`);
814
+ diff.setPrevWrites("divergent");
815
+ for (const source of this.#tables.values()) yield* source.writePendingChanges();
816
+ this.#releaseDeferredWrites(advanceContext);
817
+ }
818
+ #releaseDeferredWrites(advanceContext) {
819
+ const { reservedRows, heldBytes } = advanceContext;
820
+ if (reservedRows !== void 0) {
821
+ must(this.#deferredWrites).release(reservedRows, heldBytes);
822
+ advanceContext.reservedRows = void 0;
823
+ advanceContext.heldBytes = 0;
824
+ }
825
+ }
826
+ #recordAdvancePush(queryID, timeMs) {
827
+ const advance = this.#advanceContext;
828
+ if (advance === null) return;
829
+ const stats = getOrInsertComputed(advance.queryStats, queryID, () => ({
830
+ timeMs: 0,
831
+ changes: 0,
832
+ lastPos: -1,
833
+ timeMsByTable: /* @__PURE__ */ new Map()
834
+ }));
835
+ stats.timeMs += timeMs;
836
+ if (stats.lastPos !== advance.pos) {
837
+ stats.lastPos = advance.pos;
838
+ stats.changes++;
839
+ }
840
+ const table = advance.currentTable;
841
+ if (table !== void 0) stats.timeMsByTable.set(table, (stats.timeMsByTable.get(table) ?? 0) + timeMs);
842
+ }
843
+ /**
844
+ * The identity of the query of the pipeline for `queryID` and its
845
+ * {@link queryShape}, for logging.
846
+ */
847
+ #queryForLog(queryID) {
848
+ const pipeline = this.#pipelines.get(queryID);
849
+ if (pipeline === void 0) return;
850
+ const { transformationHash, queryName, shape } = pipeline;
851
+ return {
852
+ queryName,
853
+ shape,
854
+ fields: {
855
+ queryHash: queryID,
856
+ transformationHash,
857
+ ...queryName !== void 0 && { queryName },
858
+ queryShape: shape.hash
859
+ }
860
+ };
861
+ }
862
+ /**
863
+ * Logs each query whose pushes took longer than the slow advance
864
+ * threshold, at most once per query shape per
865
+ * {@link SLOW_ADVANCE_LOG_WINDOW_MS}.
866
+ */
867
+ #logSlowAdvances({ queryStats, numChanges }) {
868
+ if (!this.#lc.warn) return;
869
+ for (const [queryID, stats] of queryStats) {
870
+ if (stats.timeMs <= this.#logConfig.slowAdvanceThreshold) continue;
871
+ const query = this.#queryForLog(queryID);
872
+ if (query === void 0) continue;
873
+ const suppressed = slowAdvanceLogThrottle.admit(`${query.queryName ?? ""}:${query.shape.hash}`);
874
+ if (suppressed === void 0) continue;
875
+ this.#lc.warn(`Slow query advancement${query.queryName === void 0 ? "" : ` for ${query.queryName}`}: ${Math.round(stats.timeMs)} ms for ${stats.changes} of ${numChanges} changes`, {
876
+ zeroEvent: "query-slow-advance",
877
+ ...query.fields,
878
+ advanceTimeMs: stats.timeMs,
879
+ changes: stats.changes,
880
+ advancementChanges: numChanges,
881
+ timeMsByTable: Object.fromEntries(stats.timeMsByTable),
882
+ ...suppressed > 0 && { suppressedSinceLastLog: suppressed },
883
+ zql: query.shape.zql
884
+ });
605
885
  }
606
886
  }
887
+ /**
888
+ * Logs the queries that took the most time in an advancement that timed
889
+ * out, since the reset that follows would otherwise not say what was slow.
890
+ * Throttled per query shape of the slowest query.
891
+ */
892
+ #logAdvanceTimeout({ queryStats, numChanges }, reset) {
893
+ if (!this.#lc.warn) return;
894
+ const slowest = [...queryStats].toSorted(([, a], [, b]) => b.timeMs - a.timeMs).slice(0, ADVANCE_TIMEOUT_LOG_MAX_QUERIES).flatMap(([queryID, stats]) => {
895
+ const query = this.#queryForLog(queryID);
896
+ return query ? [{
897
+ query,
898
+ stats
899
+ }] : [];
900
+ });
901
+ if (slowest.length === 0) return;
902
+ const suppressed = slowAdvanceLogThrottle.admit(`timeout:${slowest[0].query.queryName ?? ""}:${slowest[0].query.shape.hash}`);
903
+ if (suppressed === void 0) return;
904
+ const summary = slowest.map(({ query, stats }) => `${query.queryName ?? query.fields.queryHash} (${Math.round(stats.timeMs)} ms)`).join(", ");
905
+ this.#lc.warn(`Advancement of ${numChanges} changes timed out. The queries that took the most time: ${summary}`, {
906
+ zeroEvent: "query-advance-timeout",
907
+ reason: reset.message,
908
+ advancementChanges: numChanges,
909
+ queries: slowest.map(({ query, stats }) => ({
910
+ ...query.fields,
911
+ advanceTimeMs: stats.timeMs,
912
+ changes: stats.changes,
913
+ zql: query.shape.zql
914
+ })),
915
+ ...suppressed > 0 && { suppressedSinceLastLog: suppressed }
916
+ });
917
+ }
607
918
  /** Implements `BuilderDelegate.getSource()` */
608
919
  #getSource(tableName) {
609
920
  return getOrInsertComputed(this.#tables, tableName, (tableName) => {
@@ -637,6 +948,28 @@ var PipelineDriver = class {
637
948
  if (!shouldFinish && elapsed > MIN_ADVANCEMENT_TIME_LIMIT_MS && (elapsed > totalHydrationTimeMs || elapsed > totalHydrationTimeMs / 2 && pos <= numChanges / 2)) throw new ResetPipelinesSignal(`Advancement exceeded timeout at ${pos} of ${numChanges} changes after ${elapsed} ms. Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`, "advancement-timeout");
638
949
  return checkYield && advanceTimer.elapsedLap() > this.#yieldThresholdMs();
639
950
  }
951
+ /**
952
+ * The rows an advancement holds in memory are bounded by its reservation,
953
+ * but their width is not known in advance. So the bytes they are estimated
954
+ * to hold are added to those held by the other advancements on this worker.
955
+ * Says whether that takes them past the budget, or if the rows are not
956
+ * bounded by the reservation after all.
957
+ */
958
+ #holdPendingRows(advanceContext) {
959
+ const { reservedRows, heldBytes, pos, numChanges } = advanceContext;
960
+ if (reservedRows === void 0) return "fits";
961
+ const budget = must(this.#deferredWrites);
962
+ const rows = this.pendingRows;
963
+ const bytes = this.pendingBytes;
964
+ advanceContext.heldBytes = bytes;
965
+ const bytesFit = budget.holdBytes(bytes - heldBytes);
966
+ if (rows > reservedRows) {
967
+ budget.recordRowOverrun();
968
+ this.#lc.error?.(`Advancement holds ${rows} rows at ${pos} of ${numChanges} changes, more than the ${reservedRows} it reserved. Writing through the rest.`);
969
+ return "row-overrun";
970
+ }
971
+ return bytesFit ? "fits" : "bytes";
972
+ }
640
973
  #throwSlowCurrentChangeReset(pos, numChanges, elapsed, currentChangeElapsedMs, totalHydrationTimeMs) {
641
974
  throw new ResetPipelinesSignal(`Advancement exceeded timeout processing current change at ${pos} of ${numChanges} changes after ${currentChangeElapsedMs} ms (${elapsed} ms total). Advancement time limited based on total hydration time of ${totalHydrationTimeMs} ms.`, "advancement-timeout");
642
975
  }
@@ -875,6 +1208,15 @@ function* toAdds(nodes) {
875
1208
  function getRowKey(cols, row) {
876
1209
  return Object.fromEntries(cols.map((col) => [col, must(row[col])]));
877
1210
  }
1211
+ /** Whether `rowKey` has exactly the columns of `primaryKey`. */
1212
+ function isKeyedBy(rowKey, primaryKey) {
1213
+ let cols = 0;
1214
+ for (const col in rowKey) {
1215
+ if (!primaryKey.includes(col)) return false;
1216
+ cols++;
1217
+ }
1218
+ return cols === primaryKey.length;
1219
+ }
878
1220
  /**
879
1221
  * Core hydration logic used by {@link PipelineDriver#addQuery}, extracted to a
880
1222
  * function for reuse by the analyze-query RPC path so that analysis hydrates