@rocicorp/zero 1.11.0-canary.0 → 1.11.0-canary.10

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 (577) hide show
  1. package/out/analyze-query/src/bin-analyze.js +1 -1
  2. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  3. package/out/replicache/src/btree/read.d.ts +12 -1
  4. package/out/replicache/src/btree/read.d.ts.map +1 -1
  5. package/out/replicache/src/btree/read.js +24 -12
  6. package/out/replicache/src/btree/read.js.map +1 -1
  7. package/out/replicache/src/dag/gc.d.ts.map +1 -1
  8. package/out/replicache/src/dag/gc.js +2 -1
  9. package/out/replicache/src/dag/gc.js.map +1 -1
  10. package/out/replicache/src/dag/invalid-ref-count-error.d.ts +12 -0
  11. package/out/replicache/src/dag/invalid-ref-count-error.d.ts.map +1 -0
  12. package/out/replicache/src/dag/invalid-ref-count-error.js +19 -0
  13. package/out/replicache/src/dag/invalid-ref-count-error.js.map +1 -0
  14. package/out/replicache/src/dag/store-impl.d.ts +12 -2
  15. package/out/replicache/src/dag/store-impl.d.ts.map +1 -1
  16. package/out/replicache/src/dag/store-impl.js +22 -8
  17. package/out/replicache/src/dag/store-impl.js.map +1 -1
  18. package/out/replicache/src/db/rebase.d.ts +14 -2
  19. package/out/replicache/src/db/rebase.d.ts.map +1 -1
  20. package/out/replicache/src/db/rebase.js +27 -5
  21. package/out/replicache/src/db/rebase.js.map +1 -1
  22. package/out/replicache/src/db/write.js.map +1 -1
  23. package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  24. package/out/replicache/src/kv/expo-sqlite/store.js +28 -5
  25. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  26. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +15 -5
  27. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +1 -1
  28. package/out/replicache/src/kv/idb-store-with-mem-fallback.js +25 -25
  29. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +1 -1
  30. package/out/replicache/src/kv/idb-store.d.ts +9 -0
  31. package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
  32. package/out/replicache/src/kv/idb-store.js +14 -2
  33. package/out/replicache/src/kv/idb-store.js.map +1 -1
  34. package/out/replicache/src/kv/mem-store.d.ts +1 -0
  35. package/out/replicache/src/kv/mem-store.d.ts.map +1 -1
  36. package/out/replicache/src/kv/mem-store.js +3 -0
  37. package/out/replicache/src/kv/mem-store.js.map +1 -1
  38. package/out/replicache/src/kv/op-sqlite/store.d.ts.map +1 -1
  39. package/out/replicache/src/kv/op-sqlite/store.js +1 -1
  40. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  41. package/out/replicache/src/kv/sqlite-store.d.ts +13 -1
  42. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  43. package/out/replicache/src/kv/sqlite-store.js +49 -4
  44. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  45. package/out/replicache/src/kv/store.d.ts +6 -0
  46. package/out/replicache/src/kv/store.d.ts.map +1 -1
  47. package/out/replicache/src/persist/collect-idb-databases.d.ts +10 -5
  48. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  49. package/out/replicache/src/persist/collect-idb-databases.js +16 -9
  50. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  51. package/out/replicache/src/persist/persist.d.ts.map +1 -1
  52. package/out/replicache/src/persist/persist.js +12 -5
  53. package/out/replicache/src/persist/persist.js.map +1 -1
  54. package/out/replicache/src/persist/refresh.js +6 -2
  55. package/out/replicache/src/persist/refresh.js.map +1 -1
  56. package/out/replicache/src/replicache-impl.d.ts +14 -3
  57. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  58. package/out/replicache/src/replicache-impl.js +118 -11
  59. package/out/replicache/src/replicache-impl.js.map +1 -1
  60. package/out/replicache/src/replicache-options.d.ts +9 -0
  61. package/out/replicache/src/replicache-options.d.ts.map +1 -1
  62. package/out/replicache/src/transactions.d.ts.map +1 -1
  63. package/out/replicache/src/transactions.js +1 -1
  64. package/out/replicache/src/transactions.js.map +1 -1
  65. package/out/shared/src/error.d.ts +11 -0
  66. package/out/shared/src/error.d.ts.map +1 -1
  67. package/out/shared/src/error.js +35 -1
  68. package/out/shared/src/error.js.map +1 -1
  69. package/out/shared/src/iterables.d.ts +2 -0
  70. package/out/shared/src/iterables.d.ts.map +1 -1
  71. package/out/shared/src/iterables.js +18 -1
  72. package/out/shared/src/iterables.js.map +1 -1
  73. package/out/shared/src/map.d.ts +7 -2
  74. package/out/shared/src/map.d.ts.map +1 -1
  75. package/out/shared/src/map.js.map +1 -1
  76. package/out/shared/src/ring-buffer.d.ts +8 -0
  77. package/out/shared/src/ring-buffer.d.ts.map +1 -1
  78. package/out/shared/src/ring-buffer.js +22 -1
  79. package/out/shared/src/ring-buffer.js.map +1 -1
  80. package/out/zero/package.js +3 -4
  81. package/out/zero/package.js.map +1 -1
  82. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  83. package/out/zero-cache/src/config/normalize.js +25 -1
  84. package/out/zero-cache/src/config/normalize.js.map +1 -1
  85. package/out/zero-cache/src/config/zero-config.d.ts +48 -2
  86. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  87. package/out/zero-cache/src/config/zero-config.js +171 -29
  88. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  89. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  90. package/out/zero-cache/src/server/change-streamer.js +25 -9
  91. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  92. package/out/zero-cache/src/server/inspector-delegate.d.ts +18 -2
  93. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  94. package/out/zero-cache/src/server/inspector-delegate.js +20 -7
  95. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  96. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  97. package/out/zero-cache/src/server/main.js +8 -1
  98. package/out/zero-cache/src/server/main.js.map +1 -1
  99. package/out/zero-cache/src/server/mutator.js +2 -0
  100. package/out/zero-cache/src/server/mutator.js.map +1 -1
  101. package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
  102. package/out/zero-cache/src/server/reaper.js +2 -0
  103. package/out/zero-cache/src/server/reaper.js.map +1 -1
  104. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  105. package/out/zero-cache/src/server/replicator.js +8 -2
  106. package/out/zero-cache/src/server/replicator.js.map +1 -1
  107. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  108. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  109. package/out/zero-cache/src/server/runner/zero-dispatcher.js +3 -0
  110. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  111. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  112. package/out/zero-cache/src/server/syncer.js +2 -0
  113. package/out/zero-cache/src/server/syncer.js.map +1 -1
  114. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  115. package/out/zero-cache/src/services/analyze.js +3 -2
  116. package/out/zero-cache/src/services/analyze.js.map +1 -1
  117. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  118. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +14 -1
  119. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  120. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
  121. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +15 -3
  122. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  123. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  124. package/out/zero-cache/src/services/change-source/common/replica-schema.js +2 -39
  125. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  126. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +156 -0
  127. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +1 -0
  128. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +15 -0
  129. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +1 -0
  130. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts +25 -0
  131. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -0
  132. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +202 -0
  133. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -0
  134. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +14 -16
  135. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  136. package/out/zero-cache/src/services/change-source/pg/change-source.js +64 -143
  137. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  138. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +28 -2
  139. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  140. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +44 -21
  141. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  142. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts +16 -0
  143. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts.map +1 -0
  144. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +113 -0
  145. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -0
  146. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +21 -15
  147. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  148. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +86 -23
  149. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  150. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -1
  151. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +68 -8
  152. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  153. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  154. package/out/zero-cache/src/services/change-source/pg/schema/init.js +32 -1
  155. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  156. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.d.ts +10 -0
  157. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.d.ts.map +1 -0
  158. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js +6 -0
  159. package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js.map +1 -0
  160. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +50 -27
  161. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  162. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +100 -34
  163. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  164. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  165. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +58 -7
  166. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  167. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +8 -2
  168. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
  169. package/out/zero-cache/src/services/change-streamer/broadcast.js +24 -4
  170. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  171. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -4
  172. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
  173. package/out/zero-cache/src/services/change-streamer/change-log-codec.js +28 -8
  174. package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
  175. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +5 -2
  176. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  177. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +21 -14
  178. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  179. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +6 -0
  180. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  181. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +97 -63
  182. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  183. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +12 -8
  184. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  185. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  186. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +3 -3
  187. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  188. package/out/zero-cache/src/services/change-streamer/forwarder.js +36 -5
  189. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  190. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +1 -1
  191. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  192. package/out/zero-cache/src/services/change-streamer/schema/tables.js +4 -4
  193. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  194. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +3 -1
  195. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
  196. package/out/zero-cache/src/services/change-streamer/snapshot.js +54 -20
  197. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  198. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +6 -1
  199. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -1
  200. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +27 -9
  201. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
  202. package/out/zero-cache/src/services/change-streamer/storer.d.ts +2 -2
  203. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  204. package/out/zero-cache/src/services/change-streamer/storer.js +8 -5
  205. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  206. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +4 -1
  207. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  208. package/out/zero-cache/src/services/change-streamer/subscriber.js +42 -0
  209. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  210. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +17 -1
  211. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -1
  212. package/out/zero-cache/src/services/change-streamer/upstream-acker.js +19 -3
  213. package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
  214. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -1
  215. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +1 -0
  216. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -1
  217. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  218. package/out/zero-cache/src/services/life-cycle.js +3 -1
  219. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  220. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  221. package/out/zero-cache/src/services/litestream/commands.js +8 -3
  222. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  223. package/out/zero-cache/src/services/litestream/config-v5.yml +15 -1
  224. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +131 -0
  225. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -0
  226. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +70 -0
  227. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -0
  228. package/out/zero-cache/src/services/litestream/litestream-controller.js +101 -0
  229. package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -0
  230. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +5 -0
  231. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  232. package/out/zero-cache/src/services/mutagen/mutagen.js +7 -1
  233. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  234. package/out/zero-cache/src/services/mutagen/pusher.js +2 -1
  235. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  236. package/out/zero-cache/src/services/profz.d.ts +7 -0
  237. package/out/zero-cache/src/services/profz.d.ts.map +1 -0
  238. package/out/zero-cache/src/services/profz.js +105 -0
  239. package/out/zero-cache/src/services/profz.js.map +1 -0
  240. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  241. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +5 -4
  242. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -1
  243. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +20 -22
  244. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  245. package/out/zero-cache/src/services/replicator/change-processor.js +1 -0
  246. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  247. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +1 -1
  248. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  249. package/out/zero-cache/src/services/replicator/incremental-sync.js +58 -17
  250. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  251. package/out/zero-cache/src/services/replicator/replication-status.d.ts +35 -1
  252. package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +1 -1
  253. package/out/zero-cache/src/services/replicator/replication-status.js +96 -6
  254. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  255. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  256. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +6 -6
  257. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
  258. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +19 -14
  259. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  260. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +31 -6
  261. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
  262. package/out/zero-cache/src/services/replicator/write-worker-client.js +5 -4
  263. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  264. package/out/zero-cache/src/services/replicator/write-worker.js +30 -5
  265. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  266. package/out/zero-cache/src/services/run-ast.d.ts +12 -0
  267. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  268. package/out/zero-cache/src/services/run-ast.js +26 -14
  269. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  270. package/out/zero-cache/src/services/runner.d.ts.map +1 -1
  271. package/out/zero-cache/src/services/runner.js +1 -1
  272. package/out/zero-cache/src/services/runner.js.map +1 -1
  273. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +1 -1
  274. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  275. package/out/zero-cache/src/services/view-syncer/client-handler.js +36 -31
  276. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  277. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +45 -0
  278. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  279. package/out/zero-cache/src/services/view-syncer/cvr.js +133 -1
  280. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  281. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +14 -1
  282. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +1 -1
  283. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +30 -4
  284. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  285. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts +33 -0
  286. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts.map +1 -0
  287. package/out/zero-cache/src/services/view-syncer/hydration-budget.js +62 -0
  288. package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -0
  289. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +51 -0
  290. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
  291. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
  292. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
  293. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +1 -1
  294. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  295. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -2
  296. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  297. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +2 -1
  298. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  299. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +33 -18
  300. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  301. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -1
  302. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  303. package/out/zero-cache/src/services/view-syncer/snapshotter.js +9 -3
  304. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  305. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
  306. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  307. package/out/zero-cache/src/services/view-syncer/view-syncer.js +507 -93
  308. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  309. package/out/zero-cache/src/types/processes.d.ts +32 -0
  310. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  311. package/out/zero-cache/src/types/processes.js +43 -4
  312. package/out/zero-cache/src/types/processes.js.map +1 -1
  313. package/out/zero-cache/src/types/profiler.d.ts +12 -0
  314. package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
  315. package/out/zero-cache/src/types/profiler.js +46 -1
  316. package/out/zero-cache/src/types/profiler.js.map +1 -1
  317. package/out/zero-cache/src/types/streams.d.ts +25 -9
  318. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  319. package/out/zero-cache/src/types/streams.js +118 -34
  320. package/out/zero-cache/src/types/streams.js.map +1 -1
  321. package/out/zero-cache/src/types/subscription.d.ts +4 -0
  322. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  323. package/out/zero-cache/src/types/subscription.js +71 -0
  324. package/out/zero-cache/src/types/subscription.js.map +1 -1
  325. package/out/zero-cache/src/types/ws.d.ts.map +1 -1
  326. package/out/zero-cache/src/types/ws.js +23 -8
  327. package/out/zero-cache/src/types/ws.js.map +1 -1
  328. package/out/zero-cache/src/workers/connection.js +2 -2
  329. package/out/zero-cache/src/workers/replicator.d.ts +1 -0
  330. package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
  331. package/out/zero-cache/src/workers/replicator.js +4 -2
  332. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  333. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  334. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +21 -16
  335. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  336. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  337. package/out/zero-cache/src/workers/syncer.js +10 -1
  338. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  339. package/out/zero-client/src/client/connection-manager.d.ts +20 -0
  340. package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
  341. package/out/zero-client/src/client/connection-manager.js +77 -13
  342. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  343. package/out/zero-client/src/client/connection-status-enum.d.ts +2 -0
  344. package/out/zero-client/src/client/connection-status-enum.d.ts.map +1 -1
  345. package/out/zero-client/src/client/connection-status-enum.js +3 -1
  346. package/out/zero-client/src/client/connection-status-enum.js.map +1 -1
  347. package/out/zero-client/src/client/connection.d.ts +6 -0
  348. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  349. package/out/zero-client/src/client/connection.js +2 -1
  350. package/out/zero-client/src/client/connection.js.map +1 -1
  351. package/out/zero-client/src/client/context.d.ts +27 -2
  352. package/out/zero-client/src/client/context.d.ts.map +1 -1
  353. package/out/zero-client/src/client/context.js +112 -1
  354. package/out/zero-client/src/client/context.js.map +1 -1
  355. package/out/zero-client/src/client/custom.js +2 -2
  356. package/out/zero-client/src/client/custom.js.map +1 -1
  357. package/out/zero-client/src/client/error.js +1 -1
  358. package/out/zero-client/src/client/inspector/lazy-inspector.js +2 -2
  359. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  360. package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
  361. package/out/zero-client/src/client/ivm-branch.js +20 -3
  362. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  363. package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
  364. package/out/zero-client/src/client/mutator-proxy.js +2 -1
  365. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  366. package/out/zero-client/src/client/options.d.ts +3 -0
  367. package/out/zero-client/src/client/options.d.ts.map +1 -1
  368. package/out/zero-client/src/client/options.js.map +1 -1
  369. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  370. package/out/zero-client/src/client/query-manager.js +42 -6
  371. package/out/zero-client/src/client/query-manager.js.map +1 -1
  372. package/out/zero-client/src/client/version.js +1 -1
  373. package/out/zero-client/src/client/zero-rep.d.ts +1 -0
  374. package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
  375. package/out/zero-client/src/client/zero-rep.js +9 -6
  376. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  377. package/out/zero-client/src/client/zero.d.ts +18 -3
  378. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  379. package/out/zero-client/src/client/zero.js +122 -34
  380. package/out/zero-client/src/client/zero.js.map +1 -1
  381. package/out/zero-client/src/types/query-result.d.ts +2 -0
  382. package/out/zero-client/src/types/query-result.d.ts.map +1 -1
  383. package/out/zero-events/src/status.d.ts +26 -0
  384. package/out/zero-events/src/status.d.ts.map +1 -1
  385. package/out/zero-protocol/src/analyze-query-result.d.ts +5 -5
  386. package/out/zero-protocol/src/analyze-query-result.d.ts.map +1 -1
  387. package/out/zero-protocol/src/analyze-query-result.js +1 -1
  388. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  389. package/out/zero-protocol/src/down.d.ts +4 -4
  390. package/out/zero-protocol/src/inspect-down.d.ts +6 -6
  391. package/out/zero-protocol/src/protocol-version.d.ts +1 -1
  392. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  393. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  394. package/out/zero-react/src/use-query.d.ts +5 -4
  395. package/out/zero-react/src/use-query.d.ts.map +1 -1
  396. package/out/zero-react/src/use-query.js +86 -9
  397. package/out/zero-react/src/use-query.js.map +1 -1
  398. package/out/zero-server/src/adapters/drizzle.d.ts +2 -1
  399. package/out/zero-server/src/adapters/drizzle.d.ts.map +1 -1
  400. package/out/zero-server/src/adapters/drizzle.js +12 -7
  401. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  402. package/out/zero-server/src/adapters/kysely.d.ts +2 -1
  403. package/out/zero-server/src/adapters/kysely.d.ts.map +1 -1
  404. package/out/zero-server/src/adapters/kysely.js +8 -2
  405. package/out/zero-server/src/adapters/kysely.js.map +1 -1
  406. package/out/zero-server/src/adapters/pg.d.ts +1 -0
  407. package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
  408. package/out/zero-server/src/adapters/pg.js +8 -2
  409. package/out/zero-server/src/adapters/pg.js.map +1 -1
  410. package/out/zero-server/src/adapters/postgresjs.d.ts +1 -0
  411. package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
  412. package/out/zero-server/src/adapters/postgresjs.js +7 -1
  413. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  414. package/out/zero-server/src/adapters/prisma.d.ts +3 -2
  415. package/out/zero-server/src/adapters/prisma.d.ts.map +1 -1
  416. package/out/zero-server/src/adapters/prisma.js +8 -2
  417. package/out/zero-server/src/adapters/prisma.js.map +1 -1
  418. package/out/zero-server/src/custom.d.ts +5 -1
  419. package/out/zero-server/src/custom.d.ts.map +1 -1
  420. package/out/zero-server/src/custom.js +6 -3
  421. package/out/zero-server/src/custom.js.map +1 -1
  422. package/out/zero-server/src/mod.d.ts +1 -1
  423. package/out/zero-server/src/mod.d.ts.map +1 -1
  424. package/out/zero-server/src/pg-query-executor.d.ts +2 -2
  425. package/out/zero-server/src/pg-query-executor.d.ts.map +1 -1
  426. package/out/zero-server/src/pg-query-executor.js +2 -2
  427. package/out/zero-server/src/pg-query-executor.js.map +1 -1
  428. package/out/zero-server/src/schema.d.ts +2 -2
  429. package/out/zero-server/src/schema.d.ts.map +1 -1
  430. package/out/zero-server/src/schema.js +2 -2
  431. package/out/zero-server/src/schema.js.map +1 -1
  432. package/out/zero-server/src/zql-database.d.ts +21 -1
  433. package/out/zero-server/src/zql-database.d.ts.map +1 -1
  434. package/out/zero-server/src/zql-database.js +33 -2
  435. package/out/zero-server/src/zql-database.js.map +1 -1
  436. package/out/zero-solid/src/solid-view.d.ts +17 -0
  437. package/out/zero-solid/src/solid-view.d.ts.map +1 -1
  438. package/out/zero-solid/src/solid-view.js +39 -1
  439. package/out/zero-solid/src/solid-view.js.map +1 -1
  440. package/out/zero-solid/src/use-query.js +5 -3
  441. package/out/zero-solid/src/use-query.js.map +1 -1
  442. package/out/zql/src/builder/builder.d.ts.map +1 -1
  443. package/out/zql/src/builder/builder.js +16 -10
  444. package/out/zql/src/builder/builder.js.map +1 -1
  445. package/out/zql/src/builder/debug-delegate.d.ts +6 -1
  446. package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
  447. package/out/zql/src/builder/debug-delegate.js +14 -2
  448. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  449. package/out/zql/src/ivm/array-view.d.ts +29 -0
  450. package/out/zql/src/ivm/array-view.d.ts.map +1 -1
  451. package/out/zql/src/ivm/array-view.js +63 -12
  452. package/out/zql/src/ivm/array-view.js.map +1 -1
  453. package/out/zql/src/ivm/cap.d.ts.map +1 -1
  454. package/out/zql/src/ivm/cap.js +5 -3
  455. package/out/zql/src/ivm/cap.js.map +1 -1
  456. package/out/zql/src/ivm/data.d.ts.map +1 -1
  457. package/out/zql/src/ivm/data.js +8 -3
  458. package/out/zql/src/ivm/data.js.map +1 -1
  459. package/out/zql/src/ivm/deferred-input.d.ts +44 -0
  460. package/out/zql/src/ivm/deferred-input.d.ts.map +1 -0
  461. package/out/zql/src/ivm/deferred-input.js +89 -0
  462. package/out/zql/src/ivm/deferred-input.js.map +1 -0
  463. package/out/zql/src/ivm/exists.d.ts +1 -1
  464. package/out/zql/src/ivm/exists.d.ts.map +1 -1
  465. package/out/zql/src/ivm/exists.js.map +1 -1
  466. package/out/zql/src/ivm/fan-in.d.ts +2 -2
  467. package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
  468. package/out/zql/src/ivm/fan-in.js +6 -5
  469. package/out/zql/src/ivm/fan-in.js.map +1 -1
  470. package/out/zql/src/ivm/fan-out.d.ts +2 -2
  471. package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
  472. package/out/zql/src/ivm/fan-out.js.map +1 -1
  473. package/out/zql/src/ivm/filter-operators.d.ts +7 -6
  474. package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
  475. package/out/zql/src/ivm/filter-operators.js +29 -12
  476. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  477. package/out/zql/src/ivm/filter.d.ts +2 -2
  478. package/out/zql/src/ivm/filter.d.ts.map +1 -1
  479. package/out/zql/src/ivm/filter.js +7 -4
  480. package/out/zql/src/ivm/filter.js.map +1 -1
  481. package/out/zql/src/ivm/flipped-join.js +2 -2
  482. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  483. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
  484. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
  485. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +6 -4
  486. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  487. package/out/zql/src/ivm/memory-source.d.ts +15 -3
  488. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  489. package/out/zql/src/ivm/memory-source.js +57 -23
  490. package/out/zql/src/ivm/memory-source.js.map +1 -1
  491. package/out/zql/src/ivm/operator.d.ts +40 -0
  492. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  493. package/out/zql/src/ivm/operator.js.map +1 -1
  494. package/out/zql/src/ivm/take.d.ts.map +1 -1
  495. package/out/zql/src/ivm/take.js +3 -4
  496. package/out/zql/src/ivm/take.js.map +1 -1
  497. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  498. package/out/zql/src/ivm/union-fan-in.js +9 -7
  499. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  500. package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
  501. package/out/zql/src/ivm/view-apply-change.js +9 -4
  502. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  503. package/out/zql/src/ivm/view.d.ts +15 -0
  504. package/out/zql/src/ivm/view.d.ts.map +1 -1
  505. package/out/zql/src/mutate/custom.d.ts +24 -1
  506. package/out/zql/src/mutate/custom.d.ts.map +1 -1
  507. package/out/zql/src/mutate/custom.js.map +1 -1
  508. package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
  509. package/out/zql/src/mutate/mutator-registry.js +2 -2
  510. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  511. package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
  512. package/out/zql/src/planner/planner-builder.js +14 -4
  513. package/out/zql/src/planner/planner-builder.js.map +1 -1
  514. package/out/zql/src/planner/planner-connection.d.ts +13 -0
  515. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  516. package/out/zql/src/planner/planner-connection.js +42 -7
  517. package/out/zql/src/planner/planner-connection.js.map +1 -1
  518. package/out/zql/src/planner/planner-debug.d.ts +1 -1
  519. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  520. package/out/zql/src/planner/planner-debug.js.map +1 -1
  521. package/out/zql/src/planner/planner-fan-out.d.ts +1 -0
  522. package/out/zql/src/planner/planner-fan-out.d.ts.map +1 -1
  523. package/out/zql/src/planner/planner-fan-out.js +3 -0
  524. package/out/zql/src/planner/planner-fan-out.js.map +1 -1
  525. package/out/zql/src/planner/planner-filter.d.ts +59 -0
  526. package/out/zql/src/planner/planner-filter.d.ts.map +1 -0
  527. package/out/zql/src/planner/planner-filter.js +116 -0
  528. package/out/zql/src/planner/planner-filter.js.map +1 -0
  529. package/out/zql/src/planner/planner-graph.d.ts +2 -0
  530. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  531. package/out/zql/src/planner/planner-graph.js +2 -0
  532. package/out/zql/src/planner/planner-graph.js.map +1 -1
  533. package/out/zql/src/planner/planner-join.d.ts +2 -0
  534. package/out/zql/src/planner/planner-join.d.ts.map +1 -1
  535. package/out/zql/src/planner/planner-join.js +7 -0
  536. package/out/zql/src/planner/planner-join.js.map +1 -1
  537. package/out/zql/src/planner/planner-node.d.ts +2 -1
  538. package/out/zql/src/planner/planner-node.d.ts.map +1 -1
  539. package/out/zql/src/planner/planner-node.js.map +1 -1
  540. package/out/zql/src/query/query-delegate-base.d.ts +9 -1
  541. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  542. package/out/zql/src/query/query-delegate-base.js +129 -33
  543. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  544. package/out/zql/src/query/query-delegate.d.ts +50 -1
  545. package/out/zql/src/query/query-delegate.d.ts.map +1 -1
  546. package/out/zql/src/query/query-impl.d.ts +13 -11
  547. package/out/zql/src/query/query-impl.d.ts.map +1 -1
  548. package/out/zql/src/query/query-impl.js +383 -60
  549. package/out/zql/src/query/query-impl.js.map +1 -1
  550. package/out/zql/src/query/query-transitions.d.ts +171 -0
  551. package/out/zql/src/query/query-transitions.d.ts.map +1 -0
  552. package/out/zql/src/query/query-transitions.js +207 -0
  553. package/out/zql/src/query/query-transitions.js.map +1 -0
  554. package/out/zql/src/query/query.d.ts +11 -2
  555. package/out/zql/src/query/query.d.ts.map +1 -1
  556. package/out/zql/src/query/runnable-query-impl.d.ts +1 -0
  557. package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
  558. package/out/zql/src/query/runnable-query-impl.js +21 -1
  559. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  560. package/out/zql/src/query/typed-view.d.ts +1 -1
  561. package/out/zql/src/query/typed-view.d.ts.map +1 -1
  562. package/out/zqlite/src/db.d.ts +9 -1
  563. package/out/zqlite/src/db.d.ts.map +1 -1
  564. package/out/zqlite/src/db.js +8 -2
  565. package/out/zqlite/src/db.js.map +1 -1
  566. package/out/zqlite/src/query-builder.d.ts +1 -1
  567. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  568. package/out/zqlite/src/query-builder.js +2 -1
  569. package/out/zqlite/src/query-builder.js.map +1 -1
  570. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  571. package/out/zqlite/src/sqlite-cost-model.js +2 -30
  572. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  573. package/out/zqlite/src/table-source.d.ts +1 -0
  574. package/out/zqlite/src/table-source.d.ts.map +1 -1
  575. package/out/zqlite/src/table-source.js +20 -8
  576. package/out/zqlite/src/table-source.js.map +1 -1
  577. package/package.json +3 -4
