@rocicorp/zero 1.12.0-canary.6 → 1.12.0-canary.7

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 (373) hide show
  1. package/out/zero/package.js +10 -10
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/auth/write-authorizer.js +9 -2
  4. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  5. package/out/zero-cache/src/server/change-streamer.js +1 -0
  6. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  7. package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
  8. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +17 -9
  9. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
  10. package/out/zero-cache/src/services/change-streamer/server-start-metrics.js +1 -1
  11. package/out/zero-cache/src/services/change-streamer/server-start-metrics.js.map +1 -1
  12. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -2
  13. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  14. package/out/zero-cache/src/services/view-syncer/view-syncer.js +19 -2
  15. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  16. package/out/zero-client/src/client/version.js +1 -1
  17. package/out/zero-protocol/src/inspect-down.d.ts +81 -81
  18. package/out/zero-protocol/src/up.d.ts +60 -60
  19. package/package.json +10 -10
  20. package/out/shared/src/cache.d.ts +0 -22
  21. package/out/shared/src/cache.d.ts.map +0 -1
  22. package/out/shared/src/custom-key-map.d.ts +0 -26
  23. package/out/shared/src/custom-key-map.d.ts.map +0 -1
  24. package/out/shared/src/custom-key-set.d.ts +0 -25
  25. package/out/shared/src/custom-key-set.d.ts.map +0 -1
  26. package/out/shared/src/logging-test-utils.d.ts +0 -12
  27. package/out/shared/src/logging-test-utils.d.ts.map +0 -1
  28. package/out/shared/src/queue.d.ts +0 -53
  29. package/out/shared/src/queue.d.ts.map +0 -1
  30. package/out/shared/src/ring-buffer.d.ts +0 -42
  31. package/out/shared/src/ring-buffer.d.ts.map +0 -1
  32. package/out/shared/src/sorted-entries.d.ts +0 -2
  33. package/out/shared/src/sorted-entries.d.ts.map +0 -1
  34. package/out/zero/src/server/change-streamer.d.ts +0 -2
  35. package/out/zero/src/server/change-streamer.d.ts.map +0 -1
  36. package/out/zero/src/server/main.d.ts +0 -2
  37. package/out/zero/src/server/main.d.ts.map +0 -1
  38. package/out/zero/src/server/reaper.d.ts +0 -2
  39. package/out/zero/src/server/reaper.d.ts.map +0 -1
  40. package/out/zero/src/server/replicator.d.ts +0 -2
  41. package/out/zero/src/server/replicator.d.ts.map +0 -1
  42. package/out/zero/src/server/syncer.d.ts +0 -2
  43. package/out/zero/src/server/syncer.d.ts.map +0 -1
  44. package/out/zero-cache/src/auth/jwt.d.ts +0 -12
  45. package/out/zero-cache/src/auth/jwt.d.ts.map +0 -1
  46. package/out/zero-cache/src/auth/load-permissions.d.ts +0 -17
  47. package/out/zero-cache/src/auth/load-permissions.d.ts.map +0 -1
  48. package/out/zero-cache/src/auth/write-authorizer.d.ts +0 -28
  49. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +0 -1
  50. package/out/zero-cache/src/config/server-context.d.ts +0 -16
  51. package/out/zero-cache/src/config/server-context.d.ts.map +0 -1
  52. package/out/zero-cache/src/custom/fetch.d.ts +0 -38
  53. package/out/zero-cache/src/custom/fetch.d.ts.map +0 -1
  54. package/out/zero-cache/src/custom/metrics.d.ts +0 -31
  55. package/out/zero-cache/src/custom/metrics.d.ts.map +0 -1
  56. package/out/zero-cache/src/custom-queries/transform-query.d.ts +0 -68
  57. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +0 -1
  58. package/out/zero-cache/src/db/create.d.ts +0 -12
  59. package/out/zero-cache/src/db/create.d.ts.map +0 -1
  60. package/out/zero-cache/src/db/delete-lite-db.d.ts +0 -2
  61. package/out/zero-cache/src/db/delete-lite-db.d.ts.map +0 -1
  62. package/out/zero-cache/src/db/index-predicate.d.ts +0 -4
  63. package/out/zero-cache/src/db/index-predicate.d.ts.map +0 -1
  64. package/out/zero-cache/src/db/migration-lite.d.ts +0 -78
  65. package/out/zero-cache/src/db/migration-lite.d.ts.map +0 -1
  66. package/out/zero-cache/src/db/migration.d.ts +0 -80
  67. package/out/zero-cache/src/db/migration.d.ts.map +0 -1
  68. package/out/zero-cache/src/db/pg-copy-binary.d.ts +0 -101
  69. package/out/zero-cache/src/db/pg-copy-binary.d.ts.map +0 -1
  70. package/out/zero-cache/src/db/pg-copy.d.ts +0 -30
  71. package/out/zero-cache/src/db/pg-copy.d.ts.map +0 -1
  72. package/out/zero-cache/src/db/pg-type-parser.d.ts +0 -15
  73. package/out/zero-cache/src/db/pg-type-parser.d.ts.map +0 -1
  74. package/out/zero-cache/src/db/transaction-pool.d.ts +0 -205
  75. package/out/zero-cache/src/db/transaction-pool.d.ts.map +0 -1
  76. package/out/zero-cache/src/db/warmup.d.ts +0 -4
  77. package/out/zero-cache/src/db/warmup.d.ts.map +0 -1
  78. package/out/zero-cache/src/observability/events.d.ts +0 -15
  79. package/out/zero-cache/src/observability/events.d.ts.map +0 -1
  80. package/out/zero-cache/src/observability/log-throttle.d.ts +0 -44
  81. package/out/zero-cache/src/observability/log-throttle.d.ts.map +0 -1
  82. package/out/zero-cache/src/server/anonymous-otel-start.d.ts +0 -21
  83. package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +0 -1
  84. package/out/zero-cache/src/server/change-streamer.d.ts +0 -3
  85. package/out/zero-cache/src/server/change-streamer.d.ts.map +0 -1
  86. package/out/zero-cache/src/server/inspector-delegate.d.ts +0 -71
  87. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +0 -1
  88. package/out/zero-cache/src/server/main.d.ts +0 -3
  89. package/out/zero-cache/src/server/main.d.ts.map +0 -1
  90. package/out/zero-cache/src/server/otel-diag-logger.d.ts +0 -16
  91. package/out/zero-cache/src/server/otel-diag-logger.d.ts.map +0 -1
  92. package/out/zero-cache/src/server/otel-start.d.ts +0 -3
  93. package/out/zero-cache/src/server/otel-start.d.ts.map +0 -1
  94. package/out/zero-cache/src/server/priority-op.d.ts +0 -8
  95. package/out/zero-cache/src/server/priority-op.d.ts.map +0 -1
  96. package/out/zero-cache/src/server/reaper.d.ts +0 -3
  97. package/out/zero-cache/src/server/reaper.d.ts.map +0 -1
  98. package/out/zero-cache/src/server/replicator.d.ts +0 -10
  99. package/out/zero-cache/src/server/replicator.d.ts.map +0 -1
  100. package/out/zero-cache/src/server/slot-keeper.d.ts +0 -26
  101. package/out/zero-cache/src/server/slot-keeper.d.ts.map +0 -1
  102. package/out/zero-cache/src/server/syncer-assigner.d.ts +0 -14
  103. package/out/zero-cache/src/server/syncer-assigner.d.ts.map +0 -1
  104. package/out/zero-cache/src/server/syncer.d.ts +0 -3
  105. package/out/zero-cache/src/server/syncer.d.ts.map +0 -1
  106. package/out/zero-cache/src/server/worker-dispatcher.d.ts +0 -17
  107. package/out/zero-cache/src/server/worker-dispatcher.d.ts.map +0 -1
  108. package/out/zero-cache/src/services/analyze.d.ts +0 -9
  109. package/out/zero-cache/src/services/analyze.d.ts.map +0 -1
  110. package/out/zero-cache/src/services/change-source/change-source.d.ts +0 -39
  111. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +0 -1
  112. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts +0 -57
  113. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +0 -1
  114. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts +0 -55
  115. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +0 -1
  116. package/out/zero-cache/src/services/change-source/common/init-cleanup.d.ts +0 -24
  117. package/out/zero-cache/src/services/change-source/common/init-cleanup.d.ts.map +0 -1
  118. package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +0 -140
  119. package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +0 -1
  120. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +0 -14
  121. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +0 -1
  122. package/out/zero-cache/src/services/change-source/custom/change-source.d.ts +0 -25
  123. package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +0 -1
  124. package/out/zero-cache/src/services/change-source/pg/backfill-metadata.d.ts +0 -14
  125. package/out/zero-cache/src/services/change-source/pg/backfill-metadata.d.ts.map +0 -1
  126. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +0 -36
  127. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +0 -1
  128. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts +0 -72
  129. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +0 -1
  130. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +0 -127
  131. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +0 -1
  132. package/out/zero-cache/src/services/change-source/pg/fork-request.d.ts +0 -57
  133. package/out/zero-cache/src/services/change-source/pg/fork-request.d.ts.map +0 -1
  134. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +0 -155
  135. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +0 -1
  136. package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.d.ts +0 -17
  137. package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.d.ts.map +0 -1
  138. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.d.ts +0 -28
  139. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.d.ts.map +0 -1
  140. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput.types.d.ts +0 -76
  141. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput.types.d.ts.map +0 -1
  142. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +0 -169
  143. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +0 -1
  144. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts +0 -16
  145. package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts.map +0 -1
  146. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.d.ts +0 -15
  147. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.d.ts.map +0 -1
  148. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +0 -127
  149. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +0 -1
  150. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +0 -9
  151. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +0 -1
  152. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +0 -68
  153. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +0 -1
  154. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +0 -72
  155. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +0 -1
  156. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +0 -50
  157. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +0 -1
  158. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +0 -12
  159. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +0 -1
  160. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +0 -57
  161. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +0 -1
  162. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +0 -98
  163. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +0 -1
  164. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +0 -34
  165. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +0 -1
  166. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts +0 -34
  167. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts.map +0 -1
  168. package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts +0 -158
  169. package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts.map +0 -1
  170. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts +0 -19
  171. package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts.map +0 -1
  172. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +0 -78
  173. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +0 -1
  174. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +0 -152
  175. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +0 -1
  176. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +0 -575
  177. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +0 -1
  178. package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts +0 -24
  179. package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts.map +0 -1
  180. package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts +0 -15
  181. package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts.map +0 -1
  182. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +0 -49
  183. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +0 -1
  184. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts +0 -80
  185. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts.map +0 -1
  186. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts +0 -88
  187. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +0 -1
  188. package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts +0 -15
  189. package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts.map +0 -1
  190. package/out/zero-cache/src/services/change-streamer/restore-admission.d.ts +0 -243
  191. package/out/zero-cache/src/services/change-streamer/restore-admission.d.ts.map +0 -1
  192. package/out/zero-cache/src/services/change-streamer/schema/init.d.ts +0 -6
  193. package/out/zero-cache/src/services/change-streamer/schema/init.d.ts.map +0 -1
  194. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +0 -152
  195. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +0 -1
  196. package/out/zero-cache/src/services/change-streamer/server-start-metrics.d.ts +0 -33
  197. package/out/zero-cache/src/services/change-streamer/server-start-metrics.d.ts.map +0 -1
  198. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts +0 -11
  199. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts.map +0 -1
  200. package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts +0 -92
  201. package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts.map +0 -1
  202. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +0 -36
  203. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +0 -1
  204. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +0 -22
  205. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +0 -1
  206. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +0 -99
  207. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +0 -1
  208. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts +0 -83
  209. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts.map +0 -1
  210. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts +0 -121
  211. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts.map +0 -1
  212. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts +0 -73
  213. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts.map +0 -1
  214. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +0 -62
  215. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +0 -1
  216. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +0 -114
  217. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +0 -1
  218. package/out/zero-cache/src/services/change-streamer/storer.d.ts +0 -276
  219. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +0 -1
  220. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +0 -485
  221. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +0 -1
  222. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +0 -205
  223. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +0 -1
  224. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +0 -52
  225. package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +0 -1
  226. package/out/zero-cache/src/services/limiter/sliding-window-limiter.d.ts +0 -57
  227. package/out/zero-cache/src/services/limiter/sliding-window-limiter.d.ts.map +0 -1
  228. package/out/zero-cache/src/services/litestream/commands.d.ts +0 -77
  229. package/out/zero-cache/src/services/litestream/commands.d.ts.map +0 -1
  230. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +0 -78
  231. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +0 -1
  232. package/out/zero-cache/src/services/litestream/metrics.d.ts +0 -31
  233. package/out/zero-cache/src/services/litestream/metrics.d.ts.map +0 -1
  234. package/out/zero-cache/src/services/litestream/restore-progress.d.ts +0 -24
  235. package/out/zero-cache/src/services/litestream/restore-progress.d.ts.map +0 -1
  236. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +0 -40
  237. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +0 -1
  238. package/out/zero-cache/src/services/mutagen/pusher.d.ts +0 -63
  239. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +0 -1
  240. package/out/zero-cache/src/services/replicator/change-log-db.d.ts +0 -368
  241. package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +0 -1
  242. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +0 -56
  243. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +0 -1
  244. package/out/zero-cache/src/services/replicator/change-processor.d.ts +0 -37
  245. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +0 -1
  246. package/out/zero-cache/src/services/replicator/drain-handler.d.ts +0 -82
  247. package/out/zero-cache/src/services/replicator/drain-handler.d.ts.map +0 -1
  248. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +0 -25
  249. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +0 -1
  250. package/out/zero-cache/src/services/replicator/notifier.d.ts +0 -9
  251. package/out/zero-cache/src/services/replicator/notifier.d.ts.map +0 -1
  252. package/out/zero-cache/src/services/replicator/replication-status.d.ts +0 -71
  253. package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +0 -1
  254. package/out/zero-cache/src/services/replicator/replicator.d.ts +0 -86
  255. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +0 -1
  256. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +0 -55
  257. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +0 -1
  258. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +0 -132
  259. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +0 -1
  260. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts +0 -120
  261. package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts.map +0 -1
  262. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +0 -111
  263. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +0 -1
  264. package/out/zero-cache/src/services/run-ast.d.ts +0 -39
  265. package/out/zero-cache/src/services/run-ast.d.ts.map +0 -1
  266. package/out/zero-cache/src/services/runner.d.ts +0 -19
  267. package/out/zero-cache/src/services/runner.d.ts.map +0 -1
  268. package/out/zero-cache/src/services/view-syncer/active-users-gauge.d.ts +0 -17
  269. package/out/zero-cache/src/services/view-syncer/active-users-gauge.d.ts.map +0 -1
  270. package/out/zero-cache/src/services/view-syncer/advancement-metrics.d.ts +0 -26
  271. package/out/zero-cache/src/services/view-syncer/advancement-metrics.d.ts.map +0 -1
  272. package/out/zero-cache/src/services/view-syncer/batch-pacer.d.ts +0 -79
  273. package/out/zero-cache/src/services/view-syncer/batch-pacer.d.ts.map +0 -1
  274. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +0 -97
  275. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +0 -1
  276. package/out/zero-cache/src/services/view-syncer/client-schema.d.ts +0 -18
  277. package/out/zero-cache/src/services/view-syncer/client-schema.d.ts.map +0 -1
  278. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +0 -184
  279. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +0 -1
  280. package/out/zero-cache/src/services/view-syncer/cvr-purger.d.ts +0 -22
  281. package/out/zero-cache/src/services/view-syncer/cvr-purger.d.ts.map +0 -1
  282. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +0 -128
  283. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +0 -1
  284. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +0 -294
  285. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +0 -1
  286. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +0 -81
  287. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +0 -1
  288. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +0 -45
  289. package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +0 -1
  290. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +0 -53
  291. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +0 -1
  292. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts +0 -33
  293. package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts.map +0 -1
  294. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +0 -51
  295. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +0 -1
  296. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +0 -34
  297. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +0 -1
  298. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +0 -296
  299. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +0 -1
  300. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +0 -7
  301. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +0 -1
  302. package/out/zero-cache/src/services/view-syncer/push-cost.d.ts +0 -132
  303. package/out/zero-cache/src/services/view-syncer/push-cost.d.ts.map +0 -1
  304. package/out/zero-cache/src/services/view-syncer/push-cutoff.d.ts +0 -55
  305. package/out/zero-cache/src/services/view-syncer/push-cutoff.d.ts.map +0 -1
  306. package/out/zero-cache/src/services/view-syncer/query-covering.d.ts +0 -28
  307. package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +0 -1
  308. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +0 -25
  309. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +0 -1
  310. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +0 -89
  311. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +0 -1
  312. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts +0 -90
  313. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +0 -1
  314. package/out/zero-cache/src/services/view-syncer/row-set-signature.d.ts +0 -17
  315. package/out/zero-cache/src/services/view-syncer/row-set-signature.d.ts.map +0 -1
  316. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts +0 -86
  317. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts.map +0 -1
  318. package/out/zero-cache/src/services/view-syncer/schema/init.d.ts +0 -5
  319. package/out/zero-cache/src/services/view-syncer/schema/init.d.ts.map +0 -1
  320. package/out/zero-cache/src/services/view-syncer/schema/types.d.ts +0 -563
  321. package/out/zero-cache/src/services/view-syncer/schema/types.d.ts.map +0 -1
  322. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts +0 -95
  323. package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts.map +0 -1
  324. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +0 -420
  325. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +0 -1
  326. package/out/zero-cache/src/services/view-syncer/tracer.d.ts +0 -2
  327. package/out/zero-cache/src/services/view-syncer/tracer.d.ts.map +0 -1
  328. package/out/zero-cache/src/services/view-syncer/ttl-clock.d.ts +0 -10
  329. package/out/zero-cache/src/services/view-syncer/ttl-clock.d.ts.map +0 -1
  330. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +0 -133
  331. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +0 -1
  332. package/out/zero-cache/src/types/downstream.d.ts +0 -11
  333. package/out/zero-cache/src/types/downstream.d.ts.map +0 -1
  334. package/out/zero-cache/src/types/error-with-level.d.ts +0 -9
  335. package/out/zero-cache/src/types/error-with-level.d.ts.map +0 -1
  336. package/out/zero-cache/src/types/pg-patch.d.ts +0 -21
  337. package/out/zero-cache/src/types/pg-patch.d.ts.map +0 -1
  338. package/out/zero-cache/src/types/pg-versions.d.ts +0 -3
  339. package/out/zero-cache/src/types/pg-versions.d.ts.map +0 -1
  340. package/out/zero-cache/src/types/poke-chunk.d.ts +0 -18
  341. package/out/zero-cache/src/types/poke-chunk.d.ts.map +0 -1
  342. package/out/zero-cache/src/types/streams.d.ts +0 -115
  343. package/out/zero-cache/src/types/streams.d.ts.map +0 -1
  344. package/out/zero-cache/src/types/subscription.d.ts +0 -197
  345. package/out/zero-cache/src/types/subscription.d.ts.map +0 -1
  346. package/out/zero-cache/src/workers/connect-params.d.ts +0 -28
  347. package/out/zero-cache/src/workers/connect-params.d.ts.map +0 -1
  348. package/out/zero-cache/src/workers/connection.d.ts +0 -77
  349. package/out/zero-cache/src/workers/connection.d.ts.map +0 -1
  350. package/out/zero-cache/src/workers/replicator.d.ts +0 -57
  351. package/out/zero-cache/src/workers/replicator.d.ts.map +0 -1
  352. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts +0 -14
  353. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +0 -1
  354. package/out/zero-cache/src/workers/syncer.d.ts +0 -67
  355. package/out/zero-cache/src/workers/syncer.d.ts.map +0 -1
  356. package/out/zero-events/src/index.d.ts +0 -27
  357. package/out/zero-events/src/index.d.ts.map +0 -1
  358. package/out/zero-events/src/json.d.ts +0 -20
  359. package/out/zero-events/src/json.d.ts.map +0 -1
  360. package/out/zero-events/src/status.d.ts +0 -144
  361. package/out/zero-events/src/status.d.ts.map +0 -1
  362. package/out/zqlite/src/database-storage.d.ts +0 -43
  363. package/out/zqlite/src/database-storage.d.ts.map +0 -1
  364. package/out/zqlite/src/explain-queries.d.ts +0 -4
  365. package/out/zqlite/src/explain-queries.d.ts.map +0 -1
  366. package/out/zqlite/src/internal/sql-inline.d.ts +0 -13
  367. package/out/zqlite/src/internal/sql-inline.d.ts.map +0 -1
  368. package/out/zqlite/src/resolve-scalar-subqueries.d.ts +0 -72
  369. package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +0 -1
  370. package/out/zqlite/src/sqlite-cost-model.d.ts +0 -17
  371. package/out/zqlite/src/sqlite-cost-model.d.ts.map +0 -1
  372. package/out/zqlite/src/sqlite-stat-fanout.d.ts +0 -121
  373. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
