@rocicorp/zero 1.11.0-canary.2 → 1.11.0-canary.21

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 (832) hide show
  1. package/out/analyze-query/src/bin-analyze.js +9 -8
  2. package/out/analyze-query/src/bin-analyze.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/bg-interval.d.ts +2 -1
  12. package/out/replicache/src/bg-interval.d.ts.map +1 -1
  13. package/out/replicache/src/bg-interval.js +13 -4
  14. package/out/replicache/src/bg-interval.js.map +1 -1
  15. package/out/replicache/src/btree/node.d.ts.map +1 -1
  16. package/out/replicache/src/btree/node.js +3 -6
  17. package/out/replicache/src/btree/node.js.map +1 -1
  18. package/out/replicache/src/dag/gc.d.ts.map +1 -1
  19. package/out/replicache/src/dag/gc.js +8 -11
  20. package/out/replicache/src/dag/gc.js.map +1 -1
  21. package/out/replicache/src/dag/invalid-ref-count-error.d.ts +12 -0
  22. package/out/replicache/src/dag/invalid-ref-count-error.d.ts.map +1 -0
  23. package/out/replicache/src/dag/invalid-ref-count-error.js +19 -0
  24. package/out/replicache/src/dag/invalid-ref-count-error.js.map +1 -0
  25. package/out/replicache/src/dag/lazy-store.d.ts +17 -4
  26. package/out/replicache/src/dag/lazy-store.d.ts.map +1 -1
  27. package/out/replicache/src/dag/lazy-store.js +35 -9
  28. package/out/replicache/src/dag/lazy-store.js.map +1 -1
  29. package/out/replicache/src/dag/store-impl.d.ts +12 -2
  30. package/out/replicache/src/dag/store-impl.d.ts.map +1 -1
  31. package/out/replicache/src/dag/store-impl.js +29 -9
  32. package/out/replicache/src/dag/store-impl.js.map +1 -1
  33. package/out/replicache/src/db/rebase.d.ts +14 -2
  34. package/out/replicache/src/db/rebase.d.ts.map +1 -1
  35. package/out/replicache/src/db/rebase.js +27 -5
  36. package/out/replicache/src/db/rebase.js.map +1 -1
  37. package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
  38. package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
  39. package/out/replicache/src/get-kv-store-provider.js +4 -4
  40. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  41. package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  42. package/out/replicache/src/kv/expo-sqlite/store.js +28 -5
  43. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  44. package/out/replicache/src/kv/idb-store.d.ts +18 -0
  45. package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
  46. package/out/replicache/src/kv/idb-store.js +30 -2
  47. package/out/replicache/src/kv/idb-store.js.map +1 -1
  48. package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
  49. package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
  50. package/out/replicache/src/kv/mem-fallback-store.js +177 -0
  51. package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
  52. package/out/replicache/src/kv/mem-store.d.ts +1 -0
  53. package/out/replicache/src/kv/mem-store.d.ts.map +1 -1
  54. package/out/replicache/src/kv/mem-store.js +3 -0
  55. package/out/replicache/src/kv/mem-store.js.map +1 -1
  56. package/out/replicache/src/kv/op-sqlite/store.d.ts.map +1 -1
  57. package/out/replicache/src/kv/op-sqlite/store.js +4 -4
  58. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  59. package/out/replicache/src/kv/sqlite-store.d.ts +22 -1
  60. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  61. package/out/replicache/src/kv/sqlite-store.js +159 -14
  62. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  63. package/out/replicache/src/kv/store.d.ts +6 -0
  64. package/out/replicache/src/kv/store.d.ts.map +1 -1
  65. package/out/replicache/src/new-client-channel.js.map +1 -1
  66. package/out/replicache/src/persist/client-gc.d.ts +2 -1
  67. package/out/replicache/src/persist/client-gc.d.ts.map +1 -1
  68. package/out/replicache/src/persist/client-gc.js +2 -2
  69. package/out/replicache/src/persist/client-gc.js.map +1 -1
  70. package/out/replicache/src/persist/client-group-gc.d.ts +2 -1
  71. package/out/replicache/src/persist/client-group-gc.d.ts.map +1 -1
  72. package/out/replicache/src/persist/client-group-gc.js +2 -2
  73. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  74. package/out/replicache/src/persist/collect-idb-databases.d.ts +28 -8
  75. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  76. package/out/replicache/src/persist/collect-idb-databases.js +97 -20
  77. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  78. package/out/replicache/src/persist/heartbeat.d.ts +2 -1
  79. package/out/replicache/src/persist/heartbeat.d.ts.map +1 -1
  80. package/out/replicache/src/persist/heartbeat.js +2 -2
  81. package/out/replicache/src/persist/heartbeat.js.map +1 -1
  82. package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
  83. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  84. package/out/replicache/src/persist/persist.d.ts.map +1 -1
  85. package/out/replicache/src/persist/persist.js +18 -8
  86. package/out/replicache/src/persist/persist.js.map +1 -1
  87. package/out/replicache/src/persist/refresh.js +6 -2
  88. package/out/replicache/src/persist/refresh.js.map +1 -1
  89. package/out/replicache/src/replicache-impl.d.ts +85 -3
  90. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  91. package/out/replicache/src/replicache-impl.js +321 -29
  92. package/out/replicache/src/replicache-impl.js.map +1 -1
  93. package/out/replicache/src/replicache-options.d.ts +9 -0
  94. package/out/replicache/src/replicache-options.d.ts.map +1 -1
  95. package/out/replicache/src/storage-failure.d.ts +37 -0
  96. package/out/replicache/src/storage-failure.d.ts.map +1 -0
  97. package/out/replicache/src/storage-failure.js +31 -0
  98. package/out/replicache/src/storage-failure.js.map +1 -0
  99. package/out/shared/src/arrays.d.ts +5 -0
  100. package/out/shared/src/arrays.d.ts.map +1 -1
  101. package/out/shared/src/arrays.js +8 -10
  102. package/out/shared/src/arrays.js.map +1 -1
  103. package/out/shared/src/error.d.ts +11 -0
  104. package/out/shared/src/error.d.ts.map +1 -1
  105. package/out/shared/src/error.js +35 -1
  106. package/out/shared/src/error.js.map +1 -1
  107. package/out/shared/src/iterables.d.ts +2 -0
  108. package/out/shared/src/iterables.d.ts.map +1 -1
  109. package/out/shared/src/iterables.js +18 -1
  110. package/out/shared/src/iterables.js.map +1 -1
  111. package/out/shared/src/map.d.ts +24 -2
  112. package/out/shared/src/map.d.ts.map +1 -1
  113. package/out/shared/src/map.js +18 -4
  114. package/out/shared/src/map.js.map +1 -1
  115. package/out/shared/src/random-values.js +12 -1
  116. package/out/shared/src/random-values.js.map +1 -1
  117. package/out/shared/src/ring-buffer.d.ts +10 -0
  118. package/out/shared/src/ring-buffer.d.ts.map +1 -1
  119. package/out/shared/src/ring-buffer.js +26 -1
  120. package/out/shared/src/ring-buffer.js.map +1 -1
  121. package/out/z2s/src/compiler.js +1 -1
  122. package/out/z2s/src/sql.d.ts.map +1 -1
  123. package/out/z2s/src/sql.js +2 -2
  124. package/out/z2s/src/sql.js.map +1 -1
  125. package/out/zero/package.js +3 -4
  126. package/out/zero/package.js.map +1 -1
  127. package/out/zero/src/zero.js +2 -1
  128. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  129. package/out/zero-cache/src/auth/write-authorizer.js +1 -0
  130. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  131. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  132. package/out/zero-cache/src/config/normalize.js +26 -2
  133. package/out/zero-cache/src/config/normalize.js.map +1 -1
  134. package/out/zero-cache/src/config/zero-config.d.ts +102 -6
  135. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  136. package/out/zero-cache/src/config/zero-config.js +254 -57
  137. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  138. package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
  139. package/out/zero-cache/src/db/lite-tables.js +3 -4
  140. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  141. package/out/zero-cache/src/observability/events.js +4 -1
  142. package/out/zero-cache/src/observability/events.js.map +1 -1
  143. package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
  144. package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
  145. package/out/zero-cache/src/observability/log-throttle.js +76 -0
  146. package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
  147. package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
  148. package/out/zero-cache/src/observability/metrics.js +2 -7
  149. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  150. package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
  151. package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
  152. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  153. package/out/zero-cache/src/server/change-streamer.js +27 -10
  154. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  155. package/out/zero-cache/src/server/inspector-delegate.d.ts +28 -2
  156. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  157. package/out/zero-cache/src/server/inspector-delegate.js +37 -17
  158. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  159. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  160. package/out/zero-cache/src/server/main.js +8 -1
  161. package/out/zero-cache/src/server/main.js.map +1 -1
  162. package/out/zero-cache/src/server/mutator.js +2 -0
  163. package/out/zero-cache/src/server/mutator.js.map +1 -1
  164. package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
  165. package/out/zero-cache/src/server/reaper.js +2 -0
  166. package/out/zero-cache/src/server/reaper.js.map +1 -1
  167. package/out/zero-cache/src/server/replicator.d.ts +3 -0
  168. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  169. package/out/zero-cache/src/server/replicator.js +79 -15
  170. package/out/zero-cache/src/server/replicator.js.map +1 -1
  171. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  172. package/out/zero-cache/src/server/runner/zero-dispatcher.js +5 -0
  173. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  174. package/out/zero-cache/src/server/syncer-assigner.d.ts +14 -0
  175. package/out/zero-cache/src/server/syncer-assigner.d.ts.map +1 -0
  176. package/out/zero-cache/src/server/syncer-assigner.js +72 -0
  177. package/out/zero-cache/src/server/syncer-assigner.js.map +1 -0
  178. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  179. package/out/zero-cache/src/server/syncer.js +24 -2
  180. package/out/zero-cache/src/server/syncer.js.map +1 -1
  181. package/out/zero-cache/src/server/worker-dispatcher.d.ts.map +1 -1
  182. package/out/zero-cache/src/server/worker-dispatcher.js +21 -4
  183. package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
  184. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  185. package/out/zero-cache/src/services/analyze.js +11 -9
  186. package/out/zero-cache/src/services/analyze.js.map +1 -1
  187. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  188. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +198 -24
  189. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  190. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
  191. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +15 -3
  192. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  193. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
  194. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  195. package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
  196. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  197. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
  198. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  199. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
  200. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  201. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts +25 -0
  202. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -0
  203. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +202 -0
  204. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -0
  205. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +14 -16
  206. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  207. package/out/zero-cache/src/services/change-source/pg/change-source.js +64 -143
  208. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  209. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +37 -2
  210. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  211. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +53 -21
  212. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  213. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts +16 -0
  214. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts.map +1 -0
  215. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +113 -0
  216. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -0
  217. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +21 -15
  218. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  219. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +86 -23
  220. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  221. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -1
  222. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +68 -8
  223. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  224. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  225. package/out/zero-cache/src/services/change-source/pg/schema/init.js +32 -1
  226. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  227. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.d.ts +10 -0
  228. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.d.ts.map +1 -0
  229. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js +6 -0
  230. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js.map +1 -0
  231. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +50 -27
  232. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  233. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +100 -34
  234. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  235. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
  236. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
  237. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
  238. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
  239. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
  240. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  241. package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
  242. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  243. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
  244. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  245. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
  246. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  247. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
  248. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  249. package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
  250. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
  251. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
  252. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
  253. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
  254. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
  255. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
  256. package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
  257. package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
  258. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
  259. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +2 -0
  260. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  261. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  262. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +58 -7
  263. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  264. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +8 -2
  265. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
  266. package/out/zero-cache/src/services/change-streamer/broadcast.js +24 -4
  267. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  268. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -4
  269. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
  270. package/out/zero-cache/src/services/change-streamer/change-log-codec.js +28 -8
  271. package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
  272. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +48 -6
  273. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  274. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +130 -34
  275. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  276. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +11 -0
  277. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  278. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +118 -68
  279. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  280. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +37 -15
  281. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  282. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  283. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +3 -3
  284. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  285. package/out/zero-cache/src/services/change-streamer/forwarder.js +36 -5
  286. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  287. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts +51 -0
  288. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts.map +1 -0
  289. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js +132 -0
  290. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js.map +1 -0
  291. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +1 -1
  292. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  293. package/out/zero-cache/src/services/change-streamer/schema/tables.js +19 -8
  294. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  295. package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts +92 -0
  296. package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts.map +1 -0
  297. package/out/zero-cache/src/services/change-streamer/snapshot-message.js +42 -0
  298. package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -0
  299. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +1 -1
  300. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
  301. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +16 -4
  302. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  303. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +7 -69
  304. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
  305. package/out/zero-cache/src/services/change-streamer/snapshot.js +21 -53
  306. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  307. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +6 -1
  308. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -1
  309. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +27 -9
  310. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
  311. package/out/zero-cache/src/services/change-streamer/storer.d.ts +2 -2
  312. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  313. package/out/zero-cache/src/services/change-streamer/storer.js +9 -6
  314. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  315. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +446 -0
  316. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -0
  317. package/out/zero-cache/src/services/change-streamer/subscribe.js +76 -0
  318. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -0
  319. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +33 -1
  320. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  321. package/out/zero-cache/src/services/change-streamer/subscriber.js +76 -0
  322. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  323. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +17 -1
  324. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -1
  325. package/out/zero-cache/src/services/change-streamer/upstream-acker.js +19 -3
  326. package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
  327. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts +5 -0
  328. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -1
  329. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +35 -5
  330. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -1
  331. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  332. package/out/zero-cache/src/services/life-cycle.js +3 -1
  333. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  334. package/out/zero-cache/src/services/litestream/commands.d.ts +15 -3
  335. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  336. package/out/zero-cache/src/services/litestream/commands.js +24 -7
  337. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  338. package/out/zero-cache/src/services/litestream/config-v5.yml +11 -4
  339. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +51 -24
  340. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
  341. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +10 -2
  342. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -1
  343. package/out/zero-cache/src/services/litestream/litestream-controller.js +3 -2
  344. package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -1
  345. package/out/zero-cache/src/services/litestream/restore-progress.d.ts +24 -0
  346. package/out/zero-cache/src/services/litestream/restore-progress.d.ts.map +1 -0
  347. package/out/zero-cache/src/services/litestream/restore-progress.js +91 -0
  348. package/out/zero-cache/src/services/litestream/restore-progress.js.map +1 -0
  349. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +5 -0
  350. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  351. package/out/zero-cache/src/services/mutagen/mutagen.js +7 -1
  352. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  353. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  354. package/out/zero-cache/src/services/mutagen/pusher.js +5 -6
  355. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  356. package/out/zero-cache/src/services/plannerz.d.ts +163 -0
  357. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
  358. package/out/zero-cache/src/services/plannerz.js +267 -0
  359. package/out/zero-cache/src/services/plannerz.js.map +1 -0
  360. package/out/zero-cache/src/services/profz.d.ts +30 -0
  361. package/out/zero-cache/src/services/profz.d.ts.map +1 -0
  362. package/out/zero-cache/src/services/profz.js +145 -0
  363. package/out/zero-cache/src/services/profz.js.map +1 -0
  364. package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
  365. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
  366. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +5 -4
  367. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -1
  368. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +20 -22
  369. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  370. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  371. package/out/zero-cache/src/services/replicator/change-processor.js +49 -5
  372. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  373. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +3 -2
  374. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  375. package/out/zero-cache/src/services/replicator/incremental-sync.js +50 -20
  376. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  377. package/out/zero-cache/src/services/replicator/replication-status.d.ts +35 -1
  378. package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +1 -1
  379. package/out/zero-cache/src/services/replicator/replication-status.js +96 -6
  380. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  381. package/out/zero-cache/src/services/replicator/replicator.d.ts +2 -1
  382. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  383. package/out/zero-cache/src/services/replicator/replicator.js +2 -2
  384. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  385. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
  386. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
  387. package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
  388. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
  389. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +6 -6
  390. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
  391. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +19 -14
  392. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  393. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
  394. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
  395. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  396. package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
  397. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  398. package/out/zero-cache/src/services/run-ast.d.ts +12 -0
  399. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  400. package/out/zero-cache/src/services/run-ast.js +26 -14
  401. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  402. package/out/zero-cache/src/services/runner.d.ts +2 -1
  403. package/out/zero-cache/src/services/runner.d.ts.map +1 -1
  404. package/out/zero-cache/src/services/runner.js +10 -2
  405. package/out/zero-cache/src/services/runner.js.map +1 -1
  406. package/out/zero-cache/src/services/statz.d.ts +4 -0
  407. package/out/zero-cache/src/services/statz.d.ts.map +1 -1
  408. package/out/zero-cache/src/services/statz.js +22 -5
  409. package/out/zero-cache/src/services/statz.js.map +1 -1
  410. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +19 -2
  411. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  412. package/out/zero-cache/src/services/view-syncer/client-handler.js +42 -30
  413. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  414. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +19 -1
  415. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  416. package/out/zero-cache/src/services/view-syncer/cvr-store.js +34 -11
  417. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  418. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +51 -2
  419. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  420. package/out/zero-cache/src/services/view-syncer/cvr.js +142 -6
  421. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  422. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
  423. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
  424. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
  425. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
  426. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +14 -1
  427. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +1 -1
  428. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +30 -4
  429. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  430. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts +33 -0
  431. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts.map +1 -0
  432. package/out/zero-cache/src/services/view-syncer/hydration-budget.js +62 -0
  433. package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -0
  434. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +51 -0
  435. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
  436. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
  437. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
  438. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +1 -1
  439. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  440. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +3 -3
  441. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  442. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +50 -2
  443. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  444. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +466 -62
  445. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  446. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
  447. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
  448. package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
  449. package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
  450. package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +1 -1
  451. package/out/zero-cache/src/services/view-syncer/query-covering.js +2 -6
  452. package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
  453. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
  454. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
  455. package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
  456. package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
  457. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
  458. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
  459. package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
  460. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
  461. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts +95 -0
  462. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts.map +1 -0
  463. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js +155 -0
  464. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js.map +1 -0
  465. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +78 -4
  466. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  467. package/out/zero-cache/src/services/view-syncer/snapshotter.js +158 -28
  468. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  469. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
  470. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  471. package/out/zero-cache/src/services/view-syncer/view-syncer.js +587 -103
  472. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  473. package/out/zero-cache/src/types/processes.d.ts +39 -0
  474. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  475. package/out/zero-cache/src/types/processes.js +43 -4
  476. package/out/zero-cache/src/types/processes.js.map +1 -1
  477. package/out/zero-cache/src/types/profiler.d.ts +12 -0
  478. package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
  479. package/out/zero-cache/src/types/profiler.js +46 -1
  480. package/out/zero-cache/src/types/profiler.js.map +1 -1
  481. package/out/zero-cache/src/types/row-key.d.ts.map +1 -1
  482. package/out/zero-cache/src/types/row-key.js +5 -5
  483. package/out/zero-cache/src/types/row-key.js.map +1 -1
  484. package/out/zero-cache/src/types/streams.d.ts +49 -7
  485. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  486. package/out/zero-cache/src/types/streams.js +190 -58
  487. package/out/zero-cache/src/types/streams.js.map +1 -1
  488. package/out/zero-cache/src/types/subscription.d.ts +15 -0
  489. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  490. package/out/zero-cache/src/types/subscription.js +123 -31
  491. package/out/zero-cache/src/types/subscription.js.map +1 -1
  492. package/out/zero-cache/src/types/websocket-handoff.d.ts +1 -1
  493. package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
  494. package/out/zero-cache/src/types/websocket-handoff.js +18 -9
  495. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  496. package/out/zero-cache/src/types/ws.d.ts.map +1 -1
  497. package/out/zero-cache/src/types/ws.js +1 -16
  498. package/out/zero-cache/src/types/ws.js.map +1 -1
  499. package/out/zero-cache/src/workers/connect-params.d.ts +1 -0
  500. package/out/zero-cache/src/workers/connect-params.d.ts.map +1 -1
  501. package/out/zero-cache/src/workers/connect-params.js.map +1 -1
  502. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  503. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +21 -16
  504. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  505. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  506. package/out/zero-cache/src/workers/syncer.js +55 -4
  507. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  508. package/out/zero-client/src/client/active-clients-manager.d.ts.map +1 -1
  509. package/out/zero-client/src/client/active-clients-manager.js +4 -1
  510. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  511. package/out/zero-client/src/client/connection-manager.d.ts +20 -0
  512. package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
  513. package/out/zero-client/src/client/connection-manager.js +77 -13
  514. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  515. package/out/zero-client/src/client/connection-status-enum.d.ts +2 -0
  516. package/out/zero-client/src/client/connection-status-enum.d.ts.map +1 -1
  517. package/out/zero-client/src/client/connection-status-enum.js +3 -1
  518. package/out/zero-client/src/client/connection-status-enum.js.map +1 -1
  519. package/out/zero-client/src/client/connection.d.ts +6 -0
  520. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  521. package/out/zero-client/src/client/connection.js +2 -1
  522. package/out/zero-client/src/client/connection.js.map +1 -1
  523. package/out/zero-client/src/client/context.d.ts +22 -12
  524. package/out/zero-client/src/client/context.d.ts.map +1 -1
  525. package/out/zero-client/src/client/context.js +92 -25
  526. package/out/zero-client/src/client/context.js.map +1 -1
  527. package/out/zero-client/src/client/custom.js +2 -2
  528. package/out/zero-client/src/client/custom.js.map +1 -1
  529. package/out/zero-client/src/client/error.js +1 -1
  530. package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
  531. package/out/zero-client/src/client/ivm-branch.js +20 -3
  532. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  533. package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
  534. package/out/zero-client/src/client/mutator-proxy.js +5 -2
  535. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  536. package/out/zero-client/src/client/options.d.ts +33 -0
  537. package/out/zero-client/src/client/options.d.ts.map +1 -1
  538. package/out/zero-client/src/client/options.js.map +1 -1
  539. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  540. package/out/zero-client/src/client/query-manager.js +44 -11
  541. package/out/zero-client/src/client/query-manager.js.map +1 -1
  542. package/out/zero-client/src/client/version.js +1 -1
  543. package/out/zero-client/src/client/zero-rep.d.ts +1 -0
  544. package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
  545. package/out/zero-client/src/client/zero-rep.js +8 -6
  546. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  547. package/out/zero-client/src/client/zero.d.ts +18 -3
  548. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  549. package/out/zero-client/src/client/zero.js +84 -47
  550. package/out/zero-client/src/client/zero.js.map +1 -1
  551. package/out/zero-client/src/mod.d.ts +1 -0
  552. package/out/zero-client/src/mod.d.ts.map +1 -1
  553. package/out/zero-client/src/mod.js +1 -0
  554. package/out/zero-client/src/types/query-result.d.ts +2 -0
  555. package/out/zero-client/src/types/query-result.d.ts.map +1 -1
  556. package/out/zero-events/src/status.d.ts +58 -0
  557. package/out/zero-events/src/status.d.ts.map +1 -1
  558. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  559. package/out/zero-protocol/src/ast.js +13 -14
  560. package/out/zero-protocol/src/ast.js.map +1 -1
  561. package/out/zero-protocol/src/down.d.ts +29 -29
  562. package/out/zero-protocol/src/query-hash.d.ts.map +1 -1
  563. package/out/zero-protocol/src/query-hash.js +2 -5
  564. package/out/zero-protocol/src/query-hash.js.map +1 -1
  565. package/out/zero-react/src/use-query.d.ts +5 -4
  566. package/out/zero-react/src/use-query.d.ts.map +1 -1
  567. package/out/zero-react/src/use-query.js +62 -4
  568. package/out/zero-react/src/use-query.js.map +1 -1
  569. package/out/zero-react/src/zero.js +1 -0
  570. package/out/zero-server/src/adapters/drizzle.d.ts +2 -1
  571. package/out/zero-server/src/adapters/drizzle.d.ts.map +1 -1
  572. package/out/zero-server/src/adapters/drizzle.js +12 -7
  573. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  574. package/out/zero-server/src/adapters/kysely.d.ts +2 -1
  575. package/out/zero-server/src/adapters/kysely.d.ts.map +1 -1
  576. package/out/zero-server/src/adapters/kysely.js +8 -2
  577. package/out/zero-server/src/adapters/kysely.js.map +1 -1
  578. package/out/zero-server/src/adapters/pg.d.ts +1 -0
  579. package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
  580. package/out/zero-server/src/adapters/pg.js +8 -2
  581. package/out/zero-server/src/adapters/pg.js.map +1 -1
  582. package/out/zero-server/src/adapters/postgresjs.d.ts +1 -0
  583. package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
  584. package/out/zero-server/src/adapters/postgresjs.js +7 -1
  585. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  586. package/out/zero-server/src/adapters/prisma.d.ts +3 -2
  587. package/out/zero-server/src/adapters/prisma.d.ts.map +1 -1
  588. package/out/zero-server/src/adapters/prisma.js +8 -2
  589. package/out/zero-server/src/adapters/prisma.js.map +1 -1
  590. package/out/zero-server/src/custom.d.ts +5 -1
  591. package/out/zero-server/src/custom.d.ts.map +1 -1
  592. package/out/zero-server/src/custom.js +6 -3
  593. package/out/zero-server/src/custom.js.map +1 -1
  594. package/out/zero-server/src/logging.d.ts +2 -2
  595. package/out/zero-server/src/logging.d.ts.map +1 -1
  596. package/out/zero-server/src/logging.js +2 -2
  597. package/out/zero-server/src/logging.js.map +1 -1
  598. package/out/zero-server/src/mod.d.ts +1 -1
  599. package/out/zero-server/src/mod.d.ts.map +1 -1
  600. package/out/zero-server/src/pg-query-executor.d.ts +2 -2
  601. package/out/zero-server/src/pg-query-executor.d.ts.map +1 -1
  602. package/out/zero-server/src/pg-query-executor.js +2 -2
  603. package/out/zero-server/src/pg-query-executor.js.map +1 -1
  604. package/out/zero-server/src/process-mutations.d.ts +12 -3
  605. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  606. package/out/zero-server/src/process-mutations.js +13 -8
  607. package/out/zero-server/src/process-mutations.js.map +1 -1
  608. package/out/zero-server/src/push-processor.d.ts +8 -2
  609. package/out/zero-server/src/push-processor.d.ts.map +1 -1
  610. package/out/zero-server/src/push-processor.js +12 -4
  611. package/out/zero-server/src/push-processor.js.map +1 -1
  612. package/out/zero-server/src/schema.d.ts +2 -2
  613. package/out/zero-server/src/schema.d.ts.map +1 -1
  614. package/out/zero-server/src/schema.js +2 -2
  615. package/out/zero-server/src/schema.js.map +1 -1
  616. package/out/zero-server/src/zql-database.d.ts +21 -1
  617. package/out/zero-server/src/zql-database.d.ts.map +1 -1
  618. package/out/zero-server/src/zql-database.js +33 -2
  619. package/out/zero-server/src/zql-database.js.map +1 -1
  620. package/out/zero-solid/src/solid-view.d.ts +17 -0
  621. package/out/zero-solid/src/solid-view.d.ts.map +1 -1
  622. package/out/zero-solid/src/solid-view.js +39 -1
  623. package/out/zero-solid/src/solid-view.js.map +1 -1
  624. package/out/zero-solid/src/use-query.js +1 -2
  625. package/out/zero-solid/src/use-query.js.map +1 -1
  626. package/out/zero-solid/src/zero.js +1 -0
  627. package/out/zql/src/builder/builder.d.ts +25 -0
  628. package/out/zql/src/builder/builder.d.ts.map +1 -1
  629. package/out/zql/src/builder/builder.js +69 -19
  630. package/out/zql/src/builder/builder.js.map +1 -1
  631. package/out/zql/src/builder/correlated-predicate-pushdown.d.ts +43 -0
  632. package/out/zql/src/builder/correlated-predicate-pushdown.d.ts.map +1 -0
  633. package/out/zql/src/builder/correlated-predicate-pushdown.js +161 -0
  634. package/out/zql/src/builder/correlated-predicate-pushdown.js.map +1 -0
  635. package/out/zql/src/builder/debug-delegate.d.ts +6 -1
  636. package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
  637. package/out/zql/src/builder/debug-delegate.js +14 -2
  638. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  639. package/out/zql/src/ivm/array-view.d.ts +29 -0
  640. package/out/zql/src/ivm/array-view.d.ts.map +1 -1
  641. package/out/zql/src/ivm/array-view.js +58 -9
  642. package/out/zql/src/ivm/array-view.js.map +1 -1
  643. package/out/zql/src/ivm/cap.d.ts +2 -1
  644. package/out/zql/src/ivm/cap.d.ts.map +1 -1
  645. package/out/zql/src/ivm/cap.js +14 -4
  646. package/out/zql/src/ivm/cap.js.map +1 -1
  647. package/out/zql/src/ivm/connection-index.d.ts +68 -0
  648. package/out/zql/src/ivm/connection-index.d.ts.map +1 -0
  649. package/out/zql/src/ivm/connection-index.js +149 -0
  650. package/out/zql/src/ivm/connection-index.js.map +1 -0
  651. package/out/zql/src/ivm/data.d.ts.map +1 -1
  652. package/out/zql/src/ivm/data.js +8 -3
  653. package/out/zql/src/ivm/data.js.map +1 -1
  654. package/out/zql/src/ivm/deferred-input.d.ts.map +1 -1
  655. package/out/zql/src/ivm/deferred-input.js +3 -2
  656. package/out/zql/src/ivm/deferred-input.js.map +1 -1
  657. package/out/zql/src/ivm/exists.d.ts +3 -1
  658. package/out/zql/src/ivm/exists.d.ts.map +1 -1
  659. package/out/zql/src/ivm/exists.js +4 -1
  660. package/out/zql/src/ivm/exists.js.map +1 -1
  661. package/out/zql/src/ivm/fan-in.d.ts +6 -2
  662. package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
  663. package/out/zql/src/ivm/fan-in.js +13 -5
  664. package/out/zql/src/ivm/fan-in.js.map +1 -1
  665. package/out/zql/src/ivm/fan-out.d.ts +5 -2
  666. package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
  667. package/out/zql/src/ivm/fan-out.js +4 -0
  668. package/out/zql/src/ivm/fan-out.js.map +1 -1
  669. package/out/zql/src/ivm/filter-operators.d.ts +8 -6
  670. package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
  671. package/out/zql/src/ivm/filter-operators.js +19 -8
  672. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  673. package/out/zql/src/ivm/filter.d.ts +5 -2
  674. package/out/zql/src/ivm/filter.d.ts.map +1 -1
  675. package/out/zql/src/ivm/filter.js +10 -4
  676. package/out/zql/src/ivm/filter.js.map +1 -1
  677. package/out/zql/src/ivm/flipped-join.d.ts +9 -4
  678. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  679. package/out/zql/src/ivm/flipped-join.js +97 -46
  680. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  681. package/out/zql/src/ivm/join-index-tap.d.ts +33 -0
  682. package/out/zql/src/ivm/join-index-tap.d.ts.map +1 -0
  683. package/out/zql/src/ivm/join-index-tap.js +70 -0
  684. package/out/zql/src/ivm/join-index-tap.js.map +1 -0
  685. package/out/zql/src/ivm/join-index.d.ts +35 -0
  686. package/out/zql/src/ivm/join-index.d.ts.map +1 -0
  687. package/out/zql/src/ivm/join-index.js +141 -0
  688. package/out/zql/src/ivm/join-index.js.map +1 -0
  689. package/out/zql/src/ivm/join-utils.d.ts +6 -0
  690. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  691. package/out/zql/src/ivm/join-utils.js +22 -1
  692. package/out/zql/src/ivm/join-utils.js.map +1 -1
  693. package/out/zql/src/ivm/join.d.ts +19 -1
  694. package/out/zql/src/ivm/join.d.ts.map +1 -1
  695. package/out/zql/src/ivm/join.js +107 -15
  696. package/out/zql/src/ivm/join.js.map +1 -1
  697. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
  698. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
  699. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +6 -4
  700. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  701. package/out/zql/src/ivm/memory-source.d.ts +28 -3
  702. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  703. package/out/zql/src/ivm/memory-source.js +69 -33
  704. package/out/zql/src/ivm/memory-source.js.map +1 -1
  705. package/out/zql/src/ivm/memory-storage.d.ts +1 -0
  706. package/out/zql/src/ivm/memory-storage.d.ts.map +1 -1
  707. package/out/zql/src/ivm/memory-storage.js +1 -0
  708. package/out/zql/src/ivm/memory-storage.js.map +1 -1
  709. package/out/zql/src/ivm/operator.d.ts +45 -10
  710. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  711. package/out/zql/src/ivm/operator.js +9 -4
  712. package/out/zql/src/ivm/operator.js.map +1 -1
  713. package/out/zql/src/ivm/push-accumulated.d.ts +6 -1
  714. package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
  715. package/out/zql/src/ivm/push-accumulated.js +47 -2
  716. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  717. package/out/zql/src/ivm/skip.d.ts +2 -1
  718. package/out/zql/src/ivm/skip.d.ts.map +1 -1
  719. package/out/zql/src/ivm/skip.js +3 -0
  720. package/out/zql/src/ivm/skip.js.map +1 -1
  721. package/out/zql/src/ivm/take-gate.d.ts +57 -0
  722. package/out/zql/src/ivm/take-gate.d.ts.map +1 -0
  723. package/out/zql/src/ivm/take-gate.js +106 -0
  724. package/out/zql/src/ivm/take-gate.js.map +1 -0
  725. package/out/zql/src/ivm/take.d.ts +8 -2
  726. package/out/zql/src/ivm/take.d.ts.map +1 -1
  727. package/out/zql/src/ivm/take.js +139 -103
  728. package/out/zql/src/ivm/take.js.map +1 -1
  729. package/out/zql/src/ivm/union-fan-in.d.ts +3 -0
  730. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  731. package/out/zql/src/ivm/union-fan-in.js +63 -10
  732. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  733. package/out/zql/src/ivm/union-fan-out.d.ts +2 -1
  734. package/out/zql/src/ivm/union-fan-out.d.ts.map +1 -1
  735. package/out/zql/src/ivm/union-fan-out.js +5 -0
  736. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  737. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  738. package/out/zql/src/ivm/view-apply-change.js +9 -4
  739. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  740. package/out/zql/src/ivm/view.d.ts +15 -0
  741. package/out/zql/src/ivm/view.d.ts.map +1 -1
  742. package/out/zql/src/mutate/custom.d.ts +24 -1
  743. package/out/zql/src/mutate/custom.d.ts.map +1 -1
  744. package/out/zql/src/mutate/custom.js.map +1 -1
  745. package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
  746. package/out/zql/src/mutate/mutator-registry.js +2 -2
  747. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  748. package/out/zql/src/planner/planner-builder.d.ts +23 -3
  749. package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
  750. package/out/zql/src/planner/planner-builder.js +34 -17
  751. package/out/zql/src/planner/planner-builder.js.map +1 -1
  752. package/out/zql/src/planner/planner-connection.d.ts +58 -2
  753. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  754. package/out/zql/src/planner/planner-connection.js +103 -11
  755. package/out/zql/src/planner/planner-connection.js.map +1 -1
  756. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  757. package/out/zql/src/planner/planner-debug.js +3 -8
  758. package/out/zql/src/planner/planner-debug.js.map +1 -1
  759. package/out/zql/src/planner/planner-graph.d.ts +10 -0
  760. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  761. package/out/zql/src/planner/planner-graph.js +14 -0
  762. package/out/zql/src/planner/planner-graph.js.map +1 -1
  763. package/out/zql/src/planner/planner-source.d.ts +2 -2
  764. package/out/zql/src/planner/planner-source.d.ts.map +1 -1
  765. package/out/zql/src/planner/planner-source.js +2 -2
  766. package/out/zql/src/planner/planner-source.js.map +1 -1
  767. package/out/zql/src/planner/planner-warnings.d.ts +83 -0
  768. package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
  769. package/out/zql/src/planner/planner-warnings.js +78 -0
  770. package/out/zql/src/planner/planner-warnings.js.map +1 -0
  771. package/out/zql/src/query/measure-push-operator.d.ts +2 -1
  772. package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
  773. package/out/zql/src/query/measure-push-operator.js +33 -4
  774. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  775. package/out/zql/src/query/query-delegate-base.d.ts +4 -2
  776. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  777. package/out/zql/src/query/query-delegate-base.js +66 -33
  778. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  779. package/out/zql/src/query/query-delegate.d.ts +33 -6
  780. package/out/zql/src/query/query-delegate.d.ts.map +1 -1
  781. package/out/zql/src/query/query-impl.d.ts +1 -0
  782. package/out/zql/src/query/query-impl.d.ts.map +1 -1
  783. package/out/zql/src/query/query-impl.js +6 -23
  784. package/out/zql/src/query/query-impl.js.map +1 -1
  785. package/out/zql/src/query/query-transitions.d.ts.map +1 -1
  786. package/out/zql/src/query/query-transitions.js +2 -5
  787. package/out/zql/src/query/query-transitions.js.map +1 -1
  788. package/out/zql/src/query/query.d.ts +11 -2
  789. package/out/zql/src/query/query.d.ts.map +1 -1
  790. package/out/zql/src/query/runnable-query-impl.d.ts +1 -0
  791. package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
  792. package/out/zql/src/query/runnable-query-impl.js +2 -10
  793. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  794. package/out/zql/src/query/typed-view.d.ts +1 -1
  795. package/out/zql/src/query/typed-view.d.ts.map +1 -1
  796. package/out/zqlite/src/database-storage.d.ts.map +1 -1
  797. package/out/zqlite/src/database-storage.js +9 -1
  798. package/out/zqlite/src/database-storage.js.map +1 -1
  799. package/out/zqlite/src/db.d.ts +9 -1
  800. package/out/zqlite/src/db.d.ts.map +1 -1
  801. package/out/zqlite/src/db.js +31 -27
  802. package/out/zqlite/src/db.js.map +1 -1
  803. package/out/zqlite/src/pending-delta.d.ts +110 -0
  804. package/out/zqlite/src/pending-delta.d.ts.map +1 -0
  805. package/out/zqlite/src/pending-delta.js +278 -0
  806. package/out/zqlite/src/pending-delta.js.map +1 -0
  807. package/out/zqlite/src/query-builder.d.ts +17 -1
  808. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  809. package/out/zqlite/src/query-builder.js +111 -20
  810. package/out/zqlite/src/query-builder.js.map +1 -1
  811. package/out/zqlite/src/query-delegate.d.ts.map +1 -1
  812. package/out/zqlite/src/query-delegate.js +10 -7
  813. package/out/zqlite/src/query-delegate.js.map +1 -1
  814. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  815. package/out/zqlite/src/sqlite-cost-model.js +52 -1
  816. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  817. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
  818. package/out/zqlite/src/sqlite-stat-fanout.js +5 -31
  819. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  820. package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
  821. package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
  822. package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
  823. package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
  824. package/out/zqlite/src/table-source.d.ts +82 -2
  825. package/out/zqlite/src/table-source.d.ts.map +1 -1
  826. package/out/zqlite/src/table-source.js +261 -38
  827. package/out/zqlite/src/table-source.js.map +1 -1
  828. package/package.json +3 -4
  829. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -25
  830. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
  831. package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
  832. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-change-log-writer.js","names":["#lc","#opts","#now","#disabled","#db","#observer","#reconcile","#resumeFromLog","#writer","#failSoft"],"sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-writer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n isSQLiteCorruption,\n logSQLiteCorruptionDiagnostics,\n} from '../../db/sqlite-corruption.ts';\nimport {StatementRunner} from '../../db/statements.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport {\n readCookieRowCounts,\n readCookies,\n} from '../replicator/change-log-cookies.ts';\nimport {\n ChangeLogRebuildRequired,\n changeLogFileName,\n changeLogWipeReason,\n deleteChangeLogDB,\n openChangeLogDBForWriting,\n readChangeLogHead,\n rebuildChangeLogDBForWriting,\n reconcileChangeLog,\n type ChangeLogAnchor,\n type ChangeLogIdentity,\n type ChangeLogResumePoint,\n type ReconcileResult,\n} from '../replicator/change-log-db.ts';\nimport {ChangeLogStreamWriter} from '../replicator/change-log-stream-writer.ts';\nimport {\n getSQLiteChangeLogInfo,\n logSQLiteChangeLogStartup,\n recordSQLiteChangeLogReconcile,\n SQLiteChangeLogObserver,\n type ChangeLogCommit,\n type SQLiteChangeLogObservabilityState,\n} from '../replicator/sqlite-change-log-observability.ts';\n\nexport type SQLiteChangeLogWriterOptions = {\n /** The replica file the log is named after, i.e. `${replicaFile}-change-log`. */\n replicaFile: string;\n /** The replica whose contents this log belongs to. */\n identity: ChangeLogIdentity;\n /**\n * Called after each transaction's log commit, with the log's new head. This\n * is what releases the catchup barrier: after the writer moved in-process,\n * the head advances here rather than at a subscriber's ACK.\n */\n onCommit?: ((watermark: string) => void) | undefined;\n /**\n * Called when the writer disables itself and deletes the file. The reader is\n * cached on the service, so a delete that leaves it open would leave an\n * in-process reader serving an unlinked inode while every new open sees\n * nothing.\n */\n onDisabled?: (() => void) | undefined;\n /** Called when reconciliation replaces the file, to close cached readers. */\n onRebuilt?: (() => void) | undefined;\n /** Overridable for tests. */\n now?: (() => number) | undefined;\n};\n\n/**\n * Appends the canonical change stream to the SQLite change log, from the\n * change-streamer's own stream loop.\n *\n * ### Ordering\n *\n * The log's commit for a transaction precedes the forward of that transaction's\n * `commit` message, and nothing `await`s in between. Everything that can\n * advance the watermark the stream would resume from — the storer's Postgres\n * commit, `lastWatermark`, the upstream ACK — runs strictly after it, so the\n * log's head is always at or above the resume point. A *hole* is therefore\n * unreachable at any crash timing, and a *phantom* is the normal state, which is\n * what makes truncate-above the whole of reconciliation.\n *\n * ### Fail-soft\n *\n * The log is a catchup cache: nothing in it is not either already durable in the\n * replica's litestream backup or re-derivable from the replication slot. A write\n * failure must therefore not stop replication for the shard. Corruption is\n * rebuilt once at open; any other write error — a full disk, an I/O error, a\n * permissions problem — logs, disables the writer, deletes the file, closes the\n * reader, and lets the stream continue. The cost is catchup reach, and reach is\n * recoverable: `too-old` sends a subscriber to a restore.\n *\n * One carve-out keeps that from hiding the bug class this topology introduces. A\n * missed truncate-above surfaces as `SQLITE_CONSTRAINT` on the writer's plain\n * `INSERT`, which is \"any write error other than corruption\" and would be\n * absorbed quietly. It is counted as an invariant failure and alerted on before\n * failing soft.\n */\nexport class SQLiteChangeLogWriter {\n readonly #lc: LogContext;\n readonly #opts: SQLiteChangeLogWriterOptions;\n readonly #now: () => number;\n\n #db: Database | undefined;\n #writer: ChangeLogStreamWriter | undefined;\n #observer: SQLiteChangeLogObserver | undefined;\n #disabled = false;\n\n constructor(lc: LogContext, opts: SQLiteChangeLogWriterOptions) {\n this.#lc = lc.withContext('component', 'sqlite-change-log-writer');\n this.#opts = opts;\n this.#now = opts.now ?? Date.now;\n }\n\n /** False once the writer has failed soft, or after {@link close}. */\n get enabled(): boolean {\n return !this.#disabled;\n }\n\n /**\n * The writer's own connection, which is the one purge runs on (§3.3): one\n * process appends *and* deletes, on one handle, with no second writable\n * connection and no cross-process barrier. `undefined` until the stream\n * loop's first reconcile creates the log, and again once the writer has\n * failed soft and deleted the file — both of which a purge cycle treats as\n * \"skip\", not as an error.\n */\n get connection(): Database | undefined {\n return this.#db;\n }\n\n /** Exposed for tests and for the integration assertions on the metrics. */\n state(): SQLiteChangeLogObservabilityState | undefined {\n return this.#observer?.state();\n }\n\n /**\n * Opens the log if necessary and reconciles it with the Postgres resume\n * point. Returns the resume point stored in the log.\n *\n * Returns `undefined` when the writer is disabled. The caller must then use\n * another source.\n *\n * This runs for every connection because Postgres supplies a new resume point\n * after each reconnect. The log can contain transactions above that point.\n * Reconciliation removes them before Postgres sends them again.\n *\n * `resumeFrom` includes cookies because the cookies must match the resume\n * watermark.\n */\n reconcile(\n resumeFrom: ChangeLogResumePoint,\n ): ChangeLogResumePoint | undefined {\n return this.#reconcile(() => resumeFrom);\n }\n\n /**\n * Reconciles the log without a Postgres resume point.\n *\n * A valid log uses its own head and cookies. A new or invalid log uses the\n * replica values returned by `seed`.\n */\n reconcileFromLog(\n seed: () => ChangeLogResumePoint,\n ): ChangeLogResumePoint | undefined {\n return this.#reconcile(db => this.#resumeFromLog(db, seed));\n }\n\n #reconcile(\n resolve: (db: Database) => ChangeLogResumePoint,\n ): ChangeLogResumePoint | undefined {\n if (this.#disabled) {\n return undefined;\n }\n const {replicaFile, identity} = this.#opts;\n const nowMs = this.#now();\n const anchor = (db: Database): ChangeLogAnchor => ({\n ...resolve(db),\n identity,\n nowMs,\n });\n try {\n const db = this.#db;\n if (db === undefined) {\n const opened = openChangeLogDBForWriting(this.#lc, replicaFile, anchor);\n this.#db = opened.db;\n if (opened.replacedFile) {\n this.#opts.onRebuilt?.();\n }\n recordSQLiteChangeLogReconcile(this.#lc, opened.result);\n const info = getSQLiteChangeLogInfo(opened.db);\n logSQLiteChangeLogStartup(\n this.#lc,\n changeLogFileName(replicaFile),\n info,\n );\n this.#observer = new SQLiteChangeLogObserver(this.#lc, info);\n } else {\n const resolved = anchor(db);\n let result: ReconcileResult;\n try {\n result = reconcileChangeLog(this.#lc, db, resolved, {\n rebuildInsteadOfUnboundedWork: true,\n });\n } catch (e) {\n if (!(e instanceof ChangeLogRebuildRequired)) {\n throw e;\n }\n // Close every handle to the old inode before unlinking it. The writer\n // is reconstructed below against the replacement connection.\n this.#writer = undefined;\n this.#db = undefined;\n db.close();\n this.#opts.onRebuilt?.();\n const rebuilt = rebuildChangeLogDBForWriting(\n this.#lc,\n replicaFile,\n resolved,\n e,\n );\n this.#db = rebuilt.db;\n result = rebuilt.result;\n }\n recordSQLiteChangeLogReconcile(this.#lc, result);\n if (result.action !== 'none') {\n this.#observer?.reconciled(\n getSQLiteChangeLogInfo(\n must(this.#db, 'the SQLite change log is not open'),\n ),\n );\n }\n }\n // A reseed drops and recreates the stream table, so the append statements\n // are prepared fresh against whatever the reconciliation left behind.\n const reconciledDB = must(this.#db, 'the SQLite change log is not open');\n this.#writer = new ChangeLogStreamWriter(\n new StatementRunner(reconciledDB),\n );\n // Read both values after reconciliation. A truncate or reseed updates the\n // head and cookies in the same transaction.\n return {\n resumeWatermark: must(\n readChangeLogHead(reconciledDB),\n 'the SQLite change log has no head after reconciliation',\n ),\n cookies: readCookies(reconciledDB),\n };\n } catch (e) {\n this.#failSoft('reconciling the SQLite change log', e);\n return undefined;\n }\n }\n\n /**\n * Returns the resume point supplied by the log.\n *\n * A valid log uses its own head and cookies. A log that must be recreated uses\n * the replica values returned by `seed`.\n */\n #resumeFromLog(\n db: Database,\n seed: () => ChangeLogResumePoint,\n ): ChangeLogResumePoint {\n if (changeLogWipeReason(db, this.#opts.identity) !== undefined) {\n return seed();\n }\n const head = readChangeLogHead(db);\n // An empty log has no resume point, so use the replica seed.\n return head === null\n ? seed()\n : {resumeWatermark: head, cookies: readCookies(db)};\n }\n\n /**\n * Writes one stream message, committing at transaction boundaries. Never\n * throws: a write failure disables the writer instead of failing the stream.\n */\n write(change: ChangeStreamData, json: string): void {\n const writer = this.#writer;\n const db = this.#db;\n if (this.#disabled || writer === undefined || db === undefined) {\n return;\n }\n const observer = this.#observer;\n observer?.messageReceived(change, json);\n const [type, msg] = change;\n const startedAt = performance.now();\n try {\n let commit: ChangeLogCommit | null = null;\n switch (type) {\n case 'begin':\n writer.begin(change[2].commitWatermark, json);\n break;\n case 'commit': {\n const {watermark} = change[2];\n const commitStart = performance.now();\n const stats = writer.commit(watermark, json, this.#now());\n commit = {\n watermark,\n stats,\n logCommitMs: performance.now() - commitStart,\n };\n break;\n }\n case 'rollback':\n writer.rollback();\n break;\n default:\n writer.append(json, msg);\n break;\n }\n observer?.messageProcessed(change, commit, performance.now() - startedAt);\n if (commit !== null && commit.stats.cookieMutations.length > 0) {\n // Only re-counted when a schema change actually moved the cookie jar,\n // which keeps the count off the forward path of every other\n // transaction. The counts are read after the commit, so they are what\n // a reader of the file would see.\n observer?.cookiesMutated(\n commit.stats.cookieMutations,\n readCookieRowCounts(db),\n );\n }\n if (commit !== null) {\n // The head advanced, and it is durable. Releasing the barrier here is\n // what makes its poll interval a backstop rather than the mechanism.\n this.#opts.onCommit?.(commit.watermark);\n }\n } catch (e) {\n observer?.messageFailed(change, e, performance.now() - startedAt);\n if (isConstraintViolation(e)) {\n // Reconciliation should have truncated whatever this collided with.\n // Report it as an invariant failure before failing soft, so that a\n // truncate-above bug cannot look like a lost cache.\n observer?.invariantFailure(\n 'SQLite change-log insert violated a constraint, which means a ' +\n 'transaction above the resume watermark was not truncated',\n {tag: msg.tag, error: String(e)},\n );\n }\n this.#failSoft('writing to the SQLite change log', e);\n }\n }\n\n /**\n * Discards the open transaction, if any. Called when the change stream is\n * interrupted mid-transaction.\n */\n abort(): void {\n const writer = this.#writer;\n if (this.#disabled || writer === undefined) {\n return;\n }\n this.#observer?.abort();\n try {\n writer.rollback();\n } catch (e) {\n this.#failSoft('rolling back the SQLite change log', e);\n }\n }\n\n close(): void {\n this.#disabled = true;\n this.#writer = undefined;\n this.#observer = undefined;\n const db = this.#db;\n this.#db = undefined;\n try {\n db?.close();\n } catch (e) {\n this.#lc.warn?.('error closing the SQLite change log', e);\n }\n }\n\n /**\n * Logs, disables the writer, deletes the file, and closes the reader, leaving\n * replication running. Disabling is for the life of the process: a log that\n * failed a write is not re-opened until the task restarts, so the behavior a\n * subscriber sees is the same as `sqliteChangeLogMode=off`.\n */\n #failSoft(what: string, e: unknown): void {\n const file = changeLogFileName(this.#opts.replicaFile);\n if (isSQLiteCorruption(e)) {\n // Open-time corruption is rebuilt by openChangeLogDBForWriting. Reaching\n // here means the file went bad under an open handle, which the same\n // delete-and-reseed answers -- on the next process start rather than now.\n logSQLiteCorruptionDiagnostics(this.#lc, 'change-log', file, e);\n }\n this.#lc.error?.(\n `error ${what}: disabling the writer and deleting ${file}. ` +\n `Replication continues; catchup reach is lost until this task restarts.`,\n e,\n );\n this.close();\n try {\n deleteChangeLogDB(this.#opts.replicaFile);\n } catch (deleteError) {\n this.#lc.error?.(`unable to delete ${file}`, deleteError);\n }\n this.#opts.onDisabled?.();\n }\n}\n\nfunction isConstraintViolation(e: unknown): boolean {\n return e instanceof SqliteError && e.code.startsWith('SQLITE_CONSTRAINT');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA,IAAa,wBAAb,MAAmC;CACjC;CACA;CACA;CAEA;CACA;CACA;CACA,YAAY;CAEZ,YAAY,IAAgB,MAAoC;EAC9D,KAAKA,MAAM,GAAG,YAAY,aAAa,0BAA0B;EACjE,KAAKC,QAAQ;EACb,KAAKC,OAAO,KAAK,OAAO,KAAK;CAC/B;;CAGA,IAAI,UAAmB;EACrB,OAAO,CAAC,KAAKC;CACf;;;;;;;;;CAUA,IAAI,aAAmC;EACrC,OAAO,KAAKC;CACd;;CAGA,QAAuD;EACrD,OAAO,KAAKC,WAAW,MAAM;CAC/B;;;;;;;;;;;;;;;CAgBA,UACE,YACkC;EAClC,OAAO,KAAKC,iBAAiB,UAAU;CACzC;;;;;;;CAQA,iBACE,MACkC;EAClC,OAAO,KAAKA,YAAW,OAAM,KAAKC,eAAe,IAAI,IAAI,CAAC;CAC5D;CAEA,WACE,SACkC;EAClC,IAAI,KAAKJ,WACP;EAEF,MAAM,EAAC,aAAa,aAAY,KAAKF;EACrC,MAAM,QAAQ,KAAKC,KAAK;EACxB,MAAM,UAAU,QAAmC;GACjD,GAAG,QAAQ,EAAE;GACb;GACA;EACF;EACA,IAAI;GACF,MAAM,KAAK,KAAKE;GAChB,IAAI,OAAO,KAAA,GAAW;IACpB,MAAM,SAAS,0BAA0B,KAAKJ,KAAK,aAAa,MAAM;IACtE,KAAKI,MAAM,OAAO;IAClB,IAAI,OAAO,cACT,KAAKH,MAAM,YAAY;IAEzB,+BAA+B,KAAKD,KAAK,OAAO,MAAM;IACtD,MAAM,OAAO,uBAAuB,OAAO,EAAE;IAC7C,0BACE,KAAKA,KACL,kBAAkB,WAAW,GAC7B,IACF;IACA,KAAKK,YAAY,IAAI,wBAAwB,KAAKL,KAAK,IAAI;GAC7D,OAAO;IACL,MAAM,WAAW,OAAO,EAAE;IAC1B,IAAI;IACJ,IAAI;KACF,SAAS,mBAAmB,KAAKA,KAAK,IAAI,UAAU,EAClD,+BAA+B,KACjC,CAAC;IACH,SAAS,GAAG;KACV,IAAI,EAAE,aAAa,2BACjB,MAAM;KAIR,KAAKQ,UAAU,KAAA;KACf,KAAKJ,MAAM,KAAA;KACX,GAAG,MAAM;KACT,KAAKH,MAAM,YAAY;KACvB,MAAM,UAAU,6BACd,KAAKD,KACL,aACA,UACA,CACF;KACA,KAAKI,MAAM,QAAQ;KACnB,SAAS,QAAQ;IACnB;IACA,+BAA+B,KAAKJ,KAAK,MAAM;IAC/C,IAAI,OAAO,WAAW,QACpB,KAAKK,WAAW,WACd,uBACE,KAAK,KAAKD,KAAK,mCAAmC,CACpD,CACF;GAEJ;GAGA,MAAM,eAAe,KAAK,KAAKA,KAAK,mCAAmC;GACvE,KAAKI,UAAU,IAAI,sBACjB,IAAI,gBAAgB,YAAY,CAClC;GAGA,OAAO;IACL,iBAAiB,KACf,kBAAkB,YAAY,GAC9B,wDACF;IACA,SAAS,YAAY,YAAY;GACnC;EACF,SAAS,GAAG;GACV,KAAKC,UAAU,qCAAqC,CAAC;GACrD;EACF;CACF;;;;;;;CAQA,eACE,IACA,MACsB;EACtB,IAAI,oBAAoB,IAAI,KAAKR,MAAM,QAAQ,MAAM,KAAA,GACnD,OAAO,KAAK;EAEd,MAAM,OAAO,kBAAkB,EAAE;EAEjC,OAAO,SAAS,OACZ,KAAK,IACL;GAAC,iBAAiB;GAAM,SAAS,YAAY,EAAE;EAAC;CACtD;;;;;CAMA,MAAM,QAA0B,MAAoB;EAClD,MAAM,SAAS,KAAKO;EACpB,MAAM,KAAK,KAAKJ;EAChB,IAAI,KAAKD,aAAa,WAAW,KAAA,KAAa,OAAO,KAAA,GACnD;EAEF,MAAM,WAAW,KAAKE;EACtB,UAAU,gBAAgB,QAAQ,IAAI;EACtC,MAAM,CAAC,MAAM,OAAO;EACpB,MAAM,YAAY,YAAY,IAAI;EAClC,IAAI;GACF,IAAI,SAAiC;GACrC,QAAQ,MAAR;IACE,KAAK;KACH,OAAO,MAAM,OAAO,GAAG,iBAAiB,IAAI;KAC5C;IACF,KAAK,UAAU;KACb,MAAM,EAAC,cAAa,OAAO;KAC3B,MAAM,cAAc,YAAY,IAAI;KAEpC,SAAS;MACP;MACA,OAHY,OAAO,OAAO,WAAW,MAAM,KAAKH,KAAK,CAGrD;MACA,aAAa,YAAY,IAAI,IAAI;KACnC;KACA;IACF;IACA,KAAK;KACH,OAAO,SAAS;KAChB;IACF;KACE,OAAO,OAAO,MAAM,GAAG;KACvB;GACJ;GACA,UAAU,iBAAiB,QAAQ,QAAQ,YAAY,IAAI,IAAI,SAAS;GACxE,IAAI,WAAW,QAAQ,OAAO,MAAM,gBAAgB,SAAS,GAK3D,UAAU,eACR,OAAO,MAAM,iBACb,oBAAoB,EAAE,CACxB;GAEF,IAAI,WAAW,MAGb,KAAKD,MAAM,WAAW,OAAO,SAAS;EAE1C,SAAS,GAAG;GACV,UAAU,cAAc,QAAQ,GAAG,YAAY,IAAI,IAAI,SAAS;GAChE,IAAI,sBAAsB,CAAC,GAIzB,UAAU,iBACR,0HAEA;IAAC,KAAK,IAAI;IAAK,OAAO,OAAO,CAAC;GAAC,CACjC;GAEF,KAAKQ,UAAU,oCAAoC,CAAC;EACtD;CACF;;;;;CAMA,QAAc;EACZ,MAAM,SAAS,KAAKD;EACpB,IAAI,KAAKL,aAAa,WAAW,KAAA,GAC/B;EAEF,KAAKE,WAAW,MAAM;EACtB,IAAI;GACF,OAAO,SAAS;EAClB,SAAS,GAAG;GACV,KAAKI,UAAU,sCAAsC,CAAC;EACxD;CACF;CAEA,QAAc;EACZ,KAAKN,YAAY;EACjB,KAAKK,UAAU,KAAA;EACf,KAAKH,YAAY,KAAA;EACjB,MAAM,KAAK,KAAKD;EAChB,KAAKA,MAAM,KAAA;EACX,IAAI;GACF,IAAI,MAAM;EACZ,SAAS,GAAG;GACV,KAAKJ,IAAI,OAAO,uCAAuC,CAAC;EAC1D;CACF;;;;;;;CAQA,UAAU,MAAc,GAAkB;EACxC,MAAM,OAAO,kBAAkB,KAAKC,MAAM,WAAW;EACrD,IAAI,mBAAmB,CAAC,GAItB,+BAA+B,KAAKD,KAAK,cAAc,MAAM,CAAC;EAEhE,KAAKA,IAAI,QACP,SAAS,KAAK,sCAAsC,KAAK,2EAEzD,CACF;EACA,KAAK,MAAM;EACX,IAAI;GACF,kBAAkB,KAAKC,MAAM,WAAW;EAC1C,SAAS,aAAa;GACpB,KAAKD,IAAI,QAAQ,oBAAoB,QAAQ,WAAW;EAC1D;EACA,KAAKC,MAAM,aAAa;CAC1B;AACF;AAEA,SAAS,sBAAsB,GAAqB;CAClD,OAAO,aAAa,eAAe,EAAE,KAAK,WAAW,mBAAmB;AAC1E"}
