@rocicorp/zero 1.10.0-canary.0 → 1.10.0-canary.2

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 (408) hide show
  1. package/out/replicache/src/db/commit.js +1 -4
  2. package/out/replicache/src/db/commit.js.map +1 -1
  3. package/out/replicache/src/db/read.d.ts +1 -0
  4. package/out/replicache/src/db/read.d.ts.map +1 -1
  5. package/out/replicache/src/db/read.js +1 -4
  6. package/out/replicache/src/db/read.js.map +1 -1
  7. package/out/replicache/src/db/write.d.ts +2 -1
  8. package/out/replicache/src/db/write.d.ts.map +1 -1
  9. package/out/replicache/src/db/write.js +4 -51
  10. package/out/replicache/src/db/write.js.map +1 -1
  11. package/out/replicache/src/persist/clients.d.ts +1 -0
  12. package/out/replicache/src/persist/clients.d.ts.map +1 -1
  13. package/out/replicache/src/persist/clients.js +4 -31
  14. package/out/replicache/src/persist/clients.js.map +1 -1
  15. package/out/replicache/src/process-env.d.ts +28 -0
  16. package/out/replicache/src/process-env.d.ts.map +1 -0
  17. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  18. package/out/replicache/src/replicache-impl.js.map +1 -1
  19. package/out/replicache/src/scan-iterator.js +2 -14
  20. package/out/replicache/src/scan-iterator.js.map +1 -1
  21. package/out/replicache/src/subscriptions.d.ts +1 -0
  22. package/out/replicache/src/subscriptions.d.ts.map +1 -1
  23. package/out/replicache/src/subscriptions.js +2 -10
  24. package/out/replicache/src/subscriptions.js.map +1 -1
  25. package/out/replicache/src/transactions.d.ts +1 -0
  26. package/out/replicache/src/transactions.d.ts.map +1 -1
  27. package/out/replicache/src/transactions.js +2 -4
  28. package/out/replicache/src/transactions.js.map +1 -1
  29. package/out/z2s/src/compiler.d.ts.map +1 -1
  30. package/out/z2s/src/compiler.js +1 -22
  31. package/out/z2s/src/compiler.js.map +1 -1
  32. package/out/zero/package.js +3 -3
  33. package/out/zero/package.js.map +1 -1
  34. package/out/zero-cache/src/config/normalize.d.ts +1 -0
  35. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  36. package/out/zero-cache/src/config/normalize.js +10 -1
  37. package/out/zero-cache/src/config/normalize.js.map +1 -1
  38. package/out/zero-cache/src/config/zero-config.d.ts +52 -1
  39. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  40. package/out/zero-cache/src/config/zero-config.js +171 -22
  41. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  42. package/out/zero-cache/src/custom/fetch.js +1 -1
  43. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  44. package/out/zero-cache/src/db/delete-lite-db.js +2 -1
  45. package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
  46. package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
  47. package/out/zero-cache/src/db/migration-lite.js +5 -2
  48. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  49. package/out/zero-cache/src/db/migration.d.ts.map +1 -1
  50. package/out/zero-cache/src/db/migration.js +4 -2
  51. package/out/zero-cache/src/db/migration.js.map +1 -1
  52. package/out/zero-cache/src/db/sqlite-corruption.d.ts +3 -2
  53. package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -1
  54. package/out/zero-cache/src/db/sqlite-corruption.js +13 -8
  55. package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
  56. package/out/zero-cache/src/observability/metrics.d.ts +6 -1
  57. package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
  58. package/out/zero-cache/src/observability/metrics.js +20 -2
  59. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  60. package/out/zero-cache/src/server/backup-watermark-poller.js +31 -0
  61. package/out/zero-cache/src/server/backup-watermark-poller.js.map +1 -0
  62. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  63. package/out/zero-cache/src/server/change-streamer.js +91 -31
  64. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  65. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  66. package/out/zero-cache/src/server/main.js +6 -16
  67. package/out/zero-cache/src/server/main.js.map +1 -1
  68. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  69. package/out/zero-cache/src/server/replicator.js +48 -5
  70. package/out/zero-cache/src/server/replicator.js.map +1 -1
  71. package/out/zero-cache/src/server/runner/run-worker.js +2 -1
  72. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  73. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  74. package/out/zero-cache/src/server/syncer.js +1 -1
  75. package/out/zero-cache/src/server/syncer.js.map +1 -1
  76. package/out/zero-cache/src/server/worker-urls.d.ts +1 -1
  77. package/out/zero-cache/src/server/worker-urls.js +2 -2
  78. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  79. package/out/zero-cache/src/services/change-source/change-source.d.ts +4 -2
  80. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
  81. package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +35 -0
  82. package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +1 -0
  83. package/out/zero-cache/src/services/change-source/common/replica-restore.js +34 -0
  84. package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -0
  85. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +2 -0
  86. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  87. package/out/zero-cache/src/services/change-source/common/replica-schema.js +73 -1
  88. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  89. package/out/zero-cache/src/services/change-source/custom/change-source.d.ts +2 -6
  90. package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/change-source/custom/change-source.js +14 -5
  92. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  93. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +4 -7
  94. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  95. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +7 -7
  96. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  97. package/out/zero-cache/src/services/change-source/pg/change-source.js +82 -25
  98. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  99. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +9 -1
  100. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  101. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +12 -4
  102. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  103. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +45 -2
  104. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  105. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +59 -8
  106. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  107. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +4 -2
  108. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  109. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +7 -4
  110. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  111. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +0 -5
  112. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  113. package/out/zero-cache/src/services/change-source/pg/schema/init.js +29 -41
  114. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  115. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +35 -16
  116. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  117. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +77 -7
  118. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  119. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +2 -1
  120. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  121. package/out/zero-cache/src/services/change-source/protocol/current/data.js +5 -13
  122. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  123. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +6 -3
  124. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  125. package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts +6 -0
  126. package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts.map +1 -0
  127. package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js +21 -0
  128. package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js.map +1 -0
  129. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +1 -0
  130. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
  131. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +1 -0
  132. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  133. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +5 -6
  134. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
  135. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +32 -9
  136. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  137. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +15 -5
  138. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  139. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +66 -0
  140. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -0
  141. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +68 -78
  142. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
  143. package/out/zero-cache/src/services/change-streamer/broadcast.js +84 -92
  144. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  145. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -0
  146. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -0
  147. package/out/zero-cache/src/services/change-streamer/change-log-codec.js +50 -0
  148. package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -0
  149. package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts +141 -0
  150. package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts.map +1 -0
  151. package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +270 -0
  152. package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -0
  153. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts +19 -0
  154. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts.map +1 -0
  155. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js +39 -0
  156. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -0
  157. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -4
  158. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  159. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +12 -17
  160. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  161. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +69 -24
  162. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  163. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +333 -66
  164. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  165. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +56 -13
  166. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  167. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  168. package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts +8 -0
  169. package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts.map +1 -1
  170. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +14 -3
  171. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  172. package/out/zero-cache/src/services/change-streamer/forwarder.js +41 -11
  173. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  174. package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.d.ts → litestream3-prometheus-poller.d.ts} +19 -45
  175. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -0
  176. package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.js → litestream3-prometheus-poller.js} +57 -171
  177. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -0
  178. package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts +15 -0
  179. package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts.map +1 -0
  180. package/out/zero-cache/src/services/change-streamer/replica-poller.js +55 -0
  181. package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -0
  182. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +5 -1
  183. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  184. package/out/zero-cache/src/services/change-streamer/schema/tables.js +2 -1
  185. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  186. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +14 -0
  187. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -0
  188. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +86 -0
  189. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -0
  190. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +4 -1
  191. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
  192. package/out/zero-cache/src/services/change-streamer/snapshot.js +39 -14
  193. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  194. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +75 -0
  195. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -0
  196. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +261 -0
  197. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -0
  198. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts +121 -0
  199. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts.map +1 -0
  200. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js +391 -0
  201. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -0
  202. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +62 -0
  203. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -0
  204. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +159 -0
  205. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -0
  206. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +109 -0
  207. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -0
  208. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +258 -0
  209. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -0
  210. package/out/zero-cache/src/services/change-streamer/storer.d.ts +28 -12
  211. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  212. package/out/zero-cache/src/services/change-streamer/storer.js +116 -121
  213. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  214. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +61 -10
  215. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  216. package/out/zero-cache/src/services/change-streamer/subscriber.js +101 -8
  217. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  218. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +34 -0
  219. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -0
  220. package/out/zero-cache/src/services/change-streamer/upstream-acker.js +86 -0
  221. package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -0
  222. package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
  223. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  224. package/out/zero-cache/src/services/life-cycle.js +4 -3
  225. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  226. package/out/zero-cache/src/services/litestream/commands.d.ts +12 -11
  227. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  228. package/out/zero-cache/src/services/litestream/commands.js +107 -148
  229. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  230. package/out/zero-cache/src/services/litestream/config-v5.yml +42 -0
  231. package/out/zero-cache/src/services/litestream/config.yml +3 -2
  232. package/out/zero-cache/src/services/litestream/metrics.d.ts +4 -4
  233. package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
  234. package/out/zero-cache/src/services/litestream/metrics.js +7 -10
  235. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  236. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +57 -0
  237. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +1 -0
  238. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +137 -0
  239. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +1 -0
  240. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  241. package/out/zero-cache/src/services/mutagen/mutagen.js +4 -1
  242. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  243. package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts +169 -0
  244. package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts.map +1 -0
  245. package/out/zero-cache/src/services/replicator/change-log-cookies.js +434 -0
  246. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -0
  247. package/out/zero-cache/src/services/replicator/change-log-db.d.ts +358 -0
  248. package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -0
  249. package/out/zero-cache/src/services/replicator/change-log-db.js +477 -0
  250. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -0
  251. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +55 -0
  252. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -0
  253. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +150 -0
  254. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -0
  255. package/out/zero-cache/src/services/replicator/change-processor.d.ts +6 -0
  256. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  257. package/out/zero-cache/src/services/replicator/change-processor.js +70 -25
  258. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  259. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +1 -1
  260. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  261. package/out/zero-cache/src/services/replicator/incremental-sync.js +19 -6
  262. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  263. package/out/zero-cache/src/services/replicator/notifier.d.ts +0 -22
  264. package/out/zero-cache/src/services/replicator/notifier.d.ts.map +1 -1
  265. package/out/zero-cache/src/services/replicator/notifier.js +7 -1
  266. package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
  267. package/out/zero-cache/src/services/replicator/replicator.d.ts +17 -2
  268. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  269. package/out/zero-cache/src/services/replicator/replicator.js +9 -3
  270. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  271. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +27 -0
  272. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
  273. package/out/zero-cache/src/services/replicator/reporter/recorder.js +32 -0
  274. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  275. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +1 -1
  276. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +119 -0
  277. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -0
  278. package/out/zero-cache/src/services/replicator/schema/backfilling.js +214 -0
  279. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -0
  280. package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +3 -3
  281. package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
  282. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  283. package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts +1 -1
  284. package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
  285. package/out/zero-cache/src/services/replicator/schema/replication-state.js +9 -5
  286. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  287. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +131 -0
  288. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -0
  289. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +413 -0
  290. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -0
  291. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts +120 -0
  292. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts.map +1 -0
  293. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +283 -0
  294. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -0
  295. package/out/zero-cache/src/services/replicator/write-worker.js +23 -12
  296. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  297. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  298. package/out/zero-cache/src/services/run-ast.js +2 -1
  299. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  300. package/out/zero-cache/src/services/running-state.d.ts +1 -1
  301. package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
  302. package/out/zero-cache/src/services/running-state.js +1 -0
  303. package/out/zero-cache/src/services/running-state.js.map +1 -1
  304. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +53 -0
  305. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -0
  306. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +68 -0
  307. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -0
  308. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  309. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +18 -3
  310. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  311. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  312. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  313. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
  314. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  315. package/out/zero-cache/src/services/view-syncer/view-syncer.js +64 -6
  316. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  317. package/out/zero-cache/src/types/processes.d.ts +1 -2
  318. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  319. package/out/zero-cache/src/types/processes.js +1 -25
  320. package/out/zero-cache/src/types/processes.js.map +1 -1
  321. package/out/zero-cache/src/types/streams.d.ts +21 -0
  322. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  323. package/out/zero-cache/src/types/streams.js +25 -4
  324. package/out/zero-cache/src/types/streams.js.map +1 -1
  325. package/out/zero-cache/src/types/subscription.d.ts +11 -0
  326. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  327. package/out/zero-cache/src/types/subscription.js +27 -0
  328. package/out/zero-cache/src/types/subscription.js.map +1 -1
  329. package/out/zero-cache/src/workers/connection.d.ts +1 -0
  330. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  331. package/out/zero-cache/src/workers/connection.js +24 -2
  332. package/out/zero-cache/src/workers/connection.js.map +1 -1
  333. package/out/zero-cache/src/workers/replicator.d.ts +25 -0
  334. package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
  335. package/out/zero-cache/src/workers/replicator.js +33 -1
  336. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  337. package/out/zero-cache/src/workers/syncer.d.ts +12 -0
  338. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  339. package/out/zero-cache/src/workers/syncer.js +75 -0
  340. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  341. package/out/zero-client/src/client/version.js +1 -1
  342. package/out/zero-client/src/client/zero.d.ts +6 -7
  343. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  344. package/out/zero-client/src/client/zero.js +156 -101
  345. package/out/zero-client/src/client/zero.js.map +1 -1
  346. package/out/zero-schema/src/builder/table-builder.d.ts +23 -0
  347. package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
  348. package/out/zero-schema/src/builder/table-builder.js +18 -0
  349. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  350. package/out/zero-server/src/custom.js +10 -3
  351. package/out/zero-server/src/custom.js.map +1 -1
  352. package/out/zero-server/src/schema.d.ts +8 -0
  353. package/out/zero-server/src/schema.d.ts.map +1 -1
  354. package/out/zero-server/src/schema.js +63 -24
  355. package/out/zero-server/src/schema.js.map +1 -1
  356. package/out/zero-types/src/schema.d.ts +11 -0
  357. package/out/zero-types/src/schema.d.ts.map +1 -1
  358. package/out/zql/src/mutate/crud.d.ts +10 -8
  359. package/out/zql/src/mutate/crud.d.ts.map +1 -1
  360. package/out/zql/src/mutate/crud.js.map +1 -1
  361. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  362. package/out/zql/src/query/query-delegate-base.js +5 -1
  363. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  364. package/out/zql/src/query/query-impl.d.ts +2 -2
  365. package/out/zql/src/query/query-impl.d.ts.map +1 -1
  366. package/out/zql/src/query/query-impl.js.map +1 -1
  367. package/out/zql/src/query/query.d.ts +83 -14
  368. package/out/zql/src/query/query.d.ts.map +1 -1
  369. package/out/zql/src/query/static-query.d.ts +1 -1
  370. package/out/zql/src/query/static-query.d.ts.map +1 -1
  371. package/out/zqlite/src/db.js +2 -2
  372. package/out/zqlite/src/resolve-scalar-subqueries.d.ts +15 -0
  373. package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
  374. package/out/zqlite/src/resolve-scalar-subqueries.js +33 -14
  375. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  376. package/out/zqlite/src/sqlite-cost-model.js +5 -5
  377. package/out/zqlite/src/table-source.js +3 -3
  378. package/package.json +3 -3
  379. package/out/replicache/src/db/index.js +0 -91
  380. package/out/replicache/src/db/index.js.map +0 -1
  381. package/out/replicache/src/lazy.d.ts +0 -2
  382. package/out/replicache/src/lazy.d.ts.map +0 -1
  383. package/out/replicache/src/lazy.js +0 -12
  384. package/out/replicache/src/lazy.js.map +0 -1
  385. package/out/zero-cache/src/server/backup-watermark-reader.js +0 -35
  386. package/out/zero-cache/src/server/backup-watermark-reader.js.map +0 -1
  387. package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.d.ts.map +0 -1
  388. package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.js.map +0 -1
  389. package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts +0 -16
  390. package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts.map +0 -1
  391. package/out/zero-cache/src/services/change-streamer/replica-monitor.js +0 -54
  392. package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +0 -1
  393. package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts +0 -25
  394. package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts.map +0 -1
  395. package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js +0 -159
  396. package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js.map +0 -1
  397. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts +0 -44
  398. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts.map +0 -1
  399. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +0 -65
  400. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js.map +0 -1
  401. package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts +0 -11
  402. package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts.map +0 -1
  403. package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js +0 -74
  404. package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js.map +0 -1
  405. package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts +0 -42
  406. package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts.map +0 -1
  407. package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js +0 -117
  408. package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { assert } from "../../../shared/src/asserts.js";