1
  //#region package.json
2
2
  var name = "@rocicorp/zero";
3
- var version = "1.12.0-canary.6";
3
+ var version = "1.12.0-canary.7";
4
4
  var description = "Zero is a web framework for serverless web development.";
5
5
  var homepage = "https://zero.rocicorp.dev";
6
6
  var bugs = { "url": "https://bugs.rocicorp.dev" };
@@ -122,15 +122,15 @@ var dependencies = {
122
122
  "@fastify/cors": "^10.1.0",
123
123
  "@fastify/websocket": "^11.2.0",
124
124
  "@google-cloud/precise-date": "^4.0.0",
125
- "@opentelemetry/api": "^1.9.1",
126
- "@opentelemetry/api-logs": "^0.223.0",
127
- "@opentelemetry/auto-instrumentations-node": "^0.81.2",
128
- "@opentelemetry/exporter-metrics-otlp-http": "^0.223.0",
129
- "@opentelemetry/resources": "^2.12.0",
130
- "@opentelemetry/sdk-metrics": "^2.12.0",
131
- "@opentelemetry/sdk-node": "^0.223.0",
132
- "@opentelemetry/sdk-trace-node": "^2.12.0",
133
- "@opentelemetry/semantic-conventions": "^1.43.0",
125
+ "@opentelemetry/api": "1.9.1",
126
+ "@opentelemetry/api-logs": "0.223.0",
127
+ "@opentelemetry/auto-instrumentations-node": "0.81.2",
128
+ "@opentelemetry/exporter-metrics-otlp-http": "0.223.0",
129
+ "@opentelemetry/resources": "2.12.0",
130
+ "@opentelemetry/sdk-metrics": "2.12.0",
131
+ "@opentelemetry/sdk-node": "0.223.0",
132
+ "@opentelemetry/sdk-trace-node": "2.12.0",
133
+ "@opentelemetry/semantic-conventions": "1.43.0",
134
134
  "@postgresql-typed/oids": "^0.2.0",
135
135
  "@rocicorp/lock": "^1.0.4",
136
136
  "@rocicorp/logger": "^6.1.0",
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.12.0-canary.6\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --quiet --config ../../oxlint.config.ts src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.5.5\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.66.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.1.0\",\n \"@fastify/websocket\": \"^11.2.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.1\",\n \"@opentelemetry/api-logs\": \"^0.223.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.81.2\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.223.0\",\n \"@opentelemetry/resources\": \"^2.12.0\",\n \"@opentelemetry/sdk-metrics\": \"^2.12.0\",\n \"@opentelemetry/sdk-node\": \"^0.223.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.12.0\",\n \"@opentelemetry/semantic-conventions\": \"^1.43.0\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^6.1.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.1.7\",\n \"@standard-schema/spec\": \"^1.1.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.18.1\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.2\",\n \"chokidar\": \"^4.0.3\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.2\",\n \"command-line-usage\": \"^7.0.4\",\n \"compare-utf8\": \"^0.2.3\",\n \"defu\": \"^6.1.7\",\n \"eventemitter3\": \"^5.0.4\",\n \"fastify\": \"^5.12.7\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.10.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.2\",\n \"nanoid\": \"^5.1.16\",\n \"oxc-parser\": \"^0.129.0\",\n \"oxfmt\": \"^0.74.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.8.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.22.0\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \"^18.2.5\",\n \"analyze-query\": \"workspace:*\",\n \"ast-to-zql\": \"workspace:*\",\n \"expo\": \"^58.0.14\",\n \"expo-sqlite\": \"^58.0.13\",\n \"react-native\": \"^0.88.0\",\n \"replicache\": \"workspace:*\",\n \"shared\": \"workspace:*\",\n \"syncpack\": \"^14.3.1\",\n \"typedoc\": \"^0.28.19\",\n \"typescript\": \"~7.0.2\",\n \"vite\": \"^8.3.4\",\n \"vitest\": \"^5.0.5\",\n \"zero-cache\": \"workspace:*\",\n \"zero-client\": \"workspace:*\",\n \"zero-pg\": \"workspace:*\",\n \"zero-react\": \"workspace:*\",\n \"zero-server\": \"workspace:*\",\n \"zero-solid\": \"workspace:*\",\n \"zqlite\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@11.11.0\"\n}\n"],"mappings":""}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.12.0-canary.7\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --quiet --config ../../oxlint.config.ts src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.5.5\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.66.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.1.0\",\n \"@fastify/websocket\": \"^11.2.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"1.9.1\",\n \"@opentelemetry/api-logs\": \"0.223.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"0.81.2\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"0.223.0\",\n \"@opentelemetry/resources\": \"2.12.0\",\n \"@opentelemetry/sdk-metrics\": \"2.12.0\",\n \"@opentelemetry/sdk-node\": \"0.223.0\",\n \"@opentelemetry/sdk-trace-node\": \"2.12.0\",\n \"@opentelemetry/semantic-conventions\": \"1.43.0\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^6.1.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.1.7\",\n \"@standard-schema/spec\": \"^1.1.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.18.1\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.2\",\n \"chokidar\": \"^4.0.3\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.2\",\n \"command-line-usage\": \"^7.0.4\",\n \"compare-utf8\": \"^0.2.3\",\n \"defu\": \"^6.1.7\",\n \"eventemitter3\": \"^5.0.4\",\n \"fastify\": \"^5.12.7\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.10.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.2\",\n \"nanoid\": \"^5.1.16\",\n \"oxc-parser\": \"^0.129.0\",\n \"oxfmt\": \"^0.74.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.8.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.22.0\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \"^18.2.5\",\n \"analyze-query\": \"workspace:*\",\n \"ast-to-zql\": \"workspace:*\",\n \"expo\": \"^58.0.14\",\n \"expo-sqlite\": \"^58.0.13\",\n \"react-native\": \"^0.88.0\",\n \"replicache\": \"workspace:*\",\n \"shared\": \"workspace:*\",\n \"syncpack\": \"^14.3.1\",\n \"typedoc\": \"^0.28.19\",\n \"typescript\": \"~7.0.2\",\n \"vite\": \"^8.3.4\",\n \"vitest\": \"^5.0.5\",\n \"zero-cache\": \"workspace:*\",\n \"zero-client\": \"workspace:*\",\n \"zero-pg\": \"workspace:*\",\n \"zero-react\": \"workspace:*\",\n \"zero-server\": \"workspace:*\",\n \"zero-solid\": \"workspace:*\",\n \"zqlite\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@11.11.0\"\n}\n"],"mappings":""}
@@ -123,8 +123,15 @@ var WriteAuthorizerImpl = class {
123
123
  return op;
124
124
  });
125
125
  }