1
+ {"version":3,"file":"sqlite-change-log-writer.js","names":["#lc","#opts","#now","#shadowValidationPercent","#disabled","#db","#observer","#reconcile","#resumeFromLog","#writer","#failSoft","#validateTransaction","#isSelectedForShadowValidation"],"sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-writer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {h32} from '../../../../shared/src/hash.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n isSQLiteCorruption,\n logSQLiteCorruptionDiagnostics,\n} from '../../db/sqlite-corruption.ts';\nimport {StatementRunner} from '../../db/statements.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport {\n readCookieRowCounts,\n readCookies,\n} from '../replicator/change-log-cookies.ts';\nimport {\n ChangeLogRebuildRequired,\n changeLogFileName,\n changeLogWipeReason,\n deleteChangeLogDB,\n openChangeLogDBForWriting,\n readChangeLogHead,\n rebuildChangeLogDBForWriting,\n reconcileChangeLog,\n type ChangeLogAnchor,\n type ChangeLogIdentity,\n type ChangeLogResumePoint,\n type ReconcileResult,\n} from '../replicator/change-log-db.ts';\nimport {ChangeLogStreamWriter} from '../replicator/change-log-stream-writer.ts';\nimport {\n getSQLiteChangeLogInfo,\n logSQLiteChangeLogStartup,\n recordSQLiteChangeLogReconcile,\n SQLiteChangeLogObserver,\n type ChangeLogCommit,\n type SQLiteChangeLogObservabilityState,\n} from '../replicator/sqlite-change-log-observability.ts';\nimport {extractChangeSubstring} from './change-log-codec.ts';\n\nexport type SQLiteChangeLogWriterOptions = {\n /** The replica file the log is named after, i.e. `${replicaFile}-change-log`. */\n replicaFile: string;\n /** The replica whose contents this log belongs to. */\n identity: ChangeLogIdentity;\n /**\n * Called after each transaction's log commit, with the log's new head. This\n * is what releases the catchup barrier: after the writer moved in-process,\n * the head advances here rather than at a subscriber's ACK.\n */\n onCommit?: ((watermark: string) => void) | undefined;\n /**\n * Called when the writer disables itself and deletes the file. The reader is\n * cached on the service, so a delete that leaves it open would leave an\n * in-process reader serving an unlinked inode while every new open sees\n * nothing.\n */\n onDisabled?: (() => void) | undefined;\n /** Called when reconciliation replaces the file, to close cached readers. */\n onRebuilt?: (() => void) | undefined;\n /** Overridable for tests. */\n now?: (() => number) | undefined;\n /**\n * Percentage of transactions that retain the rollout-only per-message\n * timing and source-vs-stored hash validation. Defaults to 1.\n */\n shadowValidationPercent?: number | undefined;\n};\n\nconst DEFAULT_SHADOW_VALIDATION_PERCENT = 1;\n\n/**\n * Appends the canonical change stream to the SQLite change log, from the\n * change-streamer's own stream loop.\n *\n * ### Ordering\n *\n * The log's commit for a transaction precedes the forward of that transaction's\n * `commit` message, and nothing `await`s in between. Everything that can\n * advance the watermark the stream would resume from — the storer's Postgres\n * commit, `lastWatermark`, the upstream ACK — runs strictly after it, so the\n * log's head is always at or above the resume point. A *hole* is therefore\n * unreachable at any crash timing, and a *phantom* is the normal state, which is\n * what makes truncate-above the whole of reconciliation.\n *\n * ### Fail-soft\n *\n * The log is a catchup cache: nothing in it is not either already durable in the\n * replica's litestream backup or re-derivable from the replication slot. A write\n * failure must therefore not stop replication for the shard. Corruption is\n * rebuilt once at open; any other write error — a full disk, an I/O error, a\n * permissions problem — logs, disables the writer, deletes the file, closes the\n * reader, and lets the stream continue. The cost is catchup reach, and reach is\n * recoverable: `too-old` sends a subscriber to a restore.\n *\n * One carve-out keeps that from hiding the bug class this topology introduces. A\n * missed truncate-above surfaces as `SQLITE_CONSTRAINT` on the writer's plain\n * `INSERT`, which is \"any write error other than corruption\" and would be\n * absorbed quietly. It is counted as an invariant failure and alerted on before\n * failing soft.\n */\nexport class SQLiteChangeLogWriter {\n readonly #lc: LogContext;\n readonly #opts: SQLiteChangeLogWriterOptions;\n readonly #now: () => number;\n readonly #shadowValidationPercent: number;\n\n #db: Database | undefined;\n #writer: ChangeLogStreamWriter | undefined;\n #observer: SQLiteChangeLogObserver | undefined;\n #disabled = false;\n #validateTransaction = false;\n\n constructor(lc: LogContext, opts: SQLiteChangeLogWriterOptions) {\n this.#lc = lc.withContext('component', 'sqlite-change-log-writer');\n this.#opts = opts;\n this.#now = opts.now ?? Date.now;\n this.#shadowValidationPercent =\n opts.shadowValidationPercent ?? DEFAULT_SHADOW_VALIDATION_PERCENT;\n assert(\n Number.isInteger(this.#shadowValidationPercent) &&\n this.#shadowValidationPercent >= 0 &&\n this.#shadowValidationPercent <= 100,\n 'SQLite change-log shadow validation percentage must be an integer between 0 and 100',\n );\n }\n\n /** False once the writer has failed soft, or after {@link close}. */\n get enabled(): boolean {\n return !this.#disabled;\n }\n\n /**\n * The writer's own connection, which is the one purge runs on (§3.3): one\n * process appends *and* deletes, on one handle, with no second writable\n * connection and no cross-process barrier. `undefined` until the stream\n * loop's first reconcile creates the log, and again once the writer has\n * failed soft and deleted the file — both of which a purge cycle treats as\n * \"skip\", not as an error.\n */\n get connection(): Database | undefined {\n return this.#db;\n }\n\n /** Exposed for tests and for the integration assertions on the metrics. */\n state(): SQLiteChangeLogObservabilityState | undefined {\n return this.#observer?.state();\n }\n\n /**\n * Opens the log if necessary and reconciles it with the Postgres resume\n * point. Returns the resume point stored in the log.\n *\n * Returns `undefined` when the writer is disabled. The caller must then use\n * another source.\n *\n * This runs for every connection because Postgres supplies a new resume point\n * after each reconnect. The log can contain transactions above that point.\n * Reconciliation removes them before Postgres sends them again.\n *\n * `resumeFrom` includes cookies because the cookies must match the resume\n * watermark.\n */\n reconcile(\n resumeFrom: ChangeLogResumePoint,\n ): ChangeLogResumePoint | undefined {\n return this.#reconcile(() => resumeFrom);\n }\n\n /**\n * Reconciles the log without a Postgres resume point.\n *\n * A valid log uses its own head and cookies. A new or invalid log uses the\n * replica values returned by `seed`.\n */\n reconcileFromLog(\n seed: () => ChangeLogResumePoint,\n ): ChangeLogResumePoint | undefined {\n return this.#reconcile(db => this.#resumeFromLog(db, seed));\n }\n\n #reconcile(\n resolve: (db: Database) => ChangeLogResumePoint,\n ): ChangeLogResumePoint | undefined {\n if (this.#disabled) {\n return undefined;\n }\n const {replicaFile, identity} = this.#opts;\n const nowMs = this.#now();\n const anchor = (db: Database): ChangeLogAnchor => ({\n ...resolve(db),\n identity,\n nowMs,\n });\n try {\n const db = this.#db;\n if (db === undefined) {\n const opened = openChangeLogDBForWriting(this.#lc, replicaFile, anchor);\n this.#db = opened.db;\n if (opened.replacedFile) {\n this.#opts.onRebuilt?.();\n }\n recordSQLiteChangeLogReconcile(this.#lc, opened.result);\n const info = getSQLiteChangeLogInfo(opened.db);\n logSQLiteChangeLogStartup(\n this.#lc,\n changeLogFileName(replicaFile),\n info,\n );\n this.#observer = new SQLiteChangeLogObserver(this.#lc, info);\n } else {\n const resolved = anchor(db);\n let result: ReconcileResult;\n try {\n result = reconcileChangeLog(this.#lc, db, resolved, {\n rebuildInsteadOfUnboundedWork: true,\n });\n } catch (e) {\n if (!(e instanceof ChangeLogRebuildRequired)) {\n throw e;\n }\n // Close every handle to the old inode before unlinking it. The writer\n // is reconstructed below against the replacement connection.\n this.#writer = undefined;\n this.#db = undefined;\n db.close();\n this.#opts.onRebuilt?.();\n const rebuilt = rebuildChangeLogDBForWriting(\n this.#lc,\n replicaFile,\n resolved,\n e,\n );\n this.#db = rebuilt.db;\n result = rebuilt.result;\n }\n recordSQLiteChangeLogReconcile(this.#lc, result);\n if (result.action !== 'none') {\n this.#observer?.reconciled(\n getSQLiteChangeLogInfo(\n must(this.#db, 'the SQLite change log is not open'),\n ),\n );\n }\n }\n // A reseed drops and recreates the stream table, so the append statements\n // are prepared fresh against whatever the reconciliation left behind.\n const reconciledDB = must(this.#db, 'the SQLite change log is not open');\n this.#writer = new ChangeLogStreamWriter(\n new StatementRunner(reconciledDB),\n );\n // Read both values after reconciliation. A truncate or reseed updates the\n // head and cookies in the same transaction.\n return {\n resumeWatermark: must(\n readChangeLogHead(reconciledDB),\n 'the SQLite change log has no head after reconciliation',\n ),\n cookies: readCookies(reconciledDB),\n };\n } catch (e) {\n this.#failSoft('reconciling the SQLite change log', e);\n return undefined;\n }\n }\n\n /**\n * Returns the resume point supplied by the log.\n *\n * A valid log uses its own head and cookies. A log that must be recreated uses\n * the replica values returned by `seed`.\n */\n #resumeFromLog(\n db: Database,\n seed: () => ChangeLogResumePoint,\n ): ChangeLogResumePoint {\n if (changeLogWipeReason(db, this.#opts.identity) !== undefined) {\n return seed();\n }\n const head = readChangeLogHead(db);\n // An empty log has no resume point, so use the replica seed.\n return head === null\n ? seed()\n : {resumeWatermark: head, cookies: readCookies(db)};\n }\n\n /**\n * Writes one stream message, committing at transaction boundaries. Never\n * throws: a write failure disables the writer instead of failing the stream.\n */\n write(\n change: ChangeStreamData,\n json: string,\n storedChange = extractChangeSubstring(json, change[1].tag),\n ): void {\n const writer = this.#writer;\n const db = this.#db;\n if (this.#disabled || writer === undefined || db === undefined) {\n return;\n }\n const [type, msg] = change;\n if (type === 'begin') {\n this.#validateTransaction = this.#isSelectedForShadowValidation(\n change[2].commitWatermark,\n );\n }\n const validateTransaction = this.#validateTransaction;\n const observer = this.#observer;\n observer?.messageReceived(change, json, validateTransaction, storedChange);\n const startedAt = validateTransaction ? performance.now() : undefined;\n try {\n let commit: ChangeLogCommit | null = null;\n switch (type) {\n case 'begin':\n writer.begin(\n change[2].commitWatermark,\n json,\n storedChange,\n validateTransaction,\n );\n break;\n case 'commit': {\n const {watermark} = change[2];\n const commitStart = performance.now();\n const stats = writer.commit(\n watermark,\n json,\n this.#now(),\n storedChange,\n );\n commit = {\n watermark,\n stats,\n logCommitMs: performance.now() - commitStart,\n };\n break;\n }\n case 'rollback':\n writer.rollback();\n break;\n default:\n writer.append(json, msg, storedChange);\n break;\n }\n observer?.messageProcessed(\n change,\n commit,\n startedAt === undefined ? undefined : performance.now() - startedAt,\n );\n if (commit !== null && commit.stats.cookieMutations.length > 0) {\n // Only re-counted when a schema change actually moved the cookie jar,\n // which keeps the count off the forward path of every other\n // transaction. The counts are read after the commit, so they are what\n // a reader of the file would see.\n observer?.cookiesMutated(\n commit.stats.cookieMutations,\n readCookieRowCounts(db),\n );\n }\n if (commit !== null) {\n // The head advanced, and it is durable. Releasing the barrier here is\n // what makes its poll interval a backstop rather than the mechanism.\n this.#opts.onCommit?.(commit.watermark);\n }\n if (type === 'commit' || type === 'rollback') {\n this.#validateTransaction = false;\n }\n } catch (e) {\n observer?.messageFailed(\n change,\n e,\n startedAt === undefined ? undefined : performance.now() - startedAt,\n );\n this.#validateTransaction = false;\n if (isConstraintViolation(e)) {\n // Reconciliation should have truncated whatever this collided with.\n // Report it as an invariant failure before failing soft, so that a\n // truncate-above bug cannot look like a lost cache.\n observer?.invariantFailure(\n 'SQLite change-log insert violated a constraint, which means a ' +\n 'transaction above the resume watermark was not truncated',\n {tag: msg.tag, error: String(e)},\n );\n }\n this.#failSoft('writing to the SQLite change log', e);\n }\n }\n\n #isSelectedForShadowValidation(watermark: string): boolean {\n const percent = this.#shadowValidationPercent;\n return (\n percent >= 100 ||\n (percent > 0 &&\n h32(\n `${this.#opts.identity.generation}/${this.#opts.identity.replicaID ?? ''}:${watermark}`,\n ) %\n 100 <\n percent)\n );\n }\n\n /**\n * Discards the open transaction, if any. Called when the change stream is\n * interrupted mid-transaction.\n */\n abort(): void {\n const writer = this.#writer;\n this.#validateTransaction = false;\n if (this.#disabled || writer === undefined) {\n return;\n }\n this.#observer?.abort();\n try {\n writer.rollback();\n } catch (e) {\n this.#failSoft('rolling back the SQLite change log', e);\n }\n }\n\n close(): void {\n this.#disabled = true;\n this.#validateTransaction = false;\n this.#writer = undefined;\n this.#observer = undefined;\n const db = this.#db;\n this.#db = undefined;\n try {\n db?.close();\n } catch (e) {\n this.#lc.warn?.('error closing the SQLite change log', e);\n }\n }\n\n /**\n * Logs, disables the writer, deletes the file, and closes the reader, leaving\n * replication running. Disabling is for the life of the process: a log that\n * failed a write is not re-opened until the task restarts, so the behavior a\n * subscriber sees is the same as `sqliteChangeLogMode=off`.\n */\n #failSoft(what: string, e: unknown): void {\n const file = changeLogFileName(this.#opts.replicaFile);\n if (isSQLiteCorruption(e)) {\n // Open-time corruption is rebuilt by openChangeLogDBForWriting. Reaching\n // here means the file went bad under an open handle, which the same\n // delete-and-reseed answers -- on the next process start rather than now.\n logSQLiteCorruptionDiagnostics(this.#lc, 'change-log', file, e);\n }\n this.#lc.error?.(\n `error ${what}: disabling the writer and deleting ${file}. ` +\n `Replication continues; catchup reach is lost until this task restarts.`,\n e,\n );\n this.close();\n try {\n deleteChangeLogDB(this.#opts.replicaFile);\n } catch (deleteError) {\n this.#lc.error?.(`unable to delete ${file}`, deleteError);\n }\n this.#opts.onDisabled?.();\n }\n}\n\nfunction isConstraintViolation(e: unknown): boolean {\n return e instanceof SqliteError && e.code.startsWith('SQLITE_CONSTRAINT');\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import type { UpstreamStatusMessage } from '../change-source/protocol/current/st
9
9
  import { type CookieSet } from '../replicator/change-log-cookies.ts';
10
10
  import type { ReplicatorMode } from '../replicator/replicator.ts';
11
11
  import type { Service } from '../service.ts';
12
- import { type ChangeLogEntry } from './change-log-codec.ts';
12
+ import { type ChangeLogEntry, type SerializedChangeStreamData } from './change-log-codec.ts';
13
13
  import type { Subscriber } from './subscriber.ts';
14
14
  /**
15
15
  * Factory for creating dynamic connection pools used by the Storer to
@@ -106,7 +106,7 @@ export declare class Storer implements Service {
106
106
  /**
107
107
  * @returns The JSON stringified stream message to be sent downstream.
108
108
  */
109
- store(watermark: string, data: ChangeStreamData): string;
109
+ store(watermark: string, data: ChangeStreamData, serialized?: SerializedChangeStreamData): string;
110
110
  abort(): void;
111
111
  catchup(subscriber: Subscriber, mode: ReplicatorMode): void;
112
112
  readyForMore(): Promise<void> | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"storer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/storer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/storer.ts"],"names":[],"mappings":"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"}