@@ -1,7 +1,8 @@
1
1
  import type { LogContext } from '@rocicorp/logger';
2
2
  import type { JSONObject } from '../../../../zero-events/src/json.ts';
3
- import type { ReplicationStage, ReplicationState, ReplicationStatusEvent, Status } from '../../../../zero-events/src/status.ts';
3
+ import type { IndexingStatus, ReplicationStage, ReplicationState, ReplicationStatusEvent, Status } from '../../../../zero-events/src/status.ts';
4
4
  import { Database } from '../../../../zqlite/src/db.ts';
5
+ import type { LiteIndexSpec } from '../../db/specs.ts';
5
6
  import { publishCriticalEvent } from '../../observability/events.ts';
6
7
  export declare class ReplicationStatusPublisher {
7
8
  #private;
@@ -10,9 +11,42 @@ export declare class ReplicationStatusPublisher {
10
11
  static forReplicaFile(file: string, publishFn?: typeof publishCriticalEvent): ReplicationStatusPublisher;
11
12
  constructor(dbRunner: <T>(lc: LogContext, fn: (db: Database) => T) => T, publishFn?: typeof publishCriticalEvent);
12
13
  publish(lc: LogContext, stage: ReplicationStage, description?: string, interval?: number, extraState?: () => Partial<ReplicationState>, now?: Date): this;
14
+ /**
15
+ * Like {@link publish}, but waits (for at most `maxWaitMs`) for the event
16
+ * to be sent. Use this before a long synchronous operation (e.g. creating
17
+ * an index) that blocks the event loop and would otherwise prevent the
18
+ * event from being sent until the operation completes.
19
+ */
20
+ publishAndFlush(lc: LogContext, stage: ReplicationStage, description?: string, interval?: number, extraState?: () => Partial<ReplicationState>, maxWaitMs?: number): Promise<void>;
13
21
  publishAndThrowError(lc: LogContext, stage: ReplicationStage, e: unknown): Promise<never>;
14
22
  stop(): this;
15
23
  }
24
+ /**
25
+ * Tracks the progress of a sequence of index creations for reporting in
26
+ * the `indexingStatus` field of {@link ReplicationState}.
27
+ */
28
+ export declare class IndexingProgress {
29
+ #private;
30
+ /**
31
+ * @param totalIndexes The total number of indexes to be created, if known.
32
+ */
33
+ constructor(totalIndexes?: number, now?: () => number);
34
+ /** Marks the start of creating the `index`. */
35
+ start(index: LiteIndexSpec): void;
36
+ /**
37
+ * Restarts the timer of the current index, e.g. to exclude time spent
38
+ * reporting its start from its creation time.
39
+ */
40
+ restartTimer(): void;
41
+ /**
42
+ * Marks the completion of the index passed to the last call to
43
+ * {@link start}, returning the milliseconds it took to create.
44
+ */
45
+ finish(): number;
46
+ /** The `extraState` for {@link ReplicationStatusPublisher.publish}. */
47
+ readonly state: () => Partial<ReplicationState>;
48
+ status(): IndexingStatus | undefined;
49
+ }
16
50
  export declare function publishReplicationError(lc: LogContext, stage: ReplicationStage, description: string, errorDetails?: JSONObject, now?: Date): Promise<void>;
17
51
  export declare function replicationStatusError(lc: LogContext, stage: ReplicationStage, e: unknown, db?: Database, now?: Date): ReplicationStatusEvent;
18
52
  export declare function replicationStatusEvent(lc: LogContext, db: Database | undefined, stage: ReplicationStage, status: Status, description?: string, now?: Date): ReplicationStatusEvent;
@@ -1 +1 @@
1
- {"version":3,"file":"replication-status.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/replication-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qCAAqC,CAAC;AACpE,OAAO,KAAK,EAGV,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,MAAM,EACP,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAC,QAAQ,EAAC,MAAM,8BAA8B,CAAC;AAGtD,OAAO,EAEL,oBAAoB,EACrB,MAAM,+BAA+B,CAAC;AAKvC,qBAAa,0BAA0B;;IAKrC,MAAM,CAAC,UAAU,+BAEhB;IAED,MAAM,CAAC,qBAAqB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,8BAAuB,8BAE1E;IAED,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,8BAAuB,8BASnE;IAED,YACE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,CAAC,KAAK,CAAC,EAC3D,SAAS,8BAAuB,EAIjC;IAED,OAAO,CACL,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,SAAI,EACZ,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,GAAG,OAAa,GACf,IAAI,CAqBN;IAEK,oBAAoB,CACxB,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,CAAC,EAAE,OAAO,GACT,OAAO,CAAC,KAAK,CAAC,CAOhB;IAED,IAAI,IAAI,IAAI,CAGX;CACF;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,UAAU,EACzB,GAAG,OAAa,iBAYjB;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,CAAC,EAAE,OAAO,EACV,EAAE,CAAC,EAAE,QAAQ,EACb,GAAG,OAAa,0BAKjB;AAGD,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,QAAQ,GAAG,SAAS,EACxB,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,GAAG,OAAa,GACf,sBAAsB,CAmCxB"}
1
+ {"version":3,"file":"replication-status.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/replication-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qCAAqC,CAAC;AACpE,OAAO,KAAK,EACV,cAAc,EAGd,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,MAAM,EACP,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAC,QAAQ,EAAC,MAAM,8BAA8B,CAAC;AAEtD,OAAO,KAAK,EAAC,aAAa,EAAgB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAEL,oBAAoB,EACrB,MAAM,+BAA+B,CAAC;AAKvC,qBAAa,0BAA0B;;IAKrC,MAAM,CAAC,UAAU,+BAEhB;IAED,MAAM,CAAC,qBAAqB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,8BAAuB,8BAE1E;IAED,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,8BAAuB,8BASnE;IAED,YACE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,CAAC,KAAK,CAAC,EAC3D,SAAS,8BAAuB,EAIjC;IAED,OAAO,CACL,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,SAAI,EACZ,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,GAAG,OAAa,GACf,IAAI,CAGN;IAED;;;;;OAKG;IACG,eAAe,CACnB,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,SAAI,EACZ,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,SAAS,SAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAmBf;IAgCK,oBAAoB,CACxB,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,CAAC,EAAE,OAAO,GACT,OAAO,CAAC,KAAK,CAAC,CAOhB;IAED,IAAI,IAAI,IAAI,CAGX;CACF;AAED;;;GAGG;AACH,qBAAa,gBAAgB;;IAS3B;;OAEG;IACH,YAAY,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,eAA0B,EAG/D;IAED,+CAA+C;IAC/C,KAAK,CAAC,KAAK,EAAE,aAAa,QAGzB;IAED;;;OAGG;IACH,YAAY,SAIX;IAED;;;OAGG;IACH,MAAM,IAAI,MAAM,CAUf;IAED,uEAAuE;IACvE,QAAQ,CAAC,KAAK,QAAO,OAAO,CAAC,gBAAgB,CAAC,CAG5C;IAEF,MAAM,IAAI,cAAc,GAAG,SAAS,CAoBnC;CACF;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,UAAU,EACzB,GAAG,OAAa,iBAYjB;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,gBAAgB,EACvB,CAAC,EAAE,OAAO,EACV,EAAE,CAAC,EAAE,QAAQ,EACb,GAAG,OAAa,0BAKjB;AAGD,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,QAAQ,GAAG,SAAS,EACxB,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,GAAG,OAAa,GACf,sBAAsB,CAmCxB"}
@@ -6,7 +6,7 @@ import { createSilentLogContext } from "../../../../shared/src/logging-test-util
6
6
  var byKeys = (a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
7
7
  var ReplicationStatusPublisher = class ReplicationStatusPublisher {
8
8
  #dbRunner;
9
- #publish;
9
+ #publishFn;
10
10
  #timer;
11
11
  static forTesting() {
12
12
  return ReplicationStatusPublisher.forReplicaFile(":memory:");
@@ -26,23 +26,44 @@ var ReplicationStatusPublisher = class ReplicationStatusPublisher {
26
26
  }
27
27
  constructor(dbRunner, publishFn = publishCriticalEvent) {
28
28
  this.#dbRunner = dbRunner;
29
- this.#publish = publishFn;
29
+ this.#publishFn = publishFn;
30
30
  }
31
31
  publish(lc, stage, description, interval = 0, extraState, now = /* @__PURE__ */ new Date()) {
32
+ this.#publish(lc, stage, description, interval, extraState, now);
33
+ return this;
34
+ }
35
+ /**
36
+ * Like {@link publish}, but waits (for at most `maxWaitMs`) for the event
37
+ * to be sent. Use this before a long synchronous operation (e.g. creating
38
+ * an index) that blocks the event loop and would otherwise prevent the
39
+ * event from being sent until the operation completes.
40
+ */
41
+ async publishAndFlush(lc, stage, description, interval = 0, extraState, maxWaitMs = 1e3) {
42
+ const published = this.#publish(lc, stage, description, interval, extraState);
43
+ let timer;
44
+ try {
45
+ await Promise.race([published, new Promise((resolve) => {
46
+ timer = setTimeout(resolve, maxWaitMs);
47
+ })]);
48
+ } finally {
49
+ clearTimeout(timer);
50
+ }
51
+ }
52
+ #publish(lc, stage, description, interval, extraState, now = /* @__PURE__ */ new Date()) {
32
53
  this.stop();
33
54
  const event = this.#dbRunner(lc, (db) => replicationStatusEvent(lc, db, stage, "OK", description, now));
34
55
  if (event.state) event.state = {
35
56
  ...event.state,
36
57
  ...extraState?.()
37
58
  };
38
- this.#publish(lc, event);
59
+ const published = this.#publishFn(lc, event);
39
60
  if (interval) this.#timer = setInterval(() => this.publish(lc, stage, description, interval, extraState), interval);
40
- return this;
61
+ return published;
41
62
  }
42
63
  async publishAndThrowError(lc, stage, e) {
43
64
  this.stop();
44
65
  const event = this.#dbRunner(lc, (db) => replicationStatusError(lc, stage, e, db));
45
- await this.#publish(lc, event);
66
+ await this.#publishFn(lc, event);
46
67
  throw e;
47
68
  }
48
69
  stop() {
@@ -50,6 +71,75 @@ var ReplicationStatusPublisher = class ReplicationStatusPublisher {
50
71
  return this;
51
72
  }
52
73
  };
74
+ /**
75
+ * Tracks the progress of a sequence of index creations for reporting in
76
+ * the `indexingStatus` field of {@link ReplicationState}.
77
+ */
78
+ var IndexingProgress = class {
79
+ #totalIndexes;
80
+ #now;
81
+ #count = 0;
82
+ #completedMs = 0;
83
+ #current;
84
+ /**
85
+ * @param totalIndexes The total number of indexes to be created, if known.
86
+ */
87
+ constructor(totalIndexes, now = () => performance.now()) {
88
+ this.#totalIndexes = totalIndexes;
89
+ this.#now = now;
90
+ }
91
+ /** Marks the start of creating the `index`. */
92
+ start(index) {
93
+ this.#count++;
94
+ this.#current = {
95
+ index,
96
+ start: this.#now(),
97
+ elapsedMs: void 0
98
+ };
99
+ }
100
+ /**
101
+ * Restarts the timer of the current index, e.g. to exclude time spent
102
+ * reporting its start from its creation time.
103
+ */
104
+ restartTimer() {
105
+ if (this.#current) this.#current.start = this.#now();
106
+ }
107
+ /**
108
+ * Marks the completion of the index passed to the last call to
109
+ * {@link start}, returning the milliseconds it took to create.
110
+ */
111
+ finish() {
112
+ const current = this.#current;
113
+ if (!current) return 0;
114
+ if (current.elapsedMs === void 0) {
115
+ current.elapsedMs = this.#now() - current.start;
116
+ this.#completedMs += current.elapsedMs;
117
+ }
118
+ return current.elapsedMs;
119
+ }
120
+ /** The `extraState` for {@link ReplicationStatusPublisher.publish}. */
121
+ state = () => {
122
+ const status = this.status();
123
+ return status ? { indexingStatus: status } : {};
124
+ };
125
+ status() {
126
+ const current = this.#current;
127
+ if (!current) return;
128
+ const { index, start, elapsedMs } = current;
129
+ const done = elapsedMs !== void 0;
130
+ return {
131
+ name: index.name,
132
+ table: index.tableName,
133
+ columns: Object.keys(index.columns),
134
+ unique: index.unique,
135
+ index: this.#count,
136
+ totalIndexes: this.#totalIndexes,
137
+ elapsedMs: Math.round(done ? elapsedMs : this.#now() - start),
138
+ completedMs: Math.round(done ? this.#completedMs - elapsedMs : this.#completedMs),
139
+ done
140
+ };
141
+ }
142
+ };
53
143
  async function publishReplicationError(lc, stage, description, errorDetails, now = /* @__PURE__ */ new Date()) {
54
144
  await publishCriticalEvent(lc, {
55
145
  type: "zero/events/status/replication/v1",
@@ -130,6 +220,6 @@ function getReplicaSize(db) {
130
220
  return pageCount * pageSize;
131
221
  }
132
222
  //#endregion
133
- export { ReplicationStatusPublisher, publishReplicationError, replicationStatusError };
223
+ export { IndexingProgress, ReplicationStatusPublisher, publishReplicationError, replicationStatusError };
134
224
 
135
225
  //# sourceMappingURL=replication-status.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"replication-status.js","names":["#dbRunner","#publish","#timer"],"sources":["../../../../../../zero-cache/src/services/replicator/replication-status.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {createSilentLogContext} from '../../../../shared/src/logging-test-utils.ts';\nimport type {JSONObject} from '../../../../zero-events/src/json.ts';\nimport type {\n ReplicatedIndex,\n ReplicatedTable,\n ReplicationStage,\n ReplicationState,\n ReplicationStatusEvent,\n Status,\n} from '../../../../zero-events/src/status.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {computeZqlSpecs, listIndexes} from '../../db/lite-tables.ts';\nimport type {LiteTableSpec} from '../../db/specs.ts';\nimport {\n makeErrorDetails,\n publishCriticalEvent,\n} from '../../observability/events.ts';\n\nconst byKeys = (a: [string, unknown], b: [string, unknown]) =>\n a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;\n\nexport class ReplicationStatusPublisher {\n readonly #dbRunner: <T>(lc: LogContext, fn: (db: Database) => T) => T;\n readonly #publish: typeof publishCriticalEvent;\n #timer: NodeJS.Timeout | undefined;\n\n static forTesting() {\n return ReplicationStatusPublisher.forReplicaFile(':memory:');\n }\n\n static forRunningTransaction(tx: Database, publishFn = publishCriticalEvent) {\n return new ReplicationStatusPublisher((_lc, fn) => fn(tx), publishFn);\n }\n\n static forReplicaFile(file: string, publishFn = publishCriticalEvent) {\n return new ReplicationStatusPublisher((lc, fn) => {\n const db = new Database(lc, file, {readonly: true});\n try {\n return fn(db);\n } finally {\n db.close();\n }\n }, publishFn);\n }\n\n constructor(\n dbRunner: <T>(lc: LogContext, fn: (db: Database) => T) => T,\n publishFn = publishCriticalEvent,\n ) {\n this.#dbRunner = dbRunner;\n this.#publish = publishFn;\n }\n\n publish(\n lc: LogContext,\n stage: ReplicationStage,\n description?: string,\n interval = 0,\n extraState?: () => Partial<ReplicationState>,\n now = new Date(),\n ): this {\n this.stop();\n\n const event = this.#dbRunner(lc, db =>\n replicationStatusEvent(lc, db, stage, 'OK', description, now),\n );\n if (event.state) {\n event.state = {\n ...event.state,\n ...extraState?.(),\n };\n }\n void this.#publish(lc, event);\n\n if (interval) {\n this.#timer = setInterval(\n () => this.publish(lc, stage, description, interval, extraState),\n interval,\n );\n }\n return this;\n }\n\n async publishAndThrowError(\n lc: LogContext,\n stage: ReplicationStage,\n e: unknown,\n ): Promise<never> {\n this.stop();\n const event = this.#dbRunner(lc, db =>\n replicationStatusError(lc, stage, e, db),\n );\n await this.#publish(lc, event);\n throw e;\n }\n\n stop(): this {\n clearInterval(this.#timer);\n return this;\n }\n}\n\nexport async function publishReplicationError(\n lc: LogContext,\n stage: ReplicationStage,\n description: string,\n errorDetails?: JSONObject,\n now = new Date(),\n) {\n const event: ReplicationStatusEvent = {\n type: 'zero/events/status/replication/v1',\n component: 'replication',\n status: 'ERROR',\n stage,\n description,\n errorDetails,\n time: now.toISOString(),\n };\n await publishCriticalEvent(lc, event);\n}\n\nexport function replicationStatusError(\n lc: LogContext,\n stage: ReplicationStage,\n e: unknown,\n db?: Database,\n now = new Date(),\n) {\n const event = replicationStatusEvent(lc, db, stage, 'ERROR', String(e), now);\n event.errorDetails = makeErrorDetails(e);\n return event;\n}\n\n// Exported for testing.\nexport function replicationStatusEvent(\n lc: LogContext,\n db: Database | undefined,\n stage: ReplicationStage,\n status: Status,\n description?: string,\n now = new Date(),\n): ReplicationStatusEvent {\n const start = performance.now();\n try {\n return {\n type: 'zero/events/status/replication/v1',\n component: 'replication',\n status,\n stage,\n description,\n time: now.toISOString(),\n state: {\n tables: db ? getReplicatedTables(db) : [],\n indexes: db ? getReplicatedIndexes(db) : [],\n replicaSize: db ? getReplicaSize(db) : undefined,\n },\n };\n } catch (e) {\n lc.warn?.(`Unable to create full ReplicationStatusEvent`, e);\n return {\n type: 'zero/events/status/replication/v1',\n component: 'replication',\n status,\n stage,\n description,\n time: now.toISOString(),\n state: {\n tables: [],\n indexes: [],\n replicaSize: 0,\n },\n };\n } finally {\n const elapsed = (performance.now() - start).toFixed(3);\n lc.debug?.(`computed schema for replication event (${elapsed} ms)`);\n }\n}\n\nfunction getReplicatedTables(db: Database): ReplicatedTable[] {\n const fullTables = new Map<string, LiteTableSpec>();\n const clientSchema = computeZqlSpecs(\n createSilentLogContext(), // avoid logging warnings about indexes\n db,\n {includeBackfillingColumns: false},\n new Map(),\n fullTables,\n );\n\n // oxlint-disable-next-line e18e/prefer-array-to-sorted\n return [...fullTables.entries()].sort(byKeys).map(([table, spec]) => ({\n table,\n columns: Object.entries(spec.columns)\n .sort(byKeys)\n .map(([column, spec]) => ({\n column,\n upstreamType: spec.dataType.split('|')[0],\n clientType: clientSchema.get(table)?.zqlSpec[column]?.type ?? null,\n })),\n }));\n}\n\nfunction getReplicatedIndexes(db: Database): ReplicatedIndex[] {\n return listIndexes(db).map(({tableName: table, columns, unique}) => ({\n table,\n unique,\n columns: Object.entries(columns)\n .sort(byKeys)\n .map(([column, dir]) => ({column, dir})),\n }));\n}\n\nfunction getReplicaSize(db: Database) {\n const [{page_count: pageCount}] = db.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{page_size: pageSize}] = db.pragma<{page_size: number}>('page_size');\n return pageCount * pageSize;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"replication-status.js","names":["#dbRunner","#publishFn","#publish","#timer","#totalIndexes","#now","#count","#current","#completedMs"],"sources":["../../../../../../zero-cache/src/services/replicator/replication-status.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {createSilentLogContext} from '../../../../shared/src/logging-test-utils.ts';\nimport type {JSONObject} from '../../../../zero-events/src/json.ts';\nimport type {\n IndexingStatus,\n ReplicatedIndex,\n ReplicatedTable,\n ReplicationStage,\n ReplicationState,\n ReplicationStatusEvent,\n Status,\n} from '../../../../zero-events/src/status.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {computeZqlSpecs, listIndexes} from '../../db/lite-tables.ts';\nimport type {LiteIndexSpec, LiteTableSpec} from '../../db/specs.ts';\nimport {\n makeErrorDetails,\n publishCriticalEvent,\n} from '../../observability/events.ts';\n\nconst byKeys = (a: [string, unknown], b: [string, unknown]) =>\n a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;\n\nexport class ReplicationStatusPublisher {\n readonly #dbRunner: <T>(lc: LogContext, fn: (db: Database) => T) => T;\n readonly #publishFn: typeof publishCriticalEvent;\n #timer: NodeJS.Timeout | undefined;\n\n static forTesting() {\n return ReplicationStatusPublisher.forReplicaFile(':memory:');\n }\n\n static forRunningTransaction(tx: Database, publishFn = publishCriticalEvent) {\n return new ReplicationStatusPublisher((_lc, fn) => fn(tx), publishFn);\n }\n\n static forReplicaFile(file: string, publishFn = publishCriticalEvent) {\n return new ReplicationStatusPublisher((lc, fn) => {\n const db = new Database(lc, file, {readonly: true});\n try {\n return fn(db);\n } finally {\n db.close();\n }\n }, publishFn);\n }\n\n constructor(\n dbRunner: <T>(lc: LogContext, fn: (db: Database) => T) => T,\n publishFn = publishCriticalEvent,\n ) {\n this.#dbRunner = dbRunner;\n this.#publishFn = publishFn;\n }\n\n publish(\n lc: LogContext,\n stage: ReplicationStage,\n description?: string,\n interval = 0,\n extraState?: () => Partial<ReplicationState>,\n now = new Date(),\n ): this {\n void this.#publish(lc, stage, description, interval, extraState, now);\n return this;\n }\n\n /**\n * Like {@link publish}, but waits (for at most `maxWaitMs`) for the event\n * to be sent. Use this before a long synchronous operation (e.g. creating\n * an index) that blocks the event loop and would otherwise prevent the\n * event from being sent until the operation completes.\n */\n async publishAndFlush(\n lc: LogContext,\n stage: ReplicationStage,\n description?: string,\n interval = 0,\n extraState?: () => Partial<ReplicationState>,\n maxWaitMs = 1000,\n ): Promise<void> {\n const published = this.#publish(\n lc,\n stage,\n description,\n interval,\n extraState,\n );\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n published,\n new Promise<void>(resolve => {\n timer = setTimeout(resolve, maxWaitMs);\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n }\n\n #publish(\n lc: LogContext,\n stage: ReplicationStage,\n description: string | undefined,\n interval: number,\n extraState: (() => Partial<ReplicationState>) | undefined,\n now = new Date(),\n ): Promise<void> {\n this.stop();\n\n const event = this.#dbRunner(lc, db =>\n replicationStatusEvent(lc, db, stage, 'OK', description, now),\n );\n if (event.state) {\n event.state = {\n ...event.state,\n ...extraState?.(),\n };\n }\n const published = this.#publishFn(lc, event);\n\n if (interval) {\n this.#timer = setInterval(\n () => this.publish(lc, stage, description, interval, extraState),\n interval,\n );\n }\n return published;\n }\n\n async publishAndThrowError(\n lc: LogContext,\n stage: ReplicationStage,\n e: unknown,\n ): Promise<never> {\n this.stop();\n const event = this.#dbRunner(lc, db =>\n replicationStatusError(lc, stage, e, db),\n );\n await this.#publishFn(lc, event);\n throw e;\n }\n\n stop(): this {\n clearInterval(this.#timer);\n return this;\n }\n}\n\n/**\n * Tracks the progress of a sequence of index creations for reporting in\n * the `indexingStatus` field of {@link ReplicationState}.\n */\nexport class IndexingProgress {\n readonly #totalIndexes: number | undefined;\n readonly #now: () => number;\n #count = 0;\n #completedMs = 0;\n #current:\n | {index: LiteIndexSpec; start: number; elapsedMs: number | undefined}\n | undefined;\n\n /**\n * @param totalIndexes The total number of indexes to be created, if known.\n */\n constructor(totalIndexes?: number, now = () => performance.now()) {\n this.#totalIndexes = totalIndexes;\n this.#now = now;\n }\n\n /** Marks the start of creating the `index`. */\n start(index: LiteIndexSpec) {\n this.#count++;\n this.#current = {index, start: this.#now(), elapsedMs: undefined};\n }\n\n /**\n * Restarts the timer of the current index, e.g. to exclude time spent\n * reporting its start from its creation time.\n */\n restartTimer() {\n if (this.#current) {\n this.#current.start = this.#now();\n }\n }\n\n /**\n * Marks the completion of the index passed to the last call to\n * {@link start}, returning the milliseconds it took to create.\n */\n finish(): number {\n const current = this.#current;\n if (!current) {\n return 0;\n }\n if (current.elapsedMs === undefined) {\n current.elapsedMs = this.#now() - current.start;\n this.#completedMs += current.elapsedMs;\n }\n return current.elapsedMs;\n }\n\n /** The `extraState` for {@link ReplicationStatusPublisher.publish}. */\n readonly state = (): Partial<ReplicationState> => {\n const status = this.status();\n return status ? {indexingStatus: status} : {};\n };\n\n status(): IndexingStatus | undefined {\n const current = this.#current;\n if (!current) {\n return undefined;\n }\n const {index, start, elapsedMs} = current;\n const done = elapsedMs !== undefined;\n return {\n name: index.name,\n table: index.tableName,\n columns: Object.keys(index.columns),\n unique: index.unique,\n index: this.#count,\n totalIndexes: this.#totalIndexes,\n elapsedMs: Math.round(done ? elapsedMs : this.#now() - start),\n completedMs: Math.round(\n done ? this.#completedMs - elapsedMs : this.#completedMs,\n ),\n done,\n };\n }\n}\n\nexport async function publishReplicationError(\n lc: LogContext,\n stage: ReplicationStage,\n description: string,\n errorDetails?: JSONObject,\n now = new Date(),\n) {\n const event: ReplicationStatusEvent = {\n type: 'zero/events/status/replication/v1',\n component: 'replication',\n status: 'ERROR',\n stage,\n description,\n errorDetails,\n time: now.toISOString(),\n };\n await publishCriticalEvent(lc, event);\n}\n\nexport function replicationStatusError(\n lc: LogContext,\n stage: ReplicationStage,\n e: unknown,\n db?: Database,\n now = new Date(),\n) {\n const event = replicationStatusEvent(lc, db, stage, 'ERROR', String(e), now);\n event.errorDetails = makeErrorDetails(e);\n return event;\n}\n\n// Exported for testing.\nexport function replicationStatusEvent(\n lc: LogContext,\n db: Database | undefined,\n stage: ReplicationStage,\n status: Status,\n description?: string,\n now = new Date(),\n): ReplicationStatusEvent {\n const start = performance.now();\n try {\n return {\n type: 'zero/events/status/replication/v1',\n component: 'replication',\n status,\n stage,\n description,\n time: now.toISOString(),\n state: {\n tables: db ? getReplicatedTables(db) : [],\n indexes: db ? getReplicatedIndexes(db) : [],\n replicaSize: db ? getReplicaSize(db) : undefined,\n },\n };\n } catch (e) {\n lc.warn?.(`Unable to create full ReplicationStatusEvent`, e);\n return {\n type: 'zero/events/status/replication/v1',\n component: 'replication',\n status,\n stage,\n description,\n time: now.toISOString(),\n state: {\n tables: [],\n indexes: [],\n replicaSize: 0,\n },\n };\n } finally {\n const elapsed = (performance.now() - start).toFixed(3);\n lc.debug?.(`computed schema for replication event (${elapsed} ms)`);\n }\n}\n\nfunction getReplicatedTables(db: Database): ReplicatedTable[] {\n const fullTables = new Map<string, LiteTableSpec>();\n const clientSchema = computeZqlSpecs(\n createSilentLogContext(), // avoid logging warnings about indexes\n db,\n {includeBackfillingColumns: false},\n new Map(),\n fullTables,\n );\n\n // oxlint-disable-next-line e18e/prefer-array-to-sorted\n return [...fullTables.entries()].sort(byKeys).map(([table, spec]) => ({\n table,\n columns: Object.entries(spec.columns)\n .sort(byKeys)\n .map(([column, spec]) => ({\n column,\n upstreamType: spec.dataType.split('|')[0],\n clientType: clientSchema.get(table)?.zqlSpec[column]?.type ?? null,\n })),\n }));\n}\n\nfunction getReplicatedIndexes(db: Database): ReplicatedIndex[] {\n return listIndexes(db).map(({tableName: table, columns, unique}) => ({\n table,\n unique,\n columns: Object.entries(columns)\n .sort(byKeys)\n .map(([column, dir]) => ({column, dir})),\n }));\n}\n\nfunction getReplicaSize(db: Database) {\n const [{page_count: pageCount}] = db.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{page_size: pageSize}] = db.pragma<{page_size: number}>('page_size');\n return pageCount * pageSize;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.js","names":["#lc","#incrementalSyncer","#shouldPublishErrors","#worker","#runPromise"],"sources":["../../../../../../zero-cache/src/services/replicator/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {ReadonlyJSONObject} from '../../../../shared/src/json.ts';\nimport type {Source} from '../../types/streams.ts';\nimport type {ChangeStreamer} from '../change-streamer/change-streamer.ts';\nimport type {Service} from '../service.ts';\nimport {IncrementalSyncer} from './incremental-sync.ts';\nimport {\n publishReplicationError,\n type ReplicationStatusPublisher,\n} from './replication-status.ts';\nimport type {WriteWorkerClient} from './write-worker-client.ts';\n\n/** See {@link ReplicaStateNotifier.subscribe()}. */\nexport type ReplicaState = {\n readonly state: 'version-ready';\n /**\n * The replica watermark that became ready. Omitted for the initial\n * \"current state is ready\" notification, which should not count as newly\n * unserved work for a freshly-started ViewSyncer.\n */\n readonly watermark?: string | undefined;\n /**\n * Millisecond epoch when `watermark` became ready to read from the replica.\n */\n readonly replicaReadyTimeMs?: number | undefined;\n\n /**\n * Millisecond epoch at which the transaction behind `watermark` committed\n * *upstream*, as reported by the ChangeSource. Unlike `replicaReadyTimeMs`,\n * which is stamped locally when the change lands in the replica, this is the\n * origin of the whole pipeline, so `now - upstreamCommitTimeMs` at the point\n * the change is poked to clients is the end-to-end serving lag.\n *\n * Note that this crosses a clock domain: it comes from the upstream\n * database, whereas it is subtracted from the local clock in the ViewSyncer.\n * The existing `zero.replication.total_lag` metric spans the same boundary.\n *\n * Undefined when the ChangeSource does not report a commit time, and for the\n * initial \"current state is ready\" notification.\n */\n readonly upstreamCommitTimeMs?: number | undefined;\n\n // Used in tests to verify behavior when additional information\n // is ferried in the future. Not set in production.\n readonly testSeqNum?: number | undefined;\n};\n\nexport interface ReplicaStateNotifier {\n /**\n * Creates a cancelable subscription of changes in the replica state.\n *\n * A `version-ready` message indicates that the replica is ready to be\n * read, and henceforth that a _new_ version is ready, i.e. whenever a\n * change is committed to the replica. The `version-ready` message itself\n * otherwise contains no other information; the subscriber queries the\n * replica for the current data.\n *\n * A `maintenance` state indicates that the replica should not be read from.\n * If a subscriber is holding any transaction locks, it should release them\n * until the next `version-ready` signal.\n *\n * Upon subscription, the current state of the replica is sent immediately\n * if known. If multiple notifications occur before the subscriber\n * can consume them, all but the last notification are discarded by the\n * Subscription object (i.e. not buffered). Thus, a subscriber only\n * ever consumes the current (i.e. known) state of the replica. This avoids\n * a buildup of \"work\" if a subscriber is too busy to consume all\n * notifications.\n */\n subscribe(): Source<ReplicaState>;\n}\n\nexport interface Replicator extends ReplicaStateNotifier {\n /**\n * Returns an opaque message for human-readable consumption. This is\n * purely for ensuring that the Replicator has started at least once to\n * bootstrap a new replica.\n */\n status(): Promise<ReadonlyJSONObject>;\n}\n\nexport type ReplicatorMode = 'backup' | 'serving';\n\nexport class ReplicatorService implements Replicator, Service {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #incrementalSyncer: IncrementalSyncer;\n readonly #shouldPublishErrors: boolean;\n readonly #worker: WriteWorkerClient;\n #runPromise: Promise<void> | undefined;\n\n constructor(\n lc: LogContext,\n taskID: string,\n id: string,\n mode: ReplicatorMode,\n changeStreamer: ChangeStreamer,\n worker: WriteWorkerClient,\n statusPublisher: ReplicationStatusPublisher | null,\n ) {\n this.id = id;\n this.#lc = lc\n .withContext('component', 'replicator')\n .withContext('serviceID', this.id);\n this.#worker = worker;\n this.#shouldPublishErrors = statusPublisher !== null;\n\n this.#incrementalSyncer = new IncrementalSyncer(\n this.#lc,\n taskID,\n `${taskID}/${id}`,\n changeStreamer,\n worker,\n mode,\n statusPublisher,\n );\n }\n\n status() {\n return Promise.resolve({status: 'ok'});\n }\n\n run() {\n this.#runPromise = this.#incrementalSyncer.run().catch(async error => {\n if (this.#shouldPublishErrors) {\n await publishReplicationError(\n this.#lc,\n 'Replicating',\n 'Replication stopped because the replica writer failed',\n );\n }\n throw error;\n });\n return this.#runPromise;\n }\n\n subscribe(): Source<ReplicaState> {\n return this.#incrementalSyncer.subscribe();\n }\n\n async stop() {\n this.#incrementalSyncer.stop(this.#lc);\n // Wait for the syncer's run loop to finish so that any in-flight\n // worker.processMessage() call completes and clears #pending\n // before we send the 'stop' message to the worker.\n await this.#runPromise;\n await this.#worker.stop();\n }\n}\n"],"mappings":";;;AAmFA,IAAa,oBAAb,MAA8D;CAC5D;CACA;CACA;CACA;CACA;CACA;CAEA,YACE,IACA,QACA,IACA,MACA,gBACA,QACA,iBACA;EACA,KAAK,KAAK;EACV,KAAKA,MAAM,GACR,YAAY,aAAa,YAAY,EACrC,YAAY,aAAa,KAAK,EAAE;EACnC,KAAKG,UAAU;EACf,KAAKD,uBAAuB,oBAAoB;EAEhD,KAAKD,qBAAqB,IAAI,kBAC5B,KAAKD,KACL,QACA,GAAG,OAAO,GAAG,MACb,gBACA,QACA,MACA,eACF;CACF;CAEA,SAAS;EACP,OAAO,QAAQ,QAAQ,EAAC,QAAQ,KAAI,CAAC;CACvC;CAEA,MAAM;EACJ,KAAKI,cAAc,KAAKH,mBAAmB,IAAI,EAAE,MAAM,OAAM,UAAS;GACpE,IAAI,KAAKC,sBACP,MAAM,wBACJ,KAAKF,KACL,eACA,uDACF;GAEF,MAAM;EACR,CAAC;EACD,OAAO,KAAKI;CACd;CAEA,YAAkC;EAChC,OAAO,KAAKH,mBAAmB,UAAU;CAC3C;CAEA,MAAM,OAAO;EACX,KAAKA,mBAAmB,KAAK,KAAKD,GAAG;EAIrC,MAAM,KAAKI;EACX,MAAM,KAAKD,QAAQ,KAAK;CAC1B;AACF"}
1
+ {"version":3,"file":"replicator.js","names":["#lc","#incrementalSyncer","#shouldPublishErrors","#worker","#runPromise"],"sources":["../../../../../../zero-cache/src/services/replicator/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {ReadonlyJSONObject} from '../../../../shared/src/json.ts';\nimport type {Source} from '../../types/streams.ts';\nimport type {ChangeStreamer} from '../change-streamer/change-streamer.ts';\nimport type {Service} from '../service.ts';\nimport {IncrementalSyncer} from './incremental-sync.ts';\nimport {\n publishReplicationError,\n type ReplicationStatusPublisher,\n} from './replication-status.ts';\nimport type {WriteWorkerClient} from './write-worker-client.ts';\n\n/** See {@link ReplicaStateNotifier.subscribe()}. */\nexport type ReplicaState = {\n readonly state: 'version-ready';\n /**\n * The replica watermark that became ready. Omitted for the initial\n * \"current state is ready\" notification, which should not count as newly\n * unserved work for a freshly-started ViewSyncer.\n */\n readonly watermark?: string | undefined;\n /**\n * Millisecond epoch when `watermark` became ready to read from the replica.\n */\n readonly replicaReadyTimeMs?: number | undefined;\n\n /**\n * Millisecond epoch at which the transaction behind `watermark` committed\n * *upstream*, as reported by the ChangeSource. Unlike `replicaReadyTimeMs`,\n * which is stamped locally when the change lands in the replica, this is the\n * origin of the whole pipeline, so `now - upstreamCommitTimeMs` at the point\n * the change is poked to clients is the end-to-end serving lag.\n *\n * Note that this crosses a clock domain: it comes from the upstream\n * database, whereas it is subtracted from the local clock in the ViewSyncer.\n * The existing `zero.replication.total_lag` metric spans the same boundary.\n *\n * Undefined when the ChangeSource does not report a commit time, and for the\n * initial \"current state is ready\" notification.\n */\n readonly upstreamCommitTimeMs?: number | undefined;\n\n // Used in tests to verify behavior when additional information\n // is ferried in the future. Not set in production.\n readonly testSeqNum?: number | undefined;\n};\n\nexport interface ReplicaStateNotifier {\n /**\n * Creates a cancelable subscription of changes in the replica state.\n *\n * A `version-ready` message indicates that the replica is ready to be\n * read, and henceforth that a _new_ version is ready, i.e. whenever a\n * change is committed to the replica. The `version-ready` message itself\n * otherwise contains no other information; the subscriber queries the\n * replica for the current data.\n *\n * A `maintenance` state indicates that the replica should not be read from.\n * If a subscriber is holding any transaction locks, it should release them\n * until the next `version-ready` signal.\n *\n * Upon subscription, the current state of the replica is sent immediately\n * if known. If multiple notifications occur before the subscriber\n * can consume them, all but the last notification are discarded by the\n * Subscription object (i.e. not buffered). Thus, a subscriber only\n * ever consumes the current (i.e. known) state of the replica. This avoids\n * a buildup of \"work\" if a subscriber is too busy to consume all\n * notifications.\n */\n subscribe(): Source<ReplicaState>;\n}\n\nexport interface Replicator extends ReplicaStateNotifier {\n /**\n * Returns an opaque message for human-readable consumption. This is\n * purely for ensuring that the Replicator has started at least once to\n * bootstrap a new replica.\n */\n status(): Promise<ReadonlyJSONObject>;\n}\n\nexport type ReplicatorMode = 'backup' | 'serving';\n\nexport class ReplicatorService implements Replicator, Service {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #incrementalSyncer: IncrementalSyncer;\n readonly #shouldPublishErrors: boolean;\n readonly #worker: WriteWorkerClient;\n #runPromise: Promise<void> | undefined;\n\n constructor(\n lc: LogContext,\n taskID: string,\n id: string,\n mode: ReplicatorMode,\n changeStreamer: ChangeStreamer,\n worker: WriteWorkerClient,\n statusPublisher: ReplicationStatusPublisher | null,\n ) {\n this.id = id;\n this.#lc = lc\n .withContext('component', 'replicator')\n .withContext('serviceID', this.id);\n this.#worker = worker;\n this.#shouldPublishErrors = statusPublisher !== null;\n\n this.#incrementalSyncer = new IncrementalSyncer(\n this.#lc,\n taskID,\n `${taskID}/${id}`,\n changeStreamer,\n worker,\n mode,\n statusPublisher,\n );\n }\n\n status() {\n return Promise.resolve({status: 'ok'});\n }\n\n run() {\n this.#runPromise = this.#incrementalSyncer.run().catch(async error => {\n if (this.#shouldPublishErrors) {\n await publishReplicationError(\n this.#lc,\n 'Replicating',\n 'Replication stopped because the replica writer failed',\n );\n }\n throw error;\n });\n return this.#runPromise;\n }\n\n subscribe(): Source<ReplicaState> {\n return this.#incrementalSyncer.subscribe();\n }\n\n async stop() {\n this.#incrementalSyncer.stop(this.#lc);\n // Wait for the syncer's run loop to finish so that any in-flight\n // worker.processMessages() call completes and clears #pending\n // before we send the 'stop' message to the worker.\n await this.#runPromise;\n await this.#worker.stop();\n }\n}\n"],"mappings":";;;AAmFA,IAAa,oBAAb,MAA8D;CAC5D;CACA;CACA;CACA;CACA;CACA;CAEA,YACE,IACA,QACA,IACA,MACA,gBACA,QACA,iBACA;EACA,KAAK,KAAK;EACV,KAAKA,MAAM,GACR,YAAY,aAAa,YAAY,EACrC,YAAY,aAAa,KAAK,EAAE;EACnC,KAAKG,UAAU;EACf,KAAKD,uBAAuB,oBAAoB;EAEhD,KAAKD,qBAAqB,IAAI,kBAC5B,KAAKD,KACL,QACA,GAAG,OAAO,GAAG,MACb,gBACA,QACA,MACA,eACF;CACF;CAEA,SAAS;EACP,OAAO,QAAQ,QAAQ,EAAC,QAAQ,KAAI,CAAC;CACvC;CAEA,MAAM;EACJ,KAAKI,cAAc,KAAKH,mBAAmB,IAAI,EAAE,MAAM,OAAM,UAAS;GACpE,IAAI,KAAKC,sBACP,MAAM,wBACJ,KAAKF,KACL,eACA,uDACF;GAEF,MAAM;EACR,CAAC;EACD,OAAO,KAAKI;CACd;CAEA,YAAkC;EAChC,OAAO,KAAKH,mBAAmB,UAAU;CAC3C;CAEA,MAAM,OAAO;EACX,KAAKA,mBAAmB,KAAK,KAAKD,GAAG;EAIrC,MAAM,KAAKI;EACX,MAAM,KAAKD,QAAQ,KAAK;CAC1B;AACF"}
@@ -102,14 +102,14 @@ export declare class SQLiteChangeLogObserver {
102
102
  #private;
103
103
  constructor(lc: LogContext, info: SQLiteChangeLogInfo);
104
104
  /**
105
- * Called as each message arrives, before it is written. Hashes the *received*
106
- * stream, which is compared against the hash the writer computes over what it
107
- * *stored*.
105
+ * Called as each message arrives, before it is written. For a sampled
106
+ * transaction, hashes the *received* stream and compares it with the hash the
107
+ * writer computes over what it *stored*.
108
108
  */
109
- messageReceived(data: ChangeStreamData, json: string): void;
109
+ messageReceived(data: ChangeStreamData, json: string, validateHash?: boolean, storedChange?: string): void;
110
110
  /** Called after the message has been written to the log. */
111
- messageProcessed(data: ChangeStreamData, commit: ChangeLogCommit | null, durationMs: number): void;
112
- messageFailed(data: ChangeStreamData, error: unknown, durationMs: number): void;
111
+ messageProcessed(data: ChangeStreamData, commit: ChangeLogCommit | null, durationMs: number | undefined): void;
112
+ messageFailed(data: ChangeStreamData, error: unknown, durationMs: number | undefined): void;
113
113
  /**
114
114
  * A committed transaction folded schema changes into the cookie jar. Reported
115
115
  * separately from {@link messageProcessed} because it costs a read of the
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-change-log-observability.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/sqlite-change-log-observability.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-change-log-observability.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/sqlite-change-log-observability.ts"],"names":[],"mappings":"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"}
@@ -137,7 +137,7 @@ var TRANSACTION_ROW_BUCKETS = [
137
137
  */
138
138
  var SQLiteChangeLogObserver = class {
139
139
  #lc;
140
- #messageProcessing = getOrCreateLatencyHistogram("replica", "sqlite_change_log.message_processing_duration", "Time to write a replication message to the SQLite change log.");
140
+ #messageProcessing = getOrCreateLatencyHistogram("replica", "sqlite_change_log.message_processing_duration", "Sampled time to write a replication message to the SQLite change log.");
141
141
  #logCommitDuration = getOrCreateLatencyHistogram("replica", "sqlite_change_log.log_commit_duration", "Time to append the commit row to the SQLite change log and commit its transaction. This is on the forward path: it precedes the forward of that transaction's commit message.");
142
142
  #transactionRows = getOrCreateValueHistogram("replica", "sqlite_change_log.transaction_rows", {
143
143
  description: "Rows stored per committed SQLite change-log transaction.",
@@ -146,7 +146,7 @@ var SQLiteChangeLogObserver = class {
146
146
  });
147
147
  #rollbackCounter = getOrCreateCounter("replica", "sqlite_change_log.rollbacks", "SQLite change-log transactions rolled back before commit.");
148
148
  #invariantFailureCounter = getOrCreateCounter("replica", "sqlite_change_log.invariant_failures", "Detected SQLite change-log writer invariant failures.");
149
- #hashComparisonCounter = getOrCreateCounter("replica", "sqlite_change_log.hash_comparisons", "Ephemeral transaction hash comparisons between the received change stream and SQLite change-log writes.");
149
+ #hashComparisonCounter = getOrCreateCounter("replica", "sqlite_change_log.hash_comparisons", "Sampled ephemeral transaction hash comparisons between the received change stream and SQLite change-log writes.");
150
150
  #hashUnpairedCounter = getOrCreateCounter("replica", "sqlite_change_log.hash_unpaired", "Committed change-log transactions without both ephemeral hashes available for comparison.");
151
151
  #cookieMutationCounter = getOrCreateCounter("replica", "sqlite_change_log.cookie_mutations", "Change-log cookie mutations folded from schema changes, by operation.");
152
152
  #receivedHead;
@@ -190,23 +190,23 @@ var SQLiteChangeLogObserver = class {
190
190
  });
191
191
  }
192
192
  /**
193
- * Called as each message arrives, before it is written. Hashes the *received*
194
- * stream, which is compared against the hash the writer computes over what it
195
- * *stored*.
193
+ * Called as each message arrives, before it is written. For a sampled
194
+ * transaction, hashes the *received* stream and compares it with the hash the
195
+ * writer computes over what it *stored*.
196
196
  */
197
- messageReceived(data, json) {
197
+ messageReceived(data, json, validateHash = true, storedChange = extractChangeSubstring(json, data[1].tag)) {
198
198
  const tag = data[1].tag;
199
199
  if (data[0] === "begin") {
200
200
  if (this.#sourceHasher !== void 0) this.#invariantFailure("Received change-log begin while a source hash is already open", { receivedWatermark: data[2].commitWatermark });
201
201
  const watermark = data[2].commitWatermark;
202
- this.#sourceHasher = new ChangeLogTransactionHasher();
202
+ this.#sourceHasher = validateHash ? new ChangeLogTransactionHasher() : void 0;
203
203
  this.#sourcePos = 0;
204
204
  this.#sourceCommitHash = void 0;
205
- this.#sourceHasher.add({
205
+ this.#sourceHasher?.add({
206
206
  watermark,
207
207
  pos: this.#sourcePos,
208
208
  tag,
209
- change: extractChangeSubstring(json, tag),
209
+ change: storedChange,
210
210
  precommit: null
211
211
  });
212
212
  return;
@@ -216,6 +216,7 @@ var SQLiteChangeLogObserver = class {
216
216
  return;
217
217
  }
218
218
  if (data[0] === "commit") this.#receivedHead = data[2].watermark;
219
+ if (!validateHash) return;
219
220
  const watermark = this.#transactionWatermark;
220
221
  if (this.#sourceHasher === void 0 || watermark === void 0) {
221
222
  this.#invariantFailure("Received change-log message without an observed transaction", { tag });
@@ -226,7 +227,7 @@ var SQLiteChangeLogObserver = class {
226
227
  watermark: commitWatermark,
227
228
  pos: ++this.#sourcePos,
228
229
  tag,
229
- change: extractChangeSubstring(json, tag),
230
+ change: storedChange,
230
231
  precommit: data[0] === "commit" ? watermark : null
231
232
  });
232
233
  if (data[0] === "commit") {
@@ -237,7 +238,7 @@ var SQLiteChangeLogObserver = class {
237
238
  /** Called after the message has been written to the log. */
238
239
  messageProcessed(data, commit, durationMs) {
239
240
  const tag = data[1].tag;
240
- this.#messageProcessing.recordMs(durationMs, {
241
+ if (durationMs !== void 0) this.#messageProcessing.recordMs(durationMs, {
241
242
  tag,
242
243
  outcome: "success"
243
244
  });
@@ -273,7 +274,7 @@ var SQLiteChangeLogObserver = class {
273
274
  this.#rows += commit.stats.rows;
274
275
  this.#estimatedBytes += commit.stats.estimatedBytes;
275
276
  this.#transactionRows.record(commit.stats.rows);
276
- this.#compareHashes(watermark, this.#sourceCommitHash, commit.stats.hash);
277
+ if (this.#sourceCommitHash !== void 0 || commit.stats.hash !== void 0) this.#compareHashes(watermark, this.#sourceCommitHash, commit.stats.hash);
277
278
  }
278
279
  if (watermark < this.#sqliteHead) this.#invariantFailure("SQLite change-log head regressed", {
279
280
  previousHead: this.#sqliteHead,
@@ -285,11 +286,11 @@ var SQLiteChangeLogObserver = class {
285
286
  }
286
287
  messageFailed(data, error, durationMs) {
287
288
  const tag = data[1].tag;
288
- this.#messageProcessing.recordMs(durationMs, {
289
+ if (durationMs !== void 0) this.#messageProcessing.recordMs(durationMs, {
289
290
  tag,
290
291
  outcome: "error"
291
292
  });
292
- if (data[0] === "commit") this.#recordUnpaired(data[2].watermark, "sqlite-commit-failed");
293
+ if (data[0] === "commit" && this.#sourceCommitHash !== void 0) this.#recordUnpaired(data[2].watermark, "sqlite-commit-failed");
293
294
  if (error instanceof Error && error.name === "ChangeLogStreamInvariantError") this.#invariantFailure(error.message);
294
295
  if (this.#transactionWatermark !== void 0 || data[0] === "begin") this.#recordRollback("processing-error");
295
296
  this.#transactionWatermark = void 0;
@@ -364,6 +365,10 @@ var SQLiteChangeLogObserver = class {
364
365
  this.#recordUnpaired(watermark, "missing-source-hash");
365
366
  return;
366
367
  }
368
+ if (sqliteHash === void 0) {
369
+ this.#recordUnpaired(watermark, "missing-sqlite-hash");
370
+ return;
371
+ }
367
372
  if (sourceHash === sqliteHash) {
368
373
  this.#hashMatches++;
369
374
  this.#hashComparisonCounter.add(1, { outcome: "match" });
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-change-log-observability.js","names":["#lc","#messageProcessing","#logCommitDuration","#transactionRows","#rollbackCounter","#invariantFailureCounter","#hashComparisonCounter","#hashUnpairedCounter","#cookieMutationCounter","#receivedHead","#sqliteHead","#rows","#estimatedBytes","#cookieRows","#sourceHasher","#invariantFailure","#sourcePos","#sourceCommitHash","#resetSourceHash","#transactionWatermark","#recordRollback","#recordUnpaired","#compareHashes","#cookieMutations","#rollbacks","#invariantFailures","#hashMatches","#hashMismatches","#hashUnpaired"],"sources":["../../../../../../zero-cache/src/services/replicator/sqlite-change-log-observability.ts"],"sourcesContent":["/**\n * Observability for the SQLite change log.\n *\n * Everything here now runs in the **change-streamer** process, which is where\n * the writer lives. The module stays beside the rest of the change-log code\n * under `services/replicator/` for the same reason `change-log-db.ts` and\n * `change-log-stream-writer.ts` do: moving files would obscure the topology\n * diff, and the writer's home is a property of who calls it rather than of\n * where it sits. Metric names are unchanged from 7H — only their emitting\n * process, and therefore their resource attributes, moved.\n */\n\nimport {stat} from 'node:fs/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../../shared/src/asserts.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateLatencyHistogram,\n getOrCreateValueHistogram,\n} from '../../observability/metrics.ts';\nimport {versionFromLexi} from '../../types/lexi-version.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport {extractChangeSubstring} from '../change-streamer/change-log-codec.ts';\nimport {ChangeLogTransactionHasher} from '../change-streamer/change-log-transaction-hash.ts';\nimport {\n readCookieRowCounts,\n type CookieOpTag,\n type CookieRowCounts,\n} from './change-log-cookies.ts';\nimport {\n CHANGE_LOG_STREAM_TABLE,\n readChangeLogBounds,\n readChangeLogMeta,\n type ChangeLogMeta,\n type ReconcileResult,\n} from './change-log-db.ts';\nimport type {ChangeLogStreamTransactionStats} from './change-log-stream-writer.ts';\n\n/**\n * Reports the reconciliation of the change-log database against the watermark\n * the stream connection resumes from. This runs once per stream connection, so\n * a reconnect storm is visible here as well as a restart.\n *\n * A wipe is not an error — it is how every abnormal change-log state is\n * collapsed into a correct one — but each wipe costs the retention window, so\n * every reconnecting subscriber below the head gets `too-old`. The `gap` reason\n * in particular means the log neither held the resume watermark nor could be\n * truncated to it, so a nonzero rate in steady state is an alert, not a\n * statistic.\n */\nexport function recordSQLiteChangeLogReconcile(\n lc: LogContext,\n result: ReconcileResult,\n): void {\n switch (result.action) {\n case 'truncated':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_truncated_rows',\n 'Phantom change-log rows removed at reconciliation, i.e. rows above ' +\n 'the watermark the change stream resumed from.',\n ).add(result.rows);\n break;\n case 'reseeded':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_wipes',\n 'Change-log wipes at reconciliation, by reason. Each one resets the ' +\n 'retention window, so subscribers below the head get `too-old`.',\n ).add(1, {reason: result.reason});\n break;\n case 'none':\n break;\n default:\n unreachable(result);\n }\n if (result.cookiesStale) {\n // The invariant-17 counter: after any reconcile that moved the head, the\n // log's cookie set has to be replaced with the one that belongs to the\n // resume watermark. A nonzero `truncated` series in steady state means\n // phantom truncation is happening on a shard where it should not.\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_reseed',\n 'Change-log cookie sets invalidated at reconciliation, by the ' +\n 'reconcile action that invalidated them.',\n ).add(1, {action: result.action});\n }\n lc.debug?.('SQLite change-log reconciliation', {\n sqliteChangeLogReconcile: result,\n });\n}\n\n/**\n * What the log says about itself. Nothing here reads the replica: after the\n * writer moved to the change-streamer, the replica's state version is a\n * consequence of this log rather than a fact about it.\n */\nexport type SQLiteChangeLogStartupInfo = ChangeLogMeta & {\n readonly minWatermark: string;\n readonly headWatermark: string;\n};\n\nexport type SQLiteChangeLogInfo = SQLiteChangeLogStartupInfo & {\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n};\n\n/**\n * Reads the meta row and covered range. Unlike {@link getSQLiteChangeLogInfo}\n * this does not scan the log; see {@link readChangeLogBounds}.\n *\n * Call this after reconciliation, which is the only point at which the head is\n * known to be the resume watermark.\n */\nexport function getSQLiteChangeLogStartupInfo(\n changeLog: Database,\n): SQLiteChangeLogStartupInfo {\n const meta = readChangeLogMeta(changeLog);\n const {minWatermark, headWatermark} = readChangeLogBounds(changeLog);\n if (minWatermark === null || headWatermark === null) {\n throw new Error('the SQLite change log must contain its seed transaction');\n }\n return {...meta, minWatermark, headWatermark};\n}\n\n/**\n * Extends {@link getSQLiteChangeLogStartupInfo} with the retained row and\n * estimated byte totals. Computing these scans the entire change-log table, so\n * only call it when the totals are consumed, i.e. when the writer (and thus the\n * observer) is enabled.\n */\nexport function getSQLiteChangeLogInfo(\n changeLog: Database,\n): SQLiteChangeLogInfo {\n const startup = getSQLiteChangeLogStartupInfo(changeLog);\n const aggregate = changeLog\n .prepare(/*sql*/ `\n SELECT count(*) AS \"rows\",\n coalesce(sum(\"estimatedBytes\"), 0) AS \"estimatedBytes\"\n FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n `)\n .get<{rows: number; estimatedBytes: number}>();\n\n return {\n ...startup,\n rows: aggregate.rows,\n estimatedBytes: aggregate.estimatedBytes,\n cookieRows: readCookieRowCounts(changeLog),\n };\n}\n\nexport function logSQLiteChangeLogStartup(\n lc: LogContext,\n file: string,\n info: SQLiteChangeLogStartupInfo,\n): void {\n lc.info?.('SQLite change-log startup', {\n sqliteChangeLog: {\n file,\n epoch: info.epoch,\n generation: info.generation,\n replicaID: info.replicaID,\n schemaVersion: info.schemaVersion,\n seedWatermark: info.seedWatermark,\n seededAtMs: info.seededAtMs,\n minWatermark: info.minWatermark,\n headWatermark: info.headWatermark,\n },\n });\n}\n\n/**\n * The on-disk footprint of a SQLite database: its main file plus its `-wal` and\n * `-wal2` sidecars, whichever exist. `-shm` is excluded — it is a\n * shared-memory index rather than retained bytes.\n *\n * A missing file counts as zero rather than warning. Which sidecars exist\n * depends on the journal mode, and the change-log database is absent entirely\n * until the writer creates it.\n */\nexport async function sqliteFileBytes(\n lc: LogContext,\n file: string,\n): Promise<number> {\n const sizes = await Promise.all(\n [file, `${file}-wal`, `${file}-wal2`].map(async path => {\n try {\n return (await stat(path)).size;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n lc.warn?.(`unable to stat ${path} for size metrics`, e);\n }\n return 0;\n }\n }),\n );\n return sizes.reduce((total, size) => total + size, 0);\n}\n\n/** What the writer reports back for a committed transaction. */\nexport type ChangeLogCommit = {\n readonly watermark: string;\n readonly stats: ChangeLogStreamTransactionStats;\n /**\n * Appending the commit row and committing the log's transaction. This is the\n * cost that sits on the forward path, once per upstream transaction.\n */\n readonly logCommitMs: number;\n};\n\nexport type SQLiteChangeLogObservabilityState = {\n readonly receivedHead: string;\n readonly sqliteHead: string;\n readonly headLag: number | undefined;\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n readonly cookieMutations: number;\n readonly rollbacks: number;\n readonly invariantFailures: number;\n readonly hashMatches: number;\n readonly hashMismatches: number;\n readonly hashUnpaired: number;\n};\n\nconst TRANSACTION_ROW_BUCKETS = [2, 3, 5, 10, 25, 50, 100, 250, 1000, 5000];\n\n/**\n * Records writer metrics in the change-streamer process. State changes only\n * after the corresponding write succeeds, except receivedHead, which advances\n * when a commit message arrives so that transient lag behind the log is\n * observable.\n */\nexport class SQLiteChangeLogObserver {\n readonly #lc: LogContext;\n readonly #messageProcessing = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.message_processing_duration',\n 'Time to write a replication message to the SQLite change log.',\n );\n // The commit that sits on the forward path. There is no second commit to\n // order against any more: the replica's belongs to a subscriber downstream of\n // this loop, so `sqlite_change_log.replica_commit_duration` and the phantom\n // window it measured are gone. A failed commit is still timed, by\n // `message_processing_duration` keyed `tag=\"commit\"`.\n readonly #logCommitDuration = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.log_commit_duration',\n 'Time to append the commit row to the SQLite change log and commit its ' +\n 'transaction. This is on the forward path: it precedes the forward of ' +\n \"that transaction's commit message.\",\n );\n readonly #transactionRows = getOrCreateValueHistogram(\n 'replica',\n 'sqlite_change_log.transaction_rows',\n {\n description: 'Rows stored per committed SQLite change-log transaction.',\n unit: '{row}',\n bucketBoundaries: TRANSACTION_ROW_BUCKETS,\n },\n );\n readonly #rollbackCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.rollbacks',\n 'SQLite change-log transactions rolled back before commit.',\n );\n readonly #invariantFailureCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.invariant_failures',\n 'Detected SQLite change-log writer invariant failures.',\n );\n readonly #hashComparisonCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_comparisons',\n 'Ephemeral transaction hash comparisons between the received change stream and SQLite change-log writes.',\n );\n readonly #hashUnpairedCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_unpaired',\n 'Committed change-log transactions without both ephemeral hashes available for comparison.',\n );\n // Near zero at steady state: cookie state only moves on schema changes.\n readonly #cookieMutationCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_mutations',\n 'Change-log cookie mutations folded from schema changes, by operation.',\n );\n\n #receivedHead: string;\n #sqliteHead: string;\n #rows: number;\n #estimatedBytes: number;\n #cookieRows: CookieRowCounts;\n #cookieMutations = 0;\n #transactionWatermark: string | undefined;\n #sourceHasher: ChangeLogTransactionHasher | undefined;\n #sourcePos = 0;\n #sourceCommitHash: string | undefined;\n #rollbacks = 0;\n #invariantFailures = 0;\n #hashMatches = 0;\n #hashMismatches = 0;\n #hashUnpaired = 0;\n\n constructor(lc: LogContext, info: SQLiteChangeLogInfo) {\n this.#lc = lc.withContext('component', 'sqlite-change-log-observer');\n // Reconciliation has just put the head at the resume watermark, which is\n // also the point the stream is about to be received from.\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.rows',\n 'Rows retained in the SQLite change log.',\n ).addCallback(result => result.observe(this.#rows));\n // Nonzero with no backfill in flight is a leaked cookie, i.e. a backfill\n // that will be re-requested forever.\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.cookie_rows',\n 'Cookie rows retained in the SQLite change log, by cookie table.',\n ).addCallback(result => {\n result.observe(this.#cookieRows.tableMetadata, {table: 'tableMetadata'});\n result.observe(this.#cookieRows.backfilling, {table: 'backfilling'});\n });\n getOrCreateGauge('replica', 'sqlite_change_log.retained_bytes', {\n description:\n 'Estimated UTF-8 payload bytes retained in the SQLite change log.',\n unit: 'By',\n }).addCallback(result => result.observe(this.#estimatedBytes));\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head',\n 'SQLite change-log head converted from its lexicographic watermark.',\n ).addCallback(result => {\n const head = watermarkValue(this.#sqliteHead);\n if (head !== undefined) {\n result.observe(head);\n }\n });\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head_lag',\n 'Distance from the latest received commit to the SQLite change-log ' +\n 'head. The log commits before the forward, so this is zero except ' +\n 'within a transaction.',\n ).addCallback(result => {\n const lag = watermarkDistance(this.#receivedHead, this.#sqliteHead);\n if (lag !== undefined) {\n result.observe(lag);\n }\n });\n }\n\n /**\n * Called as each message arrives, before it is written. Hashes the *received*\n * stream, which is compared against the hash the writer computes over what it\n * *stored*.\n */\n messageReceived(data: ChangeStreamData, json: string): void {\n const tag = data[1].tag;\n if (data[0] === 'begin') {\n if (this.#sourceHasher !== undefined) {\n this.#invariantFailure(\n 'Received change-log begin while a source hash is already open',\n {receivedWatermark: data[2].commitWatermark},\n );\n }\n const watermark = data[2].commitWatermark;\n this.#sourceHasher = new ChangeLogTransactionHasher();\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n this.#sourceHasher.add({\n watermark,\n pos: this.#sourcePos,\n tag,\n change: extractChangeSubstring(json, tag),\n precommit: null,\n });\n return;\n }\n\n if (tag === 'rollback') {\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] === 'commit') {\n this.#receivedHead = data[2].watermark;\n }\n\n const watermark = this.#transactionWatermark;\n if (this.#sourceHasher === undefined || watermark === undefined) {\n this.#invariantFailure(\n 'Received change-log message without an observed transaction',\n {tag},\n );\n return;\n }\n\n const commitWatermark =\n data[0] === 'commit' ? data[2].watermark : watermark;\n this.#sourceHasher.add({\n watermark: commitWatermark,\n pos: ++this.#sourcePos,\n tag,\n change: extractChangeSubstring(json, tag),\n precommit: data[0] === 'commit' ? watermark : null,\n });\n if (data[0] === 'commit') {\n this.#sourceCommitHash = this.#sourceHasher.digest();\n this.#sourceHasher = undefined;\n }\n }\n\n /** Called after the message has been written to the log. */\n messageProcessed(\n data: ChangeStreamData,\n commit: ChangeLogCommit | null,\n durationMs: number,\n ): void {\n const tag = data[1].tag;\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'success'});\n\n if (data[0] === 'begin') {\n if (this.#transactionWatermark !== undefined) {\n this.#invariantFailure('SQLite change-log transaction already open', {\n openWatermark: this.#transactionWatermark,\n receivedWatermark: data[2].commitWatermark,\n });\n }\n this.#transactionWatermark = data[2].commitWatermark;\n return;\n }\n\n if (tag === 'rollback') {\n this.#recordRollback('upstream');\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] !== 'commit') {\n return;\n }\n\n const watermark = data[2].watermark;\n if (commit !== null) {\n this.#logCommitDuration.recordMs(commit.logCommitMs);\n }\n if (this.#transactionWatermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit does not match the observed begin',\n {openWatermark: this.#transactionWatermark, commitWatermark: watermark},\n );\n }\n if (commit === null) {\n this.#invariantFailure(\n 'SQLite change-log commit result is missing writer statistics',\n {commitWatermark: watermark},\n );\n this.#recordUnpaired(watermark, 'missing-sqlite-hash');\n } else {\n if (commit.watermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit result has an unexpected watermark',\n {commitWatermark: watermark, resultWatermark: commit.watermark},\n );\n }\n this.#rows += commit.stats.rows;\n this.#estimatedBytes += commit.stats.estimatedBytes;\n this.#transactionRows.record(commit.stats.rows);\n this.#compareHashes(watermark, this.#sourceCommitHash, commit.stats.hash);\n }\n if (watermark < this.#sqliteHead) {\n this.#invariantFailure('SQLite change-log head regressed', {\n previousHead: this.#sqliteHead,\n commitWatermark: watermark,\n });\n }\n this.#sqliteHead = watermark;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n messageFailed(\n data: ChangeStreamData,\n error: unknown,\n durationMs: number,\n ): void {\n const tag = data[1].tag;\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'error'});\n if (data[0] === 'commit') {\n // No commit duration here: a failed commit produces no statistics, so\n // there is nothing to attribute. The attempt is timed by\n // `message_processing_duration{tag=\"commit\",outcome=\"error\"}` above.\n this.#recordUnpaired(data[2].watermark, 'sqlite-commit-failed');\n }\n if (\n error instanceof Error &&\n error.name === 'ChangeLogStreamInvariantError'\n ) {\n this.#invariantFailure(error.message);\n }\n if (this.#transactionWatermark !== undefined || data[0] === 'begin') {\n this.#recordRollback('processing-error');\n }\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n /**\n * A committed transaction folded schema changes into the cookie jar. Reported\n * separately from {@link messageProcessed} because it costs a read of the\n * cookie tables, which is only worth taking when they actually moved.\n */\n cookiesMutated(ops: readonly CookieOpTag[], rows: CookieRowCounts): void {\n for (const op of ops) {\n this.#cookieMutations++;\n this.#cookieMutationCounter.add(1, {op});\n }\n this.#cookieRows = rows;\n }\n\n /**\n * A missed truncate-above surfaces as a constraint violation on the writer's\n * plain `INSERT`, which the fail-soft policy would otherwise absorb quietly.\n * Classifying it as an invariant failure is what keeps a reconciliation bug\n * from looking like a lost cache.\n */\n invariantFailure(message: string, details?: Record<string, unknown>): void {\n this.#invariantFailure(message, details);\n }\n\n /** The source interrupted the stream mid-transaction. */\n abort(): void {\n if (this.#transactionWatermark !== undefined) {\n this.#recordRollback('source-interruption');\n this.#transactionWatermark = undefined;\n }\n this.#resetSourceHash();\n }\n\n /** Reconciliation moved the head, so the observer's totals are restated. */\n reconciled(info: SQLiteChangeLogInfo): void {\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n state(): SQLiteChangeLogObservabilityState {\n return {\n receivedHead: this.#receivedHead,\n sqliteHead: this.#sqliteHead,\n headLag: watermarkDistance(this.#receivedHead, this.#sqliteHead),\n rows: this.#rows,\n estimatedBytes: this.#estimatedBytes,\n cookieRows: this.#cookieRows,\n cookieMutations: this.#cookieMutations,\n rollbacks: this.#rollbacks,\n invariantFailures: this.#invariantFailures,\n hashMatches: this.#hashMatches,\n hashMismatches: this.#hashMismatches,\n hashUnpaired: this.#hashUnpaired,\n };\n }\n\n #recordRollback(\n reason: 'upstream' | 'processing-error' | 'source-interruption',\n ) {\n this.#rollbacks++;\n this.#rollbackCounter.add(1, {reason});\n }\n\n #invariantFailure(message: string, details?: Record<string, unknown>) {\n this.#invariantFailures++;\n this.#invariantFailureCounter.add(1);\n this.#lc.error?.(message, details);\n }\n\n #compareHashes(\n watermark: string,\n sourceHash: string | undefined,\n sqliteHash: string,\n ): void {\n if (sourceHash === undefined) {\n this.#recordUnpaired(watermark, 'missing-source-hash');\n return;\n }\n if (sourceHash === sqliteHash) {\n this.#hashMatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'match'});\n return;\n }\n this.#hashMismatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'mismatch'});\n this.#lc.error?.('SQLite change-log transaction hash mismatch', {\n sqliteChangeLogHashComparison: {\n watermark,\n sourceHash: sourceHash.slice(0, 16),\n sqliteHash: sqliteHash.slice(0, 16),\n },\n });\n }\n\n #recordUnpaired(\n watermark: string,\n reason:\n | 'missing-source-hash'\n | 'missing-sqlite-hash'\n | 'sqlite-commit-failed',\n ): void {\n this.#hashUnpaired++;\n this.#hashUnpairedCounter.add(1, {reason});\n this.#lc.warn?.('SQLite change-log transaction hash was not compared', {\n sqliteChangeLogHashComparison: {watermark, reason},\n });\n }\n\n #resetSourceHash(): void {\n this.#sourceHasher = undefined;\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n }\n}\n\nfunction watermarkValue(watermark: string): number | undefined {\n try {\n const value = Number(versionFromLexi(watermark));\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction watermarkDistance(\n receivedHead: string,\n sqliteHead: string,\n): number | undefined {\n if (receivedHead <= sqliteHead) {\n return 0;\n }\n try {\n const distance =\n versionFromLexi(receivedHead) - versionFromLexi(sqliteHead);\n const value = Number(distance);\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,+BACd,IACA,QACM;CACN,QAAQ,OAAO,QAAf;EACE,KAAK;GACH,mBACE,WACA,8CACA,kHAEF,EAAE,IAAI,OAAO,IAAI;GACjB;EACF,KAAK;GACH,mBACE,WACA,qCACA,mIAEF,EAAE,IAAI,GAAG,EAAC,QAAQ,OAAO,OAAM,CAAC;GAChC;EACF,KAAK,QACH;EACF,SACE,YAAY,MAAM;CACtB;CACA,IAAI,OAAO,cAKT,mBACE,WACA,mCACA,sGAEF,EAAE,IAAI,GAAG,EAAC,QAAQ,OAAO,OAAM,CAAC;CAElC,GAAG,QAAQ,oCAAoC,EAC7C,0BAA0B,OAC5B,CAAC;AACH;;;;;;;;AAyBA,SAAgB,8BACd,WAC4B;CAC5B,MAAM,OAAO,kBAAkB,SAAS;CACxC,MAAM,EAAC,cAAc,kBAAiB,oBAAoB,SAAS;CACnE,IAAI,iBAAiB,QAAQ,kBAAkB,MAC7C,MAAM,IAAI,MAAM,yDAAyD;CAE3E,OAAO;EAAC,GAAG;EAAM;EAAc;CAAa;AAC9C;;;;;;;AAQA,SAAgB,uBACd,WACqB;CACrB,MAAM,UAAU,8BAA8B,SAAS;CACvD,MAAM,YAAY,UACf,QAAgB;;;gBAGL,wBAAwB;KACnC,EACA,IAA4C;CAE/C,OAAO;EACL,GAAG;EACH,MAAM,UAAU;EAChB,gBAAgB,UAAU;EAC1B,YAAY,oBAAoB,SAAS;CAC3C;AACF;AAEA,SAAgB,0BACd,IACA,MACA,MACM;CACN,GAAG,OAAO,6BAA6B,EACrC,iBAAiB;EACf;EACA,OAAO,KAAK;EACZ,YAAY,KAAK;EACjB,WAAW,KAAK;EAChB,eAAe,KAAK;EACpB,eAAe,KAAK;EACpB,YAAY,KAAK;EACjB,cAAc,KAAK;EACnB,eAAe,KAAK;CACtB,EACF,CAAC;AACH;;;;;;;;;;AAWA,eAAsB,gBACpB,IACA,MACiB;CAajB,QAAO,MAZa,QAAQ,IAC1B;EAAC;EAAM,GAAG,KAAK;EAAO,GAAG,KAAK;CAAM,EAAE,IAAI,OAAM,SAAQ;EACtD,IAAI;GACF,QAAQ,MAAM,KAAK,IAAI,GAAG;EAC5B,SAAS,GAAG;GACV,IAAK,EAA4B,SAAS,UACxC,GAAG,OAAO,kBAAkB,KAAK,oBAAoB,CAAC;GAExD,OAAO;EACT;CACF,CAAC,CACH,GACa,QAAQ,OAAO,SAAS,QAAQ,MAAM,CAAC;AACtD;AA4BA,IAAM,0BAA0B;CAAC;CAAG;CAAG;CAAG;CAAI;CAAI;CAAI;CAAK;CAAK;CAAM;AAAI;;;;;;;AAQ1E,IAAa,0BAAb,MAAqC;CACnC;CACA,qBAA8B,4BAC5B,WACA,iDACA,+DACF;CAMA,qBAA8B,4BAC5B,WACA,yCACA,+KAGF;CACA,mBAA4B,0BAC1B,WACA,sCACA;EACE,aAAa;EACb,MAAM;EACN,kBAAkB;CACpB,CACF;CACA,mBAA4B,mBAC1B,WACA,+BACA,2DACF;CACA,2BAAoC,mBAClC,WACA,wCACA,uDACF;CACA,yBAAkC,mBAChC,WACA,sCACA,yGACF;CACA,uBAAgC,mBAC9B,WACA,mCACA,2FACF;CAEA,yBAAkC,mBAChC,WACA,sCACA,uEACF;CAEA;CACA;CACA;CACA;CACA;CACA,mBAAmB;CACnB;CACA;CACA,aAAa;CACb;CACA,aAAa;CACb,qBAAqB;CACrB,eAAe;CACf,kBAAkB;CAClB,gBAAgB;CAEhB,YAAY,IAAgB,MAA2B;EACrD,KAAKA,MAAM,GAAG,YAAY,aAAa,4BAA4B;EAGnE,KAAKS,gBAAgB,KAAK;EAC1B,KAAKC,cAAc,KAAK;EACxB,KAAKC,QAAQ,KAAK;EAClB,KAAKC,kBAAkB,KAAK;EAC5B,KAAKC,cAAc,KAAK;EAExB,iBACE,WACA,0BACA,yCACF,EAAE,aAAY,WAAU,OAAO,QAAQ,KAAKF,KAAK,CAAC;EAGlD,iBACE,WACA,iCACA,iEACF,EAAE,aAAY,WAAU;GACtB,OAAO,QAAQ,KAAKE,YAAY,eAAe,EAAC,OAAO,gBAAe,CAAC;GACvE,OAAO,QAAQ,KAAKA,YAAY,aAAa,EAAC,OAAO,cAAa,CAAC;EACrE,CAAC;EACD,iBAAiB,WAAW,oCAAoC;GAC9D,aACE;GACF,MAAM;EACR,CAAC,EAAE,aAAY,WAAU,OAAO,QAAQ,KAAKD,eAAe,CAAC;EAC7D,iBACE,WACA,0BACA,oEACF,EAAE,aAAY,WAAU;GACtB,MAAM,OAAO,eAAe,KAAKF,WAAW;GAC5C,IAAI,SAAS,KAAA,GACX,OAAO,QAAQ,IAAI;EAEvB,CAAC;EACD,iBACE,WACA,8BACA,0JAGF,EAAE,aAAY,WAAU;GACtB,MAAM,MAAM,kBAAkB,KAAKD,eAAe,KAAKC,WAAW;GAClE,IAAI,QAAQ,KAAA,GACV,OAAO,QAAQ,GAAG;EAEtB,CAAC;CACH;;;;;;CAOA,gBAAgB,MAAwB,MAAoB;EAC1D,MAAM,MAAM,KAAK,GAAG;EACpB,IAAI,KAAK,OAAO,SAAS;GACvB,IAAI,KAAKI,kBAAkB,KAAA,GACzB,KAAKC,kBACH,iEACA,EAAC,mBAAmB,KAAK,GAAG,gBAAe,CAC7C;GAEF,MAAM,YAAY,KAAK,GAAG;GAC1B,KAAKD,gBAAgB,IAAI,2BAA2B;GACpD,KAAKE,aAAa;GAClB,KAAKC,oBAAoB,KAAA;GACzB,KAAKH,cAAc,IAAI;IACrB;IACA,KAAK,KAAKE;IACV;IACA,QAAQ,uBAAuB,MAAM,GAAG;IACxC,WAAW;GACb,CAAC;GACD;EACF;EAEA,IAAI,QAAQ,YAAY;GACtB,KAAKE,iBAAiB;GACtB;EACF;EAEA,IAAI,KAAK,OAAO,UACd,KAAKT,gBAAgB,KAAK,GAAG;EAG/B,MAAM,YAAY,KAAKU;EACvB,IAAI,KAAKL,kBAAkB,KAAA,KAAa,cAAc,KAAA,GAAW;GAC/D,KAAKC,kBACH,+DACA,EAAC,IAAG,CACN;GACA;EACF;EAEA,MAAM,kBACJ,KAAK,OAAO,WAAW,KAAK,GAAG,YAAY;EAC7C,KAAKD,cAAc,IAAI;GACrB,WAAW;GACX,KAAK,EAAE,KAAKE;GACZ;GACA,QAAQ,uBAAuB,MAAM,GAAG;GACxC,WAAW,KAAK,OAAO,WAAW,YAAY;EAChD,CAAC;EACD,IAAI,KAAK,OAAO,UAAU;GACxB,KAAKC,oBAAoB,KAAKH,cAAc,OAAO;GACnD,KAAKA,gBAAgB,KAAA;EACvB;CACF;;CAGA,iBACE,MACA,QACA,YACM;EACN,MAAM,MAAM,KAAK,GAAG;EACpB,KAAKb,mBAAmB,SAAS,YAAY;GAAC;GAAK,SAAS;EAAS,CAAC;EAEtE,IAAI,KAAK,OAAO,SAAS;GACvB,IAAI,KAAKkB,0BAA0B,KAAA,GACjC,KAAKJ,kBAAkB,8CAA8C;IACnE,eAAe,KAAKI;IACpB,mBAAmB,KAAK,GAAG;GAC7B,CAAC;GAEH,KAAKA,wBAAwB,KAAK,GAAG;GACrC;EACF;EAEA,IAAI,QAAQ,YAAY;GACtB,KAAKC,gBAAgB,UAAU;GAC/B,KAAKD,wBAAwB,KAAA;GAC7B,KAAKD,iBAAiB;GACtB;EACF;EAEA,IAAI,KAAK,OAAO,UACd;EAGF,MAAM,YAAY,KAAK,GAAG;EAC1B,IAAI,WAAW,MACb,KAAKhB,mBAAmB,SAAS,OAAO,WAAW;EAErD,IAAI,KAAKiB,0BAA0B,WACjC,KAAKJ,kBACH,8DACA;GAAC,eAAe,KAAKI;GAAuB,iBAAiB;EAAS,CACxE;EAEF,IAAI,WAAW,MAAM;GACnB,KAAKJ,kBACH,gEACA,EAAC,iBAAiB,UAAS,CAC7B;GACA,KAAKM,gBAAgB,WAAW,qBAAqB;EACvD,OAAO;GACL,IAAI,OAAO,cAAc,WACvB,KAAKN,kBACH,+DACA;IAAC,iBAAiB;IAAW,iBAAiB,OAAO;GAAS,CAChE;GAEF,KAAKJ,SAAS,OAAO,MAAM;GAC3B,KAAKC,mBAAmB,OAAO,MAAM;GACrC,KAAKT,iBAAiB,OAAO,OAAO,MAAM,IAAI;GAC9C,KAAKmB,eAAe,WAAW,KAAKL,mBAAmB,OAAO,MAAM,IAAI;EAC1E;EACA,IAAI,YAAY,KAAKP,aACnB,KAAKK,kBAAkB,oCAAoC;GACzD,cAAc,KAAKL;GACnB,iBAAiB;EACnB,CAAC;EAEH,KAAKA,cAAc;EACnB,KAAKS,wBAAwB,KAAA;EAC7B,KAAKD,iBAAiB;CACxB;CAEA,cACE,MACA,OACA,YACM;EACN,MAAM,MAAM,KAAK,GAAG;EACpB,KAAKjB,mBAAmB,SAAS,YAAY;GAAC;GAAK,SAAS;EAAO,CAAC;EACpE,IAAI,KAAK,OAAO,UAId,KAAKoB,gBAAgB,KAAK,GAAG,WAAW,sBAAsB;EAEhE,IACE,iBAAiB,SACjB,MAAM,SAAS,iCAEf,KAAKN,kBAAkB,MAAM,OAAO;EAEtC,IAAI,KAAKI,0BAA0B,KAAA,KAAa,KAAK,OAAO,SAC1D,KAAKC,gBAAgB,kBAAkB;EAEzC,KAAKD,wBAAwB,KAAA;EAC7B,KAAKD,iBAAiB;CACxB;;;;;;CAOA,eAAe,KAA6B,MAA6B;EACvE,KAAK,MAAM,MAAM,KAAK;GACpB,KAAKK;GACL,KAAKf,uBAAuB,IAAI,GAAG,EAAC,GAAE,CAAC;EACzC;EACA,KAAKK,cAAc;CACrB;;;;;;;CAQA,iBAAiB,SAAiB,SAAyC;EACzE,KAAKE,kBAAkB,SAAS,OAAO;CACzC;;CAGA,QAAc;EACZ,IAAI,KAAKI,0BAA0B,KAAA,GAAW;GAC5C,KAAKC,gBAAgB,qBAAqB;GAC1C,KAAKD,wBAAwB,KAAA;EAC/B;EACA,KAAKD,iBAAiB;CACxB;;CAGA,WAAW,MAAiC;EAC1C,KAAKT,gBAAgB,KAAK;EAC1B,KAAKC,cAAc,KAAK;EACxB,KAAKC,QAAQ,KAAK;EAClB,KAAKC,kBAAkB,KAAK;EAC5B,KAAKC,cAAc,KAAK;EACxB,KAAKM,wBAAwB,KAAA;EAC7B,KAAKD,iBAAiB;CACxB;CAEA,QAA2C;EACzC,OAAO;GACL,cAAc,KAAKT;GACnB,YAAY,KAAKC;GACjB,SAAS,kBAAkB,KAAKD,eAAe,KAAKC,WAAW;GAC/D,MAAM,KAAKC;GACX,gBAAgB,KAAKC;GACrB,YAAY,KAAKC;GACjB,iBAAiB,KAAKU;GACtB,WAAW,KAAKC;GAChB,mBAAmB,KAAKC;GACxB,aAAa,KAAKC;GAClB,gBAAgB,KAAKC;GACrB,cAAc,KAAKC;EACrB;CACF;CAEA,gBACE,QACA;EACA,KAAKJ;EACL,KAAKpB,iBAAiB,IAAI,GAAG,EAAC,OAAM,CAAC;CACvC;CAEA,kBAAkB,SAAiB,SAAmC;EACpE,KAAKqB;EACL,KAAKpB,yBAAyB,IAAI,CAAC;EACnC,KAAKL,IAAI,QAAQ,SAAS,OAAO;CACnC;CAEA,eACE,WACA,YACA,YACM;EACN,IAAI,eAAe,KAAA,GAAW;GAC5B,KAAKqB,gBAAgB,WAAW,qBAAqB;GACrD;EACF;EACA,IAAI,eAAe,YAAY;GAC7B,KAAKK;GACL,KAAKpB,uBAAuB,IAAI,GAAG,EAAC,SAAS,QAAO,CAAC;GACrD;EACF;EACA,KAAKqB;EACL,KAAKrB,uBAAuB,IAAI,GAAG,EAAC,SAAS,WAAU,CAAC;EACxD,KAAKN,IAAI,QAAQ,+CAA+C,EAC9D,+BAA+B;GAC7B;GACA,YAAY,WAAW,MAAM,GAAG,EAAE;GAClC,YAAY,WAAW,MAAM,GAAG,EAAE;EACpC,EACF,CAAC;CACH;CAEA,gBACE,WACA,QAIM;EACN,KAAK4B;EACL,KAAKrB,qBAAqB,IAAI,GAAG,EAAC,OAAM,CAAC;EACzC,KAAKP,IAAI,OAAO,uDAAuD,EACrE,+BAA+B;GAAC;GAAW;EAAM,EACnD,CAAC;CACH;CAEA,mBAAyB;EACvB,KAAKc,gBAAgB,KAAA;EACrB,KAAKE,aAAa;EAClB,KAAKC,oBAAoB,KAAA;CAC3B;AACF;AAEA,SAAS,eAAe,WAAuC;CAC7D,IAAI;EACF,MAAM,QAAQ,OAAO,gBAAgB,SAAS,CAAC;EAC/C,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;CAC1C,QAAQ;EACN;CACF;AACF;AAEA,SAAS,kBACP,cACA,YACoB;CACpB,IAAI,gBAAgB,YAClB,OAAO;CAET,IAAI;EACF,MAAM,WACJ,gBAAgB,YAAY,IAAI,gBAAgB,UAAU;EAC5D,MAAM,QAAQ,OAAO,QAAQ;EAC7B,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;CAC1C,QAAQ;EACN;CACF;AACF"}
1
+ {"version":3,"file":"sqlite-change-log-observability.js","names":["#lc","#messageProcessing","#logCommitDuration","#transactionRows","#rollbackCounter","#invariantFailureCounter","#hashComparisonCounter","#hashUnpairedCounter","#cookieMutationCounter","#receivedHead","#sqliteHead","#rows","#estimatedBytes","#cookieRows","#sourceHasher","#invariantFailure","#sourcePos","#sourceCommitHash","#resetSourceHash","#transactionWatermark","#recordRollback","#recordUnpaired","#compareHashes","#cookieMutations","#rollbacks","#invariantFailures","#hashMatches","#hashMismatches","#hashUnpaired"],"sources":["../../../../../../zero-cache/src/services/replicator/sqlite-change-log-observability.ts"],"sourcesContent":["/**\n * Observability for the SQLite change log.\n *\n * Everything here now runs in the **change-streamer** process, which is where\n * the writer lives. The module stays beside the rest of the change-log code\n * under `services/replicator/` for the same reason `change-log-db.ts` and\n * `change-log-stream-writer.ts` do: moving files would obscure the topology\n * diff, and the writer's home is a property of who calls it rather than of\n * where it sits. Metric names are unchanged from 7H — only their emitting\n * process, and therefore their resource attributes, moved.\n */\n\nimport {stat} from 'node:fs/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../../shared/src/asserts.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateLatencyHistogram,\n getOrCreateValueHistogram,\n} from '../../observability/metrics.ts';\nimport {versionFromLexi} from '../../types/lexi-version.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport {extractChangeSubstring} from '../change-streamer/change-log-codec.ts';\nimport {ChangeLogTransactionHasher} from '../change-streamer/change-log-transaction-hash.ts';\nimport {\n readCookieRowCounts,\n type CookieOpTag,\n type CookieRowCounts,\n} from './change-log-cookies.ts';\nimport {\n CHANGE_LOG_STREAM_TABLE,\n readChangeLogBounds,\n readChangeLogMeta,\n type ChangeLogMeta,\n type ReconcileResult,\n} from './change-log-db.ts';\nimport type {ChangeLogStreamTransactionStats} from './change-log-stream-writer.ts';\n\n/**\n * Reports the reconciliation of the change-log database against the watermark\n * the stream connection resumes from. This runs once per stream connection, so\n * a reconnect storm is visible here as well as a restart.\n *\n * A wipe is not an error — it is how every abnormal change-log state is\n * collapsed into a correct one — but each wipe costs the retention window, so\n * every reconnecting subscriber below the head gets `too-old`. The `gap` reason\n * in particular means the log neither held the resume watermark nor could be\n * truncated to it, so a nonzero rate in steady state is an alert, not a\n * statistic.\n */\nexport function recordSQLiteChangeLogReconcile(\n lc: LogContext,\n result: ReconcileResult,\n): void {\n switch (result.action) {\n case 'truncated':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_truncated_rows',\n 'Phantom change-log rows removed at reconciliation, i.e. rows above ' +\n 'the watermark the change stream resumed from.',\n ).add(result.rows);\n break;\n case 'reseeded':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_wipes',\n 'Change-log wipes at reconciliation, by reason. Each one resets the ' +\n 'retention window, so subscribers below the head get `too-old`.',\n ).add(1, {reason: result.reason});\n break;\n case 'none':\n break;\n default:\n unreachable(result);\n }\n if (result.cookiesStale) {\n // The invariant-17 counter: after any reconcile that moved the head, the\n // log's cookie set has to be replaced with the one that belongs to the\n // resume watermark. A nonzero `truncated` series in steady state means\n // phantom truncation is happening on a shard where it should not.\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_reseed',\n 'Change-log cookie sets invalidated at reconciliation, by the ' +\n 'reconcile action that invalidated them.',\n ).add(1, {action: result.action});\n }\n lc.debug?.('SQLite change-log reconciliation', {\n sqliteChangeLogReconcile: result,\n });\n}\n\n/**\n * What the log says about itself. Nothing here reads the replica: after the\n * writer moved to the change-streamer, the replica's state version is a\n * consequence of this log rather than a fact about it.\n */\nexport type SQLiteChangeLogStartupInfo = ChangeLogMeta & {\n readonly minWatermark: string;\n readonly headWatermark: string;\n};\n\nexport type SQLiteChangeLogInfo = SQLiteChangeLogStartupInfo & {\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n};\n\n/**\n * Reads the meta row and covered range. Unlike {@link getSQLiteChangeLogInfo}\n * this does not scan the log; see {@link readChangeLogBounds}.\n *\n * Call this after reconciliation, which is the only point at which the head is\n * known to be the resume watermark.\n */\nexport function getSQLiteChangeLogStartupInfo(\n changeLog: Database,\n): SQLiteChangeLogStartupInfo {\n const meta = readChangeLogMeta(changeLog);\n const {minWatermark, headWatermark} = readChangeLogBounds(changeLog);\n if (minWatermark === null || headWatermark === null) {\n throw new Error('the SQLite change log must contain its seed transaction');\n }\n return {...meta, minWatermark, headWatermark};\n}\n\n/**\n * Extends {@link getSQLiteChangeLogStartupInfo} with the retained row and\n * estimated byte totals. Computing these scans the entire change-log table, so\n * only call it when the totals are consumed, i.e. when the writer (and thus the\n * observer) is enabled.\n */\nexport function getSQLiteChangeLogInfo(\n changeLog: Database,\n): SQLiteChangeLogInfo {\n const startup = getSQLiteChangeLogStartupInfo(changeLog);\n const aggregate = changeLog\n .prepare(/*sql*/ `\n SELECT count(*) AS \"rows\",\n coalesce(sum(\"estimatedBytes\"), 0) AS \"estimatedBytes\"\n FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n `)\n .get<{rows: number; estimatedBytes: number}>();\n\n return {\n ...startup,\n rows: aggregate.rows,\n estimatedBytes: aggregate.estimatedBytes,\n cookieRows: readCookieRowCounts(changeLog),\n };\n}\n\nexport function logSQLiteChangeLogStartup(\n lc: LogContext,\n file: string,\n info: SQLiteChangeLogStartupInfo,\n): void {\n lc.info?.('SQLite change-log startup', {\n sqliteChangeLog: {\n file,\n epoch: info.epoch,\n generation: info.generation,\n replicaID: info.replicaID,\n schemaVersion: info.schemaVersion,\n seedWatermark: info.seedWatermark,\n seededAtMs: info.seededAtMs,\n minWatermark: info.minWatermark,\n headWatermark: info.headWatermark,\n },\n });\n}\n\n/**\n * The on-disk footprint of a SQLite database: its main file plus its `-wal` and\n * `-wal2` sidecars, whichever exist. `-shm` is excluded — it is a\n * shared-memory index rather than retained bytes.\n *\n * A missing file counts as zero rather than warning. Which sidecars exist\n * depends on the journal mode, and the change-log database is absent entirely\n * until the writer creates it.\n */\nexport async function sqliteFileBytes(\n lc: LogContext,\n file: string,\n): Promise<number> {\n const sizes = await Promise.all(\n [file, `${file}-wal`, `${file}-wal2`].map(async path => {\n try {\n return (await stat(path)).size;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n lc.warn?.(`unable to stat ${path} for size metrics`, e);\n }\n return 0;\n }\n }),\n );\n return sizes.reduce((total, size) => total + size, 0);\n}\n\n/** What the writer reports back for a committed transaction. */\nexport type ChangeLogCommit = {\n readonly watermark: string;\n readonly stats: ChangeLogStreamTransactionStats;\n /**\n * Appending the commit row and committing the log's transaction. This is the\n * cost that sits on the forward path, once per upstream transaction.\n */\n readonly logCommitMs: number;\n};\n\nexport type SQLiteChangeLogObservabilityState = {\n readonly receivedHead: string;\n readonly sqliteHead: string;\n readonly headLag: number | undefined;\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n readonly cookieMutations: number;\n readonly rollbacks: number;\n readonly invariantFailures: number;\n readonly hashMatches: number;\n readonly hashMismatches: number;\n readonly hashUnpaired: number;\n};\n\nconst TRANSACTION_ROW_BUCKETS = [2, 3, 5, 10, 25, 50, 100, 250, 1000, 5000];\n\n/**\n * Records writer metrics in the change-streamer process. State changes only\n * after the corresponding write succeeds, except receivedHead, which advances\n * when a commit message arrives so that transient lag behind the log is\n * observable.\n */\nexport class SQLiteChangeLogObserver {\n readonly #lc: LogContext;\n readonly #messageProcessing = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.message_processing_duration',\n 'Sampled time to write a replication message to the SQLite change log.',\n );\n // The commit that sits on the forward path. There is no second commit to\n // order against any more: the replica's belongs to a subscriber downstream of\n // this loop, so `sqlite_change_log.replica_commit_duration` and the phantom\n // window it measured are gone. Sampled failed commits are still timed by\n // `message_processing_duration` keyed `tag=\"commit\"`.\n readonly #logCommitDuration = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.log_commit_duration',\n 'Time to append the commit row to the SQLite change log and commit its ' +\n 'transaction. This is on the forward path: it precedes the forward of ' +\n \"that transaction's commit message.\",\n );\n readonly #transactionRows = getOrCreateValueHistogram(\n 'replica',\n 'sqlite_change_log.transaction_rows',\n {\n description: 'Rows stored per committed SQLite change-log transaction.',\n unit: '{row}',\n bucketBoundaries: TRANSACTION_ROW_BUCKETS,\n },\n );\n readonly #rollbackCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.rollbacks',\n 'SQLite change-log transactions rolled back before commit.',\n );\n readonly #invariantFailureCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.invariant_failures',\n 'Detected SQLite change-log writer invariant failures.',\n );\n readonly #hashComparisonCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_comparisons',\n 'Sampled ephemeral transaction hash comparisons between the received change stream and SQLite change-log writes.',\n );\n readonly #hashUnpairedCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_unpaired',\n 'Committed change-log transactions without both ephemeral hashes available for comparison.',\n );\n // Near zero at steady state: cookie state only moves on schema changes.\n readonly #cookieMutationCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_mutations',\n 'Change-log cookie mutations folded from schema changes, by operation.',\n );\n\n #receivedHead: string;\n #sqliteHead: string;\n #rows: number;\n #estimatedBytes: number;\n #cookieRows: CookieRowCounts;\n #cookieMutations = 0;\n #transactionWatermark: string | undefined;\n #sourceHasher: ChangeLogTransactionHasher | undefined;\n #sourcePos = 0;\n #sourceCommitHash: string | undefined;\n #rollbacks = 0;\n #invariantFailures = 0;\n #hashMatches = 0;\n #hashMismatches = 0;\n #hashUnpaired = 0;\n\n constructor(lc: LogContext, info: SQLiteChangeLogInfo) {\n this.#lc = lc.withContext('component', 'sqlite-change-log-observer');\n // Reconciliation has just put the head at the resume watermark, which is\n // also the point the stream is about to be received from.\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.rows',\n 'Rows retained in the SQLite change log.',\n ).addCallback(result => result.observe(this.#rows));\n // Nonzero with no backfill in flight is a leaked cookie, i.e. a backfill\n // that will be re-requested forever.\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.cookie_rows',\n 'Cookie rows retained in the SQLite change log, by cookie table.',\n ).addCallback(result => {\n result.observe(this.#cookieRows.tableMetadata, {table: 'tableMetadata'});\n result.observe(this.#cookieRows.backfilling, {table: 'backfilling'});\n });\n getOrCreateGauge('replica', 'sqlite_change_log.retained_bytes', {\n description:\n 'Estimated UTF-8 payload bytes retained in the SQLite change log.',\n unit: 'By',\n }).addCallback(result => result.observe(this.#estimatedBytes));\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head',\n 'SQLite change-log head converted from its lexicographic watermark.',\n ).addCallback(result => {\n const head = watermarkValue(this.#sqliteHead);\n if (head !== undefined) {\n result.observe(head);\n }\n });\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head_lag',\n 'Distance from the latest received commit to the SQLite change-log ' +\n 'head. The log commits before the forward, so this is zero except ' +\n 'within a transaction.',\n ).addCallback(result => {\n const lag = watermarkDistance(this.#receivedHead, this.#sqliteHead);\n if (lag !== undefined) {\n result.observe(lag);\n }\n });\n }\n\n /**\n * Called as each message arrives, before it is written. For a sampled\n * transaction, hashes the *received* stream and compares it with the hash the\n * writer computes over what it *stored*.\n */\n messageReceived(\n data: ChangeStreamData,\n json: string,\n validateHash = true,\n storedChange = extractChangeSubstring(json, data[1].tag),\n ): void {\n const tag = data[1].tag;\n if (data[0] === 'begin') {\n if (this.#sourceHasher !== undefined) {\n this.#invariantFailure(\n 'Received change-log begin while a source hash is already open',\n {receivedWatermark: data[2].commitWatermark},\n );\n }\n const watermark = data[2].commitWatermark;\n this.#sourceHasher = validateHash\n ? new ChangeLogTransactionHasher()\n : undefined;\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n this.#sourceHasher?.add({\n watermark,\n pos: this.#sourcePos,\n tag,\n change: storedChange,\n precommit: null,\n });\n return;\n }\n\n if (tag === 'rollback') {\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] === 'commit') {\n this.#receivedHead = data[2].watermark;\n }\n\n if (!validateHash) {\n return;\n }\n\n const watermark = this.#transactionWatermark;\n if (this.#sourceHasher === undefined || watermark === undefined) {\n this.#invariantFailure(\n 'Received change-log message without an observed transaction',\n {tag},\n );\n return;\n }\n\n const commitWatermark =\n data[0] === 'commit' ? data[2].watermark : watermark;\n this.#sourceHasher.add({\n watermark: commitWatermark,\n pos: ++this.#sourcePos,\n tag,\n change: storedChange,\n precommit: data[0] === 'commit' ? watermark : null,\n });\n if (data[0] === 'commit') {\n this.#sourceCommitHash = this.#sourceHasher.digest();\n this.#sourceHasher = undefined;\n }\n }\n\n /** Called after the message has been written to the log. */\n messageProcessed(\n data: ChangeStreamData,\n commit: ChangeLogCommit | null,\n durationMs: number | undefined,\n ): void {\n const tag = data[1].tag;\n if (durationMs !== undefined) {\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'success'});\n }\n\n if (data[0] === 'begin') {\n if (this.#transactionWatermark !== undefined) {\n this.#invariantFailure('SQLite change-log transaction already open', {\n openWatermark: this.#transactionWatermark,\n receivedWatermark: data[2].commitWatermark,\n });\n }\n this.#transactionWatermark = data[2].commitWatermark;\n return;\n }\n\n if (tag === 'rollback') {\n this.#recordRollback('upstream');\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] !== 'commit') {\n return;\n }\n\n const watermark = data[2].watermark;\n if (commit !== null) {\n this.#logCommitDuration.recordMs(commit.logCommitMs);\n }\n if (this.#transactionWatermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit does not match the observed begin',\n {openWatermark: this.#transactionWatermark, commitWatermark: watermark},\n );\n }\n if (commit === null) {\n this.#invariantFailure(\n 'SQLite change-log commit result is missing writer statistics',\n {commitWatermark: watermark},\n );\n this.#recordUnpaired(watermark, 'missing-sqlite-hash');\n } else {\n if (commit.watermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit result has an unexpected watermark',\n {commitWatermark: watermark, resultWatermark: commit.watermark},\n );\n }\n this.#rows += commit.stats.rows;\n this.#estimatedBytes += commit.stats.estimatedBytes;\n this.#transactionRows.record(commit.stats.rows);\n if (\n this.#sourceCommitHash !== undefined ||\n commit.stats.hash !== undefined\n ) {\n this.#compareHashes(\n watermark,\n this.#sourceCommitHash,\n commit.stats.hash,\n );\n }\n }\n if (watermark < this.#sqliteHead) {\n this.#invariantFailure('SQLite change-log head regressed', {\n previousHead: this.#sqliteHead,\n commitWatermark: watermark,\n });\n }\n this.#sqliteHead = watermark;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n messageFailed(\n data: ChangeStreamData,\n error: unknown,\n durationMs: number | undefined,\n ): void {\n const tag = data[1].tag;\n if (durationMs !== undefined) {\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'error'});\n }\n if (data[0] === 'commit' && this.#sourceCommitHash !== undefined) {\n // No commit duration here: a failed commit produces no statistics, so\n // there is nothing to attribute. The attempt is timed by\n // `message_processing_duration{tag=\"commit\",outcome=\"error\"}` above.\n this.#recordUnpaired(data[2].watermark, 'sqlite-commit-failed');\n }\n if (\n error instanceof Error &&\n error.name === 'ChangeLogStreamInvariantError'\n ) {\n this.#invariantFailure(error.message);\n }\n if (this.#transactionWatermark !== undefined || data[0] === 'begin') {\n this.#recordRollback('processing-error');\n }\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n /**\n * A committed transaction folded schema changes into the cookie jar. Reported\n * separately from {@link messageProcessed} because it costs a read of the\n * cookie tables, which is only worth taking when they actually moved.\n */\n cookiesMutated(ops: readonly CookieOpTag[], rows: CookieRowCounts): void {\n for (const op of ops) {\n this.#cookieMutations++;\n this.#cookieMutationCounter.add(1, {op});\n }\n this.#cookieRows = rows;\n }\n\n /**\n * A missed truncate-above surfaces as a constraint violation on the writer's\n * plain `INSERT`, which the fail-soft policy would otherwise absorb quietly.\n * Classifying it as an invariant failure is what keeps a reconciliation bug\n * from looking like a lost cache.\n */\n invariantFailure(message: string, details?: Record<string, unknown>): void {\n this.#invariantFailure(message, details);\n }\n\n /** The source interrupted the stream mid-transaction. */\n abort(): void {\n if (this.#transactionWatermark !== undefined) {\n this.#recordRollback('source-interruption');\n this.#transactionWatermark = undefined;\n }\n this.#resetSourceHash();\n }\n\n /** Reconciliation moved the head, so the observer's totals are restated. */\n reconciled(info: SQLiteChangeLogInfo): void {\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n state(): SQLiteChangeLogObservabilityState {\n return {\n receivedHead: this.#receivedHead,\n sqliteHead: this.#sqliteHead,\n headLag: watermarkDistance(this.#receivedHead, this.#sqliteHead),\n rows: this.#rows,\n estimatedBytes: this.#estimatedBytes,\n cookieRows: this.#cookieRows,\n cookieMutations: this.#cookieMutations,\n rollbacks: this.#rollbacks,\n invariantFailures: this.#invariantFailures,\n hashMatches: this.#hashMatches,\n hashMismatches: this.#hashMismatches,\n hashUnpaired: this.#hashUnpaired,\n };\n }\n\n #recordRollback(\n reason: 'upstream' | 'processing-error' | 'source-interruption',\n ) {\n this.#rollbacks++;\n this.#rollbackCounter.add(1, {reason});\n }\n\n #invariantFailure(message: string, details?: Record<string, unknown>) {\n this.#invariantFailures++;\n this.#invariantFailureCounter.add(1);\n this.#lc.error?.(message, details);\n }\n\n #compareHashes(\n watermark: string,\n sourceHash: string | undefined,\n sqliteHash: string | undefined,\n ): void {\n if (sourceHash === undefined) {\n this.#recordUnpaired(watermark, 'missing-source-hash');\n return;\n }\n if (sqliteHash === undefined) {\n this.#recordUnpaired(watermark, 'missing-sqlite-hash');\n return;\n }\n if (sourceHash === sqliteHash) {\n this.#hashMatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'match'});\n return;\n }\n this.#hashMismatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'mismatch'});\n this.#lc.error?.('SQLite change-log transaction hash mismatch', {\n sqliteChangeLogHashComparison: {\n watermark,\n sourceHash: sourceHash.slice(0, 16),\n sqliteHash: sqliteHash.slice(0, 16),\n },\n });\n }\n\n #recordUnpaired(\n watermark: string,\n reason:\n | 'missing-source-hash'\n | 'missing-sqlite-hash'\n | 'sqlite-commit-failed',\n ): void {\n this.#hashUnpaired++;\n this.#hashUnpairedCounter.add(1, {reason});\n this.#lc.warn?.('SQLite change-log transaction hash was not compared', {\n sqliteChangeLogHashComparison: {watermark, reason},\n });\n }\n\n #resetSourceHash(): void {\n this.#sourceHasher = undefined;\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n }\n}\n\nfunction watermarkValue(watermark: string): number | undefined {\n try {\n const value = Number(versionFromLexi(watermark));\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction watermarkDistance(\n receivedHead: string,\n sqliteHead: string,\n): number | undefined {\n if (receivedHead <= sqliteHead) {\n return 0;\n }\n try {\n const distance =\n versionFromLexi(receivedHead) - versionFromLexi(sqliteHead);\n const value = Number(distance);\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}