126
- validateTableNames(ops) {
127
- for (const op of ops) if (!this.#tableSpecs.has(op.tableName)) throw new Error(`Table '${op.tableName}' is not a valid table.`);
126
+ validateOps(ops) {
127
+ return ops.map((op) => {
128
+ const tableSpec = this.#tableSpecs.get(op.tableName);
129
+ if (!tableSpec) throw new Error(`Table '${op.tableName}' is not a valid table.`);
130
+ return {
131
+ ...op,
132
+ primaryKey: tableSpec.tableSpec.primaryKey
133
+ };
134
+ });
128
135
  }
129
136
  #canInsert(phase, authData, op) {
130
137
  return this.#timedCanDo(phase, "insert", authData, op);
@@ -1 +1 @@
1
- {"version":3,"file":"write-authorizer.js","names":["v.parse"],"sources":["../../../../../zero-cache/src/auth/write-authorizer.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {MaybePromise} from '@opentelemetry/resources';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {JSONValue, ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Condition} from '../../../zero-protocol/src/ast.ts';\nimport type {\n CRUDOp,\n DeleteOp,\n InsertOp,\n UpdateOp,\n UpsertOp,\n} from '../../../zero-protocol/src/mutation.ts';\nimport {\n primaryKeyValueSchema,\n type PrimaryKeyValue,\n} from '../../../zero-protocol/src/primary-key.ts';\nimport type {Policy} from '../../../zero-schema/src/compiled-permissions.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport type {BuilderDelegate} from '../../../zql/src/builder/builder.ts';\nimport {\n bindStaticParameters,\n buildPipeline,\n} from '../../../zql/src/builder/builder.ts';\nimport {\n makeSourceChangeAdd,\n makeSourceChangeEdit,\n makeSourceChangeRemove,\n} from '../../../zql/src/ivm/source.ts';\nimport {consume} from '../../../zql/src/ivm/stream.ts';\nimport {simplifyCondition} from '../../../zql/src/query/expression.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {Query} from '../../../zql/src/query/query.ts';\nimport {newStaticQuery} from '../../../zql/src/query/static-query.ts';\nimport type {\n ClientGroupStorage,\n DatabaseStorage,\n} from '../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../zqlite/src/db.ts';\nimport {compile, sql} from '../../../zqlite/src/internal/sql.ts';\nimport {\n fromSQLiteTypes,\n TableSource,\n} from '../../../zqlite/src/table-source.ts';\nimport type {LogConfig, ZeroConfig} from '../config/zero-config.ts';\nimport {computeZqlSpecs} from '../db/lite-tables.ts';\nimport type {LiteAndZqlSpec} from '../db/specs.ts';\nimport {StatementRunner} from '../db/statements.ts';\nimport {mapLiteDataTypeToZqlSchemaValue} from '../types/lite.ts';\nimport {\n getSchema,\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from './load-permissions.ts';\n\ntype Phase = 'preMutation' | 'postMutation';\n\nexport interface WriteAuthorizer {\n canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n reloadPermissions(): void;\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[];\n\n /**\n * Validates that all table names in the operations exist in the schema.\n * @throws Error if any table name is invalid\n */\n validateTableNames(ops: CRUDOp[]): void;\n}\n\nexport class WriteAuthorizerImpl implements WriteAuthorizer {\n readonly #schema: Schema;\n readonly #replica: Database;\n readonly #builderDelegate: BuilderDelegate;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n readonly #tables = new Map<string, TableSource>();\n readonly #statementRunner: StatementRunner;\n readonly #lc: LogContext;\n readonly #appID: string;\n readonly #logConfig: LogConfig;\n readonly #cgStorage: ClientGroupStorage;\n readonly #config: ZeroConfig;\n\n #loadedPermissions: LoadedPermissions | null = null;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n replica: Database,\n appID: string,\n cgID: string,\n writeAuthzStorage: DatabaseStorage,\n ) {\n this.#appID = appID;\n this.#config = config;\n this.#lc = lc.withContext('class', 'WriteAuthorizerImpl');\n this.#logConfig = config.log;\n this.#schema = getSchema(this.#lc, replica);\n this.#replica = replica;\n this.#cgStorage = writeAuthzStorage.createClientGroupStorage(cgID);\n this.#builderDelegate = {\n disableCorrelatedPredicatePushdown:\n config.enableCorrelatedPredicatePushdown === false,\n getSource: name => this.#getSource(name),\n createStorage: () => this.#cgStorage.createStorage(),\n decorateSourceInput: input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n };\n this.#tableSpecs = computeZqlSpecs(this.#lc, replica, {\n includeBackfillingColumns: false,\n });\n this.#statementRunner = new StatementRunner(replica);\n this.reloadPermissions();\n }\n\n reloadPermissions() {\n this.#loadedPermissions = reloadPermissionsIfChanged(\n this.#lc,\n this.#statementRunner,\n this.#appID,\n this.#loadedPermissions,\n this.#config,\n ).permissions;\n }\n\n destroy() {\n this.#cgStorage.destroy();\n }\n\n async canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n // insert does not run pre-mutation checks\n break;\n case 'update':\n if (!(await this.#canUpdate('preMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n if (!(await this.#canDelete('preMutation', authData, op))) {\n return false;\n }\n break;\n }\n }\n return true;\n }\n\n async canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n this.#statementRunner.beginConcurrent();\n let opError: unknown;\n try {\n for (const op of ops) {\n const source = this.#getSource(op.tableName);\n switch (op.op) {\n case 'insert': {\n consume(source.push(makeSourceChangeAdd(op.value)));\n break;\n }\n // TODO(mlaw): what if someone updates the same thing twice?\n // TODO(aa): It seems like it will just work? source.push()\n // is going to push the row into the table source, and then the\n // next requirePreMutationRow will just return the row that was\n // pushed in.\n case 'update': {\n consume(\n source.push(\n makeSourceChangeEdit(op.value, this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n case 'delete': {\n consume(\n source.push(\n makeSourceChangeRemove(this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n }\n }\n\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n if (!(await this.#canInsert('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'update':\n if (!(await this.#canUpdate('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n // delete does not run post-mutation checks.\n break;\n }\n }\n } catch (e) {\n opError = e;\n throw e;\n } finally {\n try {\n this.#statementRunner.rollback();\n } catch (rollbackError) {\n if (opError !== undefined) {\n const combinedError = new Error(\n `canPostMutation failed and rollback also failed: operation error = ${String(opError)}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = opError;\n throw combinedError;\n }\n throw rollbackError;\n }\n }\n\n return true;\n }\n\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[] {\n return ops.map(op => {\n if (op.op === 'upsert') {\n const preMutationRow = this.#getPreMutationRow(op);\n if (preMutationRow) {\n return {\n op: 'update',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return {\n op: 'insert',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return op;\n });\n }\n\n validateTableNames(ops: CRUDOp[]): void {\n for (const op of ops) {\n if (!this.#tableSpecs.has(op.tableName)) {\n throw new Error(`Table '${op.tableName}' is not a valid table.`);\n }\n }\n }\n\n #canInsert(phase: Phase, authData: JWTPayload | undefined, op: InsertOp) {\n return this.#timedCanDo(phase, 'insert', authData, op);\n }\n\n #canUpdate(phase: Phase, authData: JWTPayload | undefined, op: UpdateOp) {\n return this.#timedCanDo(phase, 'update', authData, op);\n }\n\n #canDelete(phase: Phase, authData: JWTPayload | undefined, op: DeleteOp) {\n return this.#timedCanDo(phase, 'delete', authData, op);\n }\n\n /**\n * Gets schema-defined primary key and validates that operation contains required PK values.\n *\n * @returns Record where keys are column names and values are client-provided values\n * @throws Error if operation value is missing required primary key columns\n */\n #getPrimaryKey(\n tableName: string,\n opValue: Record<string, ReadonlyJSONValue | undefined>,\n ): Record<string, ReadonlyJSONValue> {\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const columns = tableSpec.tableSpec.primaryKey;\n\n // Extract primary key values from operation value and validate they exist\n const values: Record<string, ReadonlyJSONValue> = {};\n for (const col of columns) {\n const val = opValue[col];\n if (val === undefined) {\n throw new Error(\n `Primary key column '${col}' is missing from operation value for table ${tableName}`,\n );\n }\n values[col] = val;\n }\n\n return values;\n }\n\n #getSource(tableName: string) {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const {columns, primaryKey} = tableSpec.tableSpec;\n assert(\n primaryKey.length,\n () => `Table ${tableName} must have a primary key`,\n );\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n this.#replica,\n tableName,\n Object.fromEntries(\n Object.entries(columns).map(([name, {dataType}]) => [\n name,\n mapLiteDataTypeToZqlSchemaValue(dataType),\n ]),\n ),\n [primaryKey[0], ...primaryKey.slice(1)],\n );\n this.#tables.set(tableName, source);\n\n return source;\n }\n\n async #timedCanDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const start = performance.now();\n try {\n const ret = await this.#canDo(phase, action, authData, op);\n return ret;\n } finally {\n this.#lc.info?.(\n 'action:',\n action,\n 'duration:',\n performance.now() - start,\n 'tableName:',\n op.tableName,\n 'primaryKey:',\n op.primaryKey,\n );\n }\n }\n\n /**\n * Evaluation order is from static to dynamic, broad to specific.\n * table -> column -> row -> cell.\n *\n * If any step fails, the entire operation is denied.\n *\n * That is, table rules supersede column rules, which supersede row rules,\n *\n * All steps must allow for the operation to be allowed.\n */\n async #canDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const rules = must(this.#loadedPermissions)?.permissions?.tables?.[\n op.tableName\n ];\n const rowPolicies = rules?.row;\n let rowQuery = newStaticQuery(this.#schema, op.tableName);\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, op.value);\n\n for (const pk in primaryKeyValues) {\n rowQuery = rowQuery.where(pk, '=', primaryKeyValues[pk]);\n }\n\n let applicableRowPolicy: Policy | undefined;\n switch (action) {\n case 'insert':\n if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.insert;\n }\n break;\n case 'update':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.update?.preMutation;\n } else if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.update?.postMutation;\n }\n break;\n case 'delete':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.delete;\n }\n break;\n }\n\n const cellPolicies = rules?.cell;\n const applicableCellPolicies: Policy[] = [];\n if (cellPolicies) {\n for (const [column, policy] of Object.entries(cellPolicies)) {\n if (action === 'update' && op.value[column] === undefined) {\n // If the cell is not being updated, we do not need to check\n // the cell rules.\n continue;\n }\n switch (action) {\n case 'insert':\n if (policy.insert && phase === 'postMutation') {\n applicableCellPolicies.push(policy.insert);\n }\n break;\n case 'update':\n if (phase === 'preMutation' && policy.update?.preMutation) {\n applicableCellPolicies.push(policy.update.preMutation);\n }\n if (phase === 'postMutation' && policy.update?.postMutation) {\n applicableCellPolicies.push(policy.update.postMutation);\n }\n break;\n case 'delete':\n if (policy.delete && phase === 'preMutation') {\n applicableCellPolicies.push(policy.delete);\n }\n break;\n }\n }\n }\n\n if (\n !(await this.#passesPolicyGroup(\n applicableRowPolicy,\n applicableCellPolicies,\n authData,\n rowQuery,\n ))\n ) {\n // `op.value` is the row being written and `authData` is the decoded\n // JWT payload, so neither can go into the log. Column names and policy\n // counts are enough to debug a failed permission check.\n this.#lc.warn?.('Permission check failed', {\n action,\n phase,\n tableName: op.tableName,\n columns: Object.keys(op.value),\n rowPolicy: applicableRowPolicy !== undefined,\n cellPolicyCount: applicableCellPolicies.length,\n });\n return false;\n }\n\n return true;\n }\n\n #getPreMutationRow(op: UpsertOp | UpdateOp | DeleteOp) {\n const {value} = op;\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, value);\n\n const spec = this.#tableSpecs.get(op.tableName);\n if (!spec) {\n throw new Error(`Table ${op.tableName} not found`);\n }\n\n const conditions: SQLQuery[] = [];\n const values: PrimaryKeyValue[] = [];\n for (const pk in primaryKeyValues) {\n conditions.push(sql`${sql.ident(pk)}=?`);\n values.push(v.parse(primaryKeyValues[pk], primaryKeyValueSchema));\n }\n\n const ret = this.#statementRunner.get(\n compile(\n sql`SELECT ${sql.join(\n Object.keys(spec.zqlSpec).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(op.tableName)} WHERE ${sql.join(\n conditions,\n sql` AND `,\n )}`,\n ),\n ...values,\n );\n if (ret === undefined) {\n return ret;\n }\n return fromSQLiteTypes(spec.zqlSpec, ret, op.tableName);\n }\n\n #requirePreMutationRow(op: UpdateOp | DeleteOp) {\n const ret = this.#getPreMutationRow(op);\n assert(\n ret !== undefined,\n () => `Pre-mutation row not found for ${JSON.stringify(op.value)}`,\n );\n return ret;\n }\n\n async #passesPolicyGroup(\n applicableRowPolicy: Policy | undefined,\n applicableCellPolicies: Policy[],\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ) {\n if (!(await this.#passesPolicy(applicableRowPolicy, authData, rowQuery))) {\n return false;\n }\n\n for (const policy of applicableCellPolicies) {\n if (!(await this.#passesPolicy(policy, authData, rowQuery))) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Defaults to *false* if the policy is empty. At least one rule has to pass\n * for the policy to pass.\n */\n #passesPolicy(\n policy: Policy | undefined,\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ): MaybePromise<boolean> {\n if (policy === undefined) {\n return false;\n }\n if (policy.length === 0) {\n return false;\n }\n let rowQueryAst = asQueryInternals(rowQuery).ast;\n rowQueryAst = bindStaticParameters(\n {\n ...rowQueryAst,\n where: updateWhere(rowQueryAst.where, policy),\n },\n {\n authData: authData as Record<string, JSONValue>,\n preMutationRow: undefined,\n },\n );\n\n // call the compiler directly\n // run the sql against upstream.\n // remove the collecting into json? just need to know if a row comes back.\n\n const input = buildPipeline(rowQueryAst, this.#builderDelegate, 'query-id');\n try {\n const res = input.fetch({});\n for (const _ of res) {\n // if any row is returned at all, the\n // rule passes.\n return true;\n }\n } finally {\n input.destroy();\n }\n\n // no rows returned by any rules? The policy fails.\n return false;\n }\n}\n\nfunction updateWhere(where: Condition | undefined, policy: Policy) {\n assert(where, 'A where condition must exist for RowQuery');\n\n return simplifyCondition({\n type: 'and',\n conditions: [\n where,\n {\n type: 'or',\n conditions: policy.map(([action, rule]) => {\n assert(action, 'action must be defined in policy');\n return rule;\n }),\n },\n ],\n });\n}\n\ntype ActionOpMap = {\n insert: InsertOp;\n update: UpdateOp;\n delete: DeleteOp;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"write-authorizer.js","names":["v.parse"],"sources":["../../../../../zero-cache/src/auth/write-authorizer.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport type {MaybePromise} from '@opentelemetry/resources';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {JSONValue, ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Condition} from '../../../zero-protocol/src/ast.ts';\nimport type {\n CRUDOp,\n DeleteOp,\n InsertOp,\n UpdateOp,\n UpsertOp,\n} from '../../../zero-protocol/src/mutation.ts';\nimport {\n primaryKeyValueSchema,\n type PrimaryKeyValue,\n} from '../../../zero-protocol/src/primary-key.ts';\nimport type {Policy} from '../../../zero-schema/src/compiled-permissions.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport type {BuilderDelegate} from '../../../zql/src/builder/builder.ts';\nimport {\n bindStaticParameters,\n buildPipeline,\n} from '../../../zql/src/builder/builder.ts';\nimport {\n makeSourceChangeAdd,\n makeSourceChangeEdit,\n makeSourceChangeRemove,\n} from '../../../zql/src/ivm/source.ts';\nimport {consume} from '../../../zql/src/ivm/stream.ts';\nimport {simplifyCondition} from '../../../zql/src/query/expression.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {Query} from '../../../zql/src/query/query.ts';\nimport {newStaticQuery} from '../../../zql/src/query/static-query.ts';\nimport type {\n ClientGroupStorage,\n DatabaseStorage,\n} from '../../../zqlite/src/database-storage.ts';\nimport type {Database} from '../../../zqlite/src/db.ts';\nimport {compile, sql} from '../../../zqlite/src/internal/sql.ts';\nimport {\n fromSQLiteTypes,\n TableSource,\n} from '../../../zqlite/src/table-source.ts';\nimport type {LogConfig, ZeroConfig} from '../config/zero-config.ts';\nimport {computeZqlSpecs} from '../db/lite-tables.ts';\nimport type {LiteAndZqlSpec} from '../db/specs.ts';\nimport {StatementRunner} from '../db/statements.ts';\nimport {mapLiteDataTypeToZqlSchemaValue} from '../types/lite.ts';\nimport {\n getSchema,\n reloadPermissionsIfChanged,\n type LoadedPermissions,\n} from './load-permissions.ts';\n\ntype Phase = 'preMutation' | 'postMutation';\n\nexport interface WriteAuthorizer {\n canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ): Promise<boolean>;\n reloadPermissions(): void;\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[];\n\n /**\n * Validates that all table names in the operations exist in the schema and\n * returns the operations with `primaryKey` replaced by the table's primary\n * key from the replica. The client-supplied `primaryKey` is not trusted: the\n * authorizer checks the row identified by the replica's primary key, so the\n * writes must target that same row.\n * @throws Error if any table name is invalid\n */\n validateOps(ops: CRUDOp[]): CRUDOp[];\n}\n\nexport class WriteAuthorizerImpl implements WriteAuthorizer {\n readonly #schema: Schema;\n readonly #replica: Database;\n readonly #builderDelegate: BuilderDelegate;\n readonly #tableSpecs: Map<string, LiteAndZqlSpec>;\n readonly #tables = new Map<string, TableSource>();\n readonly #statementRunner: StatementRunner;\n readonly #lc: LogContext;\n readonly #appID: string;\n readonly #logConfig: LogConfig;\n readonly #cgStorage: ClientGroupStorage;\n readonly #config: ZeroConfig;\n\n #loadedPermissions: LoadedPermissions | null = null;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n replica: Database,\n appID: string,\n cgID: string,\n writeAuthzStorage: DatabaseStorage,\n ) {\n this.#appID = appID;\n this.#config = config;\n this.#lc = lc.withContext('class', 'WriteAuthorizerImpl');\n this.#logConfig = config.log;\n this.#schema = getSchema(this.#lc, replica);\n this.#replica = replica;\n this.#cgStorage = writeAuthzStorage.createClientGroupStorage(cgID);\n this.#builderDelegate = {\n disableCorrelatedPredicatePushdown:\n config.enableCorrelatedPredicatePushdown === false,\n getSource: name => this.#getSource(name),\n createStorage: () => this.#cgStorage.createStorage(),\n decorateSourceInput: input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n };\n this.#tableSpecs = computeZqlSpecs(this.#lc, replica, {\n includeBackfillingColumns: false,\n });\n this.#statementRunner = new StatementRunner(replica);\n this.reloadPermissions();\n }\n\n reloadPermissions() {\n this.#loadedPermissions = reloadPermissionsIfChanged(\n this.#lc,\n this.#statementRunner,\n this.#appID,\n this.#loadedPermissions,\n this.#config,\n ).permissions;\n }\n\n destroy() {\n this.#cgStorage.destroy();\n }\n\n async canPreMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n // insert does not run pre-mutation checks\n break;\n case 'update':\n if (!(await this.#canUpdate('preMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n if (!(await this.#canDelete('preMutation', authData, op))) {\n return false;\n }\n break;\n }\n }\n return true;\n }\n\n async canPostMutation(\n authData: JWTPayload | undefined,\n ops: Exclude<CRUDOp, UpsertOp>[],\n ) {\n this.#statementRunner.beginConcurrent();\n let opError: unknown;\n try {\n for (const op of ops) {\n const source = this.#getSource(op.tableName);\n switch (op.op) {\n case 'insert': {\n consume(source.push(makeSourceChangeAdd(op.value)));\n break;\n }\n // TODO(mlaw): what if someone updates the same thing twice?\n // TODO(aa): It seems like it will just work? source.push()\n // is going to push the row into the table source, and then the\n // next requirePreMutationRow will just return the row that was\n // pushed in.\n case 'update': {\n consume(\n source.push(\n makeSourceChangeEdit(op.value, this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n case 'delete': {\n consume(\n source.push(\n makeSourceChangeRemove(this.#requirePreMutationRow(op)),\n ),\n );\n break;\n }\n }\n }\n\n for (const op of ops) {\n switch (op.op) {\n case 'insert':\n if (!(await this.#canInsert('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'update':\n if (!(await this.#canUpdate('postMutation', authData, op))) {\n return false;\n }\n break;\n case 'delete':\n // delete does not run post-mutation checks.\n break;\n }\n }\n } catch (e) {\n opError = e;\n throw e;\n } finally {\n try {\n this.#statementRunner.rollback();\n } catch (rollbackError) {\n if (opError !== undefined) {\n const combinedError = new Error(\n `canPostMutation failed and rollback also failed: operation error = ${String(opError)}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = opError;\n throw combinedError;\n }\n throw rollbackError;\n }\n }\n\n return true;\n }\n\n normalizeOps(ops: CRUDOp[]): Exclude<CRUDOp, UpsertOp>[] {\n return ops.map(op => {\n if (op.op === 'upsert') {\n const preMutationRow = this.#getPreMutationRow(op);\n if (preMutationRow) {\n return {\n op: 'update',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return {\n op: 'insert',\n tableName: op.tableName,\n primaryKey: op.primaryKey,\n value: op.value,\n };\n }\n return op;\n });\n }\n\n validateOps(ops: CRUDOp[]): CRUDOp[] {\n return ops.map(op => {\n const tableSpec = this.#tableSpecs.get(op.tableName);\n if (!tableSpec) {\n throw new Error(`Table '${op.tableName}' is not a valid table.`);\n }\n return {...op, primaryKey: tableSpec.tableSpec.primaryKey};\n });\n }\n\n #canInsert(phase: Phase, authData: JWTPayload | undefined, op: InsertOp) {\n return this.#timedCanDo(phase, 'insert', authData, op);\n }\n\n #canUpdate(phase: Phase, authData: JWTPayload | undefined, op: UpdateOp) {\n return this.#timedCanDo(phase, 'update', authData, op);\n }\n\n #canDelete(phase: Phase, authData: JWTPayload | undefined, op: DeleteOp) {\n return this.#timedCanDo(phase, 'delete', authData, op);\n }\n\n /**\n * Gets schema-defined primary key and validates that operation contains required PK values.\n *\n * @returns Record where keys are column names and values are client-provided values\n * @throws Error if operation value is missing required primary key columns\n */\n #getPrimaryKey(\n tableName: string,\n opValue: Record<string, ReadonlyJSONValue | undefined>,\n ): Record<string, ReadonlyJSONValue> {\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const columns = tableSpec.tableSpec.primaryKey;\n\n // Extract primary key values from operation value and validate they exist\n const values: Record<string, ReadonlyJSONValue> = {};\n for (const col of columns) {\n const val = opValue[col];\n if (val === undefined) {\n throw new Error(\n `Primary key column '${col}' is missing from operation value for table ${tableName}`,\n );\n }\n values[col] = val;\n }\n\n return values;\n }\n\n #getSource(tableName: string) {\n let source = this.#tables.get(tableName);\n if (source) {\n return source;\n }\n const tableSpec = this.#tableSpecs.get(tableName);\n if (!tableSpec) {\n throw new Error(`Table ${tableName} not found`);\n }\n const {columns, primaryKey} = tableSpec.tableSpec;\n assert(\n primaryKey.length,\n () => `Table ${tableName} must have a primary key`,\n );\n source = new TableSource(\n this.#lc,\n this.#logConfig,\n this.#replica,\n tableName,\n Object.fromEntries(\n Object.entries(columns).map(([name, {dataType}]) => [\n name,\n mapLiteDataTypeToZqlSchemaValue(dataType),\n ]),\n ),\n [primaryKey[0], ...primaryKey.slice(1)],\n );\n this.#tables.set(tableName, source);\n\n return source;\n }\n\n async #timedCanDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const start = performance.now();\n try {\n const ret = await this.#canDo(phase, action, authData, op);\n return ret;\n } finally {\n this.#lc.info?.(\n 'action:',\n action,\n 'duration:',\n performance.now() - start,\n 'tableName:',\n op.tableName,\n 'primaryKey:',\n op.primaryKey,\n );\n }\n }\n\n /**\n * Evaluation order is from static to dynamic, broad to specific.\n * table -> column -> row -> cell.\n *\n * If any step fails, the entire operation is denied.\n *\n * That is, table rules supersede column rules, which supersede row rules,\n *\n * All steps must allow for the operation to be allowed.\n */\n async #canDo<A extends keyof ActionOpMap>(\n phase: Phase,\n action: A,\n authData: JWTPayload | undefined,\n op: ActionOpMap[A],\n ) {\n const rules = must(this.#loadedPermissions)?.permissions?.tables?.[\n op.tableName\n ];\n const rowPolicies = rules?.row;\n let rowQuery = newStaticQuery(this.#schema, op.tableName);\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, op.value);\n\n for (const pk in primaryKeyValues) {\n rowQuery = rowQuery.where(pk, '=', primaryKeyValues[pk]);\n }\n\n let applicableRowPolicy: Policy | undefined;\n switch (action) {\n case 'insert':\n if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.insert;\n }\n break;\n case 'update':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.update?.preMutation;\n } else if (phase === 'postMutation') {\n applicableRowPolicy = rowPolicies?.update?.postMutation;\n }\n break;\n case 'delete':\n if (phase === 'preMutation') {\n applicableRowPolicy = rowPolicies?.delete;\n }\n break;\n }\n\n const cellPolicies = rules?.cell;\n const applicableCellPolicies: Policy[] = [];\n if (cellPolicies) {\n for (const [column, policy] of Object.entries(cellPolicies)) {\n if (action === 'update' && op.value[column] === undefined) {\n // If the cell is not being updated, we do not need to check\n // the cell rules.\n continue;\n }\n switch (action) {\n case 'insert':\n if (policy.insert && phase === 'postMutation') {\n applicableCellPolicies.push(policy.insert);\n }\n break;\n case 'update':\n if (phase === 'preMutation' && policy.update?.preMutation) {\n applicableCellPolicies.push(policy.update.preMutation);\n }\n if (phase === 'postMutation' && policy.update?.postMutation) {\n applicableCellPolicies.push(policy.update.postMutation);\n }\n break;\n case 'delete':\n if (policy.delete && phase === 'preMutation') {\n applicableCellPolicies.push(policy.delete);\n }\n break;\n }\n }\n }\n\n if (\n !(await this.#passesPolicyGroup(\n applicableRowPolicy,\n applicableCellPolicies,\n authData,\n rowQuery,\n ))\n ) {\n // `op.value` is the row being written and `authData` is the decoded\n // JWT payload, so neither can go into the log. Column names and policy\n // counts are enough to debug a failed permission check.\n this.#lc.warn?.('Permission check failed', {\n action,\n phase,\n tableName: op.tableName,\n columns: Object.keys(op.value),\n rowPolicy: applicableRowPolicy !== undefined,\n cellPolicyCount: applicableCellPolicies.length,\n });\n return false;\n }\n\n return true;\n }\n\n #getPreMutationRow(op: UpsertOp | UpdateOp | DeleteOp) {\n const {value} = op;\n\n const primaryKeyValues = this.#getPrimaryKey(op.tableName, value);\n\n const spec = this.#tableSpecs.get(op.tableName);\n if (!spec) {\n throw new Error(`Table ${op.tableName} not found`);\n }\n\n const conditions: SQLQuery[] = [];\n const values: PrimaryKeyValue[] = [];\n for (const pk in primaryKeyValues) {\n conditions.push(sql`${sql.ident(pk)}=?`);\n values.push(v.parse(primaryKeyValues[pk], primaryKeyValueSchema));\n }\n\n const ret = this.#statementRunner.get(\n compile(\n sql`SELECT ${sql.join(\n Object.keys(spec.zqlSpec).map(c => sql.ident(c)),\n sql`,`,\n )} FROM ${sql.ident(op.tableName)} WHERE ${sql.join(\n conditions,\n sql` AND `,\n )}`,\n ),\n ...values,\n );\n if (ret === undefined) {\n return ret;\n }\n return fromSQLiteTypes(spec.zqlSpec, ret, op.tableName);\n }\n\n #requirePreMutationRow(op: UpdateOp | DeleteOp) {\n const ret = this.#getPreMutationRow(op);\n assert(\n ret !== undefined,\n () => `Pre-mutation row not found for ${JSON.stringify(op.value)}`,\n );\n return ret;\n }\n\n async #passesPolicyGroup(\n applicableRowPolicy: Policy | undefined,\n applicableCellPolicies: Policy[],\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ) {\n if (!(await this.#passesPolicy(applicableRowPolicy, authData, rowQuery))) {\n return false;\n }\n\n for (const policy of applicableCellPolicies) {\n if (!(await this.#passesPolicy(policy, authData, rowQuery))) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Defaults to *false* if the policy is empty. At least one rule has to pass\n * for the policy to pass.\n */\n #passesPolicy(\n policy: Policy | undefined,\n authData: JWTPayload | undefined,\n rowQuery: Query<string, Schema>,\n ): MaybePromise<boolean> {\n if (policy === undefined) {\n return false;\n }\n if (policy.length === 0) {\n return false;\n }\n let rowQueryAst = asQueryInternals(rowQuery).ast;\n rowQueryAst = bindStaticParameters(\n {\n ...rowQueryAst,\n where: updateWhere(rowQueryAst.where, policy),\n },\n {\n authData: authData as Record<string, JSONValue>,\n preMutationRow: undefined,\n },\n );\n\n // call the compiler directly\n // run the sql against upstream.\n // remove the collecting into json? just need to know if a row comes back.\n\n const input = buildPipeline(rowQueryAst, this.#builderDelegate, 'query-id');\n try {\n const res = input.fetch({});\n for (const _ of res) {\n // if any row is returned at all, the\n // rule passes.\n return true;\n }\n } finally {\n input.destroy();\n }\n\n // no rows returned by any rules? The policy fails.\n return false;\n }\n}\n\nfunction updateWhere(where: Condition | undefined, policy: Policy) {\n assert(where, 'A where condition must exist for RowQuery');\n\n return simplifyCondition({\n type: 'and',\n conditions: [\n where,\n {\n type: 'or',\n conditions: policy.map(([action, rule]) => {\n assert(action, 'action must be defined in policy');\n return rule;\n }),\n },\n ],\n });\n}\n\ntype ActionOpMap = {\n insert: InsertOp;\n update: UpdateOp;\n delete: DeleteOp;\n};\n"],"mappings":"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"}
@@ -165,6 +165,7 @@ async function runWorker(parent, env, ...argv) {
165
165
  backupURL = destinationBackupURL;
166
166
  waitForFirstBackupBeforeServing = waitForBackupBeforeServing;
167
167
  startMetrics.setKind(startTimings?.kind);
168
+ startMetrics.record("slot_create", startTimings?.slotCreateMs);
168
169
  startMetrics.record("fork_wait", startTimings?.forkWaitMs);
169
170
  startMetrics.record("restore", startTimings?.restoreMs);
170
171
  newBackupLineage = initNewBackupLineage;
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {DatabaseInitError} from '../../../zqlite/src/db.ts';\nimport {getServerContext} from '../config/server-context.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink, publishCriticalEvent} from '../observability/events.ts';\nimport {getOrCreateGauge} from '../observability/metrics.ts';\nimport {InitCleanup} from '../services/change-source/common/init-cleanup.ts';\nimport type {PgChangeLogPurgeLocker} from '../services/change-source/common/replica-restore.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source-init.ts';\nimport {createBackupCleanupMonitor} from '../services/change-streamer/backup-cleanup-monitor-factory.ts';\nimport type {BackupMonitor} from '../services/change-streamer/backup-monitor.ts';\nimport {ChangeStreamerHttpServer} from '../services/change-streamer/change-streamer-http.ts';\nimport {initializeStreamer} from '../services/change-streamer/change-streamer-service.ts';\nimport type {ChangeStreamerService} from '../services/change-streamer/change-streamer.ts';\nimport {DEFAULT_READINESS_GRACE_PERIOD_MS} from '../services/change-streamer/restore-admission.ts';\nimport {initChangeStreamerSchema} from '../services/change-streamer/schema/init.ts';\nimport {AutoResetSignal} from '../services/change-streamer/schema/tables.ts';\nimport {ServerStartMetrics} from '../services/change-streamer/server-start-metrics.ts';\nimport {\n PurgeLocker,\n type PurgeLock,\n} from '../services/change-streamer/storer.ts';\nimport {\n exitAfter,\n runUntilKilled,\n SupportingProcesses,\n} from '../services/life-cycle.ts';\nimport {deleteLitestreamMetaDir} from '../services/litestream/commands.ts';\nimport {\n changeLogFileName,\n deleteChangeLogDB,\n} from '../services/replicator/change-log-db.ts';\nimport {\n replicationStatusError,\n ReplicationStatusPublisher,\n} from '../services/replicator/replication-status.ts';\nimport {sqliteFileBytes} from '../services/replicator/sqlite-change-log-observability.ts';\nimport {warnIfPostgresUnpatched} from '../types/pg-patch.ts';\nimport {connectPgClient, type PostgresDB} from '../types/pg.ts';\nimport {\n broadcastWorker,\n childWorker,\n parentWorker,\n singleProcessMode,\n type ProfileResponseMessage,\n type Worker,\n} from '../types/processes.ts';\nimport {installProfileHandler} from '../types/profiler.ts';\nimport {getShardConfig, type ShardConfig} from '../types/shards.ts';\nimport type {ReplicaFileMode} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {REPLICATOR_URL} from './worker-urls.ts';\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\n// Errors thrown once the services are running are reported by the services\n// themselves (e.g. as 'Replicating' status events), and are thus not\n// published as 'Initializing' errors.\nlet running = false;\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n installProfileHandler(parent, 'change-streamer');\n const config = getNormalizedZeroConfig({env, argv});\n const {\n taskID,\n changeStreamer: {\n port,\n address,\n protocol,\n startupDelayMs,\n backPressureLimitHeapProportion,\n flowControlConsensusTimeoutProportion,\n flowControlSlowSubscriberGracePeriodSeconds,\n maxConcurrentSwitchoverRestores,\n pgChangeLogEnabled,\n sqliteChangeLogMode,\n sqliteChangeLogReadPercent,\n sqliteChangeLogColdReadPercent,\n sqliteChangeLogComparePercent,\n sqliteChangeLogRetentionMs,\n sqliteChangeLogReadBatchRows,\n sqliteChangeLogPurgeBatchRows,\n sqliteChangeLogBarrierTimeoutMs,\n },\n autoReset,\n replicationLag,\n litestream,\n upstream,\n change,\n replica,\n initialSync,\n keepaliveTimeoutMs,\n sqliteCorruptionChecks,\n } = config;\n\n startOtelAuto(\n createLogContext(config, 'change-streamer', 0, false),\n 'change-streamer',\n 0,\n );\n lc = createLogContext(config, 'change-streamer');\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'change-streamer replica',\n dbPath: replica.file,\n },\n sqliteCorruptionChecks,\n );\n initEventSink(lc, config);\n if (upstream.type === 'pg') {\n warnIfPostgresUnpatched(lc);\n }\n\n const shard = getShardConfig(config);\n\n // The change DB is only used by the PG change log. When it is disabled\n // (RMv2), no connection to the change DB is made.\n const changeDB = pgChangeLogEnabled\n ? await connectChangeDB(lc, change.db, shard)\n : null;\n\n let purgeLock = null as PurgeLock | null;\n let changeStreamer: ChangeStreamerService | undefined;\n let backupMonitor: BackupMonitor | undefined;\n let backupURL: string | undefined;\n\n const context = getServerContext(config);\n const sqliteChangeLogEnabled = sqliteChangeLogMode !== 'off';\n // The modes are cumulative, so `serve` implies `compare`.\n const sqliteChangeLogComparing =\n sqliteChangeLogMode === 'compare' || sqliteChangeLogMode === 'serve';\n if (sqliteChangeLogEnabled) {\n const changeLogFile = changeLogFileName(replica.file);\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'change-streamer change-log',\n dbPath: changeLogFile,\n },\n sqliteCorruptionChecks,\n );\n // The log's bytes are accounted for in the process that writes them. This\n // is local disk only — the log is excluded from the litestream backup — and\n // it is a whole-database total, because a table that left the replica left\n // through its wal: the replica's footprint is what shrank and this is where\n // it went.\n getOrCreateGauge('replica', 'sqlite_change_log.file_bytes', {\n description:\n `The SQLite change log's total on-disk footprint: its main db file ` +\n `plus its wal sidecars.`,\n unit: 'By',\n }).addCallback(async o =>\n o.observe(await sqliteFileBytes(lc, changeLogFile)),\n );\n }\n\n const startMetrics = new ServerStartMetrics();\n let waitForFirstBackupBeforeServing = false;\n let newBackupLineage = false;\n for (const first of [true, false]) {\n // Resources acquired by an initialization attempt (e.g. a claimed\n // replication slot, the purge lock) to be released if the attempt fails.\n const cleanup = new InitCleanup(lc);\n // When restoring from litestream, a lock is acquired to prevent change-log\n // purges. This ensures that (this) change-streamer will be able to resume\n // from the backup. The lock is acquired by the change source initialization\n // just before restoring (i.e. after a replication slot is created, if\n // applicable), and released once this change-streamer takes over the\n // change-log.\n const acquirePurgeLock: PgChangeLogPurgeLocker | undefined =\n changeDB && litestream.backupURL && litestream.executable\n ? async (slotWatermark?: string) => {\n const lock = await new PurgeLocker(lc, shard, changeDB).acquire(\n slotWatermark,\n );\n purgeLock = lock === 'behind-slot' ? null : lock;\n if (purgeLock) {\n const acquired = purgeLock;\n cleanup.onFailure('purge lock', () => {\n purgeLock = null;\n return acquired.release();\n });\n }\n return lock;\n }\n : undefined;\n\n const restoreOptions = {litestream, acquirePurgeLock, cleanup};\n try {\n // Note: This performs initial sync of the replica if necessary.\n const {\n pgReplicationEpoch: epoch,\n pgReplicationSlotPerReplica: slotPerReplica,\n pgResumeOrphanedSlotGracePeriodMs: inactiveReplicaGracePeriodMs,\n } = upstream;\n const {\n changeSource,\n subscriptionState,\n destinationBackupURL,\n replicaID,\n waitForBackupBeforeServing,\n newBackupLineage: initNewBackupLineage,\n pgChangeLogBehindSlot,\n startTimings,\n } = upstream.type === 'pg'\n ? await initializePostgresChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n {\n ...initialSync,\n replicationSlotFailover: upstream.pgReplicationSlotFailover,\n installPartialIndexTriggers: upstream.pgPartialIndexTriggers,\n },\n context,\n replicationLag.reportIntervalMs,\n restoreOptions,\n {\n epoch,\n slotPerReplica,\n inactiveReplicaGracePeriodMs,\n backupV5: litestream.backupUsingV5,\n pgChangeLog: pgChangeLogEnabled,\n },\n upstream.pgStreamInboundTimeoutMs,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n restoreOptions,\n );\n\n const replicationStatusPublisher =\n ReplicationStatusPublisher.forReplicaFile(replica.file);\n\n // Created before the change-streamer, which tracks its watermarks (and\n // requests prompt backups from it) from construction. It is run (and\n // stopped) as a service below. It monitors the backup-replicator's\n // destination (see `backupURLOverride` below).\n backupMonitor = createBackupCleanupMonitor({\n lc,\n config: destinationBackupURL\n ? {\n ...config,\n litestream: {\n ...config.litestream,\n backupURLOverride: destinationBackupURL,\n },\n }\n : config,\n replicaFile: replica.file,\n });\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n replicationStatusPublisher,\n subscriptionState,\n destinationBackupURL\n ? {\n backupURL: destinationBackupURL,\n litestreamVersion: litestream.backupUsingV5 ? 'v5' : 'legacy',\n replicaFile: replica.file,\n }\n : null,\n purgeLock,\n autoReset ?? false,\n {\n pgChangeLogBehindSlot: pgChangeLogBehindSlot ?? false,\n backPressureLimitHeapProportion,\n flowControlConsensusTimeoutProportion,\n flowControlSlowSubscriberGracePeriodMs:\n flowControlSlowSubscriberGracePeriodSeconds > 0\n ? flowControlSlowSubscriberGracePeriodSeconds * 1000\n : undefined,\n maxConcurrentSwitchoverRestores,\n // When lag reporting is enabled, a restored view-syncer waits for\n // the next lag report (sent at an interval with ~1 second of\n // jitter) before becoming ready.\n switchoverRestoreReadinessGracePeriodMs:\n (replicationLag.reportIntervalMs > 0\n ? replicationLag.reportIntervalMs + 1000\n : 0) + DEFAULT_READINESS_GRACE_PERIOD_MS,\n statementTimeoutMs: change.statementTimeoutMs,\n changeLogBatchSize: change.logBatchSize,\n sqliteCatchup: {\n changeLogFile: changeLogFileName(replica.file),\n readBatchRows: sqliteChangeLogReadBatchRows,\n barrierTimeoutMs: sqliteChangeLogBarrierTimeoutMs,\n },\n // The presence of these options is the writer's gate. This process\n // performs the restore and the initial sync, so the replica's\n // identity is in hand at the moment the log is opened.\n sqliteChangeLogWriter: sqliteChangeLogEnabled\n ? {\n replicaFile: replica.file,\n identity: {\n // RMv2 supplies the epoch; until then the generation and the\n // replica ID are the whole of the identity.\n epoch: null,\n generation: subscriptionState.replicaVersion,\n replicaID,\n },\n }\n : undefined,\n // The purge scheduler shares the writer's gate -- mode, not the\n // read path -- because `write` mode, with no read selector at all,\n // is the configuration it ships in.\n sqliteChangeLogPurge: sqliteChangeLogEnabled\n ? {\n retentionMs: sqliteChangeLogRetentionMs,\n batchRows: sqliteChangeLogPurgeBatchRows,\n }\n : undefined,\n // Compare mode runs both advisory checks. Postgres remains authoritative.\n sqliteChangeLogCompare:\n pgChangeLogEnabled && sqliteChangeLogComparing\n ? {\n replicaFile: replica.file,\n comparePercent: sqliteChangeLogComparePercent,\n retentionMs: sqliteChangeLogRetentionMs,\n readBatchRows: sqliteChangeLogReadBatchRows,\n }\n : undefined,\n // Slice 11 lands dark by default: serve mode constructs the stable\n // router, while readPercent=0 keeps every catchup on PG and emits\n // eligibility metrics before any canary traffic is enabled.\n sqliteChangeLogServe:\n sqliteChangeLogMode === 'serve'\n ? {\n readPercent: sqliteChangeLogReadPercent,\n coldReadPercent: sqliteChangeLogColdReadPercent,\n retentionMs: sqliteChangeLogRetentionMs,\n }\n : undefined,\n backupMonitor,\n },\n setTimeout,\n );\n backupURL = destinationBackupURL;\n waitForFirstBackupBeforeServing = waitForBackupBeforeServing;\n startMetrics.setKind(startTimings?.kind);\n startMetrics.record('fork_wait', startTimings?.forkWaitMs);\n startMetrics.record('restore', startTimings?.restoreMs);\n newBackupLineage = initNewBackupLineage;\n break;\n } catch (e) {\n // Release the resources acquired by the failed attempt, e.g.:\n // * The claimed replication slot, which would otherwise remain active\n // indefinitely, preventing a retry from resuming (or forking) it.\n // * The purge lock. This is safe because the purge lock exists to\n // preserve change-log entries so the new change-streamer can resume\n // from the backup replica's watermark. An AutoResetSignal means we\n // can't resume from the backup replica (e.g. its replication slot is\n // gone), so the change-log entries the lock was protecting are no\n // longer needed. The retry performs a fresh initial sync with a new\n // replication slot, independent of the old change-log. Releasing is\n // also necessary to avoid a self-deadlock when CHANGE_DB ==\n // UPSTREAM_DB: CREATE_REPLICATION_SLOT waits for all older\n // transactions to finish, including this lock's open transaction.\n await cleanup.release();\n // Not yet started (the change-streamer starts it), so nothing to wait on.\n void backupMonitor?.stop();\n backupMonitor = undefined;\n if (first && e instanceof AutoResetSignal) {\n lc.warn?.(`resetting replica ${replica.file}`, e);\n // TODO: Make deleteLiteDB work with litestream. It will probably have to be\n // a semantic wipe instead of a file delete.\n deleteLiteDB(replica.file);\n // The change log carries the identity of the replica it was written\n // beside, and the retry performs a fresh initial sync with a new\n // replicaVersion. Reconciliation would catch that on its own (as\n // 'identity-mismatch') but only after the writer opened a file that is\n // known here to be garbage.\n deleteChangeLogDB(replica.file);\n continue; // execute again with a fresh initial-sync\n }\n if (e instanceof DatabaseInitError) {\n throw new Error(\n `Cannot open ZERO_REPLICA_FILE at \"${replica.file}\". Please check that the path is valid.`,\n {cause: e},\n );\n }\n throw e;\n }\n }\n // impossible: upstream must have advanced in order for replication to be stuck.\n assert(changeStreamer, `resetting replica did not advance replicaVersion`);\n assert(backupMonitor, `backup monitor is created with the change-streamer`);\n\n // The backup-replicator is stopped with the other services, i.e. after the\n // ChangeStreamerHttpServer has drained its subscribers.\n const processes = new SupportingProcesses(lc, parent);\n const profileSubWorkers: Worker[] = [];\n const {promise: replicatorReady, resolve} = resolver();\n if (backupURL) {\n const catchUpStart = performance.now();\n void replicatorReady.then(() =>\n startMetrics.record('catch_up', performance.now() - catchUpStart),\n );\n }\n if (!backupURL) {\n resolve(); // No backup replicator to wait for, so resolve immediately.\n } else {\n lc.info?.('setting up backup to', backupURL);\n config.litestream.backupURLOverride = backupURL;\n if (newBackupLineage) {\n deleteLitestreamMetaDir(replica.file);\n }\n // Start a backup replicator, which starts up the corresponding\n // litestream backup process.\n const backupReplicator = processes\n .add(\n childWorker(\n REPLICATOR_URL,\n {...env, ZERO_LITESTREAM_BACKUP_URL_OVERRIDE: backupURL},\n 'backup' satisfies ReplicaFileMode,\n ),\n 'backup-replicator',\n )\n .onceMessageType('ready', () => resolve());\n profileSubWorkers.push(backupReplicator);\n // Relay profileResponse messages from backup-replicator up to parent\n backupReplicator.onMessageType<ProfileResponseMessage>(\n 'profileResponse',\n res => parent.send(['profileResponse', res]),\n );\n }\n\n let backupReady = promiseVoid;\n if (waitForFirstBackupBeforeServing) {\n const start = performance.now();\n lc.info?.(`awaiting initial backup ...`);\n\n backupReady = backupMonitor.firstBackupReceived().then(() => {\n const elapsed = performance.now() - start;\n lc.info?.(`initial backup confirmed after ${elapsed.toFixed(2)}ms`);\n startMetrics.record('backup_confirm', elapsed);\n });\n }\n\n // In RMv2, readiness additionally waits for the backup-replicator to catch\n // up to the replication stream. This is not done in RMv1 (i.e. when the\n // PG change-log is enabled), in which the change-streamer itself is only\n // started after the readinessGate (plus a startup delay), and thus the\n // backup-replicator cannot catch up before then.\n const readinessGate = pgChangeLogEnabled\n ? backupReady\n : Promise.all([backupReady, replicatorReady]);\n\n // Create the broadcast facade once: each broadcastWorker() adds permanent\n // 'message' forwarders to every sub worker, so creating one per /profz\n // request would leak a forwarder per request.\n const profileWorker =\n profileSubWorkers.length > 0\n ? broadcastWorker(profileSubWorkers)\n : undefined;\n const getProfileWorker = profileWorker\n ? () => Promise.resolve(profileWorker)\n : undefined;\n\n const changeStreamerWebServer = new ChangeStreamerHttpServer(\n lc,\n {\n port,\n keepaliveTimeoutMs,\n // The startup delay is only relevant when taking over the PG change-log\n // (RMv1 and RMv1.5), and is disabled for RMv2.\n startupDelayMs: pgChangeLogEnabled ? startupDelayMs : 0,\n readinessGate,\n config,\n getProfileWorker,\n },\n parent,\n changeStreamer,\n );\n\n void readinessGate.then(() => {\n startMetrics.ready();\n parent.send(['ready', {ready: true}]);\n });\n\n // Note: The changeStreamer itself is not started here; it is started by the\n // changeStreamerWebServer after a delay to ensure that routing\n // routing elements have registered the server before the\n // change-streamer takes over the replication slot.\n // TODO: Remove this delay and start the changeStreamer normally here once\n // transitioned to RMv2, since changeStreamer startup will no longer\n // disrupt an existing task.\n running = true;\n await runUntilKilled(\n lc,\n parent,\n changeStreamerWebServer,\n backupMonitor,\n processes,\n );\n}\n\n/**\n * Connects the startup-time client used for change-streamer initialization\n * and handoff / takeover, and ensures that the change DB schema is\n * initialized / up-to-date. Steady-state clients are managed by the\n * change-streamer (Storer) implementation.\n */\nasync function connectChangeDB(\n lc: LogContext,\n connectionURI: string,\n shard: ShardConfig,\n): Promise<PostgresDB> {\n const changeDB = await connectPgClient(\n lc,\n connectionURI,\n 'change-streamer-init',\n {max: 5},\n {sendStringAsJson: true},\n );\n void warmupConnections(lc, changeDB, 'change').catch(() => {});\n await initChangeStreamerSchema(lc, changeDB, shard);\n return changeDB;\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () =>\n runWorker(\n must(parentWorker),\n process.env,\n ...process.argv.slice(2),\n ).catch(async e => {\n if (!running) {\n await publishCriticalEvent(\n lc,\n replicationStatusError(lc, 'Initializing', e),\n );\n }\n throw e;\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {DatabaseInitError} from '../../../zqlite/src/db.ts';\nimport {getServerContext} from '../config/server-context.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink, publishCriticalEvent} from '../observability/events.ts';\nimport {getOrCreateGauge} from '../observability/metrics.ts';\nimport {InitCleanup} from '../services/change-source/common/init-cleanup.ts';\nimport type {PgChangeLogPurgeLocker} from '../services/change-source/common/replica-restore.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source-init.ts';\nimport {createBackupCleanupMonitor} from '../services/change-streamer/backup-cleanup-monitor-factory.ts';\nimport type {BackupMonitor} from '../services/change-streamer/backup-monitor.ts';\nimport {ChangeStreamerHttpServer} from '../services/change-streamer/change-streamer-http.ts';\nimport {initializeStreamer} from '../services/change-streamer/change-streamer-service.ts';\nimport type {ChangeStreamerService} from '../services/change-streamer/change-streamer.ts';\nimport {DEFAULT_READINESS_GRACE_PERIOD_MS} from '../services/change-streamer/restore-admission.ts';\nimport {initChangeStreamerSchema} from '../services/change-streamer/schema/init.ts';\nimport {AutoResetSignal} from '../services/change-streamer/schema/tables.ts';\nimport {ServerStartMetrics} from '../services/change-streamer/server-start-metrics.ts';\nimport {\n PurgeLocker,\n type PurgeLock,\n} from '../services/change-streamer/storer.ts';\nimport {\n exitAfter,\n runUntilKilled,\n SupportingProcesses,\n} from '../services/life-cycle.ts';\nimport {deleteLitestreamMetaDir} from '../services/litestream/commands.ts';\nimport {\n changeLogFileName,\n deleteChangeLogDB,\n} from '../services/replicator/change-log-db.ts';\nimport {\n replicationStatusError,\n ReplicationStatusPublisher,\n} from '../services/replicator/replication-status.ts';\nimport {sqliteFileBytes} from '../services/replicator/sqlite-change-log-observability.ts';\nimport {warnIfPostgresUnpatched} from '../types/pg-patch.ts';\nimport {connectPgClient, type PostgresDB} from '../types/pg.ts';\nimport {\n broadcastWorker,\n childWorker,\n parentWorker,\n singleProcessMode,\n type ProfileResponseMessage,\n type Worker,\n} from '../types/processes.ts';\nimport {installProfileHandler} from '../types/profiler.ts';\nimport {getShardConfig, type ShardConfig} from '../types/shards.ts';\nimport type {ReplicaFileMode} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {REPLICATOR_URL} from './worker-urls.ts';\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\n// Errors thrown once the services are running are reported by the services\n// themselves (e.g. as 'Replicating' status events), and are thus not\n// published as 'Initializing' errors.\nlet running = false;\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n installProfileHandler(parent, 'change-streamer');\n const config = getNormalizedZeroConfig({env, argv});\n const {\n taskID,\n changeStreamer: {\n port,\n address,\n protocol,\n startupDelayMs,\n backPressureLimitHeapProportion,\n flowControlConsensusTimeoutProportion,\n flowControlSlowSubscriberGracePeriodSeconds,\n maxConcurrentSwitchoverRestores,\n pgChangeLogEnabled,\n sqliteChangeLogMode,\n sqliteChangeLogReadPercent,\n sqliteChangeLogColdReadPercent,\n sqliteChangeLogComparePercent,\n sqliteChangeLogRetentionMs,\n sqliteChangeLogReadBatchRows,\n sqliteChangeLogPurgeBatchRows,\n sqliteChangeLogBarrierTimeoutMs,\n },\n autoReset,\n replicationLag,\n litestream,\n upstream,\n change,\n replica,\n initialSync,\n keepaliveTimeoutMs,\n sqliteCorruptionChecks,\n } = config;\n\n startOtelAuto(\n createLogContext(config, 'change-streamer', 0, false),\n 'change-streamer',\n 0,\n );\n lc = createLogContext(config, 'change-streamer');\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'change-streamer replica',\n dbPath: replica.file,\n },\n sqliteCorruptionChecks,\n );\n initEventSink(lc, config);\n if (upstream.type === 'pg') {\n warnIfPostgresUnpatched(lc);\n }\n\n const shard = getShardConfig(config);\n\n // The change DB is only used by the PG change log. When it is disabled\n // (RMv2), no connection to the change DB is made.\n const changeDB = pgChangeLogEnabled\n ? await connectChangeDB(lc, change.db, shard)\n : null;\n\n let purgeLock = null as PurgeLock | null;\n let changeStreamer: ChangeStreamerService | undefined;\n let backupMonitor: BackupMonitor | undefined;\n let backupURL: string | undefined;\n\n const context = getServerContext(config);\n const sqliteChangeLogEnabled = sqliteChangeLogMode !== 'off';\n // The modes are cumulative, so `serve` implies `compare`.\n const sqliteChangeLogComparing =\n sqliteChangeLogMode === 'compare' || sqliteChangeLogMode === 'serve';\n if (sqliteChangeLogEnabled) {\n const changeLogFile = changeLogFileName(replica.file);\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'change-streamer change-log',\n dbPath: changeLogFile,\n },\n sqliteCorruptionChecks,\n );\n // The log's bytes are accounted for in the process that writes them. This\n // is local disk only — the log is excluded from the litestream backup — and\n // it is a whole-database total, because a table that left the replica left\n // through its wal: the replica's footprint is what shrank and this is where\n // it went.\n getOrCreateGauge('replica', 'sqlite_change_log.file_bytes', {\n description:\n `The SQLite change log's total on-disk footprint: its main db file ` +\n `plus its wal sidecars.`,\n unit: 'By',\n }).addCallback(async o =>\n o.observe(await sqliteFileBytes(lc, changeLogFile)),\n );\n }\n\n const startMetrics = new ServerStartMetrics();\n let waitForFirstBackupBeforeServing = false;\n let newBackupLineage = false;\n for (const first of [true, false]) {\n // Resources acquired by an initialization attempt (e.g. a claimed\n // replication slot, the purge lock) to be released if the attempt fails.\n const cleanup = new InitCleanup(lc);\n // When restoring from litestream, a lock is acquired to prevent change-log\n // purges. This ensures that (this) change-streamer will be able to resume\n // from the backup. The lock is acquired by the change source initialization\n // just before restoring (i.e. after a replication slot is created, if\n // applicable), and released once this change-streamer takes over the\n // change-log.\n const acquirePurgeLock: PgChangeLogPurgeLocker | undefined =\n changeDB && litestream.backupURL && litestream.executable\n ? async (slotWatermark?: string) => {\n const lock = await new PurgeLocker(lc, shard, changeDB).acquire(\n slotWatermark,\n );\n purgeLock = lock === 'behind-slot' ? null : lock;\n if (purgeLock) {\n const acquired = purgeLock;\n cleanup.onFailure('purge lock', () => {\n purgeLock = null;\n return acquired.release();\n });\n }\n return lock;\n }\n : undefined;\n\n const restoreOptions = {litestream, acquirePurgeLock, cleanup};\n try {\n // Note: This performs initial sync of the replica if necessary.\n const {\n pgReplicationEpoch: epoch,\n pgReplicationSlotPerReplica: slotPerReplica,\n pgResumeOrphanedSlotGracePeriodMs: inactiveReplicaGracePeriodMs,\n } = upstream;\n const {\n changeSource,\n subscriptionState,\n destinationBackupURL,\n replicaID,\n waitForBackupBeforeServing,\n newBackupLineage: initNewBackupLineage,\n pgChangeLogBehindSlot,\n startTimings,\n } = upstream.type === 'pg'\n ? await initializePostgresChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n {\n ...initialSync,\n replicationSlotFailover: upstream.pgReplicationSlotFailover,\n installPartialIndexTriggers: upstream.pgPartialIndexTriggers,\n },\n context,\n replicationLag.reportIntervalMs,\n restoreOptions,\n {\n epoch,\n slotPerReplica,\n inactiveReplicaGracePeriodMs,\n backupV5: litestream.backupUsingV5,\n pgChangeLog: pgChangeLogEnabled,\n },\n upstream.pgStreamInboundTimeoutMs,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n restoreOptions,\n );\n\n const replicationStatusPublisher =\n ReplicationStatusPublisher.forReplicaFile(replica.file);\n\n // Created before the change-streamer, which tracks its watermarks (and\n // requests prompt backups from it) from construction. It is run (and\n // stopped) as a service below. It monitors the backup-replicator's\n // destination (see `backupURLOverride` below).\n backupMonitor = createBackupCleanupMonitor({\n lc,\n config: destinationBackupURL\n ? {\n ...config,\n litestream: {\n ...config.litestream,\n backupURLOverride: destinationBackupURL,\n },\n }\n : config,\n replicaFile: replica.file,\n });\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n replicationStatusPublisher,\n subscriptionState,\n destinationBackupURL\n ? {\n backupURL: destinationBackupURL,\n litestreamVersion: litestream.backupUsingV5 ? 'v5' : 'legacy',\n replicaFile: replica.file,\n }\n : null,\n purgeLock,\n autoReset ?? false,\n {\n pgChangeLogBehindSlot: pgChangeLogBehindSlot ?? false,\n backPressureLimitHeapProportion,\n flowControlConsensusTimeoutProportion,\n flowControlSlowSubscriberGracePeriodMs:\n flowControlSlowSubscriberGracePeriodSeconds > 0\n ? flowControlSlowSubscriberGracePeriodSeconds * 1000\n : undefined,\n maxConcurrentSwitchoverRestores,\n // When lag reporting is enabled, a restored view-syncer waits for\n // the next lag report (sent at an interval with ~1 second of\n // jitter) before becoming ready.\n switchoverRestoreReadinessGracePeriodMs:\n (replicationLag.reportIntervalMs > 0\n ? replicationLag.reportIntervalMs + 1000\n : 0) + DEFAULT_READINESS_GRACE_PERIOD_MS,\n statementTimeoutMs: change.statementTimeoutMs,\n changeLogBatchSize: change.logBatchSize,\n sqliteCatchup: {\n changeLogFile: changeLogFileName(replica.file),\n readBatchRows: sqliteChangeLogReadBatchRows,\n barrierTimeoutMs: sqliteChangeLogBarrierTimeoutMs,\n },\n // The presence of these options is the writer's gate. This process\n // performs the restore and the initial sync, so the replica's\n // identity is in hand at the moment the log is opened.\n sqliteChangeLogWriter: sqliteChangeLogEnabled\n ? {\n replicaFile: replica.file,\n identity: {\n // RMv2 supplies the epoch; until then the generation and the\n // replica ID are the whole of the identity.\n epoch: null,\n generation: subscriptionState.replicaVersion,\n replicaID,\n },\n }\n : undefined,\n // The purge scheduler shares the writer's gate -- mode, not the\n // read path -- because `write` mode, with no read selector at all,\n // is the configuration it ships in.\n sqliteChangeLogPurge: sqliteChangeLogEnabled\n ? {\n retentionMs: sqliteChangeLogRetentionMs,\n batchRows: sqliteChangeLogPurgeBatchRows,\n }\n : undefined,\n // Compare mode runs both advisory checks. Postgres remains authoritative.\n sqliteChangeLogCompare:\n pgChangeLogEnabled && sqliteChangeLogComparing\n ? {\n replicaFile: replica.file,\n comparePercent: sqliteChangeLogComparePercent,\n retentionMs: sqliteChangeLogRetentionMs,\n readBatchRows: sqliteChangeLogReadBatchRows,\n }\n : undefined,\n // Slice 11 lands dark by default: serve mode constructs the stable\n // router, while readPercent=0 keeps every catchup on PG and emits\n // eligibility metrics before any canary traffic is enabled.\n sqliteChangeLogServe:\n sqliteChangeLogMode === 'serve'\n ? {\n readPercent: sqliteChangeLogReadPercent,\n coldReadPercent: sqliteChangeLogColdReadPercent,\n retentionMs: sqliteChangeLogRetentionMs,\n }\n : undefined,\n backupMonitor,\n },\n setTimeout,\n );\n backupURL = destinationBackupURL;\n waitForFirstBackupBeforeServing = waitForBackupBeforeServing;\n startMetrics.setKind(startTimings?.kind);\n startMetrics.record('slot_create', startTimings?.slotCreateMs);\n startMetrics.record('fork_wait', startTimings?.forkWaitMs);\n startMetrics.record('restore', startTimings?.restoreMs);\n newBackupLineage = initNewBackupLineage;\n break;\n } catch (e) {\n // Release the resources acquired by the failed attempt, e.g.:\n // * The claimed replication slot, which would otherwise remain active\n // indefinitely, preventing a retry from resuming (or forking) it.\n // * The purge lock. This is safe because the purge lock exists to\n // preserve change-log entries so the new change-streamer can resume\n // from the backup replica's watermark. An AutoResetSignal means we\n // can't resume from the backup replica (e.g. its replication slot is\n // gone), so the change-log entries the lock was protecting are no\n // longer needed. The retry performs a fresh initial sync with a new\n // replication slot, independent of the old change-log. Releasing is\n // also necessary to avoid a self-deadlock when CHANGE_DB ==\n // UPSTREAM_DB: CREATE_REPLICATION_SLOT waits for all older\n // transactions to finish, including this lock's open transaction.\n await cleanup.release();\n // Not yet started (the change-streamer starts it), so nothing to wait on.\n void backupMonitor?.stop();\n backupMonitor = undefined;\n if (first && e instanceof AutoResetSignal) {\n lc.warn?.(`resetting replica ${replica.file}`, e);\n // TODO: Make deleteLiteDB work with litestream. It will probably have to be\n // a semantic wipe instead of a file delete.\n deleteLiteDB(replica.file);\n // The change log carries the identity of the replica it was written\n // beside, and the retry performs a fresh initial sync with a new\n // replicaVersion. Reconciliation would catch that on its own (as\n // 'identity-mismatch') but only after the writer opened a file that is\n // known here to be garbage.\n deleteChangeLogDB(replica.file);\n continue; // execute again with a fresh initial-sync\n }\n if (e instanceof DatabaseInitError) {\n throw new Error(\n `Cannot open ZERO_REPLICA_FILE at \"${replica.file}\". Please check that the path is valid.`,\n {cause: e},\n );\n }\n throw e;\n }\n }\n // impossible: upstream must have advanced in order for replication to be stuck.\n assert(changeStreamer, `resetting replica did not advance replicaVersion`);\n assert(backupMonitor, `backup monitor is created with the change-streamer`);\n\n // The backup-replicator is stopped with the other services, i.e. after the\n // ChangeStreamerHttpServer has drained its subscribers.\n const processes = new SupportingProcesses(lc, parent);\n const profileSubWorkers: Worker[] = [];\n const {promise: replicatorReady, resolve} = resolver();\n if (backupURL) {\n const catchUpStart = performance.now();\n void replicatorReady.then(() =>\n startMetrics.record('catch_up', performance.now() - catchUpStart),\n );\n }\n if (!backupURL) {\n resolve(); // No backup replicator to wait for, so resolve immediately.\n } else {\n lc.info?.('setting up backup to', backupURL);\n config.litestream.backupURLOverride = backupURL;\n if (newBackupLineage) {\n deleteLitestreamMetaDir(replica.file);\n }\n // Start a backup replicator, which starts up the corresponding\n // litestream backup process.\n const backupReplicator = processes\n .add(\n childWorker(\n REPLICATOR_URL,\n {...env, ZERO_LITESTREAM_BACKUP_URL_OVERRIDE: backupURL},\n 'backup' satisfies ReplicaFileMode,\n ),\n 'backup-replicator',\n )\n .onceMessageType('ready', () => resolve());\n profileSubWorkers.push(backupReplicator);\n // Relay profileResponse messages from backup-replicator up to parent\n backupReplicator.onMessageType<ProfileResponseMessage>(\n 'profileResponse',\n res => parent.send(['profileResponse', res]),\n );\n }\n\n let backupReady = promiseVoid;\n if (waitForFirstBackupBeforeServing) {\n const start = performance.now();\n lc.info?.(`awaiting initial backup ...`);\n\n backupReady = backupMonitor.firstBackupReceived().then(() => {\n const elapsed = performance.now() - start;\n lc.info?.(`initial backup confirmed after ${elapsed.toFixed(2)}ms`);\n startMetrics.record('backup_confirm', elapsed);\n });\n }\n\n // In RMv2, readiness additionally waits for the backup-replicator to catch\n // up to the replication stream. This is not done in RMv1 (i.e. when the\n // PG change-log is enabled), in which the change-streamer itself is only\n // started after the readinessGate (plus a startup delay), and thus the\n // backup-replicator cannot catch up before then.\n const readinessGate = pgChangeLogEnabled\n ? backupReady\n : Promise.all([backupReady, replicatorReady]);\n\n // Create the broadcast facade once: each broadcastWorker() adds permanent\n // 'message' forwarders to every sub worker, so creating one per /profz\n // request would leak a forwarder per request.\n const profileWorker =\n profileSubWorkers.length > 0\n ? broadcastWorker(profileSubWorkers)\n : undefined;\n const getProfileWorker = profileWorker\n ? () => Promise.resolve(profileWorker)\n : undefined;\n\n const changeStreamerWebServer = new ChangeStreamerHttpServer(\n lc,\n {\n port,\n keepaliveTimeoutMs,\n // The startup delay is only relevant when taking over the PG change-log\n // (RMv1 and RMv1.5), and is disabled for RMv2.\n startupDelayMs: pgChangeLogEnabled ? startupDelayMs : 0,\n readinessGate,\n config,\n getProfileWorker,\n },\n parent,\n changeStreamer,\n );\n\n void readinessGate.then(() => {\n startMetrics.ready();\n parent.send(['ready', {ready: true}]);\n });\n\n // Note: The changeStreamer itself is not started here; it is started by the\n // changeStreamerWebServer after a delay to ensure that routing\n // routing elements have registered the server before the\n // change-streamer takes over the replication slot.\n // TODO: Remove this delay and start the changeStreamer normally here once\n // transitioned to RMv2, since changeStreamer startup will no longer\n // disrupt an existing task.\n running = true;\n await runUntilKilled(\n lc,\n parent,\n changeStreamerWebServer,\n backupMonitor,\n processes,\n );\n}\n\n/**\n * Connects the startup-time client used for change-streamer initialization\n * and handoff / takeover, and ensures that the change DB schema is\n * initialized / up-to-date. Steady-state clients are managed by the\n * change-streamer (Storer) implementation.\n */\nasync function connectChangeDB(\n lc: LogContext,\n connectionURI: string,\n shard: ShardConfig,\n): Promise<PostgresDB> {\n const changeDB = await connectPgClient(\n lc,\n connectionURI,\n 'change-streamer-init',\n {max: 5},\n {sendStringAsJson: true},\n );\n void warmupConnections(lc, changeDB, 'change').catch(() => {});\n await initChangeStreamerSchema(lc, changeDB, shard);\n return changeDB;\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () =>\n runWorker(\n must(parentWorker),\n process.env,\n ...process.argv.slice(2),\n ).catch(async e => {\n if (!running) {\n await publishCriticalEvent(\n lc,\n replicationStatusError(lc, 'Initializing', e),\n );\n }\n throw e;\n }),\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"replica-restore.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/common/replica-restore.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {sleep} from '../../../../../shared/src/sleep.ts';\nimport type {LitestreamConfig} from '../../../config/normalize.ts';\nimport {\n tryRestore,\n type ReplicaConstraints,\n type RestoreResult,\n} from '../../litestream/commands.ts';\nimport {\n litestreamRestoreDuration,\n litestreamRestoreMetricAttrs,\n litestreamRestoreRuns,\n} from '../../litestream/metrics.ts';\nimport type {SubscriptionState} from '../../replicator/schema/replication-state.ts';\nimport type {ChangeSource} from '../change-source.ts';\nimport type {InitCleanup} from './init-cleanup.ts';\n\nexport interface PurgeLock {\n release(): Promise<void>;\n}\n\n/** A PurgeLock that also constrains the replica that can be restored. */\nexport interface ConstrainingPurgeLock extends PurgeLock, ReplicaConstraints {\n /**\n * The head of the change log when the lock was acquired, from which (or\n * from past which) the change log is resumed.\n */\n readonly headWatermark: string;\n}\n\n/**\n * Acquires a purge lock on the PG change-log (if it is not empty).\n *\n * When resuming the change-log from a replication slot, `slotWatermark` is the\n * slot's position (as a watermark). If the change-log's head is behind it, the\n * change-log may be missing transactions that the slot will not stream, in\n * which case nothing is locked and `'behind-slot'` is returned.\n */\nexport type PgChangeLogPurgeLocker = (\n slotWatermark?: string,\n) => Promise<ConstrainingPurgeLock | null | 'behind-slot'>;\n\nexport type RestoreOptions = {\n litestream?: LitestreamConfig;\n /**\n * With the PG change-log enabled, purge-locks the change-log before the\n * replica is restored, which constrains the replica to one from which the\n * change-log can be resumed. The lock is acquired as late as possible (i.e.\n * after a replication slot is created, if applicable) because, if the\n * change-db is the upstream db, a transaction holding the lock would block\n * the creation of a replication slot.\n */\n acquirePurgeLock?: PgChangeLogPurgeLocker | undefined;\n /**\n * Resources acquired by the initialization (e.g. a claimed replication slot)\n * are registered here to be released if the initialization attempt fails.\n * Note that a purge lock acquired with `acquirePurgeLock` must be registered\n * here by the locker, so that it is released on failure.\n */\n cleanup?: InitCleanup | undefined;\n};\n\nexport type InitializeResult = {\n subscriptionState: SubscriptionState;\n changeSource: ChangeSource;\n destinationBackupURL: string | undefined;\n /**\n * The replica this change stream belongs to, from the upstream `replicas`\n * table. It is part of the identity the SQLite change log records, because a\n * generation (i.e. `replicaVersion`) is shared by every sibling of a forked\n * replica and so cannot distinguish two siblings' logs. `null` when the\n * change source has no upstream table to identify itself from.\n */\n replicaID: string | null;\n\n /**\n * Whether server readiness should be gated on the first litestream backup,\n * in order to ensure that a newly connecting subscriber can restore from\n * this change-streamer's backup.\n *\n * This should be true when the backup destination differs from where the\n * backup was restored from, which is the case for (1) initial-sync and\n * (2) most initialization cases in RMv2, with the exception being when\n * an abandoned replica is resumed.\n */\n waitForBackupBeforeServing: boolean;\n\n /**\n * Whether the PG change-log's head was behind the position of the\n * replication slot from which replication resumes (see\n * {@link PgChangeLogPurgeLocker}), in which case it is re-initialized from\n * the restored replica if it does not contain the replica's changes.\n */\n pgChangeLogBehindSlot?: boolean | undefined;\n\n /**\n * Whether the litestream backup to `destinationBackupURL` starts a new\n * (litestream v5) backup lineage, i.e. one that no local litestream state\n * belongs to. Any such state (e.g. from a previous run of a restarted\n * container, whose restore reused the existing replica) describes a\n * different lineage and must be discarded before replicating, or litestream\n * resolves its position from it and fails (and auto-recovers) on its first\n * sync to the empty lineage.\n *\n * This is false when the backup continues a lineage from the local\n * litestream state (see {@link ResumableBackup}).\n */\n newBackupLineage: boolean;\n\n /**\n * How the replica was initialized, and the durations of its phases, for\n * the replication-manager's start metrics (see `ServerStartMetrics`).\n * Absent for change sources that do not report them.\n */\n startTimings?: InitializeTimings | undefined;\n};\n\n/**\n * How a replica was initialized (see {@link InitializeResult.startTimings}):\n * - `fork`: restored from the backup of a running replica, with a new slot\n * - `resume`: restored from the backup of an orphaned replica, with its slot\n * - `rmv1`: restored from the backup of the replica whose slot it takes over\n * - `initial_sync`: initially synced\n */\nexport type InitializeKind = 'fork' | 'resume' | 'rmv1' | 'initial_sync';\n\nexport type InitializeTimings = {\n kind: InitializeKind;\n /**\n * The time spent selecting the replica to fork (or resume) and creating (or\n * claiming) its slot, i.e. before restoring, including any wait for the\n * source replica to pass the new slot's position.\n */\n forkWaitMs?: number | undefined;\n /** The time spent restoring the replica from its backup. */\n restoreMs?: number | undefined;\n};\n\n/**\n * Where the backup goes after a restore, when it can continue from the\n * restored replica's local litestream state instead of starting a new lineage\n * with a full snapshot:\n * - `fork`: a new, unique subfolder of `baseURL`, which the restore seeds with\n * the restored backup files.\n */\nexport type BackupDestination = {type: 'fork'; baseURL: string};\n\n/**\n * A backup lineage at `backupPath` that the litestream backup continues from\n * the local litestream state and the restored replica's WAL.\n */\nexport type ResumableBackup = {\n backupPath: string;\n};\n\n// A short retry is much cheaper than an initial Postgres sync, while keeping a\n// persistent failure from delaying startup appreciably.\nconst MAX_RESTORE_ATTEMPTS = 3;\nconst RESTORE_RETRY_DELAY_MS = 5_000;\n\n/**\n * Restores the replica from `config.backupURL`. With a `destination`, it\n * returns the backup that continues from the restored replica, if the\n * restore prepared one (e.g. it was not skipped because the replica already\n * exists).\n */\nexport async function restoreReplica(\n lc: LogContext,\n config: LitestreamConfig,\n replicaFile: string,\n replicaConstraints: ReplicaConstraints | undefined,\n destination?: BackupDestination | undefined,\n): Promise<ResumableBackup | undefined> {\n const start = performance.now();\n let result: RestoreResult | undefined;\n try {\n // `tryRestore` returns ordinary restore outcomes (no backup or an invalid\n // replica) rather than throwing; preserve their existing immediate\n // initial-sync fallback. Retry every thrown restore error twice before\n // falling back to the initial Postgres sync.\n for (let attemptNum = 1; attemptNum <= MAX_RESTORE_ATTEMPTS; attemptNum++) {\n // Each attempt seeds a new subfolder, as a failed attempt can leave\n // files in its subfolder.\n const backupPath =\n destination?.type === 'fork' ? String(Date.now()) : undefined;\n try {\n const attempt = await tryRestore(\n lc,\n config,\n replicaFile,\n replicaConstraints,\n 'replication_manager',\n undefined,\n backupPath\n ? {url: new URL(backupPath, destination?.baseURL).toString()}\n : undefined,\n );\n result = attempt.result;\n return backupPath && attempt.forked ? {backupPath} : undefined;\n } catch (e) {\n if (attemptNum === MAX_RESTORE_ATTEMPTS) {\n lc.error?.(\n `litestream restore failed after ${attemptNum} attempts; resyncing the replica`,\n e,\n );\n break;\n }\n\n lc.warn?.(\n `litestream restore attempt ${attemptNum} failed; retrying in ${RESTORE_RETRY_DELAY_MS}ms (attempt ${attemptNum + 1} of ${MAX_RESTORE_ATTEMPTS})`,\n e,\n );\n await sleep(RESTORE_RETRY_DELAY_MS);\n }\n }\n return undefined;\n } finally {\n const attrs = litestreamRestoreMetricAttrs(config, 'replication_manager');\n const labels = {...attrs, result: result ?? 'error'};\n litestreamRestoreRuns().add(1, labels);\n litestreamRestoreDuration().recordMs(performance.now() - start, labels);\n }\n}\n"],"mappings":";;;;AA6JA,IAAM,uBAAuB;AAC7B,IAAM,yBAAyB;;;;;;;AAQ/B,eAAsB,eACpB,IACA,QACA,aACA,oBACA,aACsC;CACtC,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI;CACJ,IAAI;EAKF,KAAK,IAAI,aAAa,GAAG,cAAc,sBAAsB,cAAc;GAGzE,MAAM,aACJ,aAAa,SAAS,SAAS,OAAO,KAAK,IAAI,CAAC,IAAI,KAAA;GACtD,IAAI;IACF,MAAM,UAAU,MAAM,WACpB,IACA,QACA,aACA,oBACA,uBACA,KAAA,GACA,aACI,EAAC,KAAK,IAAI,IAAI,YAAY,aAAa,OAAO,CAAC,CAAC,SAAS,EAAC,IAC1D,KAAA,CACN;IACA,SAAS,QAAQ;IACjB,OAAO,cAAc,QAAQ,SAAS,EAAC,WAAU,IAAI,KAAA;GACvD,SAAS,GAAG;IACV,IAAI,eAAe,sBAAsB;KACvC,GAAG,QACD,mCAAmC,WAAW,mCAC9C,CACF;KACA;IACF;IAEA,GAAG,OACD,8BAA8B,WAAW,uBAAuB,uBAAuB,cAAc,aAAa,EAAE,MAAM,qBAAqB,IAC/I,CACF;IACA,MAAM,MAAM,sBAAsB;GACpC;EACF;EACA;CACF,UAAU;EAER,MAAM,SAAS;GAAC,GADF,6BAA6B,QAAQ,qBAChC;GAAO,QAAQ,UAAU;EAAO;EACnD,sBAAsB,CAAC,CAAC,IAAI,GAAG,MAAM;EACrC,0BAA0B,CAAC,CAAC,SAAS,YAAY,IAAI,IAAI,OAAO,MAAM;CACxE;AACF"}
1
+ {"version":3,"file":"replica-restore.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/common/replica-restore.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {sleep} from '../../../../../shared/src/sleep.ts';\nimport type {LitestreamConfig} from '../../../config/normalize.ts';\nimport {\n tryRestore,\n type ReplicaConstraints,\n type RestoreResult,\n} from '../../litestream/commands.ts';\nimport {\n litestreamRestoreDuration,\n litestreamRestoreMetricAttrs,\n litestreamRestoreRuns,\n} from '../../litestream/metrics.ts';\nimport type {SubscriptionState} from '../../replicator/schema/replication-state.ts';\nimport type {ChangeSource} from '../change-source.ts';\nimport type {InitCleanup} from './init-cleanup.ts';\n\nexport interface PurgeLock {\n release(): Promise<void>;\n}\n\n/** A PurgeLock that also constrains the replica that can be restored. */\nexport interface ConstrainingPurgeLock extends PurgeLock, ReplicaConstraints {\n /**\n * The head of the change log when the lock was acquired, from which (or\n * from past which) the change log is resumed.\n */\n readonly headWatermark: string;\n}\n\n/**\n * Acquires a purge lock on the PG change-log (if it is not empty).\n *\n * When resuming the change-log from a replication slot, `slotWatermark` is the\n * slot's position (as a watermark). If the change-log's head is behind it, the\n * change-log may be missing transactions that the slot will not stream, in\n * which case nothing is locked and `'behind-slot'` is returned.\n */\nexport type PgChangeLogPurgeLocker = (\n slotWatermark?: string,\n) => Promise<ConstrainingPurgeLock | null | 'behind-slot'>;\n\nexport type RestoreOptions = {\n litestream?: LitestreamConfig;\n /**\n * With the PG change-log enabled, purge-locks the change-log before the\n * replica is restored, which constrains the replica to one from which the\n * change-log can be resumed. The lock is acquired as late as possible (i.e.\n * after a replication slot is created, if applicable) because, if the\n * change-db is the upstream db, a transaction holding the lock would block\n * the creation of a replication slot.\n */\n acquirePurgeLock?: PgChangeLogPurgeLocker | undefined;\n /**\n * Resources acquired by the initialization (e.g. a claimed replication slot)\n * are registered here to be released if the initialization attempt fails.\n * Note that a purge lock acquired with `acquirePurgeLock` must be registered\n * here by the locker, so that it is released on failure.\n */\n cleanup?: InitCleanup | undefined;\n};\n\nexport type InitializeResult = {\n subscriptionState: SubscriptionState;\n changeSource: ChangeSource;\n destinationBackupURL: string | undefined;\n /**\n * The replica this change stream belongs to, from the upstream `replicas`\n * table. It is part of the identity the SQLite change log records, because a\n * generation (i.e. `replicaVersion`) is shared by every sibling of a forked\n * replica and so cannot distinguish two siblings' logs. `null` when the\n * change source has no upstream table to identify itself from.\n */\n replicaID: string | null;\n\n /**\n * Whether server readiness should be gated on the first litestream backup,\n * in order to ensure that a newly connecting subscriber can restore from\n * this change-streamer's backup.\n *\n * This should be true when the backup destination differs from where the\n * backup was restored from, which is the case for (1) initial-sync and\n * (2) most initialization cases in RMv2, with the exception being when\n * an abandoned replica is resumed.\n */\n waitForBackupBeforeServing: boolean;\n\n /**\n * Whether the PG change-log's head was behind the position of the\n * replication slot from which replication resumes (see\n * {@link PgChangeLogPurgeLocker}), in which case it is re-initialized from\n * the restored replica if it does not contain the replica's changes.\n */\n pgChangeLogBehindSlot?: boolean | undefined;\n\n /**\n * Whether the litestream backup to `destinationBackupURL` starts a new\n * (litestream v5) backup lineage, i.e. one that no local litestream state\n * belongs to. Any such state (e.g. from a previous run of a restarted\n * container, whose restore reused the existing replica) describes a\n * different lineage and must be discarded before replicating, or litestream\n * resolves its position from it and fails (and auto-recovers) on its first\n * sync to the empty lineage.\n *\n * This is false when the backup continues a lineage from the local\n * litestream state (see {@link ResumableBackup}).\n */\n newBackupLineage: boolean;\n\n /**\n * How the replica was initialized, and the durations of its phases, for\n * the replication-manager's start metrics (see `ServerStartMetrics`).\n * Absent for change sources that do not report them.\n */\n startTimings?: InitializeTimings | undefined;\n};\n\n/**\n * How a replica was initialized (see {@link InitializeResult.startTimings}):\n * - `fork`: restored from the backup of a running replica, with a new slot\n * - `resume`: restored from the backup of an orphaned replica, with its slot\n * - `rmv1`: restored from the backup of the replica whose slot it takes over\n * - `initial_sync`: initially synced\n */\nexport type InitializeKind = 'fork' | 'resume' | 'rmv1' | 'initial_sync';\n\nexport type InitializeTimings = {\n kind: InitializeKind;\n /**\n * The time spent creating the new replica and its slot, including retries\n * while slot creation is blocked by upstream transactions (which only the\n * upstream's owner controls). Absent if no slot was created, e.g. when\n * resuming an orphaned replica without first having prepared a fork.\n */\n slotCreateMs?: number | undefined;\n /**\n * The time spent selecting the replica to fork (or resume) and claiming its\n * slot, i.e. before restoring, including any wait for the source replica to\n * pass the new slot's position. It excludes {@link slotCreateMs}.\n */\n forkWaitMs?: number | undefined;\n /** The time spent restoring the replica from its backup. */\n restoreMs?: number | undefined;\n};\n\n/**\n * Where the backup goes after a restore, when it can continue from the\n * restored replica's local litestream state instead of starting a new lineage\n * with a full snapshot:\n * - `fork`: a new, unique subfolder of `baseURL`, which the restore seeds with\n * the restored backup files.\n */\nexport type BackupDestination = {type: 'fork'; baseURL: string};\n\n/**\n * A backup lineage at `backupPath` that the litestream backup continues from\n * the local litestream state and the restored replica's WAL.\n */\nexport type ResumableBackup = {\n backupPath: string;\n};\n\n// A short retry is much cheaper than an initial Postgres sync, while keeping a\n// persistent failure from delaying startup appreciably.\nconst MAX_RESTORE_ATTEMPTS = 3;\nconst RESTORE_RETRY_DELAY_MS = 5_000;\n\n/**\n * Restores the replica from `config.backupURL`. With a `destination`, it\n * returns the backup that continues from the restored replica, if the\n * restore prepared one (e.g. it was not skipped because the replica already\n * exists).\n */\nexport async function restoreReplica(\n lc: LogContext,\n config: LitestreamConfig,\n replicaFile: string,\n replicaConstraints: ReplicaConstraints | undefined,\n destination?: BackupDestination | undefined,\n): Promise<ResumableBackup | undefined> {\n const start = performance.now();\n let result: RestoreResult | undefined;\n try {\n // `tryRestore` returns ordinary restore outcomes (no backup or an invalid\n // replica) rather than throwing; preserve their existing immediate\n // initial-sync fallback. Retry every thrown restore error twice before\n // falling back to the initial Postgres sync.\n for (let attemptNum = 1; attemptNum <= MAX_RESTORE_ATTEMPTS; attemptNum++) {\n // Each attempt seeds a new subfolder, as a failed attempt can leave\n // files in its subfolder.\n const backupPath =\n destination?.type === 'fork' ? String(Date.now()) : undefined;\n try {\n const attempt = await tryRestore(\n lc,\n config,\n replicaFile,\n replicaConstraints,\n 'replication_manager',\n undefined,\n backupPath\n ? {url: new URL(backupPath, destination?.baseURL).toString()}\n : undefined,\n );\n result = attempt.result;\n return backupPath && attempt.forked ? {backupPath} : undefined;\n } catch (e) {\n if (attemptNum === MAX_RESTORE_ATTEMPTS) {\n lc.error?.(\n `litestream restore failed after ${attemptNum} attempts; resyncing the replica`,\n e,\n );\n break;\n }\n\n lc.warn?.(\n `litestream restore attempt ${attemptNum} failed; retrying in ${RESTORE_RETRY_DELAY_MS}ms (attempt ${attemptNum + 1} of ${MAX_RESTORE_ATTEMPTS})`,\n e,\n );\n await sleep(RESTORE_RETRY_DELAY_MS);\n }\n }\n return undefined;\n } finally {\n const attrs = litestreamRestoreMetricAttrs(config, 'replication_manager');\n const labels = {...attrs, result: result ?? 'error'};\n litestreamRestoreRuns().add(1, labels);\n litestreamRestoreDuration().recordMs(performance.now() - start, labels);\n }\n}\n"],"mappings":";;;;AAoKA,IAAM,uBAAuB;AAC7B,IAAM,yBAAyB;;;;;;;AAQ/B,eAAsB,eACpB,IACA,QACA,aACA,oBACA,aACsC;CACtC,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI;CACJ,IAAI;EAKF,KAAK,IAAI,aAAa,GAAG,cAAc,sBAAsB,cAAc;GAGzE,MAAM,aACJ,aAAa,SAAS,SAAS,OAAO,KAAK,IAAI,CAAC,IAAI,KAAA;GACtD,IAAI;IACF,MAAM,UAAU,MAAM,WACpB,IACA,QACA,aACA,oBACA,uBACA,KAAA,GACA,aACI,EAAC,KAAK,IAAI,IAAI,YAAY,aAAa,OAAO,CAAC,CAAC,SAAS,EAAC,IAC1D,KAAA,CACN;IACA,SAAS,QAAQ;IACjB,OAAO,cAAc,QAAQ,SAAS,EAAC,WAAU,IAAI,KAAA;GACvD,SAAS,GAAG;IACV,IAAI,eAAe,sBAAsB;KACvC,GAAG,QACD,mCAAmC,WAAW,mCAC9C,CACF;KACA;IACF;IAEA,GAAG,OACD,8BAA8B,WAAW,uBAAuB,uBAAuB,cAAc,aAAa,EAAE,MAAM,qBAAqB,IAC/I,CACF;IACA,MAAM,MAAM,sBAAsB;GACpC;EACF;EACA;CACF,UAAU;EAER,MAAM,SAAS;GAAC,GADF,6BAA6B,QAAQ,qBAChC;GAAO,QAAQ,UAAU;EAAO;EACnD,sBAAsB,CAAC,CAAC,IAAI,GAAG,MAAM;EACrC,0BAA0B,CAAC,CAAC,SAAS,YAAY,IAAI,IAAI,OAAO,MAAM;CACxE;AACF"}
@@ -118,10 +118,11 @@ async function forkOrResumeReplica(lc, sql, shard, epoch, slotFailover, replicaF
118
118
  replica: void 0,
119
119
  purgeLock: null
120
120
  };
121
- const { restoreFrom, replicateTo, slotLSN, slotSession } = result;
121
+ const { restoreFrom, replicateTo, slotLSN, slotSession, slotCreateMs } = result;
122
122
  const timings = {
123
123
  kind: restoreFrom.id === replicateTo.id ? "resume" : "fork",
124
- forkWaitMs: performance.now() - forkWaitStart
124
+ slotCreateMs,
125
+ forkWaitMs: performance.now() - forkWaitStart - (slotCreateMs ?? 0)
125
126
  };
126
127
  const slotWatermark = restoreFrom.id === replicateTo.id && restoreFrom.pgChangeLog ? void 0 : toStateVersionString(slotLSN);
127
128
  cleanup?.onFailure(`replication slot ${replicateTo.slot}`, () => slotSession.destroy());
@@ -212,6 +213,7 @@ var DEFAULT_INACTIVE_REPLICA_GRACE_PERIOD_MS = 2e4;
212
213
  async function getSourceAndDestinationReplicas(lc, sql, shard, epoch, slotFailover, gracePeriodMs, waitForStoredTxPastSlot, pollIntervalMs = REPLICA_POLL_INTERVAL_MS) {
213
214
  const inactiveSince = /* @__PURE__ */ new Map();
214
215
  let destination;
216
+ let slotCreateMs;
215
217
  let forkLSN;
216
218
  const lastLogged = /* @__PURE__ */ new Map();
217
219
  const shouldLog = (key, intervalMs) => {
@@ -241,11 +243,15 @@ async function getSourceAndDestinationReplicas(lc, sql, shard, epoch, slotFailov
241
243
  if (replica.stage === 1) {
242
244
  const { active, confirmedFlushLsn } = replica;
243
245
  if (active) {
244
- destination ??= await createReplicaAndSlot(lc, sql, "fork-replica-session", shard, epoch, Date.now().toString(), slotFailover, {
245
- backupPath: null,
246
- backupV5: true,
247
- pgChangeLog: false
248
- }, () => promiseVoid, 2);
246
+ if (!destination) {
247
+ const slotCreateStart = performance.now();
248
+ destination = await createReplicaAndSlot(lc, sql, "fork-replica-session", shard, epoch, Date.now().toString(), slotFailover, {
249
+ backupPath: null,
250
+ backupV5: true,
251
+ pgChangeLog: false
252
+ }, () => promiseVoid, 2);
253
+ slotCreateMs = performance.now() - slotCreateStart;
254
+ }
249
255
  forkLSN ??= waitForStoredTxPastSlot ? await currentWalLSN(sql) : destination.slot.consistent_point;
250
256
  if (toBigInt(confirmedFlushLsn) < toBigInt(forkLSN)) {
251
257
  if (shouldLog(`fork:${replica.id}`, PROGRESS_LOG_INTERVAL_MS)) lc.info?.(`waiting for ${replica.id}@${confirmedFlushLsn} to reach ${forkLSN} (after ${destination.slot.slot_name}@${destination.slot.consistent_point})`, { replica });
@@ -261,7 +267,8 @@ async function getSourceAndDestinationReplicas(lc, sql, shard, epoch, slotFailov
261
267
  destID: destination.replica.id
262
268
  }), `replica ${destination.replica.id} was deleted`),
263
269
  slotLSN: destination.slot.consistent_point,
264
- slotSession: destination.initialSession
270
+ slotSession: destination.initialSession,
271
+ slotCreateMs
265
272
  };
266
273
  }
267
274
  }
@@ -282,7 +289,8 @@ async function getSourceAndDestinationReplicas(lc, sql, shard, epoch, slotFailov
282
289
  restoreFrom: replica,
283
290
  replicateTo: reserved.replica,
284
291
  slotLSN: replica.confirmedFlushLsn,
285
- slotSession: reserved.reservation
292
+ slotSession: reserved.reservation,
293
+ slotCreateMs
286
294
  };
287
295
  }
288
296
  }