2
+ import { AbortError } from "../../../shared/src/abort-error.js";
2
3
  import { parse, valita_exports } from "../../../shared/src/valita.js";
3
4
  import { must } from "../../../shared/src/must.js";
4
5
  import "../types/pg.js";
@@ -35,7 +36,8 @@ async function runSchemaMigrations(log, debugName, schemaName, db, setupMigratio
35
36
  });
36
37
  log.info?.(`Running ${debugName} at schema v${codeVersion}`);
37
38
  } catch (e) {
38
- log.error?.("Error in ensureSchemaMigrated", e);
39
+ if (e instanceof AbortError) log.warn?.("Schema requires reset; resync will follow", e);
40
+ else log.error?.("Error in ensureSchemaMigrated", e);
39
41
  throw e;
40
42
  } finally {
41
43
  log.flush();
@@ -134,6 +136,6 @@ function getMinSafeVersion(log, current, proposedSafeVersion) {
134
136
  return proposedSafeVersion;
135
137
  }
136
138
  //#endregion
137
- export { getVersionHistory, runSchemaMigrations };
139
+ export { runSchemaMigrations };
138
140
 
139
141
  //# sourceMappingURL=migration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migration.js","names":[],"sources":["../../../../../zero-cache/src/db/migration.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type postgres from 'postgres';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Operations = (log: LogContext, tx: PostgresTransaction) => Promise<void>;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n schemaName: string,\n db: PostgresDB,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n log = log.withContext('initSchema', schemaName);\n\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n try {\n await runTx(db, async tx => {\n // Acquire advisory lock to prevent concurrent migrations from racing.\n // This can happen during rolling deployments when multiple pods start\n // up simultaneously. The lock auto-releases when the transaction ends.\n const lockName = `migrate-schema:${schemaName}`;\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n let versions = await ensureVersionHistory(tx, schemaName);\n\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n await updateVersionHistory(log, tx, schemaName, versions, codeVersion);\n return;\n }\n\n if (versions.dataVersion === codeVersion) {\n return;\n }\n\n const migrations =\n versions.dataVersion === 0\n ? // For an empty database (v0), only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush();\n versions = await runMigration(\n log,\n schemaName,\n tx,\n versions,\n dest,\n migration,\n );\n }\n }\n });\n\n log.info?.(`Running ${debugName} at schema v${codeVersion}`);\n } catch (e) {\n log.error?.('Error in ensureSchemaMigrated', e);\n throw e;\n } finally {\n void log.flush();\n }\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests.\nexport async function createVersionHistoryTable(\n sql: postgres.Sql,\n schemaName: string,\n) {\n // Note: The `lock` column transparently ensures that at most one row exists.\n await sql`\n CREATE SCHEMA IF NOT EXISTS ${sql(schemaName)};\n CREATE TABLE IF NOT EXISTS ${sql(schemaName)}.\"versionHistory\" (\n \"dataVersion\" int NOT NULL,\n \"schemaVersion\" int NOT NULL,\n \"minSafeVersion\" int NOT NULL,\n\n lock char(1) NOT NULL CONSTRAINT DF_schema_meta_lock DEFAULT 'v',\n CONSTRAINT PK_schema_meta_lock PRIMARY KEY (lock),\n CONSTRAINT CK_schema_meta_lock CHECK (lock='v')\n );`.simple();\n}\n\nasync function ensureVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n): Promise<VersionHistory> {\n return must(await getVersionHistory(sql, schemaName, true));\n}\n\nexport async function getVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n create = false,\n): Promise<VersionHistory | null> {\n const exists = await sql`\n SELECT nspname, relname FROM pg_class\n JOIN pg_namespace ON relnamespace = pg_namespace.oid\n WHERE nspname = ${schemaName} AND relname = ${'versionHistory'}`;\n\n if (exists.length === 0) {\n if (create) {\n await createVersionHistoryTable(sql, schemaName);\n } else {\n return null;\n }\n }\n const rows = await sql`\n SELECT \"dataVersion\", \"schemaVersion\", \"minSafeVersion\"\n FROM ${sql(schemaName)}.\"versionHistory\"`;\n\n if (rows.length === 0) {\n return create\n ? {schemaVersion: 0, dataVersion: 0, minSafeVersion: 0}\n : null;\n }\n return v.parse(rows[0], versionHistory);\n}\n\nasync function updateVersionHistory(\n log: LogContext,\n sql: postgres.Sql,\n schemaName: string,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): Promise<VersionHistory> {\n assert(newVersion > 0, 'newVersion must be positive');\n const versions = {\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n await sql`\n INSERT INTO ${sql(schemaName)}.\"versionHistory\" ${sql(versions)}\n ON CONFLICT (lock) DO UPDATE SET ${sql(versions)}\n `;\n return versions;\n}\n\nasync function runMigration(\n log: LogContext,\n schemaName: string,\n tx: PostgresTransaction,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n schemaName,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration.js","names":[],"sources":["../../../../../zero-cache/src/db/migration.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type postgres from 'postgres';\nimport {AbortError} from '../../../shared/src/abort-error.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {type PostgresDB, type PostgresTransaction} from '../types/pg.ts';\nimport {runTx} from './run-transaction.ts';\n\ntype Operations = (log: LogContext, tx: PostgresTransaction) => Promise<void>;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n schemaName: string,\n db: PostgresDB,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n log = log.withContext('initSchema', schemaName);\n\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n try {\n await runTx(db, async tx => {\n // Acquire advisory lock to prevent concurrent migrations from racing.\n // This can happen during rolling deployments when multiple pods start\n // up simultaneously. The lock auto-releases when the transaction ends.\n const lockName = `migrate-schema:${schemaName}`;\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n let versions = await ensureVersionHistory(tx, schemaName);\n\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n await updateVersionHistory(log, tx, schemaName, versions, codeVersion);\n return;\n }\n\n if (versions.dataVersion === codeVersion) {\n return;\n }\n\n const migrations =\n versions.dataVersion === 0\n ? // For an empty database (v0), only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush();\n versions = await runMigration(\n log,\n schemaName,\n tx,\n versions,\n dest,\n migration,\n );\n }\n }\n });\n\n log.info?.(`Running ${debugName} at schema v${codeVersion}`);\n } catch (e) {\n if (e instanceof AbortError) {\n // AbortErrors (e.g. AutoResetSignal) are not failures. They are the\n // expected mechanism for signaling that the schema is not initialized\n // (or otherwise needs a reset) so that the caller can backtrack and\n // resync. Logging them at `error` would trip error-based alerting for a\n // normal, self-healing path.\n log.warn?.('Schema requires reset; resync will follow', e);\n } else {\n log.error?.('Error in ensureSchemaMigrated', e);\n }\n throw e;\n } finally {\n void log.flush();\n }\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests.\nexport async function createVersionHistoryTable(\n sql: postgres.Sql,\n schemaName: string,\n) {\n // Note: The `lock` column transparently ensures that at most one row exists.\n await sql`\n CREATE SCHEMA IF NOT EXISTS ${sql(schemaName)};\n CREATE TABLE IF NOT EXISTS ${sql(schemaName)}.\"versionHistory\" (\n \"dataVersion\" int NOT NULL,\n \"schemaVersion\" int NOT NULL,\n \"minSafeVersion\" int NOT NULL,\n\n lock char(1) NOT NULL CONSTRAINT DF_schema_meta_lock DEFAULT 'v',\n CONSTRAINT PK_schema_meta_lock PRIMARY KEY (lock),\n CONSTRAINT CK_schema_meta_lock CHECK (lock='v')\n );`.simple();\n}\n\nasync function ensureVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n): Promise<VersionHistory> {\n return must(await getVersionHistory(sql, schemaName, true));\n}\n\nexport async function getVersionHistory(\n sql: postgres.Sql,\n schemaName: string,\n create = false,\n): Promise<VersionHistory | null> {\n const exists = await sql`\n SELECT nspname, relname FROM pg_class\n JOIN pg_namespace ON relnamespace = pg_namespace.oid\n WHERE nspname = ${schemaName} AND relname = ${'versionHistory'}`;\n\n if (exists.length === 0) {\n if (create) {\n await createVersionHistoryTable(sql, schemaName);\n } else {\n return null;\n }\n }\n const rows = await sql`\n SELECT \"dataVersion\", \"schemaVersion\", \"minSafeVersion\"\n FROM ${sql(schemaName)}.\"versionHistory\"`;\n\n if (rows.length === 0) {\n return create\n ? {schemaVersion: 0, dataVersion: 0, minSafeVersion: 0}\n : null;\n }\n return v.parse(rows[0], versionHistory);\n}\n\nasync function updateVersionHistory(\n log: LogContext,\n sql: postgres.Sql,\n schemaName: string,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): Promise<VersionHistory> {\n assert(newVersion > 0, 'newVersion must be positive');\n const versions = {\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n await sql`\n INSERT INTO ${sql(schemaName)}.\"versionHistory\" ${sql(versions)}\n ON CONFLICT (lock) DO UPDATE SET ${sql(versions)}\n `;\n return versions;\n}\n\nasync function runMigration(\n log: LogContext,\n schemaName: string,\n tx: PostgresTransaction,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n schemaName,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n"],"mappings":"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"}
@@ -3,9 +3,10 @@ type DiagnosticTarget = {
3
3
  readonly debugName: string;
4
4
  readonly dbPath: string;
5
5
  };
6
+ export type SQLiteCorruptionChecks = boolean;
6
7
  export declare function isSQLiteCorruption(e: unknown): boolean;
7
- export declare function registerSQLiteCorruptionDiagnosticTarget(target: DiagnosticTarget): () => void;
8
+ export declare function registerSQLiteCorruptionDiagnosticTarget(target: DiagnosticTarget, corruptionChecks?: SQLiteCorruptionChecks): () => void;
8
9
  export declare function logLastChanceSQLiteCorruptionDiagnostics(lc: LogContext, cause: unknown): boolean;
9
- export declare function logSQLiteCorruptionDiagnostics(lc: LogContext, debugName: string, dbPath: string, cause: unknown): void;
10
+ export declare function logSQLiteCorruptionDiagnostics(lc: LogContext, debugName: string, dbPath: string, cause: unknown, corruptionChecks?: SQLiteCorruptionChecks): void;
10
11
  export {};
11
12
  //# sourceMappingURL=sqlite-corruption.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-corruption.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAiCjD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAKF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED,wBAAgB,wCAAwC,CACtD,MAAM,EAAE,gBAAgB,GACvB,MAAM,IAAI,CAIZ;AAED,wBAAgB,wCAAwC,CACtD,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAQT;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,UAAU,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,IAAI,CA+CN"}
1
+ {"version":3,"file":"sqlite-corruption.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAoCjD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AASF,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAE7C,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED,wBAAgB,wCAAwC,CACtD,MAAM,EAAE,gBAAgB,EACxB,gBAAgB,GAAE,sBAA8B,GAC/C,MAAM,IAAI,CAIZ;AAED,wBAAgB,wCAAwC,CACtD,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAkBT;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,UAAU,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,gBAAgB,GAAE,sBAA8B,GAC/C,IAAI,CAiDN"}
@@ -2,7 +2,7 @@ import { Database } from "../../../zqlite/src/db.js";
2
2
  import { SqliteError } from "@rocicorp/zero-sqlite3";
3
3
  import { existsSync, statSync } from "node:fs";
4
4
  //#region ../zero-cache/src/db/sqlite-corruption.ts
5
- var SQLITE_CORRUPTION_CODES = new Set(["SQLITE_CORRUPT", "SQLITE_NOTADB"]);
5
+ var SQLITE_CORRUPTION_CODE = /^SQLITE_(CORRUPT(_|$)|NOTADB$)/;
6
6
  var SQLITE_CORRUPTION_MESSAGE = /database disk image is malformed|file is not a database|database is malformed|malformed database/i;
7
7
  var SQLITE_FILE_SUFFIXES = [
8
8
  "",
@@ -17,17 +17,20 @@ var nextDiagnosticTargetID = 0;
17
17
  function isSQLiteCorruption(e) {
18
18
  return getSQLiteCorruptionCode(e) !== void 0;
19
19
  }
20
- function registerSQLiteCorruptionDiagnosticTarget(target) {
20
+ function registerSQLiteCorruptionDiagnosticTarget(target, corruptionChecks = false) {
21
21
  const id = nextDiagnosticTargetID++;
22
- diagnosticTargets.set(id, target);
22
+ diagnosticTargets.set(id, {
23
+ ...target,
24
+ corruptionChecks
25
+ });
23
26
  return () => diagnosticTargets.delete(id);
24
27
  }
25
28
  function logLastChanceSQLiteCorruptionDiagnostics(lc, cause) {
26
29
  if (!isSQLiteCorruption(cause) || diagnosticTargets.size === 0) return false;
27
- for (const { debugName, dbPath } of diagnosticTargets.values()) logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause);
30
+ for (const { debugName, dbPath, corruptionChecks } of diagnosticTargets.values()) logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause, corruptionChecks);
28
31
  return true;
29
32
  }
30
- function logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause) {
33
+ function logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause, corruptionChecks = false) {
31
34
  const start = Date.now();
32
35
  lc.error?.("SQLite replica corruption detected", {
33
36
  debugName,
@@ -51,8 +54,10 @@ function logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause) {
51
54
  try {
52
55
  logDiagnostic(lc, debugName, "basic-pragmas", () => ({ pragmas: getBasicPragmas(db) }));
53
56
  logDiagnostic(lc, debugName, "replica-metadata", () => ({ metadata: getReplicaMetadata(db) }));
54
- logDiagnostic(lc, debugName, "quick-check", () => ({ check: runCheck(db, "quick_check") }));
55
- logDiagnostic(lc, debugName, "integrity-check", () => ({ check: runCheck(db, "integrity_check") }));
57
+ if (corruptionChecks) {
58
+ logDiagnostic(lc, debugName, "quick-check", () => ({ check: runCheck(db, "quick_check") }));
59
+ logDiagnostic(lc, debugName, "integrity-check", () => ({ check: runCheck(db, "integrity_check") }));
60
+ }
56
61
  lc.error?.("SQLite corruption diagnostics completed", {
57
62
  debugName,
58
63
  dbPath,
@@ -68,7 +73,7 @@ function getSQLiteCorruptionCode(e) {
68
73
  while (current !== void 0 && current !== null && !seen.has(current)) {
69
74
  seen.add(current);
70
75
  const code = getErrorCode(current);
71
- if (code !== void 0 && SQLITE_CORRUPTION_CODES.has(code)) return code;
76
+ if (code !== void 0 && SQLITE_CORRUPTION_CODE.test(code)) return code;
72
77
  if (current instanceof SqliteError || current instanceof Error) {
73
78
  if (SQLITE_CORRUPTION_MESSAGE.test(current.message)) return code ?? "SQLITE_CORRUPT";
74
79
  current = getErrorCause(current);
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-corruption.js","names":[],"sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"sourcesContent":["import {existsSync, statSync} from 'node:fs';\nimport type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {Database} from '../../../zqlite/src/db.ts';\n\nconst SQLITE_CORRUPTION_CODES = new Set(['SQLITE_CORRUPT', 'SQLITE_NOTADB']);\nconst SQLITE_CORRUPTION_MESSAGE =\n /database disk image is malformed|file is not a database|database is malformed|malformed database/i;\nconst SQLITE_FILE_SUFFIXES = ['', '-wal', '-wal2', '-shm', '-journal'] as const;\nconst MAX_LOGGED_CHECK_ISSUES = 20;\n\ntype ErrorSummary = {\n readonly name: string;\n readonly message: string;\n readonly code?: string | undefined;\n readonly cause?: ErrorSummary | string | undefined;\n};\n\ntype SQLiteFileStats = {\n readonly suffix: string;\n readonly path: string;\n readonly sizeBytes: number;\n readonly mtimeMs: number;\n};\n\ntype CheckSummary = {\n readonly status: 'ok' | 'failed';\n readonly rowCount: number;\n readonly issueCount: number;\n readonly loggedIssueCount: number;\n readonly truncated: boolean;\n readonly issues: string[];\n};\n\ntype DiagnosticTarget = {\n readonly debugName: string;\n readonly dbPath: string;\n};\n\nconst diagnosticTargets = new Map<number, DiagnosticTarget>();\nlet nextDiagnosticTargetID = 0;\n\nexport function isSQLiteCorruption(e: unknown): boolean {\n return getSQLiteCorruptionCode(e) !== undefined;\n}\n\nexport function registerSQLiteCorruptionDiagnosticTarget(\n target: DiagnosticTarget,\n): () => void {\n const id = nextDiagnosticTargetID++;\n diagnosticTargets.set(id, target);\n return () => diagnosticTargets.delete(id);\n}\n\nexport function logLastChanceSQLiteCorruptionDiagnostics(\n lc: LogContext,\n cause: unknown,\n): boolean {\n if (!isSQLiteCorruption(cause) || diagnosticTargets.size === 0) {\n return false;\n }\n for (const {debugName, dbPath} of diagnosticTargets.values()) {\n logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause);\n }\n return true;\n}\n\nexport function logSQLiteCorruptionDiagnostics(\n lc: LogContext,\n debugName: string,\n dbPath: string,\n cause: unknown,\n): void {\n const start = Date.now();\n lc.error?.('SQLite replica corruption detected', {\n debugName,\n dbPath,\n cause: summarizeError(cause),\n });\n\n logDiagnostic(lc, debugName, 'file-stats', () => ({\n files: getSQLiteFileStats(dbPath),\n }));\n\n let db: Database | undefined;\n try {\n db = new Database(lc, dbPath, {readonly: true});\n db.pragma('busy_timeout = 1000');\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step: 'open-readonly',\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n return;\n }\n\n try {\n logDiagnostic(lc, debugName, 'basic-pragmas', () => ({\n pragmas: getBasicPragmas(db),\n }));\n logDiagnostic(lc, debugName, 'replica-metadata', () => ({\n metadata: getReplicaMetadata(db),\n }));\n logDiagnostic(lc, debugName, 'quick-check', () => ({\n check: runCheck(db, 'quick_check'),\n }));\n logDiagnostic(lc, debugName, 'integrity-check', () => ({\n check: runCheck(db, 'integrity_check'),\n }));\n lc.error?.('SQLite corruption diagnostics completed', {\n debugName,\n dbPath,\n durationMs: Date.now() - start,\n });\n } finally {\n db.close();\n }\n}\n\nfunction getSQLiteCorruptionCode(e: unknown): string | undefined {\n let current = e;\n const seen = new Set<unknown>();\n\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n\n const code = getErrorCode(current);\n if (code !== undefined && SQLITE_CORRUPTION_CODES.has(code)) {\n return code;\n }\n\n if (current instanceof SqliteError || current instanceof Error) {\n if (SQLITE_CORRUPTION_MESSAGE.test(current.message)) {\n return code ?? 'SQLITE_CORRUPT';\n }\n current = getErrorCause(current);\n continue;\n }\n\n break;\n }\n\n return undefined;\n}\n\nfunction getErrorCode(e: unknown): string | undefined {\n if (e && typeof e === 'object' && 'code' in e) {\n const code = (e as {readonly code?: unknown}).code;\n return typeof code === 'string' ? code : undefined;\n }\n return undefined;\n}\n\nfunction getErrorCause(e: Error): unknown {\n return (e as {readonly cause?: unknown}).cause;\n}\n\nfunction summarizeError(e: unknown, depth = 0): ErrorSummary | string {\n if (!(e instanceof Error)) {\n return String(e);\n }\n const cause = getErrorCause(e);\n return {\n name: e.name,\n message: e.message,\n code: getErrorCode(e),\n cause:\n cause === undefined || depth >= 4\n ? undefined\n : summarizeError(cause, depth + 1),\n };\n}\n\nfunction getSQLiteFileStats(dbPath: string): SQLiteFileStats[] {\n const stats: SQLiteFileStats[] = [];\n for (const suffix of SQLITE_FILE_SUFFIXES) {\n const path = `${dbPath}${suffix}`;\n if (!existsSync(path)) {\n continue;\n }\n const stat = statSync(path);\n stats.push({\n suffix,\n path,\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n });\n }\n return stats;\n}\n\nfunction getBasicPragmas(db: Database) {\n return {\n pageSize: firstPragmaValue<number>(db, 'page_size', 'page_size'),\n pageCount: firstPragmaValue<number>(db, 'page_count', 'page_count'),\n freelistCount: firstPragmaValue<number>(\n db,\n 'freelist_count',\n 'freelist_count',\n ),\n journalMode: firstPragmaValue<string>(db, 'journal_mode', 'journal_mode'),\n schemaVersion: firstPragmaValue<number>(\n db,\n 'schema_version',\n 'schema_version',\n ),\n userVersion: firstPragmaValue<number>(db, 'user_version', 'user_version'),\n applicationID: firstPragmaValue<number>(\n db,\n 'application_id',\n 'application_id',\n ),\n };\n}\n\nfunction getReplicaMetadata(db: Database) {\n return {\n versionHistory: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT dataVersion, schemaVersion, minSafeVersion\n FROM \"_zero.versionHistory\"`,\n )\n .get(),\n ),\n subscriptionState: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT c.replicaVersion, s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock`,\n )\n .get(),\n ),\n sqliteCatalogCounts: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT type, count(*) as count\n FROM sqlite_master\n GROUP BY type\n ORDER BY type`,\n )\n .all(),\n ),\n };\n}\n\nfunction queryDiagnostic<T>(query: () => T):\n | {readonly ok: true; readonly value: T}\n | {\n readonly ok: false;\n readonly error: ErrorSummary | string;\n } {\n try {\n return {ok: true, value: query()};\n } catch (e) {\n return {ok: false, error: summarizeError(e)};\n }\n}\n\nfunction firstPragmaValue<T>(\n db: Database,\n pragma: string,\n column: string,\n): T | undefined {\n const [row] = db.pragma<Record<string, T>>(pragma);\n return row?.[column];\n}\n\nfunction runCheck(db: Database, pragma: 'quick_check' | 'integrity_check') {\n const rows = db.pragma<Record<string, string>>(pragma);\n const issues = rows\n .map(row => row[pragma] ?? Object.values(row)[0] ?? '')\n .filter(issue => issue !== '' && issue !== 'ok');\n const loggedIssues = issues.slice(0, MAX_LOGGED_CHECK_ISSUES);\n return {\n status: issues.length === 0 ? 'ok' : 'failed',\n rowCount: rows.length,\n issueCount: issues.length,\n loggedIssueCount: loggedIssues.length,\n truncated: issues.length > loggedIssues.length,\n issues: loggedIssues,\n } satisfies CheckSummary;\n}\n\nfunction logDiagnostic<T>(\n lc: LogContext,\n debugName: string,\n step: string,\n fn: () => T,\n): void {\n const start = Date.now();\n try {\n const result = fn();\n lc.error?.('SQLite corruption diagnostic', {\n debugName,\n step,\n durationMs: Date.now() - start,\n result,\n });\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step,\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-corruption.js","names":[],"sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"sourcesContent":["import {existsSync, statSync} from 'node:fs';\nimport type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {Database} from '../../../zqlite/src/db.ts';\n\n// Matches primary corruption codes and extended variants (e.g.\n// SQLITE_CORRUPT_INDEX, SQLITE_CORRUPT_VTAB, SQLITE_CORRUPT_SEQUENCE), which\n// the driver reports because it enables sqlite3_extended_result_codes().\nconst SQLITE_CORRUPTION_CODE = /^SQLITE_(CORRUPT(_|$)|NOTADB$)/;\nconst SQLITE_CORRUPTION_MESSAGE =\n /database disk image is malformed|file is not a database|database is malformed|malformed database/i;\nconst SQLITE_FILE_SUFFIXES = ['', '-wal', '-wal2', '-shm', '-journal'] as const;\nconst MAX_LOGGED_CHECK_ISSUES = 20;\n\ntype ErrorSummary = {\n readonly name: string;\n readonly message: string;\n readonly code?: string | undefined;\n readonly cause?: ErrorSummary | string | undefined;\n};\n\ntype SQLiteFileStats = {\n readonly suffix: string;\n readonly path: string;\n readonly sizeBytes: number;\n readonly mtimeMs: number;\n};\n\ntype CheckSummary = {\n readonly status: 'ok' | 'failed';\n readonly rowCount: number;\n readonly issueCount: number;\n readonly loggedIssueCount: number;\n readonly truncated: boolean;\n readonly issues: string[];\n};\n\ntype DiagnosticTarget = {\n readonly debugName: string;\n readonly dbPath: string;\n};\n\ntype RegisteredDiagnosticTarget = DiagnosticTarget & {\n readonly corruptionChecks: SQLiteCorruptionChecks;\n};\n\nconst diagnosticTargets = new Map<number, RegisteredDiagnosticTarget>();\nlet nextDiagnosticTargetID = 0;\n\nexport type SQLiteCorruptionChecks = boolean;\n\nexport function isSQLiteCorruption(e: unknown): boolean {\n return getSQLiteCorruptionCode(e) !== undefined;\n}\n\nexport function registerSQLiteCorruptionDiagnosticTarget(\n target: DiagnosticTarget,\n corruptionChecks: SQLiteCorruptionChecks = false,\n): () => void {\n const id = nextDiagnosticTargetID++;\n diagnosticTargets.set(id, {...target, corruptionChecks});\n return () => diagnosticTargets.delete(id);\n}\n\nexport function logLastChanceSQLiteCorruptionDiagnostics(\n lc: LogContext,\n cause: unknown,\n): boolean {\n if (!isSQLiteCorruption(cause) || diagnosticTargets.size === 0) {\n return false;\n }\n for (const {\n debugName,\n dbPath,\n corruptionChecks,\n } of diagnosticTargets.values()) {\n logSQLiteCorruptionDiagnostics(\n lc,\n debugName,\n dbPath,\n cause,\n corruptionChecks,\n );\n }\n return true;\n}\n\nexport function logSQLiteCorruptionDiagnostics(\n lc: LogContext,\n debugName: string,\n dbPath: string,\n cause: unknown,\n corruptionChecks: SQLiteCorruptionChecks = false,\n): void {\n const start = Date.now();\n lc.error?.('SQLite replica corruption detected', {\n debugName,\n dbPath,\n cause: summarizeError(cause),\n });\n\n logDiagnostic(lc, debugName, 'file-stats', () => ({\n files: getSQLiteFileStats(dbPath),\n }));\n\n let db: Database | undefined;\n try {\n db = new Database(lc, dbPath, {readonly: true});\n db.pragma('busy_timeout = 1000');\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step: 'open-readonly',\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n return;\n }\n\n try {\n logDiagnostic(lc, debugName, 'basic-pragmas', () => ({\n pragmas: getBasicPragmas(db),\n }));\n logDiagnostic(lc, debugName, 'replica-metadata', () => ({\n metadata: getReplicaMetadata(db),\n }));\n if (corruptionChecks) {\n logDiagnostic(lc, debugName, 'quick-check', () => ({\n check: runCheck(db, 'quick_check'),\n }));\n logDiagnostic(lc, debugName, 'integrity-check', () => ({\n check: runCheck(db, 'integrity_check'),\n }));\n }\n lc.error?.('SQLite corruption diagnostics completed', {\n debugName,\n dbPath,\n durationMs: Date.now() - start,\n });\n } finally {\n db.close();\n }\n}\n\nfunction getSQLiteCorruptionCode(e: unknown): string | undefined {\n let current = e;\n const seen = new Set<unknown>();\n\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n\n const code = getErrorCode(current);\n if (code !== undefined && SQLITE_CORRUPTION_CODE.test(code)) {\n return code;\n }\n\n if (current instanceof SqliteError || current instanceof Error) {\n if (SQLITE_CORRUPTION_MESSAGE.test(current.message)) {\n return code ?? 'SQLITE_CORRUPT';\n }\n current = getErrorCause(current);\n continue;\n }\n\n break;\n }\n\n return undefined;\n}\n\nfunction getErrorCode(e: unknown): string | undefined {\n if (e && typeof e === 'object' && 'code' in e) {\n const code = (e as {readonly code?: unknown}).code;\n return typeof code === 'string' ? code : undefined;\n }\n return undefined;\n}\n\nfunction getErrorCause(e: Error): unknown {\n return (e as {readonly cause?: unknown}).cause;\n}\n\nfunction summarizeError(e: unknown, depth = 0): ErrorSummary | string {\n if (!(e instanceof Error)) {\n return String(e);\n }\n const cause = getErrorCause(e);\n return {\n name: e.name,\n message: e.message,\n code: getErrorCode(e),\n cause:\n cause === undefined || depth >= 4\n ? undefined\n : summarizeError(cause, depth + 1),\n };\n}\n\nfunction getSQLiteFileStats(dbPath: string): SQLiteFileStats[] {\n const stats: SQLiteFileStats[] = [];\n for (const suffix of SQLITE_FILE_SUFFIXES) {\n const path = `${dbPath}${suffix}`;\n if (!existsSync(path)) {\n continue;\n }\n const stat = statSync(path);\n stats.push({\n suffix,\n path,\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n });\n }\n return stats;\n}\n\nfunction getBasicPragmas(db: Database) {\n return {\n pageSize: firstPragmaValue<number>(db, 'page_size', 'page_size'),\n pageCount: firstPragmaValue<number>(db, 'page_count', 'page_count'),\n freelistCount: firstPragmaValue<number>(\n db,\n 'freelist_count',\n 'freelist_count',\n ),\n journalMode: firstPragmaValue<string>(db, 'journal_mode', 'journal_mode'),\n schemaVersion: firstPragmaValue<number>(\n db,\n 'schema_version',\n 'schema_version',\n ),\n userVersion: firstPragmaValue<number>(db, 'user_version', 'user_version'),\n applicationID: firstPragmaValue<number>(\n db,\n 'application_id',\n 'application_id',\n ),\n };\n}\n\nfunction getReplicaMetadata(db: Database) {\n return {\n versionHistory: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT dataVersion, schemaVersion, minSafeVersion\n FROM \"_zero.versionHistory\"`,\n )\n .get(),\n ),\n subscriptionState: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT c.replicaVersion, s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock`,\n )\n .get(),\n ),\n sqliteCatalogCounts: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT type, count(*) as count\n FROM sqlite_master\n GROUP BY type\n ORDER BY type`,\n )\n .all(),\n ),\n };\n}\n\nfunction queryDiagnostic<T>(query: () => T):\n | {readonly ok: true; readonly value: T}\n | {\n readonly ok: false;\n readonly error: ErrorSummary | string;\n } {\n try {\n return {ok: true, value: query()};\n } catch (e) {\n return {ok: false, error: summarizeError(e)};\n }\n}\n\nfunction firstPragmaValue<T>(\n db: Database,\n pragma: string,\n column: string,\n): T | undefined {\n const [row] = db.pragma<Record<string, T>>(pragma);\n return row?.[column];\n}\n\nfunction runCheck(db: Database, pragma: 'quick_check' | 'integrity_check') {\n const rows = db.pragma<Record<string, string>>(pragma);\n const issues = rows\n .map(row => row[pragma] ?? Object.values(row)[0] ?? '')\n .filter(issue => issue !== '' && issue !== 'ok');\n const loggedIssues = issues.slice(0, MAX_LOGGED_CHECK_ISSUES);\n return {\n status: issues.length === 0 ? 'ok' : 'failed',\n rowCount: rows.length,\n issueCount: issues.length,\n loggedIssueCount: loggedIssues.length,\n truncated: issues.length > loggedIssues.length,\n issues: loggedIssues,\n } satisfies CheckSummary;\n}\n\nfunction logDiagnostic<T>(\n lc: LogContext,\n debugName: string,\n step: string,\n fn: () => T,\n): void {\n const start = Date.now();\n try {\n const result = fn();\n lc.error?.('SQLite corruption diagnostic', {\n debugName,\n step,\n durationMs: Date.now() - start,\n result,\n });\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step,\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n }\n}\n"],"mappings":";;;;AAQA,IAAM,yBAAyB;AAC/B,IAAM,4BACJ;AACF,IAAM,uBAAuB;CAAC;CAAI;CAAQ;CAAS;CAAQ;AAAU;AACrE,IAAM,0BAA0B;AAkChC,IAAM,oCAAoB,IAAI,IAAwC;AACtE,IAAI,yBAAyB;AAI7B,SAAgB,mBAAmB,GAAqB;CACtD,OAAO,wBAAwB,CAAC,MAAM,KAAA;AACxC;AAEA,SAAgB,yCACd,QACA,mBAA2C,OAC/B;CACZ,MAAM,KAAK;CACX,kBAAkB,IAAI,IAAI;EAAC,GAAG;EAAQ;CAAgB,CAAC;CACvD,aAAa,kBAAkB,OAAO,EAAE;AAC1C;AAEA,SAAgB,yCACd,IACA,OACS;CACT,IAAI,CAAC,mBAAmB,KAAK,KAAK,kBAAkB,SAAS,GAC3D,OAAO;CAET,KAAK,MAAM,EACT,WACA,QACA,sBACG,kBAAkB,OAAO,GAC5B,+BACE,IACA,WACA,QACA,OACA,gBACF;CAEF,OAAO;AACT;AAEA,SAAgB,+BACd,IACA,WACA,QACA,OACA,mBAA2C,OACrC;CACN,MAAM,QAAQ,KAAK,IAAI;CACvB,GAAG,QAAQ,sCAAsC;EAC/C;EACA;EACA,OAAO,eAAe,KAAK;CAC7B,CAAC;CAED,cAAc,IAAI,WAAW,qBAAqB,EAChD,OAAO,mBAAmB,MAAM,EAClC,EAAE;CAEF,IAAI;CACJ,IAAI;EACF,KAAK,IAAI,SAAS,IAAI,QAAQ,EAAC,UAAU,KAAI,CAAC;EAC9C,GAAG,OAAO,qBAAqB;CACjC,SAAS,GAAG;EACV,GAAG,QAAQ,uCAAuC;GAChD;GACA,MAAM;GACN,YAAY,KAAK,IAAI,IAAI;GACzB,OAAO,eAAe,CAAC;EACzB,CAAC;EACD;CACF;CAEA,IAAI;EACF,cAAc,IAAI,WAAW,wBAAwB,EACnD,SAAS,gBAAgB,EAAE,EAC7B,EAAE;EACF,cAAc,IAAI,WAAW,2BAA2B,EACtD,UAAU,mBAAmB,EAAE,EACjC,EAAE;EACF,IAAI,kBAAkB;GACpB,cAAc,IAAI,WAAW,sBAAsB,EACjD,OAAO,SAAS,IAAI,aAAa,EACnC,EAAE;GACF,cAAc,IAAI,WAAW,0BAA0B,EACrD,OAAO,SAAS,IAAI,iBAAiB,EACvC,EAAE;EACJ;EACA,GAAG,QAAQ,2CAA2C;GACpD;GACA;GACA,YAAY,KAAK,IAAI,IAAI;EAC3B,CAAC;CACH,UAAU;EACR,GAAG,MAAM;CACX;AACF;AAEA,SAAS,wBAAwB,GAAgC;CAC/D,IAAI,UAAU;CACd,MAAM,uBAAO,IAAI,IAAa;CAE9B,OAAO,YAAY,KAAA,KAAa,YAAY,QAAQ,CAAC,KAAK,IAAI,OAAO,GAAG;EACtE,KAAK,IAAI,OAAO;EAEhB,MAAM,OAAO,aAAa,OAAO;EACjC,IAAI,SAAS,KAAA,KAAa,uBAAuB,KAAK,IAAI,GACxD,OAAO;EAGT,IAAI,mBAAmB,eAAe,mBAAmB,OAAO;GAC9D,IAAI,0BAA0B,KAAK,QAAQ,OAAO,GAChD,OAAO,QAAQ;GAEjB,UAAU,cAAc,OAAO;GAC/B;EACF;EAEA;CACF;AAGF;AAEA,SAAS,aAAa,GAAgC;CACpD,IAAI,KAAK,OAAO,MAAM,YAAY,UAAU,GAAG;EAC7C,MAAM,OAAQ,EAAgC;EAC9C,OAAO,OAAO,SAAS,WAAW,OAAO,KAAA;CAC3C;AAEF;AAEA,SAAS,cAAc,GAAmB;CACxC,OAAQ,EAAiC;AAC3C;AAEA,SAAS,eAAe,GAAY,QAAQ,GAA0B;CACpE,IAAI,EAAE,aAAa,QACjB,OAAO,OAAO,CAAC;CAEjB,MAAM,QAAQ,cAAc,CAAC;CAC7B,OAAO;EACL,MAAM,EAAE;EACR,SAAS,EAAE;EACX,MAAM,aAAa,CAAC;EACpB,OACE,UAAU,KAAA,KAAa,SAAS,IAC5B,KAAA,IACA,eAAe,OAAO,QAAQ,CAAC;CACvC;AACF;AAEA,SAAS,mBAAmB,QAAmC;CAC7D,MAAM,QAA2B,CAAC;CAClC,KAAK,MAAM,UAAU,sBAAsB;EACzC,MAAM,OAAO,GAAG,SAAS;EACzB,IAAI,CAAC,WAAW,IAAI,GAClB;EAEF,MAAM,OAAO,SAAS,IAAI;EAC1B,MAAM,KAAK;GACT;GACA;GACA,WAAW,KAAK;GAChB,SAAS,KAAK;EAChB,CAAC;CACH;CACA,OAAO;AACT;AAEA,SAAS,gBAAgB,IAAc;CACrC,OAAO;EACL,UAAU,iBAAyB,IAAI,aAAa,WAAW;EAC/D,WAAW,iBAAyB,IAAI,cAAc,YAAY;EAClE,eAAe,iBACb,IACA,kBACA,gBACF;EACA,aAAa,iBAAyB,IAAI,gBAAgB,cAAc;EACxE,eAAe,iBACb,IACA,kBACA,gBACF;EACA,aAAa,iBAAyB,IAAI,gBAAgB,cAAc;EACxE,eAAe,iBACb,IACA,kBACA,gBACF;CACF;AACF;AAEA,SAAS,mBAAmB,IAAc;CACxC,OAAO;EACL,gBAAgB,sBACd,GACG,QACC;uCAEF,EACC,IAAI,CACT;EACA,mBAAmB,sBACjB,GACG,QACC;;;8BAIF,EACC,IAAI,CACT;EACA,qBAAqB,sBACnB,GACG,QACC;;;yBAIF,EACC,IAAI,CACT;CACF;AACF;AAEA,SAAS,gBAAmB,OAKtB;CACJ,IAAI;EACF,OAAO;GAAC,IAAI;GAAM,OAAO,MAAM;EAAC;CAClC,SAAS,GAAG;EACV,OAAO;GAAC,IAAI;GAAO,OAAO,eAAe,CAAC;EAAC;CAC7C;AACF;AAEA,SAAS,iBACP,IACA,QACA,QACe;CACf,MAAM,CAAC,OAAO,GAAG,OAA0B,MAAM;CACjD,OAAO,MAAM;AACf;AAEA,SAAS,SAAS,IAAc,QAA2C;CACzE,MAAM,OAAO,GAAG,OAA+B,MAAM;CACrD,MAAM,SAAS,KACZ,KAAI,QAAO,IAAI,WAAW,OAAO,OAAO,GAAG,EAAE,MAAM,EAAE,EACrD,QAAO,UAAS,UAAU,MAAM,UAAU,IAAI;CACjD,MAAM,eAAe,OAAO,MAAM,GAAG,uBAAuB;CAC5D,OAAO;EACL,QAAQ,OAAO,WAAW,IAAI,OAAO;EACrC,UAAU,KAAK;EACf,YAAY,OAAO;EACnB,kBAAkB,aAAa;EAC/B,WAAW,OAAO,SAAS,aAAa;EACxC,QAAQ;CACV;AACF;AAEA,SAAS,cACP,IACA,WACA,MACA,IACM;CACN,MAAM,QAAQ,KAAK,IAAI;CACvB,IAAI;EACF,MAAM,SAAS,GAAG;EAClB,GAAG,QAAQ,gCAAgC;GACzC;GACA;GACA,YAAY,KAAK,IAAI,IAAI;GACzB;EACF,CAAC;CACH,SAAS,GAAG;EACV,GAAG,QAAQ,uCAAuC;GAChD;GACA;GACA,YAAY,KAAK,IAAI,IAAI;GACzB,OAAO,eAAe,CAAC;EACzB,CAAC;CACH;AACF"}
@@ -6,7 +6,7 @@ type Options = MetricOptions & {
6
6
  type HistogramOptions = Options & {
7
7
  bucketBoundaries: number[];
8
8
  };
9
- export declare const NATIVE_HISTOGRAM_INSTRUMENT_NAMES: readonly ["zero.sync.view_syncer_lag", "zero.sync.view_syncer_hydration"];
9
+ export declare const NATIVE_HISTOGRAM_INSTRUMENT_NAMES: readonly ["zero.sync.view_syncer_lag", "zero.sync.view_syncer_hydration", "zero.sync.e2e_serving_lag"];
10
10
  export declare const LONG_DURATION_HISTOGRAM_BOUNDARIES_S: number[];
11
11
  export declare function getOrCreateUpDownCounter(category: Category, name: string, description: string): UpDownCounter;
12
12
  export declare function getOrCreateUpDownCounter(category: Category, name: string, opts: Options): UpDownCounter;
@@ -27,6 +27,11 @@ export type LatencyHistogram = {
27
27
  */
28
28
  recordMs(durationMs: number, attributes?: Parameters<Histogram['record']>[1]): void;
29
29
  };
30
+ /**
31
+ * Creates a histogram for dimensionless values without applying the
32
+ * millisecond-to-second conversion used by the latency helpers below.
33
+ */
34
+ export declare function getOrCreateValueHistogram(category: Category, name: string, opts: HistogramOptions): Histogram;
30
35
  export declare function getOrCreateHistogram(category: Category, name: string, opts: HistogramOptions): LatencyHistogram;
31
36
  export declare function getOrCreateNativeHistogram(category: Category, name: string, opts: Options): LatencyHistogram;
32
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/observability/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,CAAC;AAIb,KAAK,OAAO,GAAG,aAAa,GAAG;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,OAAO,GAAG;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC;AAE/D,eAAO,MAAM,iCAAiC,2EAGpC,CAAC;AAEX,eAAO,MAAM,oCAAoC,UAEhD,CAAC;AA4BF,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,aAAa,CAAC;AACjB,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,aAAa,CAAC;AAcjB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;OAMG;IACH,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAC9C,IAAI,CAAC;CACT,CAAC;AAoBF,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB,gBAAgB,CAelB;AAKD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,gBAAgB,CAQlB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,gBAAgB,CAMlB;AAID,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;AACX,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC;AAgBX,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,eAAe,CAAC;AACnB,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,eAAe,CAAC"}
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/observability/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,CAAC;AAIb,KAAK,OAAO,GAAG,aAAa,GAAG;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,OAAO,GAAG;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC;AAE/D,eAAO,MAAM,iCAAiC,wGAIpC,CAAC;AAEX,eAAO,MAAM,oCAAoC,UAEhD,CAAC;AA4BF,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,aAAa,CAAC;AACjB,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,aAAa,CAAC;AAcjB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;OAMG;IACH,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAC9C,IAAI,CAAC;CACT,CAAC;AAoBF;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB,SAAS,CAWX;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB,gBAAgB,CAelB;AAKD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,gBAAgB,CAQlB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,gBAAgB,CAMlB;AAID,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;AACX,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC;AAgBX,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,eAAe,CAAC;AACnB,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,eAAe,CAAC"}
@@ -1,7 +1,11 @@
1
1
  import { metrics } from "@opentelemetry/api";
2
2
  //#region ../zero-cache/src/observability/metrics.ts
3
3
  var meter;
4
- var NATIVE_HISTOGRAM_INSTRUMENT_NAMES = ["zero.sync.view_syncer_lag", "zero.sync.view_syncer_hydration"];
4
+ var NATIVE_HISTOGRAM_INSTRUMENT_NAMES = [
5
+ "zero.sync.view_syncer_lag",
6
+ "zero.sync.view_syncer_hydration",
7
+ "zero.sync.e2e_serving_lag"
8
+ ];
5
9
  var LONG_DURATION_HISTOGRAM_BOUNDARIES_S = [
6
10
  1,
7
11
  2,
@@ -64,6 +68,20 @@ var LATENCY_HISTOGRAM_BOUNDARIES_S = [
64
68
  30
65
69
  ];
66
70
  var histograms = cache();
71
+ /**
72
+ * Creates a histogram for dimensionless values without applying the
73
+ * millisecond-to-second conversion used by the latency helpers below.
74
+ */
75
+ function getOrCreateValueHistogram(category, name, opts) {
76
+ const { bucketBoundaries, ...metricOptions } = opts;
77
+ return histograms(name, (name) => getMeter().createHistogram(`zero.${category}.${name}`, {
78
+ ...metricOptions,
79
+ advice: {
80
+ ...metricOptions.advice,
81
+ explicitBucketBoundaries: bucketBoundaries
82
+ }
83
+ }));
84
+ }
67
85
  function getOrCreateHistogram(category, name, opts) {
68
86
  const { bucketBoundaries, ...metricOptions } = opts;
69
87
  const h = histograms(name, (name) => getMeter().createHistogram(`zero.${category}.${name}`, {
@@ -113,6 +131,6 @@ function getOrCreateGauge(category, name, opts) {
113
131
  return gauges(name, (name) => getMeter().createObservableGauge(`zero.${category}.${name}`, typeof opts === "string" ? { description: opts } : opts));
114
132
  }
115
133
  //#endregion
116
- export { LONG_DURATION_HISTOGRAM_BOUNDARIES_S, NATIVE_HISTOGRAM_INSTRUMENT_NAMES, getOrCreateCounter, getOrCreateGauge, getOrCreateHistogram, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter };
134
+ export { LONG_DURATION_HISTOGRAM_BOUNDARIES_S, NATIVE_HISTOGRAM_INSTRUMENT_NAMES, getOrCreateCounter, getOrCreateGauge, getOrCreateHistogram, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter, getOrCreateValueHistogram };
117
135
 
118
136
  //# sourceMappingURL=metrics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","names":[],"sources":["../../../../../zero-cache/src/observability/metrics.ts"],"sourcesContent":["import type {\n Counter,\n Histogram,\n Meter,\n MetricOptions,\n ObservableGauge,\n UpDownCounter,\n} from '@opentelemetry/api';\nimport {metrics} from '@opentelemetry/api';\n\n// intentional lazy initialization so it is not started before the SDK is started.\n\nexport type Category =\n | 'replication' // postgres to replica\n | 'replica' // health of replica and litestream backup\n | 'sync' // replica to client\n | 'mutation'\n | 'server';\n\nlet meter: Meter | undefined;\n\ntype Options = MetricOptions & {description: string};\ntype HistogramOptions = Options & {bucketBoundaries: number[]};\n\nexport const NATIVE_HISTOGRAM_INSTRUMENT_NAMES = [\n 'zero.sync.view_syncer_lag',\n 'zero.sync.view_syncer_hydration',\n] as const;\n\nexport const LONG_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200, 2400, 3600, 7200,\n];\n\nfunction getMeter() {\n if (!meter) {\n meter = metrics.getMeter('zero');\n }\n return meter;\n}\n\nfunction cache<TRet>(): (\n name: string,\n creator: (name: string) => TRet,\n) => TRet {\n const instruments = new Map<string, TRet>();\n return (name: string, creator: (name: string) => TRet) => {\n const existing = instruments.get(name);\n if (existing) {\n return existing;\n }\n\n const ret = creator(name);\n instruments.set(name, ret);\n return ret;\n };\n}\n\nconst upDownCounters = cache<UpDownCounter>();\n\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n description: string,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: Options,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): UpDownCounter {\n return upDownCounters(name, name =>\n getMeter().createUpDownCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\n/**\n * A latency histogram whose {@link recordMs} method accepts raw millisecond\n * durations and converts them to seconds internally.\n *\n * Use {@link getOrCreateLatencyHistogram} to create one — the unit (`'s'`),\n * bucket boundaries, and ms→s conversion are all baked in\n */\nexport type LatencyHistogram = {\n /**\n * Record a duration. Pass the raw elapsed milliseconds — the conversion to\n * seconds (required by the `unit: 's'` OTel histogram) is handled internally.\n *\n * @param durationMs Elapsed time in **milliseconds** (do NOT pre-divide).\n * @param attributes Optional OTel attributes to attach to the observation.\n */\n recordMs(\n durationMs: number,\n attributes?: Parameters<Histogram['record']>[1],\n ): void;\n};\n\n/**\n * Bucket boundaries (in seconds) for zero's latency histograms.\n *\n * The operational range is 1 ms – 5,000 ms (including customers actively\n * tuning queries). ~2× logarithmic steps give proportionally consistent\n * `histogram_quantile` accuracy regardless of where values cluster within\n * that range. 10,000 ms and 30,000 ms are overflow catchers for truly broken\n * states.\n *\n * 1 ms, 2 ms, 5 ms, 10 ms, 20 ms, 50 ms, 100 ms, 200 ms, 500 ms,\n * 1 s, 2 s, 5 s, 10 s, 30 s\n */\nconst LATENCY_HISTOGRAM_BOUNDARIES_S = [\n 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30,\n];\n\nconst histograms = cache<Histogram>();\n\nexport function getOrCreateHistogram(\n category: Category,\n name: string,\n opts: HistogramOptions,\n): LatencyHistogram {\n const {bucketBoundaries, ...metricOptions} = opts;\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, {\n ...metricOptions,\n advice: {\n ...metricOptions.advice,\n explicitBucketBoundaries: bucketBoundaries,\n },\n }),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n// Add instruments created through this helper to\n// NATIVE_HISTOGRAM_INSTRUMENT_NAMES so the SDK exports them as exponential\n// histograms.\nexport function getOrCreateNativeHistogram(\n category: Category,\n name: string,\n opts: Options,\n): LatencyHistogram {\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, opts),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n/**\n * Creates (or retrieves) a latency histogram for the given metric.\n *\n * - `unit` is always `'s'` (seconds), matching the OTel convention.\n * - Bucket boundaries are pre-set for zero's typical operation range\n * (1 ms – 5 s); see {@link LATENCY_HISTOGRAM_BOUNDARIES_S}.\n * - The returned {@link LatencyHistogram} accepts **milliseconds** via\n * `recordMs()`, so callers never need to divide by 1000.\n *\n * @example\n * ```ts\n * readonly #hydrationTime = getOrCreateLatencyHistogram(\n * 'sync', 'hydration-time', 'Time to hydrate a query.',\n * );\n * // ...\n * this.#hydrationTime.recordMs(performance.now() - start);\n * ```\n */\nexport function getOrCreateLatencyHistogram(\n category: Category,\n name: string,\n description: string,\n): LatencyHistogram {\n return getOrCreateHistogram(category, name, {\n description,\n unit: 's',\n bucketBoundaries: LATENCY_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nconst counters = cache<Counter>();\n\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n description: string,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: Options,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): Counter {\n return counters(name, name =>\n getMeter().createCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\nconst gauges = cache<ObservableGauge>();\n\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n description: string,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: Options,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: string | Options,\n): ObservableGauge {\n return gauges(name, name =>\n getMeter().createObservableGauge(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n"],"mappings":";;AAmBA,IAAI;AAKJ,IAAa,oCAAoC,CAC/C,6BACA,iCACF;AAEA,IAAa,uCAAuC;CAClD;CAAG;CAAG;CAAG;CAAI;CAAI;CAAI;CAAK;CAAK;CAAK;CAAM;CAAM;CAAM;AACxD;AAEA,SAAS,WAAW;CAClB,IAAI,CAAC,OACH,QAAQ,QAAQ,SAAS,MAAM;CAEjC,OAAO;AACT;AAEA,SAAS,QAGC;CACR,MAAM,8BAAc,IAAI,IAAkB;CAC1C,QAAQ,MAAc,YAAoC;EACxD,MAAM,WAAW,YAAY,IAAI,IAAI;EACrC,IAAI,UACF,OAAO;EAGT,MAAM,MAAM,QAAQ,IAAI;EACxB,YAAY,IAAI,MAAM,GAAG;EACzB,OAAO;CACT;AACF;AAEA,IAAM,iBAAiB,MAAqB;AAY5C,SAAgB,yBACd,UACA,MACA,MACe;CACf,OAAO,eAAe,OAAM,SAC1B,SAAS,EAAE,oBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;;;;;;;;;;;;;AAmCA,IAAM,iCAAiC;CACrC;CAAO;CAAO;CAAO;CAAM;CAAM;CAAM;CAAK;CAAK;CAAK;CAAG;CAAG;CAAG;CAAI;AACrE;AAEA,IAAM,aAAa,MAAiB;AAEpC,SAAgB,qBACd,UACA,MACA,MACkB;CAClB,MAAM,EAAC,kBAAkB,GAAG,kBAAiB;CAC7C,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ;EACrD,GAAG;EACH,QAAQ;GACN,GAAG,cAAc;GACjB,0BAA0B;EAC5B;CACF,CAAC,CACH;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;AAKA,SAAgB,2BACd,UACA,MACA,MACkB;CAClB,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ,IAAI,CAC7D;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,4BACd,UACA,MACA,aACkB;CAClB,OAAO,qBAAqB,UAAU,MAAM;EAC1C;EACA,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH;AAEA,IAAM,WAAW,MAAe;AAYhC,SAAgB,mBACd,UACA,MACA,MACS;CACT,OAAO,SAAS,OAAM,SACpB,SAAS,EAAE,cACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;AAEA,IAAM,SAAS,MAAuB;AAYtC,SAAgB,iBACd,UACA,MACA,MACiB;CACjB,OAAO,OAAO,OAAM,SAClB,SAAS,EAAE,sBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF"}
1
+ {"version":3,"file":"metrics.js","names":[],"sources":["../../../../../zero-cache/src/observability/metrics.ts"],"sourcesContent":["import type {\n Counter,\n Histogram,\n Meter,\n MetricOptions,\n ObservableGauge,\n UpDownCounter,\n} from '@opentelemetry/api';\nimport {metrics} from '@opentelemetry/api';\n\n// intentional lazy initialization so it is not started before the SDK is started.\n\nexport type Category =\n | 'replication' // postgres to replica\n | 'replica' // health of replica and litestream backup\n | 'sync' // replica to client\n | 'mutation'\n | 'server';\n\nlet meter: Meter | undefined;\n\ntype Options = MetricOptions & {description: string};\ntype HistogramOptions = Options & {bucketBoundaries: number[]};\n\nexport const NATIVE_HISTOGRAM_INSTRUMENT_NAMES = [\n 'zero.sync.view_syncer_lag',\n 'zero.sync.view_syncer_hydration',\n 'zero.sync.e2e_serving_lag',\n] as const;\n\nexport const LONG_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200, 2400, 3600, 7200,\n];\n\nfunction getMeter() {\n if (!meter) {\n meter = metrics.getMeter('zero');\n }\n return meter;\n}\n\nfunction cache<TRet>(): (\n name: string,\n creator: (name: string) => TRet,\n) => TRet {\n const instruments = new Map<string, TRet>();\n return (name: string, creator: (name: string) => TRet) => {\n const existing = instruments.get(name);\n if (existing) {\n return existing;\n }\n\n const ret = creator(name);\n instruments.set(name, ret);\n return ret;\n };\n}\n\nconst upDownCounters = cache<UpDownCounter>();\n\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n description: string,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: Options,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): UpDownCounter {\n return upDownCounters(name, name =>\n getMeter().createUpDownCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\n/**\n * A latency histogram whose {@link recordMs} method accepts raw millisecond\n * durations and converts them to seconds internally.\n *\n * Use {@link getOrCreateLatencyHistogram} to create one — the unit (`'s'`),\n * bucket boundaries, and ms→s conversion are all baked in\n */\nexport type LatencyHistogram = {\n /**\n * Record a duration. Pass the raw elapsed milliseconds — the conversion to\n * seconds (required by the `unit: 's'` OTel histogram) is handled internally.\n *\n * @param durationMs Elapsed time in **milliseconds** (do NOT pre-divide).\n * @param attributes Optional OTel attributes to attach to the observation.\n */\n recordMs(\n durationMs: number,\n attributes?: Parameters<Histogram['record']>[1],\n ): void;\n};\n\n/**\n * Bucket boundaries (in seconds) for zero's latency histograms.\n *\n * The operational range is 1 ms – 5,000 ms (including customers actively\n * tuning queries). ~2× logarithmic steps give proportionally consistent\n * `histogram_quantile` accuracy regardless of where values cluster within\n * that range. 10,000 ms and 30,000 ms are overflow catchers for truly broken\n * states.\n *\n * 1 ms, 2 ms, 5 ms, 10 ms, 20 ms, 50 ms, 100 ms, 200 ms, 500 ms,\n * 1 s, 2 s, 5 s, 10 s, 30 s\n */\nconst LATENCY_HISTOGRAM_BOUNDARIES_S = [\n 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30,\n];\n\nconst histograms = cache<Histogram>();\n\n/**\n * Creates a histogram for dimensionless values without applying the\n * millisecond-to-second conversion used by the latency helpers below.\n */\nexport function getOrCreateValueHistogram(\n category: Category,\n name: string,\n opts: HistogramOptions,\n): Histogram {\n const {bucketBoundaries, ...metricOptions} = opts;\n return histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, {\n ...metricOptions,\n advice: {\n ...metricOptions.advice,\n explicitBucketBoundaries: bucketBoundaries,\n },\n }),\n );\n}\n\nexport function getOrCreateHistogram(\n category: Category,\n name: string,\n opts: HistogramOptions,\n): LatencyHistogram {\n const {bucketBoundaries, ...metricOptions} = opts;\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, {\n ...metricOptions,\n advice: {\n ...metricOptions.advice,\n explicitBucketBoundaries: bucketBoundaries,\n },\n }),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n// Add instruments created through this helper to\n// NATIVE_HISTOGRAM_INSTRUMENT_NAMES so the SDK exports them as exponential\n// histograms.\nexport function getOrCreateNativeHistogram(\n category: Category,\n name: string,\n opts: Options,\n): LatencyHistogram {\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, opts),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n/**\n * Creates (or retrieves) a latency histogram for the given metric.\n *\n * - `unit` is always `'s'` (seconds), matching the OTel convention.\n * - Bucket boundaries are pre-set for zero's typical operation range\n * (1 ms – 5 s); see {@link LATENCY_HISTOGRAM_BOUNDARIES_S}.\n * - The returned {@link LatencyHistogram} accepts **milliseconds** via\n * `recordMs()`, so callers never need to divide by 1000.\n *\n * @example\n * ```ts\n * readonly #hydrationTime = getOrCreateLatencyHistogram(\n * 'sync', 'hydration-time', 'Time to hydrate a query.',\n * );\n * // ...\n * this.#hydrationTime.recordMs(performance.now() - start);\n * ```\n */\nexport function getOrCreateLatencyHistogram(\n category: Category,\n name: string,\n description: string,\n): LatencyHistogram {\n return getOrCreateHistogram(category, name, {\n description,\n unit: 's',\n bucketBoundaries: LATENCY_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nconst counters = cache<Counter>();\n\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n description: string,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: Options,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): Counter {\n return counters(name, name =>\n getMeter().createCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\nconst gauges = cache<ObservableGauge>();\n\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n description: string,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: Options,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: string | Options,\n): ObservableGauge {\n return gauges(name, name =>\n getMeter().createObservableGauge(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n"],"mappings":";;AAmBA,IAAI;AAKJ,IAAa,oCAAoC;CAC/C;CACA;CACA;AACF;AAEA,IAAa,uCAAuC;CAClD;CAAG;CAAG;CAAG;CAAI;CAAI;CAAI;CAAK;CAAK;CAAK;CAAM;CAAM;CAAM;AACxD;AAEA,SAAS,WAAW;CAClB,IAAI,CAAC,OACH,QAAQ,QAAQ,SAAS,MAAM;CAEjC,OAAO;AACT;AAEA,SAAS,QAGC;CACR,MAAM,8BAAc,IAAI,IAAkB;CAC1C,QAAQ,MAAc,YAAoC;EACxD,MAAM,WAAW,YAAY,IAAI,IAAI;EACrC,IAAI,UACF,OAAO;EAGT,MAAM,MAAM,QAAQ,IAAI;EACxB,YAAY,IAAI,MAAM,GAAG;EACzB,OAAO;CACT;AACF;AAEA,IAAM,iBAAiB,MAAqB;AAY5C,SAAgB,yBACd,UACA,MACA,MACe;CACf,OAAO,eAAe,OAAM,SAC1B,SAAS,EAAE,oBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;;;;;;;;;;;;;AAmCA,IAAM,iCAAiC;CACrC;CAAO;CAAO;CAAO;CAAM;CAAM;CAAM;CAAK;CAAK;CAAK;CAAG;CAAG;CAAG;CAAI;AACrE;AAEA,IAAM,aAAa,MAAiB;;;;;AAMpC,SAAgB,0BACd,UACA,MACA,MACW;CACX,MAAM,EAAC,kBAAkB,GAAG,kBAAiB;CAC7C,OAAO,WAAW,OAAM,SACtB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ;EACrD,GAAG;EACH,QAAQ;GACN,GAAG,cAAc;GACjB,0BAA0B;EAC5B;CACF,CAAC,CACH;AACF;AAEA,SAAgB,qBACd,UACA,MACA,MACkB;CAClB,MAAM,EAAC,kBAAkB,GAAG,kBAAiB;CAC7C,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ;EACrD,GAAG;EACH,QAAQ;GACN,GAAG,cAAc;GACjB,0BAA0B;EAC5B;CACF,CAAC,CACH;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;AAKA,SAAgB,2BACd,UACA,MACA,MACkB;CAClB,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ,IAAI,CAC7D;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,4BACd,UACA,MACA,aACkB;CAClB,OAAO,qBAAqB,UAAU,MAAM;EAC1C;EACA,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH;AAEA,IAAM,WAAW,MAAe;AAYhC,SAAgB,mBACd,UACA,MACA,MACS;CACT,OAAO,SAAS,OAAM,SACpB,SAAS,EAAE,cACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;AAEA,IAAM,SAAS,MAAuB;AAYtC,SAAgB,iBACd,UACA,MACA,MACiB;CACjB,OAAO,OAAO,OAAM,SAClB,SAAS,EAAE,sBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF"}
@@ -0,0 +1,31 @@
1
+ import { must } from "../../../shared/src/must.js";
2
+ import { parentWorker, singleProcessMode } from "../types/processes.js";
3
+ import { getZeroConfig } from "../config/zero-config.js";
4
+ import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
5
+ import { createLogContext } from "./logging.js";
6
+ import { VfsBackupWatermarkPoller } from "../services/litestream/vfs-watermark-poller.js";
7
+ import { LogContext, consoleLogSink } from "@rocicorp/logger";
8
+ //#region ../zero-cache/src/server/backup-watermark-poller.ts
9
+ var lc = new LogContext("info", {}, consoleLogSink);
10
+ async function runWorker(parent, env, ...argv) {
11
+ const config = getZeroConfig({
12
+ env,
13
+ argv
14
+ });
15
+ lc = createLogContext(config, "backup-watermark-poller");
16
+ const { litestream, replica } = config;
17
+ parent.send(["ready", { ready: true }]);
18
+ await runUntilKilled(lc, parent, new VfsBackupWatermarkPoller(lc, parent, {
19
+ localDbFile: replica.file,
20
+ backupURL: must(litestream.backupURL, "Missing --litestream-backup-url for backup watermark reader"),
21
+ endpoint: litestream.endpoint,
22
+ region: litestream.region,
23
+ extensionPath: litestream.vfsExtensionPath,
24
+ remotePollIntervalMs: litestream.vfsProbeIntervalMs
25
+ }));
26
+ }
27
+ if (!singleProcessMode()) exitAfter(() => lc, () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)));
28
+ //#endregion
29
+ export { runWorker as default };
30
+
31
+ //# sourceMappingURL=backup-watermark-poller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-watermark-poller.js","names":[],"sources":["../../../../../zero-cache/src/server/backup-watermark-poller.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {must} from '../../../shared/src/must.ts';\nimport {getZeroConfig} from '../config/zero-config.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {VfsBackupWatermarkPoller} from '../services/litestream/vfs-watermark-poller.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {createLogContext} from './logging.ts';\n\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const config = getZeroConfig({env, argv});\n lc = createLogContext(config, 'backup-watermark-poller');\n const {litestream, replica} = config;\n\n parent.send(['ready', {ready: true}]);\n\n await runUntilKilled(\n lc,\n parent,\n new VfsBackupWatermarkPoller(lc, parent, {\n localDbFile: replica.file,\n backupURL: must(\n litestream.backupURL,\n 'Missing --litestream-backup-url for backup watermark reader',\n ),\n endpoint: litestream.endpoint,\n region: litestream.region,\n extensionPath: litestream.vfsExtensionPath,\n remotePollIntervalMs: litestream.vfsProbeIntervalMs,\n }),\n );\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;AAYA,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,MAAM,SAAS,cAAc;EAAC;EAAK;CAAI,CAAC;CACxC,KAAK,iBAAiB,QAAQ,yBAAyB;CACvD,MAAM,EAAC,YAAY,YAAW;CAE9B,OAAO,KAAK,CAAC,SAAS,EAAC,OAAO,KAAI,CAAC,CAAC;CAEpC,MAAM,eACJ,IACA,QACA,IAAI,yBAAyB,IAAI,QAAQ;EACvC,aAAa,QAAQ;EACrB,WAAW,KACT,WAAW,WACX,6DACF;EACA,UAAU,WAAW;EACrB,QAAQ,WAAW;EACnB,eAAe,WAAW;EAC1B,sBAAsB,WAAW;CACnC,CAAC,CACH;AACF;AAGA,IAAI,CAAC,kBAAkB,GACrB,gBACQ,UACA,UAAU,KAAK,YAAY,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAC3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AA+BA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAQ/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAsMf"}
1
+ {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AAqCA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAU/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA6Tf"}
@@ -1,80 +1,117 @@
1
1
  import { assert } from "../../../shared/src/asserts.js";
2
2
  import { must } from "../../../shared/src/must.js";
3
- import { parentWorker, singleProcessMode } from "../types/processes.js";
3
+ import { childWorker, parentWorker, singleProcessMode } from "../types/processes.js";
4
4
  import { getShardConfig } from "../types/shards.js";
5
5
  import { getNormalizedZeroConfig } from "../config/zero-config.js";
6
6
  import { DatabaseInitError } from "../../../zqlite/src/db.js";
7
+ import { getOrCreateGauge } from "../observability/metrics.js";
7
8
  import { connectPgClient } from "../types/pg.js";
8
9
  import { registerSQLiteCorruptionDiagnosticTarget } from "../db/sqlite-corruption.js";
9
- import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
10
+ import { ProcessManager, exitAfter, runUntilKilled } from "../services/life-cycle.js";
10
11
  import { createLogContext } from "./logging.js";
12
+ import { REPLICATOR_URL } from "./worker-urls.js";
11
13
  import { getServerContext } from "../config/server-context.js";
12
14
  import { deleteLiteDB } from "../db/delete-lite-db.js";
13
15
  import { warmupConnections } from "../db/warmup.js";
14
16
  import { initEventSink, publishCriticalEvent } from "../observability/events.js";
15
17
  import { AutoResetSignal } from "../services/change-streamer/schema/tables.js";
16
- import { upgradeReplica } from "../services/change-source/common/replica-schema.js";
17
18
  import { ReplicationStatusPublisher, replicationStatusError } from "../services/replicator/replication-status.js";
19
+ import { changeLogFileName, deleteChangeLogDB } from "../services/replicator/change-log-db.js";
20
+ import { startReplicaBackupProcess } from "../services/litestream/commands.js";
18
21
  import { initializeCustomChangeSource } from "../services/change-source/custom/change-source.js";
19
22
  import { initializePostgresChangeSource } from "../services/change-source/pg/change-source.js";
20
- import { ChangeStreamerHttpServer } from "../services/change-streamer/change-streamer-http.js";
21
- import { BackupNotFoundException, restoreReplica } from "../services/litestream/commands.js";
22
23
  import { createBackupCleanupMonitor } from "../services/change-streamer/backup-cleanup-monitor-factory.js";
23
- import { initChangeStreamerSchema } from "../services/change-streamer/schema/init.js";
24
+ import { ChangeStreamerHttpServer } from "../services/change-streamer/change-streamer-http.js";
25
+ import { sqliteFileBytes } from "../services/replicator/sqlite-change-log-observability.js";
24
26
  import { PurgeLocker } from "../services/change-streamer/storer.js";
25
27
  import { initializeStreamer } from "../services/change-streamer/change-streamer-service.js";
26
- import { ReplicaMonitor } from "../services/change-streamer/replica-monitor.js";
28
+ import { initChangeStreamerSchema } from "../services/change-streamer/schema/init.js";
27
29
  import { startOtelAuto } from "./otel-start.js";
30
+ import { resolver } from "@rocicorp/resolver";
28
31
  import { LogContext, consoleLogSink } from "@rocicorp/logger";
29
32
  //#region ../zero-cache/src/server/change-streamer.ts
30
33
  var lc = new LogContext("info", {}, consoleLogSink);
31
34
  async function runWorker(parent, env, ...argv) {
32
- const workerStartTime = Date.now();
33
35
  const config = getNormalizedZeroConfig({
34
36
  env,
35
37
  argv
36
38
  });
37
- const { taskID, changeStreamer: { port, address, protocol, startupDelayMs, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds }, upstream, change, replica, initialSync, keepaliveTimeoutMs } = config;
39
+ const { taskID, changeStreamer: { port, address, protocol, startupDelayMs, backPressureLimitHeapProportion, flowControlConsensusTimeoutProportion, flowControlSlowSubscriberGracePeriodSeconds, sqliteChangeLogMode, sqliteChangeLogRetentionMs, sqliteChangeLogReadBatchRows, sqliteChangeLogPurgeBatchRows, sqliteChangeLogBarrierTimeoutMs }, autoReset, replicationLag, litestream, upstream, change, replica, initialSync, keepaliveTimeoutMs, sqliteCorruptionChecks } = config;
38
40
  startOtelAuto(createLogContext(config, "change-streamer", 0, false), "change-streamer", 0);
39
41
  lc = createLogContext(config, "change-streamer");
40
42
  registerSQLiteCorruptionDiagnosticTarget({
41
43
  debugName: "change-streamer replica",
42
44
  dbPath: replica.file
43
- });
45
+ }, sqliteCorruptionChecks);
44
46
  initEventSink(lc, config);
45
47
  const changeDB = await connectPgClient(lc, change.db, "change-streamer", { max: change.maxConns }, { sendStringAsJson: true });
46
48
  warmupConnections(lc, changeDB, "change").catch(() => {});
47
- const { autoReset, replicationLag } = config;
48
49
  const shard = getShardConfig(config);
49
50
  await initChangeStreamerSchema(lc, changeDB, shard);
50
- let purgeLock = await new PurgeLocker(lc, shard, changeDB).acquire();
51
- if (purgeLock) try {
52
- await restoreReplica(lc, config, purgeLock);
53
- } catch (e) {
54
- const log = e instanceof BackupNotFoundException ? "warn" : "error";
55
- lc[log]?.(`error restoring backup. resyncing the replica: ${String(e)}`, e);
56
- await purgeLock.release();
57
- purgeLock = null;
58
- }
51
+ let purgeLock = litestream.backupURL && litestream.executable ? await new PurgeLocker(lc, shard, changeDB).acquire() : null;
52
+ const restoreOptions = {
53
+ litestream,
54
+ constraints: purgeLock ?? void 0
55
+ };
59
56
  let changeStreamer;
57
+ let backupURL;
60
58
  const context = getServerContext(config);
59
+ const sqliteChangeLogEnabled = sqliteChangeLogMode !== "off";
60
+ const sqliteChangeLogComparing = sqliteChangeLogMode === "compare" || sqliteChangeLogMode === "serve";
61
+ if (sqliteChangeLogEnabled) {
62
+ const changeLogFile = changeLogFileName(replica.file);
63
+ registerSQLiteCorruptionDiagnosticTarget({
64
+ debugName: "change-streamer change-log",
65
+ dbPath: changeLogFile
66
+ }, sqliteCorruptionChecks);
67
+ getOrCreateGauge("replica", "sqlite_change_log.file_bytes", {
68
+ description: "The SQLite change log's total on-disk footprint: its main db file plus its wal sidecars.",
69
+ unit: "By"
70
+ }).addCallback(async (o) => o.observe(await sqliteFileBytes(lc, changeLogFile)));
71
+ }
72
+ let waitForFirstBackupBeforeServing = false;
61
73
  for (const first of [true, false]) try {
62
- const { changeSource, subscriptionState } = upstream.type === "pg" ? await initializePostgresChangeSource(lc, upstream.db, shard, replica.file, {
74
+ const { changeSource, subscriptionState, destinationBackupURL, replicaID, waitForBackupBeforeServing } = upstream.type === "pg" ? await initializePostgresChangeSource(lc, upstream.db, shard, replica.file, {
63
75
  ...initialSync,
64
76
  replicationSlotFailover: upstream.pgReplicationSlotFailover
65
- }, context, replicationLag.reportIntervalMs) : await initializeCustomChangeSource(lc, upstream.db, shard, replica.file, context);
77
+ }, context, replicationLag.reportIntervalMs, restoreOptions, { backupV5: litestream.backupUsingV5 }, purgeLock, upstream.pgStreamInboundTimeoutMs) : await initializeCustomChangeSource(lc, upstream.db, shard, replica.file, context, restoreOptions);
66
78
  const replicationStatusPublisher = ReplicationStatusPublisher.forReplicaFile(replica.file);
67
- changeStreamer = await initializeStreamer(lc, shard, taskID, address, protocol, changeDB, changeSource, replicationStatusPublisher, subscriptionState, purgeLock, autoReset ?? false, {
79
+ changeStreamer = await initializeStreamer(lc, shard, taskID, address, protocol, changeDB, changeSource, replicationStatusPublisher, subscriptionState, destinationBackupURL ? {
80
+ backupURL: destinationBackupURL,
81
+ litestreamVersion: litestream.backupUsingV5 ? "v5" : "legacy"
82
+ } : null, purgeLock, autoReset ?? false, {
68
83
  backPressureLimitHeapProportion,
69
- flowControlConsensusPaddingSeconds,
84
+ flowControlConsensusTimeoutProportion,
85
+ flowControlSlowSubscriberGracePeriodMs: flowControlSlowSubscriberGracePeriodSeconds > 0 ? flowControlSlowSubscriberGracePeriodSeconds * 1e3 : void 0,
70
86
  statementTimeoutMs: change.statementTimeoutMs,
71
- changeLogBatchSize: change.logBatchSize
87
+ changeLogBatchSize: change.logBatchSize,
88
+ sqliteCatchup: {
89
+ changeLogFile: changeLogFileName(replica.file),
90
+ readBatchRows: sqliteChangeLogReadBatchRows,
91
+ barrierTimeoutMs: sqliteChangeLogBarrierTimeoutMs
92
+ },
93
+ sqliteChangeLogWriter: sqliteChangeLogEnabled ? {
94
+ replicaFile: replica.file,
95
+ identity: {
96
+ epoch: null,
97
+ generation: subscriptionState.replicaVersion,
98
+ replicaID
99
+ }
100
+ } : void 0,
101
+ sqliteChangeLogPurge: sqliteChangeLogEnabled ? {
102
+ retentionMs: sqliteChangeLogRetentionMs,
103
+ batchRows: sqliteChangeLogPurgeBatchRows
104
+ } : void 0,
105
+ sqliteChangeLogCompare: sqliteChangeLogComparing ? { replicaFile: replica.file } : void 0
72
106
  }, setTimeout);
107
+ backupURL = destinationBackupURL;
108
+ waitForFirstBackupBeforeServing = waitForBackupBeforeServing;
73
109
  break;
74
110
  } catch (e) {
75
111
  if (first && e instanceof AutoResetSignal) {
76
112
  lc.warn?.(`resetting replica ${replica.file}`, e);
77
113
  deleteLiteDB(replica.file);
114
+ deleteChangeLogDB(replica.file);
78
115
  await purgeLock?.release();
79
116
  purgeLock = null;
80
117
  continue;
@@ -83,23 +120,46 @@ async function runWorker(parent, env, ...argv) {
83
120
  throw e;
84
121
  }
85
122
  assert(changeStreamer, `resetting replica did not advance replicaVersion`);
86
- await upgradeReplica(lc, "change-streamer-init", replica.file);
123
+ const processes = new ProcessManager(lc, parent);
124
+ if (backupURL) {
125
+ lc.info?.("setting up backup to", backupURL);
126
+ litestream.backupURL = backupURL;
127
+ const { promise: backupStarted, resolve } = resolver();
128
+ processes.addWorker(childWorker(REPLICATOR_URL, env, "backup"), "supporting", "backup-replicator").once("message", () => {
129
+ processes.addSubprocess(startReplicaBackupProcess(lc, litestream, replica.file), "supporting", "litestream");
130
+ resolve();
131
+ });
132
+ await backupStarted;
133
+ }
87
134
  const backupMonitor = createBackupCleanupMonitor({
88
135
  lc,
136
+ processes,
89
137
  config,
90
138
  replicaFile: replica.file,
91
139
  changeStreamer,
92
- initialCleanupDelayMs: Date.now() - workerStartTime,
93
140
  env
94
141
  });
95
- const monitor = backupMonitor ?? new ReplicaMonitor(lc, replica.file, changeStreamer);
96
142
  const changeStreamerWebServer = new ChangeStreamerHttpServer(lc, {
97
143
  port,
98
144
  keepaliveTimeoutMs,
99
145
  startupDelayMs
100
- }, parent, changeStreamer, backupMonitor);
101
- parent.send(["ready", { ready: true }]);
102
- return runUntilKilled(lc, parent, changeStreamerWebServer, monitor);
146
+ }, parent, changeStreamer);
147
+ if (waitForFirstBackupBeforeServing) {
148
+ const start = performance.now();
149
+ lc.info?.(`awaiting initial backup ...`);
150
+ backupMonitor.firstBackupReceived().then(() => {
151
+ const elapsed = performance.now() - start;
152
+ lc.info?.(`initial backup confirmed after ${elapsed.toFixed(2)}ms`);
153
+ parent.send(["ready", { ready: true }]);
154
+ });
155
+ } else parent.send(["ready", { ready: true }]);
156
+ try {
157
+ await runUntilKilled(lc, parent, changeStreamerWebServer, backupMonitor);
158
+ } catch (err) {
159
+ processes.logErrorAndExit(err, "change-streamer");
160
+ } finally {
161
+ await processes.shutdown();
162
+ }
103
163
  }
104
164
  if (!singleProcessMode()) exitAfter(() => lc, () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)).catch(async (e) => {
105
165
  await publishCriticalEvent(lc, replicationStatusError(lc, "Initializing", e));