@@ -16,7 +16,7 @@ import { Queue } from "../../../../shared/src/queue.js";
16
16
  import { AutoResetSignal, markResetRequired } from "./schema/tables.js";
17
17
  import { TransactionPool, sharedSnapshot } from "../../db/transaction-pool.js";
18
18
  import { orTimeout } from "../../types/timeout.js";
19
- import { extractChangeSubstring, reconstructWatermarkedChange, serializeChangeStreamData } from "./change-log-codec.js";
19
+ import { reconstructWatermarkedChange, serializeChangeStreamDataWithChange } from "./change-log-codec.js";
20
20
  import { resolver } from "@rocicorp/resolver";
21
21
  import "postgres";
22
22
  import { getHeapStatistics } from "node:v8";
@@ -164,7 +164,7 @@ var Storer = class {
164
164
  'name', b."table",
165
165
  'metadata', t."metadata"
166
166
  ) as "table",
167
- json_object_agg(b."column", b."backfill")
167
+ json_object_agg(b."column", json_build_object('id', b."backfill"))
168
168
  as "columns"
169
169
  FROM ${this.#cdc("backfilling")} as b
170
170
  LEFT JOIN ${this.#cdc("tableMetadata")} as t
@@ -263,14 +263,15 @@ var Storer = class {
263
263
  /**
264
264
  * @returns The JSON stringified stream message to be sent downstream.
265
265
  */
266
- store(watermark, data) {
267
- const json = serializeChangeStreamData(data);
266
+ store(watermark, data, serialized = serializeChangeStreamDataWithChange(data)) {
267
+ const { json, change: storedChange } = serialized;
268
268
  this.#approximateQueuedBytes += json.length;
269
269
  const change = data[1];
270
270
  this.#queue.enqueue([
271
271
  "change",
272
272
  watermark,
273
273
  json,
274
+ storedChange,
274
275
  isDataChange(change) ? null : change
275
276
  ]);
276
277
  return json;
@@ -411,7 +412,7 @@ var Storer = class {
411
412
  }
412
413
  continue;
413
414
  }
414
- const [_, watermark, json, change] = msg;
415
+ const [_, watermark, json, storedChange, change] = msg;
415
416
  const tag = change?.tag;
416
417
  this.#approximateQueuedBytes -= json.length;
417
418
  if (tag === "begin") {
@@ -441,7 +442,7 @@ var Storer = class {
441
442
  watermark: tag === "commit" ? watermark : tx.preCommitWatermark,
442
443
  precommit: tag === "commit" ? tx.preCommitWatermark : null,
443
444
  pos: tx.pos,
444
- change: extractChangeSubstring(json, tag)
445
+ change: storedChange
445
446
  };
446
447
  if (change !== null && isSchemaChange(change)) {
447
448
  await this.#flushChangeLog(tx);
@@ -510,6 +511,8 @@ var Storer = class {
510
511
  lc.info?.(`snapshotted db at ${lastWatermark} (${await snapshotID}) to catchup ${numCatchups} subscriber(s)`);
511
512
  } catch (e) {
512
513
  subs.map(({ subscriber }) => subscriber.fail(e));
514
+ reader.abort();
515
+ catchupConns.end().catch(() => {});
513
516
  throw e;
514
517
  } finally {
515
518
  catchupSnapshotted();
@@ -1 +1 @@
1
- {"version":3,"file":"storer.js","names":["#lc","#shard","#taskID","#discoveryAddress","#discoveryProtocol","#makeConnectionPool","#db","#inserter","#purger","#replicaVersion","#onCommitted","#onFatal","#queue","#backPressureThresholdBytes","#statementTimeoutMs","#changeLogBatchSize","#drainTimeoutMs","#progressMonitor","#withTimeout","#cdc","#approximateQueuedBytes","#running","#readyForMore","#stopped","#processQueue","#cancelQueueEntries","#startCatchup","#flushChangeLog","#trackBackfillMetadata","#maybeReleaseBackPressure","#catchup","#cookieStmts","#upsertTableMetadataStmt","#upsertColumnBackfillStmt","#tx","#released","#failureThresholdMs","#onFailure","#taskUpdates","#timer"],"sources":["../../../../../../zero-cache/src/services/change-streamer/storer.ts"],"sourcesContent":["import {getHeapStatistics} from 'node:v8';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver, type Resolver} from '@rocicorp/resolver';\nimport {type PendingQuery, type Row} from 'postgres';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {\n BigIntJSON,\n type JSONObject,\n} from '../../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../../shared/src/queue.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {sharedSnapshot, TransactionPool} from '../../db/transaction-pool.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../../types/pg.ts';\nimport {cdcSchema, type ShardID} from '../../types/shards.ts';\nimport {orTimeout} from '../../types/timeout.ts';\nimport {\n backfillRequestSchema,\n isDataChange,\n isSchemaChange,\n type BackfillID,\n type BackfillRequest,\n type Change,\n type DataChange,\n type Identifier,\n type SchemaChange,\n type TableMetadata,\n} from '../change-source/protocol/current.ts';\nimport {\n type ChangeStreamData,\n type Commit,\n} from '../change-source/protocol/current/downstream.ts';\nimport type {UpstreamStatusMessage} from '../change-source/protocol/current/status.ts';\nimport {\n cookieOps,\n type BackfillCookie,\n type CookieOp,\n type CookieSet,\n type TableMetadataCookie,\n} from '../replicator/change-log-cookies.ts';\nimport type {ReplicatorMode} from '../replicator/replicator.ts';\nimport type {Service} from '../service.ts';\nimport {\n extractChangeSubstring,\n reconstructWatermarkedChange,\n serializeChangeStreamData,\n type ChangeLogEntry,\n} from './change-log-codec.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport {\n AutoResetSignal,\n markResetRequired,\n type BackfillingColumn,\n type TableMetadataRow,\n} from './schema/tables.ts';\nimport type {Subscriber} from './subscriber.ts';\n\n/**\n * Factory for creating dynamic connection pools used by the Storer to\n * isolate concurrent transactions into separate pg clients.\n */\nexport type PostgresDBProvider = (\n applicationName: string,\n maxConns: number,\n) => PostgresDB;\n\ntype SubscriberAndMode = {\n subscriber: Subscriber;\n mode: ReplicatorMode;\n};\n\ntype QueueEntry =\n | [\n 'change',\n watermark: string,\n json: string,\n orig: Exclude<Change, DataChange> | null, // null for DataChanges\n ]\n | ['ready', callback: () => void]\n | ['subscriber', SubscriberAndMode]\n | ['abort']\n | 'stop';\n\ntype PendingTransaction = {\n pool: TransactionPool;\n preCommitWatermark: string;\n pos: number;\n startingReplicationState: Promise<ReplicationOwner>;\n ack: boolean;\n // changeLog rows buffered for the next multi-row INSERT flush.\n batch: ChangeLogRow[];\n // The most recently issued flush (or metadata) process, awaited to bound\n // pipeline depth and to order the commit-time replicationState update.\n lastFlush: Promise<unknown> | undefined;\n};\n\ntype ReplicationOwner = {\n owner: string | null;\n};\n\nconst backfillRequestsSchema = v.array(backfillRequestSchema);\n\nexport type TuningOptions = {\n backPressureLimitHeapProportion: number;\n statementTimeoutMs: number;\n changeLogBatchSize: number;\n drainTimeoutMs?: number | undefined;\n};\n\n/**\n * A single `changeLog` row, accumulated in {@link PendingTransaction.batch}\n * and written via `json_to_recordset()` (see `#flushChangeLog()`).\n */\ntype ChangeLogRow = {\n watermark: string;\n precommit: string | null;\n pos: number;\n change: string;\n};\n\n/**\n * Handles the storage of changes and the catchup of subscribers\n * that are behind.\n *\n * In the context of catchup and cleanup, it is the responsibility of the\n * Storer to decide whether a client can be caught up, or whether the\n * changes needed to catch a client up have been purged.\n *\n * **Maintained invariant**: The Change DB is only empty for a\n * completely new replica (i.e. initial-sync with no changes from the\n * replication stream).\n * * In this case, all new subscribers are expected start from the\n * `replicaVersion`, which is the version at which initial sync\n * was performed, and any attempts to catchup from a different\n * point fail.\n *\n * Conversely, if non-initial changes have flowed through the system\n * (i.e. via the replication stream), the ChangeDB must *not* be empty,\n * and the earliest change in the `changeLog` represents the earliest\n * \"commit\" from (after) which a subscriber can be caught up.\n * * Any attempts to catchup from an earlier point must fail with\n * a `WatermarkTooOld` error.\n * * Failure to do so could result in streaming changes to the\n * subscriber such that there is a gap in its replication history.\n *\n * Note: Subscribers (i.e. `incremental-syncer`) consider an \"error\" signal\n * an unrecoverable error and shut down in response. This allows the\n * production system to replace it with a new task and fresh copy of the\n * replica backup.\n */\nexport class Storer implements Service {\n readonly id = 'storer';\n readonly #lc: LogContext;\n readonly #shard: ShardID;\n readonly #taskID: string;\n readonly #discoveryAddress: string;\n readonly #discoveryProtocol: string;\n readonly #makeConnectionPool: PostgresDBProvider;\n\n // Isolated connection pools for steady-state, potentially concurrent\n // queries, in order to avoid a postgres.js connection-swapping bug:\n // https://github.com/porsager/postgres/issues/1204\n readonly #db: PostgresDB;\n readonly #inserter: PostgresDB;\n readonly #purger: PostgresDB;\n\n readonly #replicaVersion: string;\n readonly #onCommitted: (c: Commit) => void;\n readonly #onFatal: (err: Error) => void;\n readonly #queue = new Queue<QueueEntry>();\n readonly #backPressureThresholdBytes: number;\n readonly #statementTimeoutMs: number;\n readonly #changeLogBatchSize: number;\n readonly #drainTimeoutMs: number;\n readonly #progressMonitor: ProgressMonitor;\n\n #approximateQueuedBytes = 0;\n #running = false;\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n taskID: string,\n discoveryAddress: string,\n discoveryProtocol: string,\n dbProvider: PostgresDBProvider,\n replicaVersion: string,\n onCommitted: (c: Commit | UpstreamStatusMessage) => void,\n onFatal: (err: Error) => void,\n {\n backPressureLimitHeapProportion,\n statementTimeoutMs,\n changeLogBatchSize,\n drainTimeoutMs = 30_000,\n }: TuningOptions,\n ) {\n this.#lc = lc.withContext('component', 'change-log');\n this.#shard = shard;\n this.#taskID = taskID;\n this.#discoveryAddress = discoveryAddress;\n this.#discoveryProtocol = discoveryProtocol;\n this.#makeConnectionPool = dbProvider;\n this.#db = dbProvider('change-stream-init', 3);\n this.#inserter = dbProvider('change-log-inserter', 1);\n this.#purger = dbProvider('change-log-purger', 1);\n this.#replicaVersion = replicaVersion;\n this.#onCommitted = onCommitted;\n this.#onFatal = onFatal;\n this.#statementTimeoutMs = statementTimeoutMs;\n this.#changeLogBatchSize = Math.max(1, changeLogBatchSize);\n this.#drainTimeoutMs = drainTimeoutMs;\n this.#progressMonitor = new ProgressMonitor(\n lc,\n statementTimeoutMs,\n onFatal,\n );\n\n const heapStats = getHeapStatistics();\n this.#backPressureThresholdBytes =\n (heapStats.heap_size_limit - heapStats.used_heap_size) *\n backPressureLimitHeapProportion;\n\n this.#lc.info?.(\n `Using up to ${(this.#backPressureThresholdBytes / 1024 ** 2).toFixed(2)} MB of ` +\n `--max-old-space-size (~${(heapStats.heap_size_limit / 1024 ** 2).toFixed(2)} MB) ` +\n `to absorb upstream spikes`,\n {heapStats},\n );\n }\n\n // For readability in SQL statements.\n #cdc(table: string) {\n return this.#db(`${cdcSchema(this.#shard)}.${table}`);\n }\n\n /**\n * Bounds a one-off db call (i.e. not part of the main storer loop or the\n * background catchup read, which are tracked by the ProgressMonitor\n * instead) with a plain timeout. This covers calls like\n * {@link assumeOwnership} and {@link getStartStreamInitializationParameters}\n * that are made by the caller *before* {@link run()} -- and thus before the\n * ProgressMonitor's polling starts -- as well as ones made well after,\n * where a continuously-polling watchdog would be overkill for a single\n * bounded db round trip.\n *\n * Does not cancel `promise` on timeout (there is no cancellation mechanism\n * for a postgres.js query); it just stops waiting for it, and swallows a\n * later rejection so it doesn't surface as an unhandled rejection.\n */\n async #withTimeout<T>(name: string, promise: Promise<T>): Promise<T> {\n const result = await orTimeout(promise, this.#statementTimeoutMs);\n if (result === 'timed-out') {\n void promise.catch(() => {});\n throw new AbortError(\n `${name} did not complete within ${this.#statementTimeoutMs}ms`,\n );\n }\n return result;\n }\n\n async assumeOwnership(purgeLock?: PurgeLock | null) {\n const db = this.#db;\n const owner = this.#taskID;\n const ownerAddress = this.#discoveryAddress;\n const ownerProtocol = this.#discoveryProtocol;\n // we omit `ws://` so that old view syncer versions that are not expecting the protocol continue to not get it\n const addressWithProtocol =\n ownerProtocol === 'ws'\n ? ownerAddress\n : `${ownerProtocol}://${ownerAddress}`;\n this.#lc.info?.(`assuming ownership at ${addressWithProtocol}`);\n const start = performance.now();\n await this.#withTimeout(\n 'assume-ownership',\n db`UPDATE ${this.#cdc('replicationState')} SET ${db({owner, ownerAddress: addressWithProtocol})}`,\n );\n const elapsed = (performance.now() - start).toFixed(2);\n this.#lc.info?.(\n `assumed ownership at ${addressWithProtocol} (${elapsed} ms)`,\n );\n\n if (purgeLock) {\n // Once ownership has been assumed, any initial purge-lock preventing the\n // purging of change-log records can be released, as a change-streamer\n // that was attempting to purge records will correspondingly abort on the\n // ownership check.\n void purgeLock.release();\n }\n }\n\n /**\n * The whole of what a stream connection starts from: where to resume, what to\n * ask the change source to backfill, and the raw cookie set those requests\n * were derived from.\n *\n * All four statements run in one REPEATABLE READ transaction, so they see one\n * snapshot. That is invariant 15 — the resume watermark and the cookies must\n * come from the same store, read at the same position — and it is why the\n * cookies are read here rather than by a second call: a cookie set paired\n * with a watermark it was not folded to loses every backfill that completed\n * in the interval, and those rows are not replayable from the slot.\n *\n * `cookies` is not `backfillRequests` in another shape. The request list is\n * driven off `backfilling`, so it drops the metadata of every table with no\n * in-flight backfill — correct for starting a stream, lossy as the cookie\n * snapshot that the SQLite change log continues folding onto.\n */\n async getStartStreamInitializationParameters(): Promise<{\n lastWatermark: string;\n backfillRequests: BackfillRequest[];\n cookies: CookieSet;\n }> {\n const [[{lastWatermark}], result, tableMetadata, backfilling] =\n await this.#withTimeout(\n 'get-stream-params',\n runTx(\n this.#db,\n sql => [\n sql<{lastWatermark: string}[]>`\n SELECT \"lastWatermark\" FROM ${this.#cdc('replicationState')}`,\n\n // Formats a BackfillRequest using json_object_agg() to construct the\n // `columns` object. It is LEFT JOIN'ed with the `tableMetadata` table\n // to make it optional and possibly `null`.\n sql`\n SELECT\n json_build_object(\n 'schema', b.\"schema\",\n 'name', b.\"table\",\n 'metadata', t.\"metadata\"\n ) as \"table\",\n json_object_agg(b.\"column\", b.\"backfill\")\n as \"columns\"\n FROM ${this.#cdc('backfilling')} as b\n LEFT JOIN ${this.#cdc('tableMetadata')} as t\n ON (b.\"schema\" = t.\"schema\" AND b.\"table\" = t.\"table\")\n GROUP BY b.\"schema\", b.\"table\", t.\"metadata\"\n `,\n\n // Ordered by primary key, so that this set and the SQLite change log's\n // can be compared row for row. `COLLATE \"C\"` because that comparison is\n // against a store whose TEXT columns sort by byte: a linguistic\n // collation orders `foo-bar` and `foobar` differently than SQLite's\n // BINARY does, which would read as a divergence rather than as the\n // identical set it is.\n sql<TableMetadataCookie[]>`\n SELECT \"schema\", \"table\", \"metadata\" FROM ${this.#cdc('tableMetadata')}\n ORDER BY \"schema\" COLLATE \"C\", \"table\" COLLATE \"C\"`,\n\n sql<BackfillCookie[]>`\n SELECT \"schema\", \"table\", \"column\", \"backfill\" FROM ${this.#cdc('backfilling')}\n ORDER BY \"schema\" COLLATE \"C\", \"table\" COLLATE \"C\", \"column\" COLLATE \"C\"`,\n ],\n {mode: Mode.READONLY},\n ),\n );\n\n return {\n lastWatermark,\n backfillRequests: v.parse(result, backfillRequestsSchema),\n cookies: {\n tableMetadata: [...tableMetadata],\n backfilling: [...backfilling],\n },\n };\n }\n\n async getMinWatermarkForCatchup(): Promise<string | null> {\n const [{minWatermark}] = await this.#withTimeout(\n 'get-min-watermark',\n this.#db<{minWatermark: string | null}[]> /*sql*/ `\n SELECT min(watermark) as \"minWatermark\" FROM ${this.#cdc('changeLog')}`,\n );\n return minWatermark;\n }\n\n /**\n * Returns the retained Postgres catchup bounds.\n * `minWatermark` is the oldest transaction, or `null` for a new replica.\n * `lastWatermark` is the durable head.\n */\n async getCatchupBounds(): Promise<{\n minWatermark: string | null;\n lastWatermark: string;\n }> {\n const [bounds] = await this.#withTimeout(\n 'get-catchup-bounds',\n this.#db<{minWatermark: string | null; lastWatermark: string}[]> /*sql*/ `\n SELECT\n (SELECT min(watermark) FROM ${this.#cdc('changeLog')}) as \"minWatermark\",\n (SELECT \"lastWatermark\" FROM ${this.#cdc('replicationState')}) as \"lastWatermark\"`,\n );\n return bounds;\n }\n\n /**\n * Lists at most `limit` committed transaction watermarks in\n * `(afterWatermark, throughWatermark]`, in ascending order.\n */\n async listCommitWatermarks(\n afterWatermark: string,\n throughWatermark: string,\n limit: number,\n ): Promise<string[]> {\n const rows = await this.#db<{watermark: string}[]> /*sql*/ `\n SELECT watermark FROM ${this.#cdc('changeLog')}\n WHERE precommit IS NOT NULL\n AND watermark > ${afterWatermark}\n AND watermark <= ${throughWatermark}\n ORDER BY watermark\n LIMIT ${limit}`;\n return rows.map(({watermark}) => watermark);\n }\n\n /**\n * Reads `(afterWatermark, throughWatermark]` in stream order and bounded batches.\n * This query matches the subscriber catchup query.\n *\n * Both queries fail when an escaped NUL reaches `change->'tag'`.\n * Postgres cannot convert that value to text.\n */\n readCatchupRange(\n afterWatermark: string,\n throughWatermark: string,\n batchRows = 2000,\n ): AsyncIterable<ChangeLogEntry[]> {\n assert(\n Number.isSafeInteger(batchRows) && batchRows > 0,\n 'Postgres change log batch size must be a positive safe integer',\n );\n return this.#db<ChangeLogEntry[]> /*sql*/ `\n SELECT watermark, change->'tag' as tag, change::text FROM ${this.#cdc('changeLog')}\n WHERE watermark > ${afterWatermark}\n AND watermark <= ${throughWatermark}\n ORDER BY watermark, pos`.cursor(batchRows) as AsyncIterable<\n ChangeLogEntry[]\n >;\n }\n\n // Note: These are the only db `await`s that are not tracked by the\n // ProgressMonitor, as the delete can legitimately block and/or take\n // an arbitrary amount of time. Luckily, this is a background call and\n // thus cannot cause the storer loop to hang.\n purgeRecordsBefore(watermark: string): Promise<number> {\n return runTx(this.#purger, async sql => {\n // This NOWAIT pre-check is an optimization to abort the transaction\n // (and release associated resources) early.\n await sql<{watermark: string}[]>`\n SELECT watermark FROM ${this.#cdc('changeLog')}\n ORDER BY watermark, pos LIMIT 1\n FOR UPDATE NOWAIT\n `;\n // If the row is purge-locked by an incoming replication-manager, it\n // will assume ownership of the change-log before releasing the lock.\n // This DELETE blocks until the lock is released, allowing the change\n // in ownership to be reliably detected (and the transaction aborted)\n // in the subsequent check.\n const [{deleted}] = await sql<{deleted: bigint}[]>`\n -- The backup watermark can be ahead of the durable changeLog if the\n -- storer is behind but the backup replica has consumed forwarded\n -- changes. Preserve the latest durable changeLog transaction as the\n -- catchup boundary instead of assuming the backup watermark exists.\n -- The storer inserts each changeLog transaction atomically, so any\n -- durable row for a watermark implies the full transaction is durable.\n WITH keep AS (\n SELECT max(watermark) AS watermark\n FROM ${this.#cdc('changeLog')}\n ), purged AS (\n DELETE FROM ${this.#cdc('changeLog')} WHERE watermark < ${watermark} \n AND watermark < (SELECT watermark FROM keep)\n RETURNING watermark, pos\n ) SELECT COUNT(*) as deleted FROM purged;`;\n\n const [{owner}] = await sql<ReplicationOwner[]>`\n SELECT \"owner\" FROM ${this.#cdc('replicationState')} FOR SHARE`;\n if (owner !== this.#taskID) {\n throw new AbortError(\n `aborting changeLog purge to ${watermark} because ownership has been taken by ${owner}`,\n );\n }\n return Number(deleted);\n });\n }\n\n /**\n * @returns The JSON stringified stream message to be sent downstream.\n */\n store(watermark: string, data: ChangeStreamData) {\n // Eagerly stringify the JSON payload to:\n // - avoid redundant stringification when fanning out to subscribers\n // - efficiently estimate the amount of memory the payload consumes\n const json = serializeChangeStreamData(data);\n this.#approximateQueuedBytes += json.length;\n\n const change = data[1];\n this.#queue.enqueue([\n 'change',\n watermark,\n json,\n isDataChange(change) ? null : change, // drop DataChanges to save memory\n ]);\n\n return json;\n }\n\n abort() {\n this.#queue.enqueue(['abort']);\n }\n\n catchup(subscriber: Subscriber, mode: ReplicatorMode) {\n this.#queue.enqueue(['subscriber', {subscriber, mode}]);\n }\n\n #readyForMore: Resolver<void> | null = null;\n\n readyForMore(): Promise<void> | undefined {\n if (!this.#running) {\n return undefined;\n }\n if (\n this.#readyForMore === null &&\n this.#approximateQueuedBytes > this.#backPressureThresholdBytes\n ) {\n this.#lc.warn?.(\n `applying back pressure with ${this.#queue.size()} queued changes (~${(this.#approximateQueuedBytes / 1024 ** 2).toFixed(2)} MB)\\n` +\n `\\n` +\n `To inspect changeLog backlog in your change DB:\\n` +\n ` SELECT\\n` +\n ` (change->'relation'->>'schema') || '.' || (change->'relation'->>'name') AS table_name,\\n` +\n ` change->>'tag' AS operation,\\n` +\n ` COUNT(*) AS count\\n` +\n ` FROM \"<app_id>/cdc\".\"changeLog\"\\n` +\n ` GROUP BY 1, 2\\n` +\n ` ORDER BY 3 DESC\\n` +\n ` LIMIT 20;`,\n );\n this.#readyForMore = resolver();\n }\n return this.#readyForMore?.promise;\n }\n\n #maybeReleaseBackPressure() {\n if (this.#readyForMore !== null) {\n // Wait for at least 20% of the threshold to free up.\n if (\n this.#approximateQueuedBytes <\n this.#backPressureThresholdBytes * 0.8\n ) {\n this.#lc.info?.(\n `releasing back pressure with ${this.#queue.size()} queued changes (~${(this.#approximateQueuedBytes / 1024 ** 2).toFixed(2)} MB)`,\n );\n this.#readyForMore.resolve();\n this.#readyForMore = null;\n }\n }\n }\n\n /**\n * Flushes any buffered {@link PendingTransaction.batch} rows to the changeLog.\n *\n * Uses `json_to_recordset()` so the batch is a single JSON parameter: the\n * statement text stays constant regardless of batch size, avoiding the\n * unbounded prepared-statement variants (and Postgres memory growth) that a\n * multi-row INSERT would produce. See rocicorp/mono#3511.\n *\n * Returns (and updates) {@link PendingTransaction.lastFlush}; a no-op when the\n * batch is empty.\n */\n #flushChangeLog(tx: PendingTransaction): Promise<unknown> | undefined {\n const {batch} = tx;\n if (batch.length === 0) {\n return tx.lastFlush;\n }\n tx.batch = [];\n tx.lastFlush = tx.pool.process(sql => [\n sql`\n INSERT INTO ${this.#cdc('changeLog')} (\"watermark\", \"pos\", \"change\", \"precommit\")\n SELECT \"watermark\", \"pos\", \"change\"::json, \"precommit\"\n FROM json_to_recordset(${batch}) AS x(\n \"watermark\" TEXT,\n \"pos\" INT8,\n \"change\" TEXT,\n \"precommit\" TEXT\n )`,\n ]);\n return tx.lastFlush;\n }\n\n #stopped = promiseVoid;\n\n /**\n * Runs the storer loop until {@link stop()} is called, or an error is thrown.\n * Once {@link run()} completes, it can be called again.\n */\n async run() {\n assert(!this.#running, `storer is already running`);\n\n const {promise: stopped, resolve: signalStopped} = resolver();\n this.#running = true;\n this.#stopped = stopped;\n\n this.#lc.info?.('starting storer');\n this.#progressMonitor.start(); // Note: This is stopped in stop()\n let err: unknown;\n try {\n await this.#processQueue();\n } catch (e) {\n err = e; // used in finally\n throw e;\n } finally {\n // Release any pending backpressure so the upstream can proceed\n if (this.#readyForMore !== null) {\n this.#readyForMore.resolve();\n this.#readyForMore = null;\n }\n this.#cancelQueueEntries(\n this.#queue.drain().filter(entry => entry !== undefined),\n err,\n );\n this.#running = false;\n signalStopped();\n this.#lc.info?.('storer stopped');\n }\n }\n\n #cancelQueueEntries(queue: QueueEntry[], e: unknown) {\n if (queue.length === 0) {\n return;\n }\n this.#lc.info?.(\n `canceling ${queue.length} entries from the changeLog queue`,\n );\n const err = e instanceof Error ? e : new AbortError('server shutting down');\n for (const entry of queue) {\n if (entry === 'stop') {\n continue;\n }\n const type = entry[0];\n switch (type) {\n case 'subscriber': {\n // Disconnect subscribers waiting to be caught up so that they can\n // reconnect and try again.\n const {subscriber} = entry[1];\n this.#lc.info?.(`disconnecting ${subscriber.id}`);\n subscriber.fail(err);\n break;\n }\n }\n }\n }\n\n async #processQueue() {\n let tx: PendingTransaction | null = null;\n let msg: QueueEntry | false;\n\n // Track the progress of each (previous and next) queue entry before it is\n // processed in the loop.\n let lastTaskDone: TaskDoneFn | undefined;\n const nextMessage = async () => {\n lastTaskDone?.();\n lastTaskDone = undefined;\n const entry = await this.#queue.dequeue();\n if (entry !== 'stop') {\n lastTaskDone = this.#progressMonitor.trackTask({\n task: 'queue-entry',\n // Note: TaskKeys are logged, so only include entry[0] (the type) to\n // avoid logging application data.\n type: entry[0],\n });\n }\n return entry;\n };\n\n const catchupQueue: SubscriberAndMode[] = [];\n try {\n while ((msg = await nextMessage()) !== 'stop') {\n const [msgType] = msg;\n switch (msgType) {\n case 'ready': {\n const signalReady = msg[1];\n signalReady();\n continue;\n }\n case 'subscriber': {\n const subscriber = msg[1];\n if (tx) {\n catchupQueue.push(subscriber); // Wait for the current tx to complete.\n } else {\n await this.#startCatchup([subscriber]); // Catch up immediately.\n }\n continue;\n }\n case 'abort': {\n if (tx) {\n tx.pool.abort();\n await tx.pool.done();\n tx = null;\n }\n continue;\n }\n }\n // msgType === 'change'\n const [_, watermark, json, change] = msg;\n const tag = change?.tag;\n this.#approximateQueuedBytes -= json.length;\n\n if (tag === 'begin') {\n assert(!tx, 'received BEGIN in the middle of a transaction');\n const {promise, resolve, reject} = resolver<ReplicationOwner>();\n void promise.catch(() => {}); // handle rejections before the await\n tx = {\n pool: new TransactionPool(\n this.#lc.withContext('watermark', watermark),\n {mode: Mode.READ_COMMITTED},\n ),\n preCommitWatermark: watermark,\n pos: 0,\n startingReplicationState: promise,\n ack: !change.skipAck,\n batch: [],\n lastFlush: undefined,\n };\n tx.pool.run(this.#inserter);\n // Acquire a lock on the replicationState row to detect and/or prevent\n // a concurrent ownership change.\n void tx.pool.process(tx => {\n tx<ReplicationOwner[]> /*sql*/ `\n SELECT \"owner\" FROM ${this.#cdc('replicationState')} FOR UPDATE`.then(\n ([result]) => resolve(result),\n reject,\n );\n return [];\n });\n } else {\n assert(tx, () => `received change outside of transaction: ${json}`);\n tx.pos++;\n }\n\n const entry: ChangeLogRow = {\n watermark: tag === 'commit' ? watermark : tx.preCommitWatermark,\n precommit: tag === 'commit' ? tx.preCommitWatermark : null,\n pos: tx.pos,\n // For backwards compatibility, only the change message is stored\n // in the cdc changeLog.\n change: extractChangeSubstring(json, tag),\n };\n\n if (change !== null && isSchemaChange(change)) {\n // Schema changes carry backfill / table-metadata statements that\n // must be applied in stream order relative to the changeLog rows.\n // Flush any buffered rows first, then write this row together with\n // its metadata statements as a single unit (preserving the previous\n // per-change ordering for schema changes).\n await this.#flushChangeLog(tx);\n tx.lastFlush = tx.pool.process(sql => [\n sql`INSERT INTO ${this.#cdc('changeLog')} ${sql(entry)}`,\n ...this.#trackBackfillMetadata(sql, change),\n ]);\n } else {\n // Accumulate plain changeLog rows (begin, data changes, commit) and\n // write them as a single multi-row INSERT. Collapsing the per-change\n // single-row INSERTs into batches is the dominant cost reduction for\n // large transactions, where the previous one-statement-per-change\n // path dominated the upstream replication lag.\n tx.batch.push(entry);\n if (tx.batch.length >= this.#changeLogBatchSize) {\n // Bound pipeline depth (and thus memory) by awaiting the previous\n // flush before issuing the next. This is the batched analog of the\n // previous per-100-statement backpressure await, and likewise\n // guards against memory blowup on very large transactions.\n const prevFlush = tx.lastFlush;\n void this.#flushChangeLog(tx);\n await prevFlush;\n }\n }\n this.#maybeReleaseBackPressure();\n\n if (tag === 'commit') {\n // Flush any remaining buffered changeLog rows (including this commit\n // row) before updating the replication state, so the state update is\n // ordered after all changeLog inserts for this transaction.\n void this.#flushChangeLog(tx);\n\n const {owner} = await tx.startingReplicationState;\n if (owner !== this.#taskID) {\n // Ownership change reflected in the replicationState read in 'begin'.\n tx.pool.fail(\n new AbortError(\n `changeLog ownership has been assumed by ${owner}`,\n ),\n );\n } else {\n // Update the replication state.\n const lastWatermark = watermark;\n void tx.pool.process(tx => [\n tx`\n UPDATE ${this.#cdc('replicationState')} SET ${tx({lastWatermark})}`,\n ]);\n tx.pool.setDone();\n }\n\n await tx.pool.done();\n\n // ACK the LSN to the upstream Postgres.\n if (tx.ack) {\n this.#onCommitted(['commit', change, {watermark}]);\n }\n tx = null;\n\n // Before beginning the next transaction, open a READONLY snapshot to\n // concurrently catchup any queued subscribers.\n await this.#startCatchup(catchupQueue.splice(0));\n } else if (tag === 'rollback') {\n // Aborted transactions are not stored in the changeLog. Abort the current tx\n // and process catchup of subscribers that were waiting for it to end.\n tx.pool.abort();\n await tx.pool.done();\n tx = null;\n\n await this.#startCatchup(catchupQueue.splice(0));\n }\n }\n } catch (e) {\n catchupQueue.forEach(({subscriber}) => subscriber.fail(e));\n throw e;\n }\n }\n\n async #startCatchup(subs: SubscriberAndMode[]) {\n const numCatchups = subs.length;\n if (numCatchups === 0) {\n return;\n }\n\n const lc = this.#lc.withContext('pool', 'catchup');\n const {init, cleanup, snapshotID} = sharedSnapshot();\n const reader = new TransactionPool(lc, {\n mode: Mode.READONLY,\n init,\n cleanup,\n initialWorkers: subs.length,\n });\n\n // A dynamic connection pool is created for catchup, sized to the number\n // of subscribers being caught up.\n const catchupConns = this.#makeConnectionPool(\n 'subscriber-catchup',\n numCatchups,\n );\n reader.run(catchupConns);\n\n let lastWatermark: string | undefined;\n const catchupSnapshotted = this.#progressMonitor.trackTask({\n task: 'capture-catchup-snapshot',\n subscribers: subs.map(({subscriber: s}) => s.id),\n });\n try {\n // Ensure that the transaction has started (and is thus holding a snapshot\n // of the database) before continuing on to commit more changes. This is\n // done by performing a single read on the db, which determines the\n // snapshot for the REPEATABLE_READ transaction.\n [{lastWatermark}] = await reader.processReadTask(\n sql => sql<{lastWatermark: string}[]>`\n SELECT \"lastWatermark\" FROM ${this.#cdc('replicationState')}\n `,\n );\n lc.info?.(\n `snapshotted db at ${lastWatermark} (${await snapshotID}) ` +\n `to catchup ${numCatchups} subscriber(s)`,\n );\n } catch (e) {\n subs.map(({subscriber}) => subscriber.fail(e));\n throw e;\n } finally {\n catchupSnapshotted();\n }\n\n // Run the actual catchup queries in the background. Errors are handled in\n // #catchup() by disconnecting the associated subscriber.\n void Promise.allSettled(\n subs.map(sub =>\n this.#catchup(\n lc.withContext('subscriber', sub.subscriber.id),\n sub,\n lastWatermark,\n reader,\n ),\n ),\n ).finally(() => {\n reader.setDone();\n void catchupConns.end().catch(() => {});\n });\n }\n\n async #catchup(\n lc: LogContext,\n {subscriber: sub, mode}: SubscriberAndMode,\n lastWatermark: string,\n reader: TransactionPool,\n ) {\n let entriesReceived = 0;\n let catchupProgressed = this.#progressMonitor.trackTask({\n task: 'catchup',\n subscriber: sub.id,\n entriesReceived,\n });\n try {\n lc.info?.(`starting catchup`);\n await reader.processReadTask(async tx => {\n lc.info?.(`catching up`);\n const start = Date.now();\n\n // When starting from initial-sync, there won't be a change with a watermark\n // equal to the replica version. This is the empty changeLog scenario.\n let watermarkFound = sub.watermark === this.#replicaVersion;\n let count = 0;\n let lastBatchConsumed: Promise<unknown> | undefined;\n\n for await (const entries of tx<ChangeLogEntry[]> /*sql*/ `\n SELECT watermark, change->'tag' as tag, change::text FROM ${this.#cdc('changeLog')}\n WHERE watermark >= ${sub.watermark}\n AND watermark <= ${lastWatermark}\n ORDER BY watermark, pos`.cursor(2000)) {\n catchupProgressed();\n entriesReceived += entries.length;\n\n try {\n // Wait for the last batch of entries to be consumed by the\n // subscriber before sending down the current batch. This pipelining\n // allows one batch of changes to be received from the change-db\n // while the previous batch of changes are sent to the subscriber,\n // resulting in flow control that caps the number of changes\n // referenced in memory to 2 * batch-size.\n const start = performance.now();\n await lastBatchConsumed;\n const elapsed = performance.now() - start;\n if (lastBatchConsumed) {\n lc[elapsed > 100 ? 'info' : 'debug']?.(\n `waited ${elapsed.toFixed(3)} ms for ${sub.id} to consume last batch of catchup entries`,\n );\n }\n\n for (const entry of entries) {\n if (entry.watermark === sub.watermark) {\n // This should be the first entry.\n // Catchup starts from *after* the watermark.\n watermarkFound = true;\n } else if (watermarkFound) {\n lastBatchConsumed = sub.catchup(\n reconstructWatermarkedChange(entry),\n );\n count++;\n } else if (mode === 'backup') {\n throw new AutoResetSignal(\n `backup replica at watermark ${sub.watermark} is behind change db: ${entry.watermark})`,\n );\n } else {\n lc.warn?.(\n `rejecting subscriber at watermark ${sub.watermark} (earliest watermark: ${entry.watermark})`,\n );\n sub.close(\n ErrorType.WatermarkTooOld,\n `earliest supported watermark is ${entry.watermark} (requested ${sub.watermark})`,\n );\n return;\n }\n }\n } finally {\n // Track the db's read of the next chunk of changes.\n catchupProgressed = this.#progressMonitor.trackTask({\n task: 'catchup',\n subscriber: sub.id,\n entriesReceived,\n });\n }\n }\n catchupProgressed();\n\n if (watermarkFound) {\n await lastBatchConsumed;\n lc.info?.(\n `caught up ${sub.id} with ${count} changes (${\n Date.now() - start\n } ms)`,\n );\n } else {\n // The subscriber is ahead of the latest durable changeLog entry\n // (lastWatermark). This can legitimately happen: changes are\n // forwarded to subscribers (the backup replica and view-syncers)\n // concurrently with — and can outrun — the durable store, so a\n // replica may briefly lead the change DB after the storer falls\n // behind or the change-streamer restarts. No catchup is possible or\n // needed; once the change DB catches back up, forwarding resumes and\n // the subscriber dedups any watermarks it already has. Unlike the\n // AutoResetSignal / WatermarkTooOld cases above, this is not a gap in\n // replication history, so the subscriber is simply marked caught up.\n lc.warn?.(\n `subscriber ${sub.id} at watermark ${sub.watermark} is ahead of ` +\n `the latest durable watermark ${lastWatermark}; waiting for the ` +\n `change DB to catch up`,\n );\n }\n // Start draining messages buffered during catchup. The returned promise\n // is intentionally not awaited here: while the drain is in progress,\n // new sends keep appending to the subscriber backlog and inherit its\n // byte-based backpressure.\n void sub.setCaughtUp();\n });\n } catch (err) {\n lc.error?.(`error while catching up subscriber ${sub.id}`, err);\n if (err instanceof AutoResetSignal) {\n await markResetRequired(this.#db, this.#shard);\n this.#onFatal(err);\n }\n sub.fail(err);\n } finally {\n catchupProgressed();\n }\n }\n\n /**\n * Returns the db statements necessary to track backfill and table metadata\n * presented in the `change`, if any.\n *\n * This is the Postgres interpreter of {@link cookieOps}, which is also\n * interpreted against the SQLite change log by `ChangeLogCookieWriter`. The\n * two stores have to agree on every transition forever, and the failure mode\n * if they drift is a backfill that is silently never re-requested — so the\n * decision of what a schema change *means* lives in one place and only its\n * transport lives here.\n */\n #trackBackfillMetadata(sql: PostgresTransaction, change: SchemaChange) {\n return cookieOps(change).flatMap(op => this.#cookieStmts(sql, op));\n }\n\n #cookieStmts(sql: PostgresTransaction, op: CookieOp): PendingQuery<Row[]>[] {\n switch (op.op) {\n case 'upsert-metadata':\n return [this.#upsertTableMetadataStmt(sql, op.table, op.metadata)];\n\n case 'upsert-backfill':\n return [\n this.#upsertColumnBackfillStmt(sql, op.table, op.column, op.backfill),\n ];\n\n case 'rename-table': {\n const {old} = op;\n const row = {schema: op.new.schema, table: op.new.name};\n return [\n sql`UPDATE ${this.#cdc('tableMetadata')} SET ${sql(row)}\n WHERE \"schema\" = ${old.schema} AND \"table\" = ${old.name}`,\n sql`UPDATE ${this.#cdc('backfilling')} SET ${sql(row)}\n WHERE \"schema\" = ${old.schema} AND \"table\" = ${old.name}`,\n ];\n }\n\n case 'drop-table': {\n const {schema, name} = op.table;\n return [\n sql`DELETE FROM ${this.#cdc('tableMetadata')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${name}`,\n sql`DELETE FROM ${this.#cdc('backfilling')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${name}`,\n ];\n }\n\n case 'rename-column': {\n const {schema, name: table} = op.table;\n return [\n sql`UPDATE ${this.#cdc('backfilling')} SET \"column\" = ${op.new}\n WHERE \"schema\" = ${schema} AND \"table\" = ${table} AND \"column\" = ${op.old}`,\n ];\n }\n\n case 'drop-column': {\n const {schema, name} = op.table;\n return [\n sql`DELETE FROM ${this.#cdc('backfilling')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${name} AND \"column\" = ${op.column}`,\n ];\n }\n\n case 'complete-backfill': {\n const {schema, name: table} = op.table;\n return [\n sql`DELETE FROM ${this.#cdc('backfilling')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${table} AND \"column\" IN ${sql([...op.columns])}`,\n ];\n }\n\n default:\n unreachable(op);\n }\n }\n\n #upsertTableMetadataStmt(\n sql: PostgresTransaction,\n {schema, name: table}: Identifier,\n metadata: TableMetadata,\n ) {\n const row: TableMetadataRow = {schema, table, metadata};\n return sql`\n INSERT INTO ${this.#cdc('tableMetadata')} ${sql(row)}\n ON CONFLICT (\"schema\", \"table\")\n DO UPDATE SET ${sql(row)};\n `;\n }\n\n #upsertColumnBackfillStmt(\n sql: PostgresTransaction,\n {schema, name: table}: Identifier,\n column: string,\n backfill: BackfillID,\n ) {\n const row: BackfillingColumn = {schema, table, column, backfill};\n return sql`\n INSERT INTO ${this.#cdc('backfilling')} ${sql(row)}\n ON CONFLICT (\"schema\", \"table\", \"column\")\n DO UPDATE SET ${sql(row)};\n `;\n }\n\n /**\n * Waits until all currently queued entries have been processed.\n * This is only used in tests.\n */\n async allProcessed() {\n if (this.#running) {\n const {promise, resolve} = resolver();\n this.#queue.enqueue(['ready', resolve]);\n await promise;\n }\n }\n\n /**\n * Stops the storer and waits up to a drain timeout for entries to drain,\n * throwing an exception if it doesn't drain in time, in order to abort\n * the server when PG (or the connection to it) appears to be wedged.\n */\n async stop() {\n if (this.#running) {\n this.#progressMonitor.stop();\n this.#lc.info?.(`draining ${this.#queue.size()} changeLog entries`);\n this.abort(); // for cleanliness, abort any open transactions\n this.#queue.enqueue('stop');\n }\n if (\n (await orTimeout(this.#stopped, this.#drainTimeoutMs)) === 'timed-out'\n ) {\n throw new AbortError(\n `changeLog did not drain within ${this.#drainTimeoutMs}ms`,\n );\n }\n }\n}\n\nexport class PurgeLock {\n readonly #lc: LogContext;\n readonly #tx: TransactionPool;\n readonly replicaVersion: string;\n readonly minWatermark: string;\n\n constructor(\n lc: LogContext,\n tx: TransactionPool,\n replicaVersion: string,\n watermark: string,\n ) {\n this.#lc = lc;\n this.#tx = tx;\n this.replicaVersion = replicaVersion;\n this.minWatermark = watermark;\n }\n\n #released = false;\n\n async release() {\n if (this.#released) {\n return;\n }\n this.#released = true;\n this.#tx.setDone();\n await this.#tx\n .done()\n .catch(e => this.#lc.warn?.(`error from purge-lock release`, e));\n this.#lc.info?.(`released purge lock on ${this.minWatermark}`);\n }\n}\n\nexport class PurgeLocker {\n readonly #lc: LogContext;\n readonly #shard: ShardID;\n readonly #db: PostgresDB;\n\n constructor(lc: LogContext, shard: ShardID, db: PostgresDB) {\n this.#lc = lc.withContext('component', 'purge-locker');\n this.#shard = shard;\n this.#db = db;\n }\n\n // For readability in SQL statements.\n #cdc(table: string) {\n return this.#db(`${cdcSchema(this.#shard)}.${table}`);\n }\n\n async acquire() {\n const tx = new TransactionPool(this.#lc, {mode: Mode.READ_COMMITTED}).run(\n this.#db,\n );\n const row = await tx.processReadTask(\n sql => sql<{watermark: string}[]>`\n SELECT watermark FROM ${this.#cdc('changeLog')}\n ORDER BY watermark, pos LIMIT 1\n FOR SHARE \n `,\n );\n if (row.length === 0) {\n this.#lc.info?.(`changeLog is empty. No rows to purge-lock.`);\n tx.setDone();\n await tx.done();\n return null;\n }\n const [{watermark}] = row;\n const [{replicaVersion}] = await tx.processReadTask(\n sql => sql<{replicaVersion: string}[]>`\n SELECT \"replicaVersion\" FROM ${this.#cdc('replicationConfig')}\n `,\n );\n this.#lc.info?.(\n `locked watermark ${watermark} from being purged from replica@${replicaVersion}`,\n );\n return new PurgeLock(this.#lc, tx, replicaVersion, watermark);\n }\n}\n\n// A TaskKey, keyed by identity, representing an db task monitored by the\n// ProgressMonitor. The contents are stringified in error messages if the\n// task fails to make progress for more than a failure threshold interval.\ntype TaskKey = JSONObject;\n\ntype TaskDoneFn = () => void;\n\n/**\n * Periodic monitor for detecting the pathological condition in which a db\n * query hangs forever because of a connection problem, such as a half-closed\n * socket.\n *\n * Use this for db calls that recur or accumulate progress within a single\n * logical operation on the main storer loop (e.g. the queue-processing loop,\n * or a multi-batch catchup read), where \"no progress for N ms\" is the\n * meaningful signal. For a one-off db call -- including ones made before\n * {@link Storer.run} starts this monitor's polling -- use a plain timeout\n * (see `Storer.#withTimeout`) instead.\n *\n * The failure handler should stop the server. The ProgressMonitor will\n * assume this and stop itself after calling it.\n *\n * Internal to the Storer, but exported for testing.\n */\nexport class ProgressMonitor {\n readonly #lc: LogContext;\n readonly #failureThresholdMs;\n readonly #onFailure: (err: Error) => void;\n readonly #taskUpdates = new Map<TaskKey, Date>();\n\n #timer: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n failureThresholdMs: number,\n onFailure: (err: Error) => void,\n ) {\n this.#lc = lc.withContext('component', 'storer-progress-monitor');\n this.#failureThresholdMs = failureThresholdMs;\n this.#onFailure = onFailure;\n }\n\n start() {\n clearInterval(this.#timer);\n this.#timer = setInterval(\n () => this.checkTaskProgress(new Date()),\n this.#failureThresholdMs,\n ).unref();\n }\n\n // Called by an internal periodic timer, but exported for testing.\n checkTaskProgress(now: Date) {\n for (const [task, lastUpdate] of this.#taskUpdates.entries()) {\n if (now.getTime() - lastUpdate.getTime() >= this.#failureThresholdMs) {\n this.#lc.error?.(\n `Last task update was more than ${this.#failureThresholdMs}ms ago`,\n {task, lastUpdate},\n );\n this.#onFailure(\n new Error(\n `Task failed to progress for over ${this.#failureThresholdMs}ms: ${BigIntJSON.stringify(task)}`,\n ),\n );\n this.stop();\n return;\n }\n }\n }\n\n /**\n * Starts a task to be tracked.\n *\n * @return a callback to invoke when the task is done.\n */\n trackTask(task: TaskKey, now = new Date()): TaskDoneFn {\n this.#taskUpdates.set(task, now);\n return () => {\n this.#taskUpdates.delete(task);\n };\n }\n\n stop() {\n clearInterval(this.#timer);\n this.#taskUpdates.clear();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"storer.js","names":["#lc","#shard","#taskID","#discoveryAddress","#discoveryProtocol","#makeConnectionPool","#db","#inserter","#purger","#replicaVersion","#onCommitted","#onFatal","#queue","#backPressureThresholdBytes","#statementTimeoutMs","#changeLogBatchSize","#drainTimeoutMs","#progressMonitor","#withTimeout","#cdc","#approximateQueuedBytes","#running","#readyForMore","#stopped","#processQueue","#cancelQueueEntries","#startCatchup","#flushChangeLog","#trackBackfillMetadata","#maybeReleaseBackPressure","#catchup","#cookieStmts","#upsertTableMetadataStmt","#upsertColumnBackfillStmt","#tx","#released","#failureThresholdMs","#onFailure","#taskUpdates","#timer"],"sources":["../../../../../../zero-cache/src/services/change-streamer/storer.ts"],"sourcesContent":["import {getHeapStatistics} from 'node:v8';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver, type Resolver} from '@rocicorp/resolver';\nimport {type PendingQuery, type Row} from 'postgres';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {\n BigIntJSON,\n type JSONObject,\n} from '../../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../../shared/src/queue.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {sharedSnapshot, TransactionPool} from '../../db/transaction-pool.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../../types/pg.ts';\nimport {cdcSchema, type ShardID} from '../../types/shards.ts';\nimport {orTimeout} from '../../types/timeout.ts';\nimport {\n backfillRequestSchema,\n isDataChange,\n isSchemaChange,\n type BackfillID,\n type BackfillRequest,\n type Change,\n type DataChange,\n type Identifier,\n type SchemaChange,\n type TableMetadata,\n} from '../change-source/protocol/current.ts';\nimport {\n type ChangeStreamData,\n type Commit,\n} from '../change-source/protocol/current/downstream.ts';\nimport type {UpstreamStatusMessage} from '../change-source/protocol/current/status.ts';\nimport {\n cookieOps,\n type BackfillCookie,\n type CookieOp,\n type CookieSet,\n type TableMetadataCookie,\n} from '../replicator/change-log-cookies.ts';\nimport type {ReplicatorMode} from '../replicator/replicator.ts';\nimport type {Service} from '../service.ts';\nimport {\n reconstructWatermarkedChange,\n serializeChangeStreamDataWithChange,\n type ChangeLogEntry,\n type SerializedChangeStreamData,\n} from './change-log-codec.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport {\n AutoResetSignal,\n markResetRequired,\n type BackfillingColumn,\n type TableMetadataRow,\n} from './schema/tables.ts';\nimport type {Subscriber} from './subscriber.ts';\n\n/**\n * Factory for creating dynamic connection pools used by the Storer to\n * isolate concurrent transactions into separate pg clients.\n */\nexport type PostgresDBProvider = (\n applicationName: string,\n maxConns: number,\n) => PostgresDB;\n\ntype SubscriberAndMode = {\n subscriber: Subscriber;\n mode: ReplicatorMode;\n};\n\ntype QueueEntry =\n | [\n 'change',\n watermark: string,\n json: string,\n storedChange: string,\n orig: Exclude<Change, DataChange> | null, // null for DataChanges\n ]\n | ['ready', callback: () => void]\n | ['subscriber', SubscriberAndMode]\n | ['abort']\n | 'stop';\n\ntype PendingTransaction = {\n pool: TransactionPool;\n preCommitWatermark: string;\n pos: number;\n startingReplicationState: Promise<ReplicationOwner>;\n ack: boolean;\n // changeLog rows buffered for the next multi-row INSERT flush.\n batch: ChangeLogRow[];\n // The most recently issued flush (or metadata) process, awaited to bound\n // pipeline depth and to order the commit-time replicationState update.\n lastFlush: Promise<unknown> | undefined;\n};\n\ntype ReplicationOwner = {\n owner: string | null;\n};\n\nconst backfillRequestsSchema = v.array(backfillRequestSchema);\n\nexport type TuningOptions = {\n backPressureLimitHeapProportion: number;\n statementTimeoutMs: number;\n changeLogBatchSize: number;\n drainTimeoutMs?: number | undefined;\n};\n\n/**\n * A single `changeLog` row, accumulated in {@link PendingTransaction.batch}\n * and written via `json_to_recordset()` (see `#flushChangeLog()`).\n */\ntype ChangeLogRow = {\n watermark: string;\n precommit: string | null;\n pos: number;\n change: string;\n};\n\n/**\n * Handles the storage of changes and the catchup of subscribers\n * that are behind.\n *\n * In the context of catchup and cleanup, it is the responsibility of the\n * Storer to decide whether a client can be caught up, or whether the\n * changes needed to catch a client up have been purged.\n *\n * **Maintained invariant**: The Change DB is only empty for a\n * completely new replica (i.e. initial-sync with no changes from the\n * replication stream).\n * * In this case, all new subscribers are expected start from the\n * `replicaVersion`, which is the version at which initial sync\n * was performed, and any attempts to catchup from a different\n * point fail.\n *\n * Conversely, if non-initial changes have flowed through the system\n * (i.e. via the replication stream), the ChangeDB must *not* be empty,\n * and the earliest change in the `changeLog` represents the earliest\n * \"commit\" from (after) which a subscriber can be caught up.\n * * Any attempts to catchup from an earlier point must fail with\n * a `WatermarkTooOld` error.\n * * Failure to do so could result in streaming changes to the\n * subscriber such that there is a gap in its replication history.\n *\n * Note: Subscribers (i.e. `incremental-syncer`) consider an \"error\" signal\n * an unrecoverable error and shut down in response. This allows the\n * production system to replace it with a new task and fresh copy of the\n * replica backup.\n */\nexport class Storer implements Service {\n readonly id = 'storer';\n readonly #lc: LogContext;\n readonly #shard: ShardID;\n readonly #taskID: string;\n readonly #discoveryAddress: string;\n readonly #discoveryProtocol: string;\n readonly #makeConnectionPool: PostgresDBProvider;\n\n // Isolated connection pools for steady-state, potentially concurrent\n // queries, in order to avoid a postgres.js connection-swapping bug:\n // https://github.com/porsager/postgres/issues/1204\n readonly #db: PostgresDB;\n readonly #inserter: PostgresDB;\n readonly #purger: PostgresDB;\n\n readonly #replicaVersion: string;\n readonly #onCommitted: (c: Commit) => void;\n readonly #onFatal: (err: Error) => void;\n readonly #queue = new Queue<QueueEntry>();\n readonly #backPressureThresholdBytes: number;\n readonly #statementTimeoutMs: number;\n readonly #changeLogBatchSize: number;\n readonly #drainTimeoutMs: number;\n readonly #progressMonitor: ProgressMonitor;\n\n #approximateQueuedBytes = 0;\n #running = false;\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n taskID: string,\n discoveryAddress: string,\n discoveryProtocol: string,\n dbProvider: PostgresDBProvider,\n replicaVersion: string,\n onCommitted: (c: Commit | UpstreamStatusMessage) => void,\n onFatal: (err: Error) => void,\n {\n backPressureLimitHeapProportion,\n statementTimeoutMs,\n changeLogBatchSize,\n drainTimeoutMs = 30_000,\n }: TuningOptions,\n ) {\n this.#lc = lc.withContext('component', 'change-log');\n this.#shard = shard;\n this.#taskID = taskID;\n this.#discoveryAddress = discoveryAddress;\n this.#discoveryProtocol = discoveryProtocol;\n this.#makeConnectionPool = dbProvider;\n this.#db = dbProvider('change-stream-init', 3);\n this.#inserter = dbProvider('change-log-inserter', 1);\n this.#purger = dbProvider('change-log-purger', 1);\n this.#replicaVersion = replicaVersion;\n this.#onCommitted = onCommitted;\n this.#onFatal = onFatal;\n this.#statementTimeoutMs = statementTimeoutMs;\n this.#changeLogBatchSize = Math.max(1, changeLogBatchSize);\n this.#drainTimeoutMs = drainTimeoutMs;\n this.#progressMonitor = new ProgressMonitor(\n lc,\n statementTimeoutMs,\n onFatal,\n );\n\n const heapStats = getHeapStatistics();\n this.#backPressureThresholdBytes =\n (heapStats.heap_size_limit - heapStats.used_heap_size) *\n backPressureLimitHeapProportion;\n\n this.#lc.info?.(\n `Using up to ${(this.#backPressureThresholdBytes / 1024 ** 2).toFixed(2)} MB of ` +\n `--max-old-space-size (~${(heapStats.heap_size_limit / 1024 ** 2).toFixed(2)} MB) ` +\n `to absorb upstream spikes`,\n {heapStats},\n );\n }\n\n // For readability in SQL statements.\n #cdc(table: string) {\n return this.#db(`${cdcSchema(this.#shard)}.${table}`);\n }\n\n /**\n * Bounds a one-off db call (i.e. not part of the main storer loop or the\n * background catchup read, which are tracked by the ProgressMonitor\n * instead) with a plain timeout. This covers calls like\n * {@link assumeOwnership} and {@link getStartStreamInitializationParameters}\n * that are made by the caller *before* {@link run()} -- and thus before the\n * ProgressMonitor's polling starts -- as well as ones made well after,\n * where a continuously-polling watchdog would be overkill for a single\n * bounded db round trip.\n *\n * Does not cancel `promise` on timeout (there is no cancellation mechanism\n * for a postgres.js query); it just stops waiting for it, and swallows a\n * later rejection so it doesn't surface as an unhandled rejection.\n */\n async #withTimeout<T>(name: string, promise: Promise<T>): Promise<T> {\n const result = await orTimeout(promise, this.#statementTimeoutMs);\n if (result === 'timed-out') {\n void promise.catch(() => {});\n throw new AbortError(\n `${name} did not complete within ${this.#statementTimeoutMs}ms`,\n );\n }\n return result;\n }\n\n async assumeOwnership(purgeLock?: PurgeLock | null) {\n const db = this.#db;\n const owner = this.#taskID;\n const ownerAddress = this.#discoveryAddress;\n const ownerProtocol = this.#discoveryProtocol;\n // we omit `ws://` so that old view syncer versions that are not expecting the protocol continue to not get it\n const addressWithProtocol =\n ownerProtocol === 'ws'\n ? ownerAddress\n : `${ownerProtocol}://${ownerAddress}`;\n this.#lc.info?.(`assuming ownership at ${addressWithProtocol}`);\n const start = performance.now();\n await this.#withTimeout(\n 'assume-ownership',\n db`UPDATE ${this.#cdc('replicationState')} SET ${db({owner, ownerAddress: addressWithProtocol})}`,\n );\n const elapsed = (performance.now() - start).toFixed(2);\n this.#lc.info?.(\n `assumed ownership at ${addressWithProtocol} (${elapsed} ms)`,\n );\n\n if (purgeLock) {\n // Once ownership has been assumed, any initial purge-lock preventing the\n // purging of change-log records can be released, as a change-streamer\n // that was attempting to purge records will correspondingly abort on the\n // ownership check.\n void purgeLock.release();\n }\n }\n\n /**\n * The whole of what a stream connection starts from: where to resume, what to\n * ask the change source to backfill, and the raw cookie set those requests\n * were derived from.\n *\n * All four statements run in one REPEATABLE READ transaction, so they see one\n * snapshot. That is invariant 15 — the resume watermark and the cookies must\n * come from the same store, read at the same position — and it is why the\n * cookies are read here rather than by a second call: a cookie set paired\n * with a watermark it was not folded to loses every backfill that completed\n * in the interval, and those rows are not replayable from the slot.\n *\n * `cookies` is not `backfillRequests` in another shape. The request list is\n * driven off `backfilling`, so it drops the metadata of every table with no\n * in-flight backfill — correct for starting a stream, lossy as the cookie\n * snapshot that the SQLite change log continues folding onto.\n */\n async getStartStreamInitializationParameters(): Promise<{\n lastWatermark: string;\n backfillRequests: BackfillRequest[];\n cookies: CookieSet;\n }> {\n const [[{lastWatermark}], result, tableMetadata, backfilling] =\n await this.#withTimeout(\n 'get-stream-params',\n runTx(\n this.#db,\n sql => [\n sql<{lastWatermark: string}[]>`\n SELECT \"lastWatermark\" FROM ${this.#cdc('replicationState')}`,\n\n // Formats a BackfillRequest using json_object_agg() to construct the\n // `columns` object. It is LEFT JOIN'ed with the `tableMetadata` table\n // to make it optional and possibly `null`. The change log does not\n // track backfill progress, so the columns have no `progress`\n // (i.e. they are backfilled from scratch).\n sql`\n SELECT\n json_build_object(\n 'schema', b.\"schema\",\n 'name', b.\"table\",\n 'metadata', t.\"metadata\"\n ) as \"table\",\n json_object_agg(b.\"column\", json_build_object('id', b.\"backfill\"))\n as \"columns\"\n FROM ${this.#cdc('backfilling')} as b\n LEFT JOIN ${this.#cdc('tableMetadata')} as t\n ON (b.\"schema\" = t.\"schema\" AND b.\"table\" = t.\"table\")\n GROUP BY b.\"schema\", b.\"table\", t.\"metadata\"\n `,\n\n // Ordered by primary key, so that this set and the SQLite change log's\n // can be compared row for row. `COLLATE \"C\"` because that comparison is\n // against a store whose TEXT columns sort by byte: a linguistic\n // collation orders `foo-bar` and `foobar` differently than SQLite's\n // BINARY does, which would read as a divergence rather than as the\n // identical set it is.\n sql<TableMetadataCookie[]>`\n SELECT \"schema\", \"table\", \"metadata\" FROM ${this.#cdc('tableMetadata')}\n ORDER BY \"schema\" COLLATE \"C\", \"table\" COLLATE \"C\"`,\n\n sql<BackfillCookie[]>`\n SELECT \"schema\", \"table\", \"column\", \"backfill\" FROM ${this.#cdc('backfilling')}\n ORDER BY \"schema\" COLLATE \"C\", \"table\" COLLATE \"C\", \"column\" COLLATE \"C\"`,\n ],\n {mode: Mode.READONLY},\n ),\n );\n\n return {\n lastWatermark,\n backfillRequests: v.parse(result, backfillRequestsSchema),\n cookies: {\n tableMetadata: [...tableMetadata],\n backfilling: [...backfilling],\n },\n };\n }\n\n async getMinWatermarkForCatchup(): Promise<string | null> {\n const [{minWatermark}] = await this.#withTimeout(\n 'get-min-watermark',\n this.#db<{minWatermark: string | null}[]> /*sql*/ `\n SELECT min(watermark) as \"minWatermark\" FROM ${this.#cdc('changeLog')}`,\n );\n return minWatermark;\n }\n\n /**\n * Returns the retained Postgres catchup bounds.\n * `minWatermark` is the oldest transaction, or `null` for a new replica.\n * `lastWatermark` is the durable head.\n */\n async getCatchupBounds(): Promise<{\n minWatermark: string | null;\n lastWatermark: string;\n }> {\n const [bounds] = await this.#withTimeout(\n 'get-catchup-bounds',\n this.#db<{minWatermark: string | null; lastWatermark: string}[]> /*sql*/ `\n SELECT\n (SELECT min(watermark) FROM ${this.#cdc('changeLog')}) as \"minWatermark\",\n (SELECT \"lastWatermark\" FROM ${this.#cdc('replicationState')}) as \"lastWatermark\"`,\n );\n return bounds;\n }\n\n /**\n * Lists at most `limit` committed transaction watermarks in\n * `(afterWatermark, throughWatermark]`, in ascending order.\n */\n async listCommitWatermarks(\n afterWatermark: string,\n throughWatermark: string,\n limit: number,\n ): Promise<string[]> {\n const rows = await this.#db<{watermark: string}[]> /*sql*/ `\n SELECT watermark FROM ${this.#cdc('changeLog')}\n WHERE precommit IS NOT NULL\n AND watermark > ${afterWatermark}\n AND watermark <= ${throughWatermark}\n ORDER BY watermark\n LIMIT ${limit}`;\n return rows.map(({watermark}) => watermark);\n }\n\n /**\n * Reads `(afterWatermark, throughWatermark]` in stream order and bounded batches.\n * This query matches the subscriber catchup query.\n *\n * Both queries fail when an escaped NUL reaches `change->'tag'`.\n * Postgres cannot convert that value to text.\n */\n readCatchupRange(\n afterWatermark: string,\n throughWatermark: string,\n batchRows = 2000,\n ): AsyncIterable<ChangeLogEntry[]> {\n assert(\n Number.isSafeInteger(batchRows) && batchRows > 0,\n 'Postgres change log batch size must be a positive safe integer',\n );\n return this.#db<ChangeLogEntry[]> /*sql*/ `\n SELECT watermark, change->'tag' as tag, change::text FROM ${this.#cdc('changeLog')}\n WHERE watermark > ${afterWatermark}\n AND watermark <= ${throughWatermark}\n ORDER BY watermark, pos`.cursor(batchRows) as AsyncIterable<\n ChangeLogEntry[]\n >;\n }\n\n // Note: These are the only db `await`s that are not tracked by the\n // ProgressMonitor, as the delete can legitimately block and/or take\n // an arbitrary amount of time. Luckily, this is a background call and\n // thus cannot cause the storer loop to hang.\n purgeRecordsBefore(watermark: string): Promise<number> {\n return runTx(this.#purger, async sql => {\n // This NOWAIT pre-check is an optimization to abort the transaction\n // (and release associated resources) early.\n await sql<{watermark: string}[]>`\n SELECT watermark FROM ${this.#cdc('changeLog')}\n ORDER BY watermark, pos LIMIT 1\n FOR UPDATE NOWAIT\n `;\n // If the row is purge-locked by an incoming replication-manager, it\n // will assume ownership of the change-log before releasing the lock.\n // This DELETE blocks until the lock is released, allowing the change\n // in ownership to be reliably detected (and the transaction aborted)\n // in the subsequent check.\n const [{deleted}] = await sql<{deleted: bigint}[]>`\n -- The backup watermark can be ahead of the durable changeLog if the\n -- storer is behind but the backup replica has consumed forwarded\n -- changes. Preserve the latest durable changeLog transaction as the\n -- catchup boundary instead of assuming the backup watermark exists.\n -- The storer inserts each changeLog transaction atomically, so any\n -- durable row for a watermark implies the full transaction is durable.\n WITH keep AS (\n SELECT max(watermark) AS watermark\n FROM ${this.#cdc('changeLog')}\n ), purged AS (\n DELETE FROM ${this.#cdc('changeLog')} WHERE watermark < ${watermark} \n AND watermark < (SELECT watermark FROM keep)\n RETURNING watermark, pos\n ) SELECT COUNT(*) as deleted FROM purged;`;\n\n const [{owner}] = await sql<ReplicationOwner[]>`\n SELECT \"owner\" FROM ${this.#cdc('replicationState')} FOR SHARE`;\n if (owner !== this.#taskID) {\n throw new AbortError(\n `aborting changeLog purge to ${watermark} because ownership has been taken by ${owner}`,\n );\n }\n return Number(deleted);\n });\n }\n\n /**\n * @returns The JSON stringified stream message to be sent downstream.\n */\n store(\n watermark: string,\n data: ChangeStreamData,\n serialized: SerializedChangeStreamData = serializeChangeStreamDataWithChange(\n data,\n ),\n ) {\n // Eagerly stringify the JSON payload to:\n // - avoid redundant stringification when fanning out to subscribers\n // - efficiently estimate the amount of memory the payload consumes\n const {json, change: storedChange} = serialized;\n this.#approximateQueuedBytes += json.length;\n\n const change = data[1];\n this.#queue.enqueue([\n 'change',\n watermark,\n json,\n storedChange,\n isDataChange(change) ? null : change, // drop DataChanges to save memory\n ]);\n\n return json;\n }\n\n abort() {\n this.#queue.enqueue(['abort']);\n }\n\n catchup(subscriber: Subscriber, mode: ReplicatorMode) {\n this.#queue.enqueue(['subscriber', {subscriber, mode}]);\n }\n\n #readyForMore: Resolver<void> | null = null;\n\n readyForMore(): Promise<void> | undefined {\n if (!this.#running) {\n return undefined;\n }\n if (\n this.#readyForMore === null &&\n this.#approximateQueuedBytes > this.#backPressureThresholdBytes\n ) {\n this.#lc.warn?.(\n `applying back pressure with ${this.#queue.size()} queued changes (~${(this.#approximateQueuedBytes / 1024 ** 2).toFixed(2)} MB)\\n` +\n `\\n` +\n `To inspect changeLog backlog in your change DB:\\n` +\n ` SELECT\\n` +\n ` (change->'relation'->>'schema') || '.' || (change->'relation'->>'name') AS table_name,\\n` +\n ` change->>'tag' AS operation,\\n` +\n ` COUNT(*) AS count\\n` +\n ` FROM \"<app_id>/cdc\".\"changeLog\"\\n` +\n ` GROUP BY 1, 2\\n` +\n ` ORDER BY 3 DESC\\n` +\n ` LIMIT 20;`,\n );\n this.#readyForMore = resolver();\n }\n return this.#readyForMore?.promise;\n }\n\n #maybeReleaseBackPressure() {\n if (this.#readyForMore !== null) {\n // Wait for at least 20% of the threshold to free up.\n if (\n this.#approximateQueuedBytes <\n this.#backPressureThresholdBytes * 0.8\n ) {\n this.#lc.info?.(\n `releasing back pressure with ${this.#queue.size()} queued changes (~${(this.#approximateQueuedBytes / 1024 ** 2).toFixed(2)} MB)`,\n );\n this.#readyForMore.resolve();\n this.#readyForMore = null;\n }\n }\n }\n\n /**\n * Flushes any buffered {@link PendingTransaction.batch} rows to the changeLog.\n *\n * Uses `json_to_recordset()` so the batch is a single JSON parameter: the\n * statement text stays constant regardless of batch size, avoiding the\n * unbounded prepared-statement variants (and Postgres memory growth) that a\n * multi-row INSERT would produce. See rocicorp/mono#3511.\n *\n * Returns (and updates) {@link PendingTransaction.lastFlush}; a no-op when the\n * batch is empty.\n */\n #flushChangeLog(tx: PendingTransaction): Promise<unknown> | undefined {\n const {batch} = tx;\n if (batch.length === 0) {\n return tx.lastFlush;\n }\n tx.batch = [];\n tx.lastFlush = tx.pool.process(sql => [\n sql`\n INSERT INTO ${this.#cdc('changeLog')} (\"watermark\", \"pos\", \"change\", \"precommit\")\n SELECT \"watermark\", \"pos\", \"change\"::json, \"precommit\"\n FROM json_to_recordset(${batch}) AS x(\n \"watermark\" TEXT,\n \"pos\" INT8,\n \"change\" TEXT,\n \"precommit\" TEXT\n )`,\n ]);\n return tx.lastFlush;\n }\n\n #stopped = promiseVoid;\n\n /**\n * Runs the storer loop until {@link stop()} is called, or an error is thrown.\n * Once {@link run()} completes, it can be called again.\n */\n async run() {\n assert(!this.#running, `storer is already running`);\n\n const {promise: stopped, resolve: signalStopped} = resolver();\n this.#running = true;\n this.#stopped = stopped;\n\n this.#lc.info?.('starting storer');\n this.#progressMonitor.start(); // Note: This is stopped in stop()\n let err: unknown;\n try {\n await this.#processQueue();\n } catch (e) {\n err = e; // used in finally\n throw e;\n } finally {\n // Release any pending backpressure so the upstream can proceed\n if (this.#readyForMore !== null) {\n this.#readyForMore.resolve();\n this.#readyForMore = null;\n }\n this.#cancelQueueEntries(\n this.#queue.drain().filter(entry => entry !== undefined),\n err,\n );\n this.#running = false;\n signalStopped();\n this.#lc.info?.('storer stopped');\n }\n }\n\n #cancelQueueEntries(queue: QueueEntry[], e: unknown) {\n if (queue.length === 0) {\n return;\n }\n this.#lc.info?.(\n `canceling ${queue.length} entries from the changeLog queue`,\n );\n const err = e instanceof Error ? e : new AbortError('server shutting down');\n for (const entry of queue) {\n if (entry === 'stop') {\n continue;\n }\n const type = entry[0];\n switch (type) {\n case 'subscriber': {\n // Disconnect subscribers waiting to be caught up so that they can\n // reconnect and try again.\n const {subscriber} = entry[1];\n this.#lc.info?.(`disconnecting ${subscriber.id}`);\n subscriber.fail(err);\n break;\n }\n }\n }\n }\n\n async #processQueue() {\n let tx: PendingTransaction | null = null;\n let msg: QueueEntry | false;\n\n // Track the progress of each (previous and next) queue entry before it is\n // processed in the loop.\n let lastTaskDone: TaskDoneFn | undefined;\n const nextMessage = async () => {\n lastTaskDone?.();\n lastTaskDone = undefined;\n const entry = await this.#queue.dequeue();\n if (entry !== 'stop') {\n lastTaskDone = this.#progressMonitor.trackTask({\n task: 'queue-entry',\n // Note: TaskKeys are logged, so only include entry[0] (the type) to\n // avoid logging application data.\n type: entry[0],\n });\n }\n return entry;\n };\n\n const catchupQueue: SubscriberAndMode[] = [];\n try {\n while ((msg = await nextMessage()) !== 'stop') {\n const [msgType] = msg;\n switch (msgType) {\n case 'ready': {\n const signalReady = msg[1];\n signalReady();\n continue;\n }\n case 'subscriber': {\n const subscriber = msg[1];\n if (tx) {\n catchupQueue.push(subscriber); // Wait for the current tx to complete.\n } else {\n await this.#startCatchup([subscriber]); // Catch up immediately.\n }\n continue;\n }\n case 'abort': {\n if (tx) {\n tx.pool.abort();\n await tx.pool.done();\n tx = null;\n }\n continue;\n }\n }\n // msgType === 'change'\n const [_, watermark, json, storedChange, change] = msg;\n const tag = change?.tag;\n this.#approximateQueuedBytes -= json.length;\n\n if (tag === 'begin') {\n assert(!tx, 'received BEGIN in the middle of a transaction');\n const {promise, resolve, reject} = resolver<ReplicationOwner>();\n void promise.catch(() => {}); // handle rejections before the await\n tx = {\n pool: new TransactionPool(\n this.#lc.withContext('watermark', watermark),\n {mode: Mode.READ_COMMITTED},\n ),\n preCommitWatermark: watermark,\n pos: 0,\n startingReplicationState: promise,\n ack: !change.skipAck,\n batch: [],\n lastFlush: undefined,\n };\n tx.pool.run(this.#inserter);\n // Acquire a lock on the replicationState row to detect and/or prevent\n // a concurrent ownership change.\n void tx.pool.process(tx => {\n tx<ReplicationOwner[]> /*sql*/ `\n SELECT \"owner\" FROM ${this.#cdc('replicationState')} FOR UPDATE`.then(\n ([result]) => resolve(result),\n reject,\n );\n return [];\n });\n } else {\n assert(tx, () => `received change outside of transaction: ${json}`);\n tx.pos++;\n }\n\n const entry: ChangeLogRow = {\n watermark: tag === 'commit' ? watermark : tx.preCommitWatermark,\n precommit: tag === 'commit' ? tx.preCommitWatermark : null,\n pos: tx.pos,\n // For backwards compatibility, only the change message is stored\n // in the cdc changeLog. It was serialized separately with the\n // downstream envelope, so no full-message scan is needed here.\n change: storedChange,\n };\n\n if (change !== null && isSchemaChange(change)) {\n // Schema changes carry backfill / table-metadata statements that\n // must be applied in stream order relative to the changeLog rows.\n // Flush any buffered rows first, then write this row together with\n // its metadata statements as a single unit (preserving the previous\n // per-change ordering for schema changes).\n await this.#flushChangeLog(tx);\n tx.lastFlush = tx.pool.process(sql => [\n sql`INSERT INTO ${this.#cdc('changeLog')} ${sql(entry)}`,\n ...this.#trackBackfillMetadata(sql, change),\n ]);\n } else {\n // Accumulate plain changeLog rows (begin, data changes, commit) and\n // write them as a single multi-row INSERT. Collapsing the per-change\n // single-row INSERTs into batches is the dominant cost reduction for\n // large transactions, where the previous one-statement-per-change\n // path dominated the upstream replication lag.\n tx.batch.push(entry);\n if (tx.batch.length >= this.#changeLogBatchSize) {\n // Bound pipeline depth (and thus memory) by awaiting the previous\n // flush before issuing the next. This is the batched analog of the\n // previous per-100-statement backpressure await, and likewise\n // guards against memory blowup on very large transactions.\n const prevFlush = tx.lastFlush;\n void this.#flushChangeLog(tx);\n await prevFlush;\n }\n }\n this.#maybeReleaseBackPressure();\n\n if (tag === 'commit') {\n // Flush any remaining buffered changeLog rows (including this commit\n // row) before updating the replication state, so the state update is\n // ordered after all changeLog inserts for this transaction.\n void this.#flushChangeLog(tx);\n\n const {owner} = await tx.startingReplicationState;\n if (owner !== this.#taskID) {\n // Ownership change reflected in the replicationState read in 'begin'.\n tx.pool.fail(\n new AbortError(\n `changeLog ownership has been assumed by ${owner}`,\n ),\n );\n } else {\n // Update the replication state.\n const lastWatermark = watermark;\n void tx.pool.process(tx => [\n tx`\n UPDATE ${this.#cdc('replicationState')} SET ${tx({lastWatermark})}`,\n ]);\n tx.pool.setDone();\n }\n\n await tx.pool.done();\n\n // ACK the LSN to the upstream Postgres.\n if (tx.ack) {\n this.#onCommitted(['commit', change, {watermark}]);\n }\n tx = null;\n\n // Before beginning the next transaction, open a READONLY snapshot to\n // concurrently catchup any queued subscribers.\n await this.#startCatchup(catchupQueue.splice(0));\n } else if (tag === 'rollback') {\n // Aborted transactions are not stored in the changeLog. Abort the current tx\n // and process catchup of subscribers that were waiting for it to end.\n tx.pool.abort();\n await tx.pool.done();\n tx = null;\n\n await this.#startCatchup(catchupQueue.splice(0));\n }\n }\n } catch (e) {\n catchupQueue.forEach(({subscriber}) => subscriber.fail(e));\n throw e;\n }\n }\n\n async #startCatchup(subs: SubscriberAndMode[]) {\n const numCatchups = subs.length;\n if (numCatchups === 0) {\n return;\n }\n\n const lc = this.#lc.withContext('pool', 'catchup');\n const {init, cleanup, snapshotID} = sharedSnapshot();\n const reader = new TransactionPool(lc, {\n mode: Mode.READONLY,\n init,\n cleanup,\n initialWorkers: subs.length,\n });\n\n // A dynamic connection pool is created for catchup, sized to the number\n // of subscribers being caught up.\n const catchupConns = this.#makeConnectionPool(\n 'subscriber-catchup',\n numCatchups,\n );\n reader.run(catchupConns);\n\n let lastWatermark: string | undefined;\n const catchupSnapshotted = this.#progressMonitor.trackTask({\n task: 'capture-catchup-snapshot',\n subscribers: subs.map(({subscriber: s}) => s.id),\n });\n try {\n // Ensure that the transaction has started (and is thus holding a snapshot\n // of the database) before continuing on to commit more changes. This is\n // done by performing a single read on the db, which determines the\n // snapshot for the REPEATABLE_READ transaction.\n [{lastWatermark}] = await reader.processReadTask(\n sql => sql<{lastWatermark: string}[]>`\n SELECT \"lastWatermark\" FROM ${this.#cdc('replicationState')}\n `,\n );\n lc.info?.(\n `snapshotted db at ${lastWatermark} (${await snapshotID}) ` +\n `to catchup ${numCatchups} subscriber(s)`,\n );\n } catch (e) {\n subs.map(({subscriber}) => subscriber.fail(e));\n // Nothing will run the catchup tasks that normally finalize the pool\n // (below), so tear it down here. Otherwise its workers (each holding an\n // open READ ONLY transaction) and the connection pool are leaked.\n reader.abort();\n void catchupConns.end().catch(() => {});\n throw e;\n } finally {\n catchupSnapshotted();\n }\n\n // Run the actual catchup queries in the background. Errors are handled in\n // #catchup() by disconnecting the associated subscriber.\n void Promise.allSettled(\n subs.map(sub =>\n this.#catchup(\n lc.withContext('subscriber', sub.subscriber.id),\n sub,\n lastWatermark,\n reader,\n ),\n ),\n ).finally(() => {\n reader.setDone();\n void catchupConns.end().catch(() => {});\n });\n }\n\n async #catchup(\n lc: LogContext,\n {subscriber: sub, mode}: SubscriberAndMode,\n lastWatermark: string,\n reader: TransactionPool,\n ) {\n let entriesReceived = 0;\n let catchupProgressed = this.#progressMonitor.trackTask({\n task: 'catchup',\n subscriber: sub.id,\n entriesReceived,\n });\n try {\n lc.info?.(`starting catchup`);\n await reader.processReadTask(async tx => {\n lc.info?.(`catching up`);\n const start = Date.now();\n\n // When starting from initial-sync, there won't be a change with a watermark\n // equal to the replica version. This is the empty changeLog scenario.\n let watermarkFound = sub.watermark === this.#replicaVersion;\n let count = 0;\n let lastBatchConsumed: Promise<unknown> | undefined;\n\n for await (const entries of tx<ChangeLogEntry[]> /*sql*/ `\n SELECT watermark, change->'tag' as tag, change::text FROM ${this.#cdc('changeLog')}\n WHERE watermark >= ${sub.watermark}\n AND watermark <= ${lastWatermark}\n ORDER BY watermark, pos`.cursor(2000)) {\n catchupProgressed();\n entriesReceived += entries.length;\n\n try {\n // Wait for the last batch of entries to be consumed by the\n // subscriber before sending down the current batch. This pipelining\n // allows one batch of changes to be received from the change-db\n // while the previous batch of changes are sent to the subscriber,\n // resulting in flow control that caps the number of changes\n // referenced in memory to 2 * batch-size.\n const start = performance.now();\n await lastBatchConsumed;\n const elapsed = performance.now() - start;\n if (lastBatchConsumed) {\n lc[elapsed > 100 ? 'info' : 'debug']?.(\n `waited ${elapsed.toFixed(3)} ms for ${sub.id} to consume last batch of catchup entries`,\n );\n }\n\n for (const entry of entries) {\n if (entry.watermark === sub.watermark) {\n // This should be the first entry.\n // Catchup starts from *after* the watermark.\n watermarkFound = true;\n } else if (watermarkFound) {\n lastBatchConsumed = sub.catchup(\n reconstructWatermarkedChange(entry),\n );\n count++;\n } else if (mode === 'backup') {\n throw new AutoResetSignal(\n `backup replica at watermark ${sub.watermark} is behind change db: ${entry.watermark})`,\n );\n } else {\n lc.warn?.(\n `rejecting subscriber at watermark ${sub.watermark} (earliest watermark: ${entry.watermark})`,\n );\n sub.close(\n ErrorType.WatermarkTooOld,\n `earliest supported watermark is ${entry.watermark} (requested ${sub.watermark})`,\n );\n return;\n }\n }\n } finally {\n // Track the db's read of the next chunk of changes.\n catchupProgressed = this.#progressMonitor.trackTask({\n task: 'catchup',\n subscriber: sub.id,\n entriesReceived,\n });\n }\n }\n catchupProgressed();\n\n if (watermarkFound) {\n await lastBatchConsumed;\n lc.info?.(\n `caught up ${sub.id} with ${count} changes (${\n Date.now() - start\n } ms)`,\n );\n } else {\n // The subscriber is ahead of the latest durable changeLog entry\n // (lastWatermark). This can legitimately happen: changes are\n // forwarded to subscribers (the backup replica and view-syncers)\n // concurrently with — and can outrun — the durable store, so a\n // replica may briefly lead the change DB after the storer falls\n // behind or the change-streamer restarts. No catchup is possible or\n // needed; once the change DB catches back up, forwarding resumes and\n // the subscriber dedups any watermarks it already has. Unlike the\n // AutoResetSignal / WatermarkTooOld cases above, this is not a gap in\n // replication history, so the subscriber is simply marked caught up.\n lc.warn?.(\n `subscriber ${sub.id} at watermark ${sub.watermark} is ahead of ` +\n `the latest durable watermark ${lastWatermark}; waiting for the ` +\n `change DB to catch up`,\n );\n }\n // Start draining messages buffered during catchup. The returned promise\n // is intentionally not awaited here: while the drain is in progress,\n // new sends keep appending to the subscriber backlog and inherit its\n // byte-based backpressure.\n void sub.setCaughtUp();\n });\n } catch (err) {\n lc.error?.(`error while catching up subscriber ${sub.id}`, err);\n if (err instanceof AutoResetSignal) {\n await markResetRequired(this.#db, this.#shard);\n this.#onFatal(err);\n }\n sub.fail(err);\n } finally {\n catchupProgressed();\n }\n }\n\n /**\n * Returns the db statements necessary to track backfill and table metadata\n * presented in the `change`, if any.\n *\n * This is the Postgres interpreter of {@link cookieOps}, which is also\n * interpreted against the SQLite change log by `ChangeLogCookieWriter`. The\n * two stores have to agree on every transition forever, and the failure mode\n * if they drift is a backfill that is silently never re-requested — so the\n * decision of what a schema change *means* lives in one place and only its\n * transport lives here.\n */\n #trackBackfillMetadata(sql: PostgresTransaction, change: SchemaChange) {\n return cookieOps(change).flatMap(op => this.#cookieStmts(sql, op));\n }\n\n #cookieStmts(sql: PostgresTransaction, op: CookieOp): PendingQuery<Row[]>[] {\n switch (op.op) {\n case 'upsert-metadata':\n return [this.#upsertTableMetadataStmt(sql, op.table, op.metadata)];\n\n case 'upsert-backfill':\n return [\n this.#upsertColumnBackfillStmt(sql, op.table, op.column, op.backfill),\n ];\n\n case 'rename-table': {\n const {old} = op;\n const row = {schema: op.new.schema, table: op.new.name};\n return [\n sql`UPDATE ${this.#cdc('tableMetadata')} SET ${sql(row)}\n WHERE \"schema\" = ${old.schema} AND \"table\" = ${old.name}`,\n sql`UPDATE ${this.#cdc('backfilling')} SET ${sql(row)}\n WHERE \"schema\" = ${old.schema} AND \"table\" = ${old.name}`,\n ];\n }\n\n case 'drop-table': {\n const {schema, name} = op.table;\n return [\n sql`DELETE FROM ${this.#cdc('tableMetadata')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${name}`,\n sql`DELETE FROM ${this.#cdc('backfilling')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${name}`,\n ];\n }\n\n case 'rename-column': {\n const {schema, name: table} = op.table;\n return [\n sql`UPDATE ${this.#cdc('backfilling')} SET \"column\" = ${op.new}\n WHERE \"schema\" = ${schema} AND \"table\" = ${table} AND \"column\" = ${op.old}`,\n ];\n }\n\n case 'drop-column': {\n const {schema, name} = op.table;\n return [\n sql`DELETE FROM ${this.#cdc('backfilling')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${name} AND \"column\" = ${op.column}`,\n ];\n }\n\n case 'complete-backfill': {\n const {schema, name: table} = op.table;\n return [\n sql`DELETE FROM ${this.#cdc('backfilling')}\n WHERE \"schema\" = ${schema} AND \"table\" = ${table} AND \"column\" IN ${sql([...op.columns])}`,\n ];\n }\n\n default:\n unreachable(op);\n }\n }\n\n #upsertTableMetadataStmt(\n sql: PostgresTransaction,\n {schema, name: table}: Identifier,\n metadata: TableMetadata,\n ) {\n const row: TableMetadataRow = {schema, table, metadata};\n return sql`\n INSERT INTO ${this.#cdc('tableMetadata')} ${sql(row)}\n ON CONFLICT (\"schema\", \"table\")\n DO UPDATE SET ${sql(row)};\n `;\n }\n\n #upsertColumnBackfillStmt(\n sql: PostgresTransaction,\n {schema, name: table}: Identifier,\n column: string,\n backfill: BackfillID,\n ) {\n const row: BackfillingColumn = {schema, table, column, backfill};\n return sql`\n INSERT INTO ${this.#cdc('backfilling')} ${sql(row)}\n ON CONFLICT (\"schema\", \"table\", \"column\")\n DO UPDATE SET ${sql(row)};\n `;\n }\n\n /**\n * Waits until all currently queued entries have been processed.\n * This is only used in tests.\n */\n async allProcessed() {\n if (this.#running) {\n const {promise, resolve} = resolver();\n this.#queue.enqueue(['ready', resolve]);\n await promise;\n }\n }\n\n /**\n * Stops the storer and waits up to a drain timeout for entries to drain,\n * throwing an exception if it doesn't drain in time, in order to abort\n * the server when PG (or the connection to it) appears to be wedged.\n */\n async stop() {\n if (this.#running) {\n this.#progressMonitor.stop();\n this.#lc.info?.(`draining ${this.#queue.size()} changeLog entries`);\n this.abort(); // for cleanliness, abort any open transactions\n this.#queue.enqueue('stop');\n }\n if (\n (await orTimeout(this.#stopped, this.#drainTimeoutMs)) === 'timed-out'\n ) {\n throw new AbortError(\n `changeLog did not drain within ${this.#drainTimeoutMs}ms`,\n );\n }\n }\n}\n\nexport class PurgeLock {\n readonly #lc: LogContext;\n readonly #tx: TransactionPool;\n readonly replicaVersion: string;\n readonly minWatermark: string;\n\n constructor(\n lc: LogContext,\n tx: TransactionPool,\n replicaVersion: string,\n watermark: string,\n ) {\n this.#lc = lc;\n this.#tx = tx;\n this.replicaVersion = replicaVersion;\n this.minWatermark = watermark;\n }\n\n #released = false;\n\n async release() {\n if (this.#released) {\n return;\n }\n this.#released = true;\n this.#tx.setDone();\n await this.#tx\n .done()\n .catch(e => this.#lc.warn?.(`error from purge-lock release`, e));\n this.#lc.info?.(`released purge lock on ${this.minWatermark}`);\n }\n}\n\nexport class PurgeLocker {\n readonly #lc: LogContext;\n readonly #shard: ShardID;\n readonly #db: PostgresDB;\n\n constructor(lc: LogContext, shard: ShardID, db: PostgresDB) {\n this.#lc = lc.withContext('component', 'purge-locker');\n this.#shard = shard;\n this.#db = db;\n }\n\n // For readability in SQL statements.\n #cdc(table: string) {\n return this.#db(`${cdcSchema(this.#shard)}.${table}`);\n }\n\n async acquire() {\n const tx = new TransactionPool(this.#lc, {mode: Mode.READ_COMMITTED}).run(\n this.#db,\n );\n const row = await tx.processReadTask(\n sql => sql<{watermark: string}[]>`\n SELECT watermark FROM ${this.#cdc('changeLog')}\n ORDER BY watermark, pos LIMIT 1\n FOR SHARE \n `,\n );\n if (row.length === 0) {\n this.#lc.info?.(`changeLog is empty. No rows to purge-lock.`);\n tx.setDone();\n await tx.done();\n return null;\n }\n const [{watermark}] = row;\n const [{replicaVersion}] = await tx.processReadTask(\n sql => sql<{replicaVersion: string}[]>`\n SELECT \"replicaVersion\" FROM ${this.#cdc('replicationConfig')}\n `,\n );\n this.#lc.info?.(\n `locked watermark ${watermark} from being purged from replica@${replicaVersion}`,\n );\n return new PurgeLock(this.#lc, tx, replicaVersion, watermark);\n }\n}\n\n// A TaskKey, keyed by identity, representing an db task monitored by the\n// ProgressMonitor. The contents are stringified in error messages if the\n// task fails to make progress for more than a failure threshold interval.\ntype TaskKey = JSONObject;\n\ntype TaskDoneFn = () => void;\n\n/**\n * Periodic monitor for detecting the pathological condition in which a db\n * query hangs forever because of a connection problem, such as a half-closed\n * socket.\n *\n * Use this for db calls that recur or accumulate progress within a single\n * logical operation on the main storer loop (e.g. the queue-processing loop,\n * or a multi-batch catchup read), where \"no progress for N ms\" is the\n * meaningful signal. For a one-off db call -- including ones made before\n * {@link Storer.run} starts this monitor's polling -- use a plain timeout\n * (see `Storer.#withTimeout`) instead.\n *\n * The failure handler should stop the server. The ProgressMonitor will\n * assume this and stop itself after calling it.\n *\n * Internal to the Storer, but exported for testing.\n */\nexport class ProgressMonitor {\n readonly #lc: LogContext;\n readonly #failureThresholdMs;\n readonly #onFailure: (err: Error) => void;\n readonly #taskUpdates = new Map<TaskKey, Date>();\n\n #timer: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n failureThresholdMs: number,\n onFailure: (err: Error) => void,\n ) {\n this.#lc = lc.withContext('component', 'storer-progress-monitor');\n this.#failureThresholdMs = failureThresholdMs;\n this.#onFailure = onFailure;\n }\n\n start() {\n clearInterval(this.#timer);\n this.#timer = setInterval(\n () => this.checkTaskProgress(new Date()),\n this.#failureThresholdMs,\n ).unref();\n }\n\n // Called by an internal periodic timer, but exported for testing.\n checkTaskProgress(now: Date) {\n for (const [task, lastUpdate] of this.#taskUpdates.entries()) {\n if (now.getTime() - lastUpdate.getTime() >= this.#failureThresholdMs) {\n this.#lc.error?.(\n `Last task update was more than ${this.#failureThresholdMs}ms ago`,\n {task, lastUpdate},\n );\n this.#onFailure(\n new Error(\n `Task failed to progress for over ${this.#failureThresholdMs}ms: ${BigIntJSON.stringify(task)}`,\n ),\n );\n this.stop();\n return;\n }\n }\n }\n\n /**\n * Starts a task to be tracked.\n *\n * @return a callback to invoke when the task is done.\n */\n trackTask(task: TaskKey, now = new Date()): TaskDoneFn {\n this.#taskUpdates.set(task, now);\n return () => {\n this.#taskUpdates.delete(task);\n };\n }\n\n stop() {\n clearInterval(this.#timer);\n this.#taskUpdates.clear();\n }\n}\n"],"mappings":"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"}