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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/out/replicache/src/btree/read.d.ts +12 -1
  2. package/out/replicache/src/btree/read.d.ts.map +1 -1
  3. package/out/replicache/src/btree/read.js +24 -12
  4. package/out/replicache/src/btree/read.js.map +1 -1
  5. package/out/replicache/src/db/rebase.d.ts +14 -2
  6. package/out/replicache/src/db/rebase.d.ts.map +1 -1
  7. package/out/replicache/src/db/rebase.js +27 -5
  8. package/out/replicache/src/db/rebase.js.map +1 -1
  9. package/out/replicache/src/db/write.js.map +1 -1
  10. package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  11. package/out/replicache/src/kv/expo-sqlite/store.js +27 -4
  12. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  13. package/out/replicache/src/kv/sqlite-store.d.ts +11 -0
  14. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  15. package/out/replicache/src/kv/sqlite-store.js +43 -3
  16. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  17. package/out/replicache/src/persist/persist.d.ts.map +1 -1
  18. package/out/replicache/src/persist/persist.js +12 -5
  19. package/out/replicache/src/persist/persist.js.map +1 -1
  20. package/out/replicache/src/persist/refresh.js +6 -2
  21. package/out/replicache/src/persist/refresh.js.map +1 -1
  22. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  23. package/out/replicache/src/replicache-impl.js +14 -2
  24. package/out/replicache/src/replicache-impl.js.map +1 -1
  25. package/out/replicache/src/replicache-options.d.ts +9 -0
  26. package/out/replicache/src/replicache-options.d.ts.map +1 -1
  27. package/out/replicache/src/transactions.d.ts.map +1 -1
  28. package/out/replicache/src/transactions.js +1 -1
  29. package/out/replicache/src/transactions.js.map +1 -1
  30. package/out/shared/src/error.d.ts +11 -0
  31. package/out/shared/src/error.d.ts.map +1 -1
  32. package/out/shared/src/error.js +35 -1
  33. package/out/shared/src/error.js.map +1 -1
  34. package/out/shared/src/hash.d.ts.map +1 -1
  35. package/out/shared/src/hash.js +105 -10
  36. package/out/shared/src/hash.js.map +1 -1
  37. package/out/shared/src/map.d.ts +7 -2
  38. package/out/shared/src/map.d.ts.map +1 -1
  39. package/out/shared/src/map.js.map +1 -1
  40. package/out/shared/src/promise-race.d.ts +14 -0
  41. package/out/shared/src/promise-race.d.ts.map +1 -1
  42. package/out/shared/src/promise-race.js +32 -1
  43. package/out/shared/src/promise-race.js.map +1 -1
  44. package/out/zero/package.js +2 -3
  45. package/out/zero/package.js.map +1 -1
  46. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  47. package/out/zero-cache/src/auth/write-authorizer.js +8 -1
  48. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  49. package/out/zero-cache/src/config/normalize.d.ts +5 -0
  50. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  51. package/out/zero-cache/src/config/normalize.js +16 -3
  52. package/out/zero-cache/src/config/normalize.js.map +1 -1
  53. package/out/zero-cache/src/config/zero-config.d.ts +36 -7
  54. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  55. package/out/zero-cache/src/config/zero-config.js +121 -46
  56. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  57. package/out/zero-cache/src/db/create.d.ts.map +1 -1
  58. package/out/zero-cache/src/db/create.js +4 -1
  59. package/out/zero-cache/src/db/create.js.map +1 -1
  60. package/out/zero-cache/src/db/index-predicate.d.ts +4 -0
  61. package/out/zero-cache/src/db/index-predicate.d.ts.map +1 -0
  62. package/out/zero-cache/src/db/index-predicate.js +46 -0
  63. package/out/zero-cache/src/db/index-predicate.js.map +1 -0
  64. package/out/zero-cache/src/db/lite-tables.d.ts +10 -2
  65. package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
  66. package/out/zero-cache/src/db/lite-tables.js +8 -3
  67. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  68. package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
  69. package/out/zero-cache/src/db/migration-lite.js +5 -3
  70. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  71. package/out/zero-cache/src/db/specs.d.ts +41 -0
  72. package/out/zero-cache/src/db/specs.d.ts.map +1 -1
  73. package/out/zero-cache/src/db/specs.js +30 -3
  74. package/out/zero-cache/src/db/specs.js.map +1 -1
  75. package/out/zero-cache/src/db/transaction-pool.d.ts +5 -15
  76. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  77. package/out/zero-cache/src/db/transaction-pool.js +39 -73
  78. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  79. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  80. package/out/zero-cache/src/server/change-streamer.js +27 -15
  81. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  82. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  83. package/out/zero-cache/src/server/main.js +4 -4
  84. package/out/zero-cache/src/server/main.js.map +1 -1
  85. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  86. package/out/zero-cache/src/server/replicator.js +8 -4
  87. package/out/zero-cache/src/server/replicator.js.map +1 -1
  88. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  89. package/out/zero-cache/src/server/worker-urls.d.ts +0 -1
  90. package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
  91. package/out/zero-cache/src/server/worker-urls.js +1 -2
  92. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  93. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/analyze.js +1 -0
  95. package/out/zero-cache/src/services/analyze.js.map +1 -1
  96. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts +12 -2
  97. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +14 -3
  99. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  100. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  101. package/out/zero-cache/src/services/change-source/common/replica-schema.js +10 -41
  102. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  103. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +3 -2
  104. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  105. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +42 -16
  106. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  107. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +34 -2
  108. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  109. package/out/zero-cache/src/services/change-source/pg/change-source.js +47 -19
  110. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  111. package/out/zero-cache/src/services/change-source/pg/decommission.d.ts.map +1 -1
  112. package/out/zero-cache/src/services/change-source/pg/decommission.js +20 -11
  113. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  114. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +1 -0
  115. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  116. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +58 -80
  117. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  118. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +20 -0
  119. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  120. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +43 -3
  121. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  122. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +24 -1
  123. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  124. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +35 -5
  125. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  126. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +1513 -229
  127. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  128. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +2 -2
  129. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  130. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts +19 -0
  131. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -0
  132. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +581 -0
  133. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -0
  134. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +1 -1
  135. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  136. package/out/zero-cache/src/services/change-source/pg/schema/init.js +15 -6
  137. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  138. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +137 -20
  139. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
  140. package/out/zero-cache/src/services/change-source/pg/schema/published.js +82 -13
  141. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  142. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +128 -21
  143. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  144. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +5 -5
  145. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  146. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +2 -0
  147. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  148. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +3 -0
  149. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  150. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +1 -4
  151. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
  152. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +11 -18
  153. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  154. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  155. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +8 -3
  156. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  157. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
  158. package/out/zero-cache/src/services/change-streamer/broadcast.js +4 -1
  159. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  160. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts +34 -0
  161. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts.map +1 -0
  162. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js +67 -0
  163. package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js.map +1 -0
  164. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -4
  165. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  166. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +8 -2
  167. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  168. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +34 -13
  169. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  170. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +165 -36
  171. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  172. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +4 -9
  173. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  174. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  175. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  176. package/out/zero-cache/src/services/change-streamer/forwarder.js +12 -3
  177. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  178. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -1
  179. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +11 -5
  180. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
  181. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts +11 -0
  182. package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts.map +1 -0
  183. package/out/zero-cache/src/services/change-streamer/shard-sampling.js +19 -0
  184. package/out/zero-cache/src/services/change-streamer/shard-sampling.js.map +1 -0
  185. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +11 -1
  186. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
  187. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +38 -5
  188. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  189. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +25 -1
  190. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -1
  191. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +95 -12
  192. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
  193. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts +83 -0
  194. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts.map +1 -0
  195. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +417 -0
  196. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -0
  197. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts +73 -0
  198. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts.map +1 -0
  199. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js +157 -0
  200. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -0
  201. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +1 -1
  202. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -1
  203. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +8 -3
  204. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
  205. package/out/zero-cache/src/services/change-streamer/storer.d.ts +63 -1
  206. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  207. package/out/zero-cache/src/services/change-streamer/storer.js +217 -49
  208. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  209. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +8 -1
  210. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  211. package/out/zero-cache/src/services/change-streamer/subscriber.js +10 -1
  212. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  213. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts +29 -0
  214. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -0
  215. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +154 -0
  216. package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -0
  217. package/out/zero-cache/src/services/http-service.d.ts +1 -0
  218. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  219. package/out/zero-cache/src/services/http-service.js +7 -3
  220. package/out/zero-cache/src/services/http-service.js.map +1 -1
  221. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  222. package/out/zero-cache/src/services/litestream/commands.js +16 -7
  223. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  224. package/out/zero-cache/src/services/litestream/config-v5.yml +15 -1
  225. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +131 -0
  226. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -0
  227. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +70 -0
  228. package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -0
  229. package/out/zero-cache/src/services/litestream/litestream-controller.js +101 -0
  230. package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -0
  231. package/out/zero-cache/src/services/litestream/metrics.d.ts +1 -0
  232. package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
  233. package/out/zero-cache/src/services/litestream/metrics.js +4 -1
  234. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  235. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  236. package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
  237. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  238. package/out/zero-cache/src/services/replicator/change-log-db.d.ts +10 -0
  239. package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -1
  240. package/out/zero-cache/src/services/replicator/change-log-db.js +15 -1
  241. package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
  242. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  243. package/out/zero-cache/src/services/replicator/change-processor.js +18 -21
  244. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  245. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +1 -1
  246. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  247. package/out/zero-cache/src/services/replicator/incremental-sync.js +2 -6
  248. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  249. package/out/zero-cache/src/services/replicator/replicator.d.ts +1 -1
  250. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  251. package/out/zero-cache/src/services/replicator/replicator.js +2 -2
  252. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  253. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +3 -2
  254. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
  255. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +8 -6
  256. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  257. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +27 -2
  258. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
  259. package/out/zero-cache/src/services/replicator/write-worker-client.js +3 -2
  260. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  261. package/out/zero-cache/src/services/replicator/write-worker.js +24 -5
  262. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  263. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +3 -2
  264. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  265. package/out/zero-cache/src/services/view-syncer/client-handler.js +25 -17
  266. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  267. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  268. package/out/zero-cache/src/services/view-syncer/cvr-store.js +14 -1
  269. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  270. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +3 -3
  271. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  272. package/out/zero-cache/src/services/view-syncer/view-syncer.js +6 -2
  273. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  274. package/out/zero-cache/src/types/downstream.d.ts +11 -0
  275. package/out/zero-cache/src/types/downstream.d.ts.map +1 -0
  276. package/out/zero-cache/src/types/pg.d.ts +15 -2
  277. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  278. package/out/zero-cache/src/types/pg.js +21 -8
  279. package/out/zero-cache/src/types/pg.js.map +1 -1
  280. package/out/zero-cache/src/types/poke-chunk.d.ts +18 -0
  281. package/out/zero-cache/src/types/poke-chunk.d.ts.map +1 -0
  282. package/out/zero-cache/src/types/poke-chunk.js +57 -0
  283. package/out/zero-cache/src/types/poke-chunk.js.map +1 -0
  284. package/out/zero-cache/src/types/processes.js +1 -4
  285. package/out/zero-cache/src/types/processes.js.map +1 -1
  286. package/out/zero-cache/src/types/streams.d.ts +4 -9
  287. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  288. package/out/zero-cache/src/types/streams.js.map +1 -1
  289. package/out/zero-cache/src/types/subscription.d.ts +2 -11
  290. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  291. package/out/zero-cache/src/types/subscription.js +4 -25
  292. package/out/zero-cache/src/types/subscription.js.map +1 -1
  293. package/out/zero-cache/src/workers/connection.d.ts +22 -4
  294. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  295. package/out/zero-cache/src/workers/connection.js +112 -14
  296. package/out/zero-cache/src/workers/connection.js.map +1 -1
  297. package/out/zero-cache/src/workers/replicator.d.ts +1 -0
  298. package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
  299. package/out/zero-cache/src/workers/replicator.js +4 -2
  300. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  301. package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
  302. package/out/zero-client/src/client/connection-manager.js +46 -0
  303. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  304. package/out/zero-client/src/client/context.d.ts +15 -0
  305. package/out/zero-client/src/client/context.d.ts.map +1 -1
  306. package/out/zero-client/src/client/context.js +44 -0
  307. package/out/zero-client/src/client/context.js.map +1 -1
  308. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
  309. package/out/zero-client/src/client/inspector/lazy-inspector.js +19 -12
  310. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  311. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  312. package/out/zero-client/src/client/version.js +1 -1
  313. package/out/zero-client/src/client/zero-poke-handler.d.ts +7 -2
  314. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  315. package/out/zero-client/src/client/zero-poke-handler.js +52 -2
  316. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  317. package/out/zero-client/src/client/zero-rep.d.ts +1 -0
  318. package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
  319. package/out/zero-client/src/client/zero-rep.js +9 -6
  320. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  321. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  322. package/out/zero-client/src/client/zero.js +98 -25
  323. package/out/zero-client/src/client/zero.js.map +1 -1
  324. package/out/zero-protocol/src/custom-queries.d.ts +1 -1
  325. package/out/zero-protocol/src/down.d.ts +1 -1
  326. package/out/zero-protocol/src/error.d.ts +4 -4
  327. package/out/zero-protocol/src/inspect-down.js +1 -1
  328. package/out/zero-protocol/src/mutate-server.d.ts +2 -2
  329. package/out/zero-protocol/src/poke.d.ts +89 -7
  330. package/out/zero-protocol/src/poke.d.ts.map +1 -1
  331. package/out/zero-protocol/src/poke.js +16 -8
  332. package/out/zero-protocol/src/poke.js.map +1 -1
  333. package/out/zero-protocol/src/protocol-version.d.ts +12 -7
  334. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  335. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  336. package/out/zero-protocol/src/push.d.ts +1 -1
  337. package/out/zero-protocol/src/query-server.d.ts +2 -2
  338. package/out/zql/src/ivm/array-view.d.ts.map +1 -1
  339. package/out/zql/src/ivm/array-view.js +5 -3
  340. package/out/zql/src/ivm/array-view.js.map +1 -1
  341. package/out/zql/src/ivm/data.d.ts.map +1 -1
  342. package/out/zql/src/ivm/data.js +8 -3
  343. package/out/zql/src/ivm/data.js.map +1 -1
  344. package/out/zql/src/ivm/deferred-input.d.ts +44 -0
  345. package/out/zql/src/ivm/deferred-input.d.ts.map +1 -0
  346. package/out/zql/src/ivm/deferred-input.js +88 -0
  347. package/out/zql/src/ivm/deferred-input.js.map +1 -0
  348. package/out/zql/src/ivm/memory-source.d.ts +10 -2
  349. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  350. package/out/zql/src/ivm/memory-source.js +19 -16
  351. package/out/zql/src/ivm/memory-source.js.map +1 -1
  352. package/out/zql/src/ivm/stream.d.ts +0 -1
  353. package/out/zql/src/ivm/stream.d.ts.map +1 -1
  354. package/out/zql/src/ivm/stream.js +1 -7
  355. package/out/zql/src/ivm/stream.js.map +1 -1
  356. package/out/zql/src/ivm/union-fan-in.d.ts +1 -1
  357. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  358. package/out/zql/src/ivm/union-fan-in.js +11 -2
  359. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  360. package/out/zql/src/ivm/view-apply-change.js +6 -1
  361. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  362. package/out/zql/src/query/query-delegate-base.d.ts +6 -0
  363. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  364. package/out/zql/src/query/query-delegate-base.js +73 -10
  365. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  366. package/out/zql/src/query/query-delegate.d.ts +22 -0
  367. package/out/zql/src/query/query-delegate.d.ts.map +1 -1
  368. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  369. package/out/zqlite/src/query-builder.js +2 -1
  370. package/out/zqlite/src/query-builder.js.map +1 -1
  371. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
  372. package/out/zqlite/src/sqlite-stat-fanout.js +1 -0
  373. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  374. package/out/zqlite/src/table-source.d.ts +1 -1
  375. package/out/zqlite/src/table-source.d.ts.map +1 -1
  376. package/out/zqlite/src/table-source.js +3 -2
  377. package/out/zqlite/src/table-source.js.map +1 -1
  378. package/package.json +2 -3
  379. package/out/zero-cache/src/server/backup-watermark-poller.js +0 -31
  380. package/out/zero-cache/src/server/backup-watermark-poller.js.map +0 -1
  381. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +0 -57
  382. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +0 -1
  383. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +0 -137
  384. package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +0 -1
@@ -25,7 +25,18 @@ export declare class BTreeRead implements AsyncIterable<Entry<FrozenJSONValue>>
25
25
  get(key: string): Promise<FrozenJSONValue | undefined>;
26
26
  has(key: string): Promise<boolean>;
27
27
  isEmpty(): Promise<boolean>;
28
- scan(fromKey: string): AsyncIterableIterator<Entry<FrozenJSONValue>>;
28
+ /**
29
+ * We don't do any encoding of the key in the map, so we have no way of
30
+ * determining from an entry.key alone whether it is a regular key or an
31
+ * encoded IndexKey in an index map. Without encoding regular map keys the
32
+ * caller has to deal with encoding and decoding the keys for the index map.
33
+ *
34
+ * @param prefetch Read sibling nodes in parallel before traversing them so
35
+ * stores can batch the reads. This can read nodes that the caller never
36
+ * visits. Enable it only when the caller expects to consume most of the scan
37
+ * or explicitly accepts that extra work.
38
+ */
39
+ scan(fromKey: string, prefetch: boolean): AsyncIterableIterator<Entry<FrozenJSONValue>>;
29
40
  keys(): AsyncIterableIterator<string>;
30
41
  entries(): AsyncIterableIterator<Entry<FrozenJSONValue>>;
31
42
  [Symbol.asyncIterator](): AsyncIterableIterator<Entry<FrozenJSONValue>>;
@@ -1 +1 @@
1
- {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/btree/read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAGtD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAC,KAAK,IAAI,EAAY,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAC9D,OAAO,EACL,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAU3B,MAAM,WAAW,CAAC;AASnB,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,qBAAa,SAAU,YAAW,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACrE,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,YAAY,GAAG,gBAAgB,CAAC,CACzD;IAEZ,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC;IACjD,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;gBAG/B,OAAO,EAAE,IAAI,EACb,aAAa,EAAE,aAAa,EAC5B,IAAI,GAAE,IAAgB,EACtB,YAAY,GAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAuB,EAC3D,eAAe,SAAmB;IAS9B,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,gBAAgB,CAAC;IA0B7D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAStD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMlC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAcjC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAwB7D,IAAI,IAAI,qBAAqB,CAAC,MAAM,CAAC;IAKrC,OAAO,IAAI,qBAAqB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAK/D,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAIhE,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,qBAAqB,CAAC,qBAAqB,CAAC;CAO3E;AA8KD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,EACd,EAAE,EAAE,KAAK,GAAG,KAAK,GAChB,OAAO,CAAC,YAAY,CAAC,CAmBvB"}
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/btree/read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAGtD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAC,KAAK,IAAI,EAAY,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAC9D,OAAO,EACL,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAU3B,MAAM,WAAW,CAAC;AASnB,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,qBAAa,SAAU,YAAW,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACrE,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,YAAY,GAAG,gBAAgB,CAAC,CACzD;IAEZ,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC;IACjD,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;gBAG/B,OAAO,EAAE,IAAI,EACb,aAAa,EAAE,aAAa,EAC5B,IAAI,GAAE,IAAgB,EACtB,YAAY,GAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAuB,EAC3D,eAAe,SAAmB;IAS9B,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,gBAAgB,CAAC;IA0B7D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAStD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMlC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAUjC;;;;;;;;;;OAUG;IACH,IAAI,CACF,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,GAChB,qBAAqB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAczC,IAAI,IAAI,qBAAqB,CAAC,MAAM,CAAC;IAKrC,OAAO,IAAI,qBAAqB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAK/D,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAIhE,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,qBAAqB,CAAC,qBAAqB,CAAC;CAO3E;AAyMD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,EACd,EAAE,EAAE,KAAK,GAAG,KAAK,GAChB,OAAO,CAAC,YAAY,CAAC,CAmBvB"}
@@ -42,12 +42,22 @@ var BTreeRead = class {
42
42
  if (this.rootHash !== rootHash) return this.isEmpty();
43
43
  return node.entries.length === 0;
44
44
  }
45
- scan(fromKey) {
45
+ /**
46
+ * We don't do any encoding of the key in the map, so we have no way of
47
+ * determining from an entry.key alone whether it is a regular key or an
48
+ * encoded IndexKey in an index map. Without encoding regular map keys the
49
+ * caller has to deal with encoding and decoding the keys for the index map.
50
+ *
51
+ * @param prefetch Read sibling nodes in parallel before traversing them so
52
+ * stores can batch the reads. This can read nodes that the caller never
53
+ * visits. Enable it only when the caller expects to consume most of the scan
54
+ * or explicitly accepts that extra work.
55
+ */
56
+ scan(fromKey, prefetch) {
46
57
  return scanForHash(this.rootHash, () => this.rootHash, this.rootHash, fromKey, async (hash) => {
47
- const cached = await this.getNode(hash);
48
- if (cached) return [cached.level, cached.isMutable ? cached.entries.slice() : cached.entries];
49
- return parseBTreeNode((await this._dagRead.mustGetChunk(hash)).data, this._formatVersion, this.getEntrySize);
50
- });
58
+ const { level, isMutable, entries } = await this.getNode(hash);
59
+ return [level, isMutable ? entries.slice() : entries];
60
+ }, prefetch);
51
61
  }
52
62
  async *keys() {
53
63
  yield* (await this.getNode(this.rootHash)).keys(this);
@@ -126,20 +136,22 @@ function* diffEntries(lastEntries, currentEntries) {
126
136
  newValue: currentEntries[j][1]
127
137
  };
128
138
  }
129
- async function* scanForHash(expectedRootHash, getRootHash, hash, fromKey, readNode) {
139
+ async function* scanForHash(expectedRootHash, getRootHash, hash, fromKey, readNode, prefetch) {
130
140
  if (hash === emptyHash) return;
131
141
  const data = await readNode(hash);
132
142
  const entries = data[1];
133
143
  let i = 0;
134
144
  if (fromKey) i = binarySearch(fromKey, entries);
135
- if (data[0] > 0) for (; i < entries.length; i++) {
136
- yield* scanForHash(expectedRootHash, getRootHash, entries[i][1], fromKey, readNode);
137
- fromKey = "";
138
- }
139
- else for (; i < entries.length; i++) {
145
+ if (data[0] > 0) {
146
+ if (prefetch) await Promise.all(entries.slice(i).map((entry) => readNode(entry[1]).catch(() => void 0)));
147
+ for (; i < entries.length; i++) {
148
+ yield* scanForHash(expectedRootHash, getRootHash, entries[i][1], fromKey, readNode, prefetch);
149
+ fromKey = "";
150
+ }
151
+ } else for (; i < entries.length; i++) {
140
152
  const rootHash = getRootHash();
141
153
  if (expectedRootHash !== rootHash) {
142
- yield* scanForHash(rootHash, getRootHash, rootHash, entries[i][0], readNode);
154
+ yield* scanForHash(rootHash, getRootHash, rootHash, entries[i][0], readNode, prefetch);
143
155
  return;
144
156
  }
145
157
  yield entries[i];
@@ -1 +1 @@
1
- {"version":3,"file":"read.js","names":[],"sources":["../../../../../replicache/src/btree/read.ts"],"sourcesContent":["import type {Enum} from '../../../shared/src/enum.ts';\nimport {deepEqual} from '../../../shared/src/json.ts';\nimport {getSizeOfEntry} from '../../../shared/src/size-of-value.ts';\nimport type {Read} from '../dag/store.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, emptyHash} from '../hash.ts';\nimport type {DataNodeImpl, InternalNodeImpl} from './node.ts';\nimport {\n type Entry,\n type InternalDiff,\n type InternalDiffOperation,\n NODE_ENTRIES,\n NODE_LEVEL,\n binarySearch,\n binarySearchFound,\n emptyDataNodeImpl,\n findLeaf,\n isDataNodeImpl,\n newNodeImpl,\n parseBTreeNode,\n} from './node.ts';\nimport {\n SPLICE_ADDED,\n SPLICE_AT,\n SPLICE_FROM,\n SPLICE_REMOVED,\n computeSplices,\n} from './splice.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\n/**\n * The size of the header of a node. (If we had compile time\n * constants we would have used that).\n *\n * There is a test ensuring this is correct.\n */\nexport const NODE_HEADER_SIZE = 11;\n\nexport class BTreeRead implements AsyncIterable<Entry<FrozenJSONValue>> {\n protected readonly _cache: Map<Hash, DataNodeImpl | InternalNodeImpl> =\n new Map();\n\n protected readonly _dagRead: Read;\n protected readonly _formatVersion: FormatVersion;\n rootHash: Hash;\n readonly getEntrySize: <K, V>(k: K, v: V) => number;\n readonly chunkHeaderSize: number;\n\n constructor(\n dagRead: Read,\n formatVersion: FormatVersion,\n root: Hash = emptyHash,\n getEntrySize: <K, V>(k: K, v: V) => number = getSizeOfEntry,\n chunkHeaderSize = NODE_HEADER_SIZE,\n ) {\n this._dagRead = dagRead;\n this._formatVersion = formatVersion;\n this.rootHash = root;\n this.getEntrySize = getEntrySize;\n this.chunkHeaderSize = chunkHeaderSize;\n }\n\n async getNode(hash: Hash): Promise<DataNodeImpl | InternalNodeImpl> {\n if (hash === emptyHash) {\n return emptyDataNodeImpl;\n }\n\n const cached = this._cache.get(hash);\n if (cached) {\n return cached;\n }\n\n const chunk = await this._dagRead.mustGetChunk(hash);\n const data = parseBTreeNode(\n chunk.data,\n this._formatVersion,\n this.getEntrySize,\n );\n const impl = newNodeImpl(\n data[NODE_ENTRIES] as Entry<FrozenJSONValue>[],\n hash,\n data[NODE_LEVEL],\n false,\n );\n this._cache.set(hash, impl);\n return impl;\n }\n\n async get(key: string): Promise<FrozenJSONValue | undefined> {\n const leaf = await findLeaf(key, this.rootHash, this, this.rootHash);\n const index = binarySearch(key, leaf.entries);\n if (!binarySearchFound(index, leaf.entries, key)) {\n return undefined;\n }\n return leaf.entries[index][1];\n }\n\n async has(key: string): Promise<boolean> {\n const leaf = await findLeaf(key, this.rootHash, this, this.rootHash);\n const index = binarySearch(key, leaf.entries);\n return binarySearchFound(index, leaf.entries, key);\n }\n\n async isEmpty(): Promise<boolean> {\n const {rootHash} = this;\n const node = await this.getNode(this.rootHash);\n // The root hash has changed, so the tree has been modified.\n if (this.rootHash !== rootHash) {\n return this.isEmpty();\n }\n return node.entries.length === 0;\n }\n\n // We don't do any encoding of the key in the map, so we have no way of\n // determining from an entry.key alone whether it is a regular key or an\n // encoded IndexKey in an index map. Without encoding regular map keys the\n // caller has to deal with encoding and decoding the keys for the index map.\n scan(fromKey: string): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n return scanForHash(\n this.rootHash,\n () => this.rootHash,\n this.rootHash,\n fromKey,\n async hash => {\n const cached = await this.getNode(hash);\n if (cached) {\n return [\n cached.level,\n cached.isMutable ? cached.entries.slice() : cached.entries,\n ];\n }\n const chunk = await this._dagRead.mustGetChunk(hash);\n return parseBTreeNode(\n chunk.data,\n this._formatVersion,\n this.getEntrySize,\n );\n },\n );\n }\n\n async *keys(): AsyncIterableIterator<string> {\n const node = await this.getNode(this.rootHash);\n yield* node.keys(this);\n }\n\n async *entries(): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n const node = await this.getNode(this.rootHash);\n yield* node.entriesIter(this);\n }\n\n [Symbol.asyncIterator](): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n return this.entries();\n }\n\n async *diff(last: BTreeRead): AsyncIterableIterator<InternalDiffOperation> {\n const [currentNode, lastNode] = await Promise.all([\n this.getNode(this.rootHash),\n last.getNode(last.rootHash),\n ]);\n yield* diffNodes(lastNode, currentNode, last, this);\n }\n}\n\nasync function* diffNodes(\n last: InternalNodeImpl | DataNodeImpl,\n current: InternalNodeImpl | DataNodeImpl,\n lastTree: BTreeRead,\n currentTree: BTreeRead,\n): AsyncIterableIterator<InternalDiffOperation> {\n if (last.level > current.level) {\n // merge all of last's children into a new node\n // We know last is an internal node because level > 0.\n const lastChild = (await (last as InternalNodeImpl).getCompositeChildren(\n 0,\n last.entries.length,\n lastTree,\n )) as InternalNodeImpl;\n yield* diffNodes(lastChild, current, lastTree, currentTree);\n return;\n }\n\n if (current.level > last.level) {\n // We know current is an internal node because level > 0.\n const currentChild = (await (\n current as InternalNodeImpl\n ).getCompositeChildren(\n 0,\n current.entries.length,\n currentTree,\n )) as InternalNodeImpl;\n yield* diffNodes(last, currentChild, lastTree, currentTree);\n return;\n }\n\n if (isDataNodeImpl(last) && isDataNodeImpl(current)) {\n yield* diffEntries(\n (last as DataNodeImpl).entries,\n (current as DataNodeImpl).entries,\n );\n return;\n }\n\n // Now we have two internal nodes with the same level. We compute the diff as\n // splices for the internal node entries. We then flatten these and call diff\n // recursively.\n const initialSplices = computeSplices(\n (last as InternalNodeImpl).entries,\n (current as InternalNodeImpl).entries,\n );\n for (const splice of initialSplices) {\n const [lastChild, currentChild] = await Promise.all([\n (last as InternalNodeImpl).getCompositeChildren(\n splice[SPLICE_AT],\n splice[SPLICE_REMOVED],\n lastTree,\n ),\n (current as InternalNodeImpl).getCompositeChildren(\n splice[SPLICE_FROM],\n splice[SPLICE_ADDED],\n currentTree,\n ),\n ]);\n yield* diffNodes(lastChild, currentChild, lastTree, currentTree);\n }\n}\n\nfunction* diffEntries(\n lastEntries: readonly Entry<FrozenJSONValue>[],\n currentEntries: readonly Entry<FrozenJSONValue>[],\n): IterableIterator<InternalDiffOperation> {\n const lastLength = lastEntries.length;\n const currentLength = currentEntries.length;\n let i = 0;\n let j = 0;\n while (i < lastLength && j < currentLength) {\n const lastKey = lastEntries[i][0];\n const currentKey = currentEntries[j][0];\n if (lastKey === currentKey) {\n if (!deepEqual(lastEntries[i][1], currentEntries[j][1])) {\n yield {\n op: 'change',\n key: lastKey,\n oldValue: lastEntries[i][1],\n newValue: currentEntries[j][1],\n };\n }\n i++;\n j++;\n } else if (lastKey < currentKey) {\n yield {\n op: 'del',\n key: lastKey,\n oldValue: lastEntries[i][1],\n };\n i++;\n } else {\n yield {\n op: 'add',\n key: currentKey,\n newValue: currentEntries[j][1],\n };\n j++;\n }\n }\n for (; i < lastLength; i++) {\n yield {\n op: 'del',\n key: lastEntries[i][0],\n oldValue: lastEntries[i][1],\n };\n }\n for (; j < currentLength; j++) {\n yield {\n op: 'add',\n key: currentEntries[j][0],\n newValue: currentEntries[j][1],\n };\n }\n}\n\n// Redefine the type here to allow the optional size in the tuple.\ntype ReadNodeResult = readonly [\n level: number,\n data: readonly Entry<FrozenJSONValue>[] | readonly Entry<Hash>[],\n];\n\ntype ReadNode = (hash: Hash) => Promise<ReadNodeResult>;\n\nasync function* scanForHash(\n expectedRootHash: Hash,\n getRootHash: () => Hash,\n hash: Hash,\n fromKey: string,\n readNode: ReadNode,\n): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n if (hash === emptyHash) {\n return;\n }\n\n const data = await readNode(hash);\n const entries = data[NODE_ENTRIES];\n let i = 0;\n if (fromKey) {\n i = binarySearch(fromKey, entries);\n }\n if (data[NODE_LEVEL] > 0) {\n for (; i < entries.length; i++) {\n yield* scanForHash(\n expectedRootHash,\n getRootHash,\n (entries[i] as Entry<Hash>)[1],\n fromKey,\n readNode,\n );\n fromKey = '';\n }\n } else {\n for (; i < entries.length; i++) {\n const rootHash = getRootHash();\n // If rootHash changed then we start a new iterator from the key.\n if (expectedRootHash !== rootHash) {\n yield* scanForHash(\n rootHash,\n getRootHash,\n rootHash,\n entries[i][0],\n readNode,\n );\n return;\n }\n yield entries[i] as Entry<FrozenJSONValue>;\n }\n }\n}\n\nexport async function allEntriesAsDiff(\n map: BTreeRead,\n op: 'add' | 'del',\n): Promise<InternalDiff> {\n const diff: InternalDiffOperation[] = [];\n const make: (entry: Entry<FrozenJSONValue>) => InternalDiffOperation =\n op === 'add'\n ? entry => ({\n op: 'add',\n key: entry[0],\n newValue: entry[1],\n })\n : entry => ({\n op: 'del',\n key: entry[0],\n oldValue: entry[1],\n });\n\n for await (const entry of map.entries()) {\n diff.push(make(entry));\n }\n return diff;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"read.js","names":[],"sources":["../../../../../replicache/src/btree/read.ts"],"sourcesContent":["import type {Enum} from '../../../shared/src/enum.ts';\nimport {deepEqual} from '../../../shared/src/json.ts';\nimport {getSizeOfEntry} from '../../../shared/src/size-of-value.ts';\nimport type {Read} from '../dag/store.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, emptyHash} from '../hash.ts';\nimport type {DataNodeImpl, InternalNodeImpl} from './node.ts';\nimport {\n type Entry,\n type InternalDiff,\n type InternalDiffOperation,\n NODE_ENTRIES,\n NODE_LEVEL,\n binarySearch,\n binarySearchFound,\n emptyDataNodeImpl,\n findLeaf,\n isDataNodeImpl,\n newNodeImpl,\n parseBTreeNode,\n} from './node.ts';\nimport {\n SPLICE_ADDED,\n SPLICE_AT,\n SPLICE_FROM,\n SPLICE_REMOVED,\n computeSplices,\n} from './splice.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\n/**\n * The size of the header of a node. (If we had compile time\n * constants we would have used that).\n *\n * There is a test ensuring this is correct.\n */\nexport const NODE_HEADER_SIZE = 11;\n\nexport class BTreeRead implements AsyncIterable<Entry<FrozenJSONValue>> {\n protected readonly _cache: Map<Hash, DataNodeImpl | InternalNodeImpl> =\n new Map();\n\n protected readonly _dagRead: Read;\n protected readonly _formatVersion: FormatVersion;\n rootHash: Hash;\n readonly getEntrySize: <K, V>(k: K, v: V) => number;\n readonly chunkHeaderSize: number;\n\n constructor(\n dagRead: Read,\n formatVersion: FormatVersion,\n root: Hash = emptyHash,\n getEntrySize: <K, V>(k: K, v: V) => number = getSizeOfEntry,\n chunkHeaderSize = NODE_HEADER_SIZE,\n ) {\n this._dagRead = dagRead;\n this._formatVersion = formatVersion;\n this.rootHash = root;\n this.getEntrySize = getEntrySize;\n this.chunkHeaderSize = chunkHeaderSize;\n }\n\n async getNode(hash: Hash): Promise<DataNodeImpl | InternalNodeImpl> {\n if (hash === emptyHash) {\n return emptyDataNodeImpl;\n }\n\n const cached = this._cache.get(hash);\n if (cached) {\n return cached;\n }\n\n const chunk = await this._dagRead.mustGetChunk(hash);\n const data = parseBTreeNode(\n chunk.data,\n this._formatVersion,\n this.getEntrySize,\n );\n const impl = newNodeImpl(\n data[NODE_ENTRIES] as Entry<FrozenJSONValue>[],\n hash,\n data[NODE_LEVEL],\n false,\n );\n this._cache.set(hash, impl);\n return impl;\n }\n\n async get(key: string): Promise<FrozenJSONValue | undefined> {\n const leaf = await findLeaf(key, this.rootHash, this, this.rootHash);\n const index = binarySearch(key, leaf.entries);\n if (!binarySearchFound(index, leaf.entries, key)) {\n return undefined;\n }\n return leaf.entries[index][1];\n }\n\n async has(key: string): Promise<boolean> {\n const leaf = await findLeaf(key, this.rootHash, this, this.rootHash);\n const index = binarySearch(key, leaf.entries);\n return binarySearchFound(index, leaf.entries, key);\n }\n\n async isEmpty(): Promise<boolean> {\n const {rootHash} = this;\n const node = await this.getNode(this.rootHash);\n // The root hash has changed, so the tree has been modified.\n if (this.rootHash !== rootHash) {\n return this.isEmpty();\n }\n return node.entries.length === 0;\n }\n\n /**\n * We don't do any encoding of the key in the map, so we have no way of\n * determining from an entry.key alone whether it is a regular key or an\n * encoded IndexKey in an index map. Without encoding regular map keys the\n * caller has to deal with encoding and decoding the keys for the index map.\n *\n * @param prefetch Read sibling nodes in parallel before traversing them so\n * stores can batch the reads. This can read nodes that the caller never\n * visits. Enable it only when the caller expects to consume most of the scan\n * or explicitly accepts that extra work.\n */\n scan(\n fromKey: string,\n prefetch: boolean,\n ): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n return scanForHash(\n this.rootHash,\n () => this.rootHash,\n this.rootHash,\n fromKey,\n async hash => {\n const {level, isMutable, entries} = await this.getNode(hash);\n return [level, isMutable ? entries.slice() : entries];\n },\n prefetch,\n );\n }\n\n async *keys(): AsyncIterableIterator<string> {\n const node = await this.getNode(this.rootHash);\n yield* node.keys(this);\n }\n\n async *entries(): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n const node = await this.getNode(this.rootHash);\n yield* node.entriesIter(this);\n }\n\n [Symbol.asyncIterator](): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n return this.entries();\n }\n\n async *diff(last: BTreeRead): AsyncIterableIterator<InternalDiffOperation> {\n const [currentNode, lastNode] = await Promise.all([\n this.getNode(this.rootHash),\n last.getNode(last.rootHash),\n ]);\n yield* diffNodes(lastNode, currentNode, last, this);\n }\n}\n\nasync function* diffNodes(\n last: InternalNodeImpl | DataNodeImpl,\n current: InternalNodeImpl | DataNodeImpl,\n lastTree: BTreeRead,\n currentTree: BTreeRead,\n): AsyncIterableIterator<InternalDiffOperation> {\n if (last.level > current.level) {\n // merge all of last's children into a new node\n // We know last is an internal node because level > 0.\n const lastChild = (await (last as InternalNodeImpl).getCompositeChildren(\n 0,\n last.entries.length,\n lastTree,\n )) as InternalNodeImpl;\n yield* diffNodes(lastChild, current, lastTree, currentTree);\n return;\n }\n\n if (current.level > last.level) {\n // We know current is an internal node because level > 0.\n const currentChild = (await (\n current as InternalNodeImpl\n ).getCompositeChildren(\n 0,\n current.entries.length,\n currentTree,\n )) as InternalNodeImpl;\n yield* diffNodes(last, currentChild, lastTree, currentTree);\n return;\n }\n\n if (isDataNodeImpl(last) && isDataNodeImpl(current)) {\n yield* diffEntries(\n (last as DataNodeImpl).entries,\n (current as DataNodeImpl).entries,\n );\n return;\n }\n\n // Now we have two internal nodes with the same level. We compute the diff as\n // splices for the internal node entries. We then flatten these and call diff\n // recursively.\n const initialSplices = computeSplices(\n (last as InternalNodeImpl).entries,\n (current as InternalNodeImpl).entries,\n );\n for (const splice of initialSplices) {\n const [lastChild, currentChild] = await Promise.all([\n (last as InternalNodeImpl).getCompositeChildren(\n splice[SPLICE_AT],\n splice[SPLICE_REMOVED],\n lastTree,\n ),\n (current as InternalNodeImpl).getCompositeChildren(\n splice[SPLICE_FROM],\n splice[SPLICE_ADDED],\n currentTree,\n ),\n ]);\n yield* diffNodes(lastChild, currentChild, lastTree, currentTree);\n }\n}\n\nfunction* diffEntries(\n lastEntries: readonly Entry<FrozenJSONValue>[],\n currentEntries: readonly Entry<FrozenJSONValue>[],\n): IterableIterator<InternalDiffOperation> {\n const lastLength = lastEntries.length;\n const currentLength = currentEntries.length;\n let i = 0;\n let j = 0;\n while (i < lastLength && j < currentLength) {\n const lastKey = lastEntries[i][0];\n const currentKey = currentEntries[j][0];\n if (lastKey === currentKey) {\n if (!deepEqual(lastEntries[i][1], currentEntries[j][1])) {\n yield {\n op: 'change',\n key: lastKey,\n oldValue: lastEntries[i][1],\n newValue: currentEntries[j][1],\n };\n }\n i++;\n j++;\n } else if (lastKey < currentKey) {\n yield {\n op: 'del',\n key: lastKey,\n oldValue: lastEntries[i][1],\n };\n i++;\n } else {\n yield {\n op: 'add',\n key: currentKey,\n newValue: currentEntries[j][1],\n };\n j++;\n }\n }\n for (; i < lastLength; i++) {\n yield {\n op: 'del',\n key: lastEntries[i][0],\n oldValue: lastEntries[i][1],\n };\n }\n for (; j < currentLength; j++) {\n yield {\n op: 'add',\n key: currentEntries[j][0],\n newValue: currentEntries[j][1],\n };\n }\n}\n\n// Redefine the type here to allow the optional size in the tuple.\ntype ReadNodeResult = readonly [\n level: number,\n data: readonly Entry<FrozenJSONValue>[] | readonly Entry<Hash>[],\n];\n\ntype ReadNode = (hash: Hash) => Promise<ReadNodeResult>;\n\nasync function* scanForHash(\n expectedRootHash: Hash,\n getRootHash: () => Hash,\n hash: Hash,\n fromKey: string,\n readNode: ReadNode,\n prefetch: boolean,\n): AsyncIterableIterator<Entry<FrozenJSONValue>> {\n if (hash === emptyHash) {\n return;\n }\n\n const data = await readNode(hash);\n const entries = data[NODE_ENTRIES];\n let i = 0;\n if (fromKey) {\n i = binarySearch(fromKey, entries);\n }\n if (data[NODE_LEVEL] > 0) {\n if (prefetch) {\n // Preload children in parallel so the backing store can coalesce their\n // reads. This can waste work when a caller stops before visiting every\n // child, so callers must explicitly opt in when they expect to consume\n // the full scan. Zero currently accepts that tradeoff: startup scans the\n // entire cached entity range into IVM memory before becoming ready (see\n // `ZeroRep.init` in packages/zero-client/src/client/zero-rep.ts), and\n // client views are intended to remain small. If views commonly outgrow\n // the 100 MB `LAZY_STORE_SOURCE_CHUNK_CACHE_SIZE_LIMIT`, reconsider the\n // preload window, although batching may still be a useful general\n // optimization. Longer term, the Replicache/IVM boundary should also be\n // revisited because materializing entities into `MemorySource`\n // duplicates B-tree storage.\n //\n // The serial scan below preserves traversal order and remains the\n // authoritative read, so speculative failures are ignored here.\n await Promise.all(\n entries\n .slice(i)\n .map(entry =>\n readNode((entry as Entry<Hash>)[1]).catch(() => undefined),\n ),\n );\n }\n for (; i < entries.length; i++) {\n yield* scanForHash(\n expectedRootHash,\n getRootHash,\n (entries[i] as Entry<Hash>)[1],\n fromKey,\n readNode,\n prefetch,\n );\n fromKey = '';\n }\n } else {\n for (; i < entries.length; i++) {\n const rootHash = getRootHash();\n // If rootHash changed then we start a new iterator from the key.\n if (expectedRootHash !== rootHash) {\n yield* scanForHash(\n rootHash,\n getRootHash,\n rootHash,\n entries[i][0],\n readNode,\n prefetch,\n );\n return;\n }\n yield entries[i] as Entry<FrozenJSONValue>;\n }\n }\n}\n\nexport async function allEntriesAsDiff(\n map: BTreeRead,\n op: 'add' | 'del',\n): Promise<InternalDiff> {\n const diff: InternalDiffOperation[] = [];\n const make: (entry: Entry<FrozenJSONValue>) => InternalDiffOperation =\n op === 'add'\n ? entry => ({\n op: 'add',\n key: entry[0],\n newValue: entry[1],\n })\n : entry => ({\n op: 'del',\n key: entry[0],\n oldValue: entry[1],\n });\n\n for await (const entry of map.entries()) {\n diff.push(make(entry));\n }\n return diff;\n}\n"],"mappings":"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"}
@@ -8,7 +8,19 @@ import type { ClientID } from '../sync/ids.ts';
8
8
  import type { MutatorDefs } from '../types.ts';
9
9
  import { type Commit, type LocalMeta, type Meta } from './commit.ts';
10
10
  type FormatVersion = Enum<typeof FormatVersion>;
11
- export declare function rebaseMutationAndPutCommit(mutation: Commit<LocalMeta>, dagWrite: DagWrite, basis: Hash, mutators: MutatorDefs, lc: LogContext, mutationClientID: ClientID, formatVersion: FormatVersion, zeroData: ZeroTxData | undefined): Promise<Commit<Meta>>;
12
- export declare function rebaseMutationAndCommit(mutation: Commit<LocalMeta>, dagWrite: DagWrite, basis: Hash, headName: string, mutators: MutatorDefs, lc: LogContext, mutationClientID: ClientID, formatVersion: FormatVersion, zeroData: ZeroTxData | undefined): Promise<Hash>;
11
+ /**
12
+ * The result of replaying one mutation.
13
+ *
14
+ * `zeroData` is the Zero transaction data to use for the next mutation in the
15
+ * same replay. Each mutation runs against its own fork. On success that fork is
16
+ * returned, and if the mutator throws the caller gets back the one it passed
17
+ * in, without the failed mutation's IVM writes. See `rebaseMutation`.
18
+ */
19
+ export type RebaseResult<T> = {
20
+ result: T;
21
+ zeroData: ZeroTxData | undefined;
22
+ };
23
+ export declare function rebaseMutationAndPutCommit(mutation: Commit<LocalMeta>, dagWrite: DagWrite, basis: Hash, mutators: MutatorDefs, lc: LogContext, mutationClientID: ClientID, formatVersion: FormatVersion, zeroData: ZeroTxData | undefined): Promise<RebaseResult<Commit<Meta>>>;
24
+ export declare function rebaseMutationAndCommit(mutation: Commit<LocalMeta>, dagWrite: DagWrite, basis: Hash, headName: string, mutators: MutatorDefs, lc: LogContext, mutationClientID: ClientID, formatVersion: FormatVersion, zeroData: ZeroTxData | undefined): Promise<RebaseResult<Hash>>;
13
25
  export {};
14
26
  //# sourceMappingURL=rebase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rebase.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/db/rebase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AACtD,OAAO,KAAK,EAAC,KAAK,IAAI,QAAQ,EAAC,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AACrC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,KAAK,MAAM,EACX,KAAK,SAAS,EAEd,KAAK,IAAI,EAIV,MAAM,aAAa,CAAC;AAIrB,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AA8EhD,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,EAC3B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,UAAU,EAGd,gBAAgB,EAAE,QAAQ,EAC1B,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,UAAU,GAAG,SAAS,GAC/B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAYvB;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,EAC3B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,UAAU,EAGd,gBAAgB,EAAE,QAAQ,EAC1B,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,UAAU,GAAG,SAAS,GAC/B,OAAO,CAAC,IAAI,CAAC,CAYf"}
1
+ {"version":3,"file":"rebase.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/db/rebase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AACtD,OAAO,KAAK,EAAC,KAAK,IAAI,QAAQ,EAAC,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AACrC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,KAAK,MAAM,EACX,KAAK,SAAS,EAEd,KAAK,IAAI,EAIV,MAAM,aAAa,CAAC;AAIrB,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B,MAAM,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;CAClC,CAAC;AA8GF,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,EAC3B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,UAAU,EAGd,gBAAgB,EAAE,QAAQ,EAC1B,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,UAAU,GAAG,SAAS,GAC/B,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAYrC;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,EAC3B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,UAAU,EAGd,gBAAgB,EAAE,QAAQ,EAC1B,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,UAAU,GAAG,SAAS,GAC/B,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAY7B"}
@@ -14,15 +14,37 @@ async function rebaseMutation(mutation, dagWrite, basisHash, mutators, lc, mutat
14
14
  const nextMutationID = await (await commitFromHash(basisHash, dagWrite)).getNextMutationID(mutationClientID, dagWrite);
15
15
  if (nextMutationID !== localMeta.mutationID) throw new Error(`Inconsistent mutation ID: original: ${localMeta.mutationID}, next: ${nextMutationID} - mutationClientID: ${mutationClientID} mutatorName: ${name}`);
16
16
  if (formatVersion >= 5) assertLocalMetaDD31(localMeta);
17
- const dbWrite = await newWriteLocal(basisHash, name, args, mutation.chunk.hash, dagWrite, localMeta.timestamp, mutationClientID, formatVersion);
18
- await mutatorImpl(new WriteTransactionImpl(mutationClientID, await dbWrite.getMutationID(), "rebase", zeroData, dbWrite, lc), args);
19
- return dbWrite;
17
+ const newWrite = () => newWriteLocal(basisHash, name, args, mutation.chunk.hash, dagWrite, localMeta.timestamp, mutationClientID, formatVersion);
18
+ const dbWrite = await newWrite();
19
+ const txData = zeroData?.fork();
20
+ const tx = new WriteTransactionImpl(mutationClientID, await dbWrite.getMutationID(), "rebase", txData, dbWrite, lc);
21
+ try {
22
+ await mutatorImpl(tx, args);
23
+ } catch (e) {
24
+ lc.info?.(`Rebase of mutator ${name} threw, abandoning its prediction`, e);
25
+ return {
26
+ result: await newWrite(),
27
+ zeroData
28
+ };
29
+ }
30
+ return {
31
+ result: dbWrite,
32
+ zeroData: txData
33
+ };
20
34
  }
21
35
  async function rebaseMutationAndPutCommit(mutation, dagWrite, basis, mutators, lc, mutationClientID, formatVersion, zeroData) {
22
- return (await rebaseMutation(mutation, dagWrite, basis, mutators, lc, mutationClientID, formatVersion, zeroData)).putCommit();
36
+ const { result: tx, zeroData: next } = await rebaseMutation(mutation, dagWrite, basis, mutators, lc, mutationClientID, formatVersion, zeroData);
37
+ return {
38
+ result: await tx.putCommit(),
39
+ zeroData: next
40
+ };
23
41
  }
24
42
  async function rebaseMutationAndCommit(mutation, dagWrite, basis, headName, mutators, lc, mutationClientID, formatVersion, zeroData) {
25
- return (await rebaseMutation(mutation, dagWrite, basis, mutators, lc, mutationClientID, formatVersion, zeroData)).commit(headName);
43
+ const { result: dbWrite, zeroData: next } = await rebaseMutation(mutation, dagWrite, basis, mutators, lc, mutationClientID, formatVersion, zeroData);
44
+ return {
45
+ result: await dbWrite.commit(headName),
46
+ zeroData: next
47
+ };
26
48
  }
27
49
  //#endregion
28
50
  export { rebaseMutationAndCommit, rebaseMutationAndPutCommit };
@@ -1 +1 @@
1
- {"version":3,"file":"rebase.js","names":[],"sources":["../../../../../replicache/src/db/rebase.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport type {Write as DagWrite} from '../dag/store.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport type {Hash} from '../hash.ts';\nimport type {ZeroTxData} from '../replicache-options.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport {WriteTransactionImpl} from '../transactions.ts';\nimport type {MutatorDefs} from '../types.ts';\nimport {\n type Commit,\n type LocalMeta,\n type LocalMetaDD31,\n type Meta,\n assertLocalMetaDD31,\n commitFromHash,\n isLocalMetaDD31,\n} from './commit.ts';\nimport type {Write} from './write.ts';\nimport {newWriteLocal} from './write.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nasync function rebaseMutation(\n mutation: Commit<LocalMetaDD31>,\n dagWrite: DagWrite,\n basisHash: Hash,\n mutators: MutatorDefs,\n lc: LogContext,\n mutationClientID: ClientID,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<Write> {\n const localMeta = mutation.meta;\n const name = localMeta.mutatorName;\n if (isLocalMetaDD31(localMeta)) {\n assert(\n localMeta.clientID === mutationClientID,\n 'mutationClientID must match clientID of LocalMeta',\n );\n }\n const maybeMutatorImpl = mutators[name];\n if (!maybeMutatorImpl) {\n // Developers must not remove mutator names from code deployed with the\n // same schemaVersion because Replicache needs to be able to replay\n // mutations during pull.\n //\n // If we detect that this has happened, stub in a no-op mutator so that at\n // least sync can move forward. Note that the server-side mutation will\n // still get sent. This doesn't remove the queued local mutation, it just\n // removes its visible effects.\n lc.error?.(`Cannot rebase unknown mutator ${name}`);\n }\n const mutatorImpl =\n maybeMutatorImpl ||\n (async () => {\n // no op\n });\n\n const args = localMeta.mutatorArgsJSON;\n\n const basisCommit = await commitFromHash(basisHash, dagWrite);\n const nextMutationID = await basisCommit.getNextMutationID(\n mutationClientID,\n dagWrite,\n );\n if (nextMutationID !== localMeta.mutationID) {\n throw new Error(\n `Inconsistent mutation ID: original: ${localMeta.mutationID}, next: ${nextMutationID} - mutationClientID: ${mutationClientID} mutatorName: ${name}`,\n );\n }\n\n if (formatVersion >= FormatVersion.DD31) {\n assertLocalMetaDD31(localMeta);\n }\n\n const dbWrite = await newWriteLocal(\n basisHash,\n name,\n args,\n mutation.chunk.hash,\n dagWrite,\n localMeta.timestamp,\n mutationClientID,\n formatVersion,\n );\n\n const tx = new WriteTransactionImpl(\n mutationClientID,\n await dbWrite.getMutationID(),\n 'rebase',\n zeroData,\n dbWrite,\n lc,\n );\n await mutatorImpl(tx, args);\n return dbWrite;\n}\n\nexport async function rebaseMutationAndPutCommit(\n mutation: Commit<LocalMeta>,\n dagWrite: DagWrite,\n basis: Hash,\n mutators: MutatorDefs,\n lc: LogContext,\n // TODO(greg): mutationClientID can be retrieved from mutation if LocalMeta\n // is a LocalMetaDD31. As part of DD31 cleanup we can remove this arg.\n mutationClientID: ClientID,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<Commit<Meta>> {\n const tx = await rebaseMutation(\n mutation,\n dagWrite,\n basis,\n mutators,\n lc,\n mutationClientID,\n formatVersion,\n zeroData,\n );\n return tx.putCommit();\n}\n\nexport async function rebaseMutationAndCommit(\n mutation: Commit<LocalMeta>,\n dagWrite: DagWrite,\n basis: Hash,\n headName: string,\n mutators: MutatorDefs,\n lc: LogContext,\n // TODO(greg): mutationClientID can be retrieved from mutation if LocalMeta\n // is a LocalMetaDD31. As part of DD31 cleanup we can remove this arg.\n mutationClientID: ClientID,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<Hash> {\n const dbWrite = await rebaseMutation(\n mutation,\n dagWrite,\n basis,\n mutators,\n lc,\n mutationClientID,\n formatVersion,\n zeroData,\n );\n return dbWrite.commit(headName);\n}\n"],"mappings":";;;;;AAwBA,eAAe,eACb,UACA,UACA,WACA,UACA,IACA,kBACA,eACA,UACgB;CAChB,MAAM,YAAY,SAAS;CAC3B,MAAM,OAAO,UAAU;CACvB,IAAI,gBAAgB,SAAS,GAC3B,OACE,UAAU,aAAa,kBACvB,mDACF;CAEF,MAAM,mBAAmB,SAAS;CAClC,IAAI,CAAC,kBASH,GAAG,QAAQ,iCAAiC,MAAM;CAEpD,MAAM,cACJ,qBACC,YAAY,CAEb;CAEF,MAAM,OAAO,UAAU;CAGvB,MAAM,iBAAiB,OAAM,MADH,eAAe,WAAW,QAAQ,GACnB,kBACvC,kBACA,QACF;CACA,IAAI,mBAAmB,UAAU,YAC/B,MAAM,IAAI,MACR,uCAAuC,UAAU,WAAW,UAAU,eAAe,uBAAuB,iBAAiB,gBAAgB,MAC/I;CAGF,IAAI,iBAAiB,GACnB,oBAAoB,SAAS;CAG/B,MAAM,UAAU,MAAM,cACpB,WACA,MACA,MACA,SAAS,MAAM,MACf,UACA,UAAU,WACV,kBACA,aACF;CAUA,MAAM,YAAY,IARH,qBACb,kBACA,MAAM,QAAQ,cAAc,GAC5B,UACA,UACA,SACA,EAEgB,GAAI,IAAI;CAC1B,OAAO;AACT;AAEA,eAAsB,2BACpB,UACA,UACA,OACA,UACA,IAGA,kBACA,eACA,UACuB;CAWvB,QAAO,MAVU,eACf,UACA,UACA,OACA,UACA,IACA,kBACA,eACA,QACF,GACU,UAAU;AACtB;AAEA,eAAsB,wBACpB,UACA,UACA,OACA,UACA,UACA,IAGA,kBACA,eACA,UACe;CAWf,QAAO,MAVe,eACpB,UACA,UACA,OACA,UACA,IACA,kBACA,eACA,QACF,GACe,OAAO,QAAQ;AAChC"}
1
+ {"version":3,"file":"rebase.js","names":[],"sources":["../../../../../replicache/src/db/rebase.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport type {Write as DagWrite} from '../dag/store.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport type {Hash} from '../hash.ts';\nimport type {ZeroTxData} from '../replicache-options.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport {WriteTransactionImpl} from '../transactions.ts';\nimport type {MutatorDefs} from '../types.ts';\nimport {\n type Commit,\n type LocalMeta,\n type LocalMetaDD31,\n type Meta,\n assertLocalMetaDD31,\n commitFromHash,\n isLocalMetaDD31,\n} from './commit.ts';\nimport type {Write} from './write.ts';\nimport {newWriteLocal} from './write.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\n/**\n * The result of replaying one mutation.\n *\n * `zeroData` is the Zero transaction data to use for the next mutation in the\n * same replay. Each mutation runs against its own fork. On success that fork is\n * returned, and if the mutator throws the caller gets back the one it passed\n * in, without the failed mutation's IVM writes. See `rebaseMutation`.\n */\nexport type RebaseResult<T> = {\n result: T;\n zeroData: ZeroTxData | undefined;\n};\n\nasync function rebaseMutation(\n mutation: Commit<LocalMetaDD31>,\n dagWrite: DagWrite,\n basisHash: Hash,\n mutators: MutatorDefs,\n lc: LogContext,\n mutationClientID: ClientID,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<RebaseResult<Write>> {\n const localMeta = mutation.meta;\n const name = localMeta.mutatorName;\n if (isLocalMetaDD31(localMeta)) {\n assert(\n localMeta.clientID === mutationClientID,\n 'mutationClientID must match clientID of LocalMeta',\n );\n }\n const maybeMutatorImpl = mutators[name];\n if (!maybeMutatorImpl) {\n // Developers must not remove mutator names from code deployed with the\n // same schemaVersion because Replicache needs to be able to replay\n // mutations during pull.\n //\n // If we detect that this has happened, stub in a no-op mutator so that at\n // least sync can move forward. Note that the server-side mutation will\n // still get sent. This doesn't remove the queued local mutation, it just\n // removes its visible effects.\n lc.error?.(`Cannot rebase unknown mutator ${name}`);\n }\n const mutatorImpl =\n maybeMutatorImpl ||\n (async () => {\n // no op\n });\n\n const args = localMeta.mutatorArgsJSON;\n\n const basisCommit = await commitFromHash(basisHash, dagWrite);\n const nextMutationID = await basisCommit.getNextMutationID(\n mutationClientID,\n dagWrite,\n );\n if (nextMutationID !== localMeta.mutationID) {\n throw new Error(\n `Inconsistent mutation ID: original: ${localMeta.mutationID}, next: ${nextMutationID} - mutationClientID: ${mutationClientID} mutatorName: ${name}`,\n );\n }\n\n if (formatVersion >= FormatVersion.DD31) {\n assertLocalMetaDD31(localMeta);\n }\n\n const newWrite = () =>\n newWriteLocal(\n basisHash,\n name,\n args,\n mutation.chunk.hash,\n dagWrite,\n localMeta.timestamp,\n mutationClientID,\n formatVersion,\n );\n\n const dbWrite = await newWrite();\n\n // Run the mutator against a fork so that if it throws, its IVM writes are\n // discarded along with its `Write`.\n const txData = zeroData?.fork();\n\n const tx = new WriteTransactionImpl(\n mutationClientID,\n await dbWrite.getMutationID(),\n 'rebase',\n txData,\n dbWrite,\n lc,\n );\n\n try {\n await mutatorImpl(tx, args);\n } catch (e) {\n // A mutator can throw here without anything being wrong: it is being run\n // again against a newer server snapshot, so a check that passed when the\n // user first ran it can fail now. Rethrowing fails the whole rebase, which\n // in Zero fails the poke and disconnects the client. The mutation stays\n // pending, so the next poke fails the same way.\n //\n // Only the local prediction is dropped. The mutation is still pending and\n // is still sent to the server, which decides whether it applied.\n //\n // `dbWrite` is dropped rather than closed: it shares `dagWrite` with the\n // caller. Dropping it is enough because a `Write` does not modify anything\n // outside itself and only writes chunks when it is committed.\n //\n // Engine errors raised inside the mutator are retired the same way, rather\n // than being sorted out here. A client whose state was garbage collected\n // throws ChunkNotFoundError from its reads, and that is detected on its\n // next mutation, where replicache-impl converts it to a\n // ClientStateNotFoundError and calls onClientStateNotFound.\n lc.info?.(`Rebase of mutator ${name} threw, abandoning its prediction`, e);\n return {result: await newWrite(), zeroData};\n }\n\n return {result: dbWrite, zeroData: txData};\n}\n\nexport async function rebaseMutationAndPutCommit(\n mutation: Commit<LocalMeta>,\n dagWrite: DagWrite,\n basis: Hash,\n mutators: MutatorDefs,\n lc: LogContext,\n // TODO(greg): mutationClientID can be retrieved from mutation if LocalMeta\n // is a LocalMetaDD31. As part of DD31 cleanup we can remove this arg.\n mutationClientID: ClientID,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<RebaseResult<Commit<Meta>>> {\n const {result: tx, zeroData: next} = await rebaseMutation(\n mutation,\n dagWrite,\n basis,\n mutators,\n lc,\n mutationClientID,\n formatVersion,\n zeroData,\n );\n return {result: await tx.putCommit(), zeroData: next};\n}\n\nexport async function rebaseMutationAndCommit(\n mutation: Commit<LocalMeta>,\n dagWrite: DagWrite,\n basis: Hash,\n headName: string,\n mutators: MutatorDefs,\n lc: LogContext,\n // TODO(greg): mutationClientID can be retrieved from mutation if LocalMeta\n // is a LocalMetaDD31. As part of DD31 cleanup we can remove this arg.\n mutationClientID: ClientID,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<RebaseResult<Hash>> {\n const {result: dbWrite, zeroData: next} = await rebaseMutation(\n mutation,\n dagWrite,\n basis,\n mutators,\n lc,\n mutationClientID,\n formatVersion,\n zeroData,\n );\n return {result: await dbWrite.commit(headName), zeroData: next};\n}\n"],"mappings":";;;;;AAqCA,eAAe,eACb,UACA,UACA,WACA,UACA,IACA,kBACA,eACA,UAC8B;CAC9B,MAAM,YAAY,SAAS;CAC3B,MAAM,OAAO,UAAU;CACvB,IAAI,gBAAgB,SAAS,GAC3B,OACE,UAAU,aAAa,kBACvB,mDACF;CAEF,MAAM,mBAAmB,SAAS;CAClC,IAAI,CAAC,kBASH,GAAG,QAAQ,iCAAiC,MAAM;CAEpD,MAAM,cACJ,qBACC,YAAY,CAEb;CAEF,MAAM,OAAO,UAAU;CAGvB,MAAM,iBAAiB,OAAM,MADH,eAAe,WAAW,QAAQ,GACnB,kBACvC,kBACA,QACF;CACA,IAAI,mBAAmB,UAAU,YAC/B,MAAM,IAAI,MACR,uCAAuC,UAAU,WAAW,UAAU,eAAe,uBAAuB,iBAAiB,gBAAgB,MAC/I;CAGF,IAAI,iBAAiB,GACnB,oBAAoB,SAAS;CAG/B,MAAM,iBACJ,cACE,WACA,MACA,MACA,SAAS,MAAM,MACf,UACA,UAAU,WACV,kBACA,aACF;CAEF,MAAM,UAAU,MAAM,SAAS;CAI/B,MAAM,SAAS,UAAU,KAAK;CAE9B,MAAM,KAAK,IAAI,qBACb,kBACA,MAAM,QAAQ,cAAc,GAC5B,UACA,QACA,SACA,EACF;CAEA,IAAI;EACF,MAAM,YAAY,IAAI,IAAI;CAC5B,SAAS,GAAG;EAmBV,GAAG,OAAO,qBAAqB,KAAK,oCAAoC,CAAC;EACzE,OAAO;GAAC,QAAQ,MAAM,SAAS;GAAG;EAAQ;CAC5C;CAEA,OAAO;EAAC,QAAQ;EAAS,UAAU;CAAM;AAC3C;AAEA,eAAsB,2BACpB,UACA,UACA,OACA,UACA,IAGA,kBACA,eACA,UACqC;CACrC,MAAM,EAAC,QAAQ,IAAI,UAAU,SAAQ,MAAM,eACzC,UACA,UACA,OACA,UACA,IACA,kBACA,eACA,QACF;CACA,OAAO;EAAC,QAAQ,MAAM,GAAG,UAAU;EAAG,UAAU;CAAI;AACtD;AAEA,eAAsB,wBACpB,UACA,UACA,OACA,UACA,UACA,IAGA,kBACA,eACA,UAC6B;CAC7B,MAAM,EAAC,QAAQ,SAAS,UAAU,SAAQ,MAAM,eAC9C,UACA,UACA,OACA,UACA,IACA,kBACA,eACA,QACF;CACA,OAAO;EAAC,QAAQ,MAAM,QAAQ,OAAO,QAAQ;EAAG,UAAU;CAAI;AAChE"}
@@ -1 +1 @@
1
- {"version":3,"file":"write.js","names":["#dagWrite","#basis","#meta","#clientID","#formatVersion","#generateDiffs"],"sources":["../../../../../replicache/src/db/write.ts"],"sourcesContent":["import '../process-env.ts';\nimport type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {diff} from '../btree/diff.ts';\nimport type {InternalDiff} from '../btree/node.ts';\nimport {BTreeRead, allEntriesAsDiff} from '../btree/read.ts';\nimport {BTreeWrite} from '../btree/write.ts';\nimport type {FrozenCookie} from '../cookies.ts';\nimport type {Write as DagWrite} from '../dag/store.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport type {FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, emptyHash} from '../hash.ts';\nimport type {DiffComputationConfig} from '../sync/diff.ts';\nimport {DiffsMap} from '../sync/diff.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport type {Commit} from './commit.ts';\nimport {\n type Meta as CommitMeta,\n type IndexRecord,\n type Meta,\n baseSnapshotHashFromHash,\n commitFromHash,\n newLocalDD31 as commitNewLocalDD31,\n newSnapshotDD31 as commitNewSnapshotDD31,\n getMutationID,\n} from './commit.ts';\nimport * as IndexOperation from './index-operation-enum.ts';\nimport type {IndexRead} from './index.ts';\nimport {IndexWrite, indexValue} from './index.ts';\nimport * as MetaType from './meta-type-enum.ts';\nimport {Read, readIndexesForRead} from './read.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport class Write extends Read {\n readonly #dagWrite: DagWrite;\n readonly #basis: Commit<CommitMeta> | undefined;\n readonly #meta: CommitMeta;\n\n declare map: BTreeWrite;\n\n declare readonly indexes: Map<string, IndexWrite>;\n readonly #clientID: ClientID;\n readonly #formatVersion: FormatVersion;\n\n constructor(\n dagWrite: DagWrite,\n map: BTreeWrite,\n basis: Commit<CommitMeta> | undefined,\n meta: CommitMeta,\n indexes: Map<string, IndexWrite>,\n clientID: ClientID,\n formatVersion: FormatVersion,\n ) {\n // TypeScript has trouble\n super(dagWrite, map, indexes);\n this.#dagWrite = dagWrite;\n this.#basis = basis;\n this.#meta = meta;\n this.#clientID = clientID;\n this.#formatVersion = formatVersion;\n\n // TODO(arv): if (DEBUG) { ...\n if (basis === undefined) {\n assert(\n meta.basisHash === emptyHash,\n 'Expected basisHash to be emptyHash when basis is undefined',\n );\n } else {\n assert(\n meta.basisHash === basis.chunk.hash,\n 'Expected meta.basisHash to equal basis.chunk.hash',\n );\n }\n }\n\n /**\n * The value needs to be frozen since it is kept in memory and used later for\n * comparison as well as returned in `get`.\n */\n async put(\n lc: LogContext,\n key: string,\n value: FrozenJSONValue,\n ): Promise<void> {\n if (!process.env.DISABLE_REPLICACHE_INDEXES && this.indexes.size > 0) {\n await updateIndexes(lc, this.map, this.indexes, key, value);\n }\n await this.map.put(key, value);\n }\n\n /**\n * Inserts or updates multiple entries in a single bulk operation. More\n * efficient than calling {@link put} in a loop because it uses\n * {@link BTreeWrite.putMany} to build or merge tree nodes in one pass.\n *\n * `entries` must be sorted in ascending key order.\n */\n async putMany(\n lc: LogContext,\n entries: ReadonlyArray<readonly [string, FrozenJSONValue]>,\n ): Promise<void> {\n if (entries.length === 0) {\n return;\n }\n\n // Update indexes for all entries\n if (!process.env.DISABLE_REPLICACHE_INDEXES && this.indexes.size > 0) {\n // TODO(arv): Indexes can use the optimizePatch pattern too.\n for (const [key, value] of entries) {\n await updateIndexes(lc, this.map, this.indexes, key, value);\n }\n }\n\n // BTreeWrite.putMany handles both empty and non-empty trees optimally\n await this.map.putMany(entries);\n }\n\n getMutationID(): Promise<number> {\n return getMutationID(this.#clientID, this.#dagWrite, this.#meta);\n }\n\n async del(lc: LogContext, key: string): Promise<boolean> {\n if (!process.env.DISABLE_REPLICACHE_INDEXES && this.indexes.size > 0) {\n await updateIndexes(lc, this.map, this.indexes, key, undefined);\n }\n return this.map.del(key);\n }\n\n async clear(): Promise<void> {\n await this.map.clear();\n const ps = [];\n for (const idx of this.indexes.values()) {\n ps.push(idx.clear());\n }\n await Promise.all(ps);\n }\n\n async putCommit(): Promise<Commit<CommitMeta>> {\n const valueHash = await this.map.flush();\n const indexRecords: IndexRecord[] = [];\n\n for (const index of this.indexes.values()) {\n const valueHash = await index.flush();\n const indexRecord: IndexRecord = {\n definition: index.meta.definition,\n valueHash,\n };\n indexRecords.push(indexRecord);\n }\n\n let commit: Commit<Meta>;\n const meta = this.#meta;\n switch (meta.type) {\n case MetaType.LocalDD31: {\n assert(\n this.#formatVersion >= FormatVersion.DD31,\n 'Expected formatVersion >= DD31 for LocalDD31 commit',\n );\n const {\n basisHash,\n mutationID,\n mutatorName,\n mutatorArgsJSON,\n originalHash,\n timestamp,\n } = meta;\n commit = commitNewLocalDD31(\n this.#dagWrite.createChunk,\n basisHash,\n await baseSnapshotHashFromHash(basisHash, this.#dagWrite),\n mutationID,\n mutatorName,\n mutatorArgsJSON,\n originalHash,\n valueHash,\n indexRecords,\n timestamp,\n this.#clientID,\n );\n break;\n }\n\n case MetaType.SnapshotDD31: {\n assert(\n this.#formatVersion > FormatVersion.DD31,\n 'Expected formatVersion > DD31 for SnapshotDD31 commit',\n );\n const {basisHash, lastMutationIDs, cookieJSON} = meta;\n commit = commitNewSnapshotDD31(\n this.#dagWrite.createChunk,\n basisHash,\n lastMutationIDs,\n cookieJSON,\n valueHash,\n indexRecords,\n );\n break;\n }\n }\n await this.#dagWrite.putChunk(commit.chunk);\n return commit;\n }\n\n // Return value is the hash of the new commit.\n async commit(headName: string): Promise<Hash> {\n const commit = await this.putCommit();\n const commitHash = commit.chunk.hash;\n await this.#dagWrite.setHead(headName, commitHash);\n await this.#dagWrite.commit();\n return commitHash;\n }\n\n async commitWithDiffs(\n headName: string,\n diffConfig: DiffComputationConfig,\n ): Promise<[Hash, DiffsMap]> {\n const commit = this.putCommit();\n const diffMap = await this.#generateDiffs(diffConfig);\n const commitHash = (await commit).chunk.hash;\n await this.#dagWrite.setHead(headName, commitHash);\n await this.#dagWrite.commit();\n return [commitHash, diffMap];\n }\n\n async #generateDiffs(diffConfig: DiffComputationConfig): Promise<DiffsMap> {\n const diffsMap = new DiffsMap();\n if (!diffConfig.shouldComputeDiffs()) {\n return diffsMap;\n }\n\n let valueDiff: InternalDiff = [];\n if (this.#basis) {\n const basisMap = new BTreeRead(\n this.#dagWrite,\n this.#formatVersion,\n this.#basis.valueHash,\n );\n valueDiff = await diff(basisMap, this.map);\n }\n diffsMap.set('', valueDiff);\n if (process.env.DISABLE_REPLICACHE_INDEXES) {\n return diffsMap;\n }\n let basisIndexes: Map<string, IndexRead>;\n if (this.#basis) {\n basisIndexes = readIndexesForRead(\n this.#basis,\n this.#dagWrite,\n this.#formatVersion,\n );\n } else {\n basisIndexes = new Map();\n }\n\n for (const [name, index] of this.indexes) {\n if (!diffConfig.shouldComputeDiffsForIndex(name)) {\n continue;\n }\n const basisIndex = basisIndexes.get(name);\n assert(index !== basisIndex, 'Expected index to differ from basisIndex');\n\n const indexDiffResult = await (basisIndex\n ? diff(basisIndex.map, index.map)\n : // No basis. All keys are new.\n allEntriesAsDiff(index.map, 'add'));\n diffsMap.set(name, indexDiffResult);\n }\n\n // Handle indexes in basisIndex but not in this.indexes. All keys are\n // deleted.\n for (const [name, basisIndex] of basisIndexes) {\n if (\n !this.indexes.has(name) &&\n diffConfig.shouldComputeDiffsForIndex(name)\n ) {\n const indexDiffResult = await allEntriesAsDiff(basisIndex.map, 'del');\n diffsMap.set(name, indexDiffResult);\n }\n }\n return diffsMap;\n }\n\n close(): void {\n this.#dagWrite.release();\n }\n}\n\nexport async function newWriteLocal(\n basisHash: Hash,\n mutatorName: string,\n mutatorArgsJSON: FrozenJSONValue,\n originalHash: Hash | null,\n dagWrite: DagWrite,\n timestamp: number,\n clientID: ClientID,\n formatVersion: FormatVersion,\n): Promise<Write> {\n const basis = await commitFromHash(basisHash, dagWrite);\n const bTreeWrite = new BTreeWrite(dagWrite, formatVersion, basis.valueHash);\n const mutationID = await basis.getNextMutationID(clientID, dagWrite);\n const indexes = readIndexesForWrite(basis, dagWrite, formatVersion);\n assert(\n formatVersion >= FormatVersion.DD31,\n 'Expected formatVersion >= DD31 for newWriteLocal',\n );\n return new Write(\n dagWrite,\n bTreeWrite,\n basis,\n\n {\n type: MetaType.LocalDD31,\n basisHash,\n baseSnapshotHash: await baseSnapshotHashFromHash(basisHash, dagWrite),\n mutatorName,\n mutatorArgsJSON,\n mutationID,\n originalHash,\n timestamp,\n clientID,\n },\n indexes,\n clientID,\n formatVersion,\n );\n}\n\nexport async function newWriteSnapshotDD31(\n basisHash: Hash,\n lastMutationIDs: Record<ClientID, number>,\n cookieJSON: FrozenCookie,\n dagWrite: DagWrite,\n clientID: ClientID,\n formatVersion: FormatVersion,\n): Promise<Write> {\n const basis = await commitFromHash(basisHash, dagWrite);\n const bTreeWrite = new BTreeWrite(dagWrite, formatVersion, basis.valueHash);\n return new Write(\n dagWrite,\n bTreeWrite,\n basis,\n {basisHash, type: MetaType.SnapshotDD31, lastMutationIDs, cookieJSON},\n readIndexesForWrite(basis, dagWrite, formatVersion),\n clientID,\n formatVersion,\n );\n}\n\nexport async function updateIndexes(\n lc: LogContext,\n map: BTreeRead,\n indexes: Map<string, IndexWrite>,\n key: string,\n newVal: FrozenJSONValue | undefined,\n): Promise<void> {\n let oldValPromise: Promise<FrozenJSONValue | undefined> | undefined;\n\n const ps: Promise<void>[] = [];\n for (const idx of indexes.values()) {\n const {keyPrefix} = idx.meta.definition;\n if (!keyPrefix || key.startsWith(keyPrefix)) {\n const oldVal = await (oldValPromise ??= map.get(key));\n if (oldVal !== undefined) {\n ps.push(\n indexValue(\n lc,\n idx.map,\n IndexOperation.Remove,\n key,\n oldVal,\n idx.meta.definition.jsonPointer,\n idx.meta.definition.allowEmpty ?? false,\n ),\n );\n }\n if (newVal !== undefined) {\n ps.push(\n indexValue(\n lc,\n idx.map,\n IndexOperation.Add,\n key,\n newVal,\n idx.meta.definition.jsonPointer,\n idx.meta.definition.allowEmpty ?? false,\n ),\n );\n }\n }\n }\n await Promise.all(ps);\n}\n\nexport function readIndexesForWrite(\n commit: Commit<CommitMeta>,\n dagWrite: DagWrite,\n formatVersion: FormatVersion,\n): Map<string, IndexWrite> {\n const m: Map<string, IndexWrite> = new Map();\n if (process.env.DISABLE_REPLICACHE_INDEXES) {\n return m;\n }\n for (const index of commit.indexes) {\n m.set(\n index.definition.name,\n new IndexWrite(\n index,\n new BTreeWrite(dagWrite, formatVersion, index.valueHash),\n ),\n );\n }\n return m;\n}\n\nexport async function createIndexBTree(\n lc: LogContext,\n dagWrite: DagWrite,\n valueMap: BTreeRead,\n prefix: string,\n jsonPointer: string,\n allowEmpty: boolean,\n formatVersion: FormatVersion,\n): Promise<BTreeWrite> {\n const indexMap = new BTreeWrite(dagWrite, formatVersion);\n for await (const entry of valueMap.scan(prefix)) {\n const key = entry[0];\n if (!key.startsWith(prefix)) {\n break;\n }\n await indexValue(\n lc,\n indexMap,\n IndexOperation.Add,\n key,\n entry[1],\n jsonPointer,\n allowEmpty,\n );\n }\n return indexMap;\n}\n"],"mappings":";;;;;;;;;AAmCA,IAAa,QAAb,cAA2B,KAAK;CAC9B;CACA;CACA;CAKA;CACA;CAEA,YACE,UACA,KACA,OACA,MACA,SACA,UACA,eACA;EAEA,MAAM,UAAU,KAAK,OAAO;EAC5B,KAAKA,YAAY;EACjB,KAAKC,SAAS;EACd,KAAKC,QAAQ;EACb,KAAKC,YAAY;EACjB,KAAKC,iBAAiB;EAGtB,IAAI,UAAU,KAAA,GACZ,OACE,KAAK,cAAc,WACnB,4DACF;OAEA,OACE,KAAK,cAAc,MAAM,MAAM,MAC/B,mDACF;CAEJ;;;;;CAMA,MAAM,IACJ,IACA,KACA,OACe;EAIf,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK;CAC/B;;;;;;;;CASA,MAAM,QACJ,IACA,SACe;EACf,IAAI,QAAQ,WAAW,GACrB;EAYF,MAAM,KAAK,IAAI,QAAQ,OAAO;CAChC;CAEA,gBAAiC;EAC/B,OAAO,cAAc,KAAKD,WAAW,KAAKH,WAAW,KAAKE,KAAK;CACjE;CAEA,MAAM,IAAI,IAAgB,KAA+B;EAIvD,OAAO,KAAK,IAAI,IAAI,GAAG;CACzB;CAEA,MAAM,QAAuB;EAC3B,MAAM,KAAK,IAAI,MAAM;EACrB,MAAM,KAAK,CAAC;EACZ,KAAK,MAAM,OAAO,KAAK,QAAQ,OAAO,GACpC,GAAG,KAAK,IAAI,MAAM,CAAC;EAErB,MAAM,QAAQ,IAAI,EAAE;CACtB;CAEA,MAAM,YAAyC;EAC7C,MAAM,YAAY,MAAM,KAAK,IAAI,MAAM;EACvC,MAAM,eAA8B,CAAC;EAErC,KAAK,MAAM,SAAS,KAAK,QAAQ,OAAO,GAAG;GACzC,MAAM,YAAY,MAAM,MAAM,MAAM;GACpC,MAAM,cAA2B;IAC/B,YAAY,MAAM,KAAK;IACvB;GACF;GACA,aAAa,KAAK,WAAW;EAC/B;EAEA,IAAI;EACJ,MAAM,OAAO,KAAKA;EAClB,QAAQ,KAAK,MAAb;GACE,KAAK,GAAoB;IACvB,OACE,KAAKE,kBAAkB,GACvB,qDACF;IACA,MAAM,EACJ,WACA,YACA,aACA,iBACA,cACA,cACE;IACJ,SAAS,aACP,KAAKJ,UAAU,aACf,WACA,MAAM,yBAAyB,WAAW,KAAKA,SAAS,GACxD,YACA,aACA,iBACA,cACA,WACA,cACA,WACA,KAAKG,SACP;IACA;GACF;GAEA,KAAK,GAAuB;IAC1B,OACE,KAAKC,iBAAiB,GACtB,uDACF;IACA,MAAM,EAAC,WAAW,iBAAiB,eAAc;IACjD,SAAS,gBACP,KAAKJ,UAAU,aACf,WACA,iBACA,YACA,WACA,YACF;IACA;GACF;EACF;EACA,MAAM,KAAKA,UAAU,SAAS,OAAO,KAAK;EAC1C,OAAO;CACT;CAGA,MAAM,OAAO,UAAiC;EAE5C,MAAM,cAAa,MADE,KAAK,UAAU,GACV,MAAM;EAChC,MAAM,KAAKA,UAAU,QAAQ,UAAU,UAAU;EACjD,MAAM,KAAKA,UAAU,OAAO;EAC5B,OAAO;CACT;CAEA,MAAM,gBACJ,UACA,YAC2B;EAC3B,MAAM,SAAS,KAAK,UAAU;EAC9B,MAAM,UAAU,MAAM,KAAKK,eAAe,UAAU;EACpD,MAAM,cAAc,MAAM,QAAQ,MAAM;EACxC,MAAM,KAAKL,UAAU,QAAQ,UAAU,UAAU;EACjD,MAAM,KAAKA,UAAU,OAAO;EAC5B,OAAO,CAAC,YAAY,OAAO;CAC7B;CAEA,MAAMK,eAAe,YAAsD;EACzE,MAAM,WAAW,IAAI,SAAS;EAC9B,IAAI,CAAC,WAAW,mBAAmB,GACjC,OAAO;EAGT,IAAI,YAA0B,CAAC;EAC/B,IAAI,KAAKJ,QAMP,YAAY,MAAM,KAAK,IALF,UACnB,KAAKD,WACL,KAAKI,gBACL,KAAKH,OAAO,SAES,GAAU,KAAK,GAAG;EAE3C,SAAS,IAAI,IAAI,SAAS;EAExB,OAAO;CAuCX;CAEA,QAAc;EACZ,KAAKD,UAAU,QAAQ;CACzB;AACF;AAEA,eAAsB,cACpB,WACA,aACA,iBACA,cACA,UACA,WACA,UACA,eACgB;CAChB,MAAM,QAAQ,MAAM,eAAe,WAAW,QAAQ;CACtD,MAAM,aAAa,IAAI,WAAW,UAAU,eAAe,MAAM,SAAS;CAC1E,MAAM,aAAa,MAAM,MAAM,kBAAkB,UAAU,QAAQ;CACnE,MAAM,UAAU,oBAAoB,OAAO,UAAU,aAAa;CAClE,OACE,iBAAiB,GACjB,kDACF;CACA,OAAO,IAAI,MACT,UACA,YACA,OAEA;EACE,MAAM;EACN;EACA,kBAAkB,MAAM,yBAAyB,WAAW,QAAQ;EACpE;EACA;EACA;EACA;EACA;EACA;CACF,GACA,SACA,UACA,aACF;AACF;AAEA,eAAsB,qBACpB,WACA,iBACA,YACA,UACA,UACA,eACgB;CAChB,MAAM,QAAQ,MAAM,eAAe,WAAW,QAAQ;CAEtD,OAAO,IAAI,MACT,UACA,IAHqB,WAAW,UAAU,eAAe,MAAM,SAG/D,GACA,OACA;EAAC;EAAW,MAAM;EAAuB;EAAiB;CAAU,GACpE,oBAAoB,OAAO,UAAU,aAAa,GAClD,UACA,aACF;AACF;AA+CA,SAAgB,oBACd,QACA,UACA,eACyB;CAGvB,uBAAO,IAF8B,IAE9B;AAYX"}
1
+ {"version":3,"file":"write.js","names":["#dagWrite","#basis","#meta","#clientID","#formatVersion","#generateDiffs"],"sources":["../../../../../replicache/src/db/write.ts"],"sourcesContent":["import '../process-env.ts';\nimport type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {diff} from '../btree/diff.ts';\nimport type {InternalDiff} from '../btree/node.ts';\nimport {BTreeRead, allEntriesAsDiff} from '../btree/read.ts';\nimport {BTreeWrite} from '../btree/write.ts';\nimport type {FrozenCookie} from '../cookies.ts';\nimport type {Write as DagWrite} from '../dag/store.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport type {FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, emptyHash} from '../hash.ts';\nimport type {DiffComputationConfig} from '../sync/diff.ts';\nimport {DiffsMap} from '../sync/diff.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport type {Commit} from './commit.ts';\nimport {\n type Meta as CommitMeta,\n type IndexRecord,\n type Meta,\n baseSnapshotHashFromHash,\n commitFromHash,\n newLocalDD31 as commitNewLocalDD31,\n newSnapshotDD31 as commitNewSnapshotDD31,\n getMutationID,\n} from './commit.ts';\nimport * as IndexOperation from './index-operation-enum.ts';\nimport type {IndexRead} from './index.ts';\nimport {IndexWrite, indexValue} from './index.ts';\nimport * as MetaType from './meta-type-enum.ts';\nimport {Read, readIndexesForRead} from './read.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport class Write extends Read {\n readonly #dagWrite: DagWrite;\n readonly #basis: Commit<CommitMeta> | undefined;\n readonly #meta: CommitMeta;\n\n declare map: BTreeWrite;\n\n declare readonly indexes: Map<string, IndexWrite>;\n readonly #clientID: ClientID;\n readonly #formatVersion: FormatVersion;\n\n constructor(\n dagWrite: DagWrite,\n map: BTreeWrite,\n basis: Commit<CommitMeta> | undefined,\n meta: CommitMeta,\n indexes: Map<string, IndexWrite>,\n clientID: ClientID,\n formatVersion: FormatVersion,\n ) {\n // TypeScript has trouble\n super(dagWrite, map, indexes);\n this.#dagWrite = dagWrite;\n this.#basis = basis;\n this.#meta = meta;\n this.#clientID = clientID;\n this.#formatVersion = formatVersion;\n\n // TODO(arv): if (DEBUG) { ...\n if (basis === undefined) {\n assert(\n meta.basisHash === emptyHash,\n 'Expected basisHash to be emptyHash when basis is undefined',\n );\n } else {\n assert(\n meta.basisHash === basis.chunk.hash,\n 'Expected meta.basisHash to equal basis.chunk.hash',\n );\n }\n }\n\n /**\n * The value needs to be frozen since it is kept in memory and used later for\n * comparison as well as returned in `get`.\n */\n async put(\n lc: LogContext,\n key: string,\n value: FrozenJSONValue,\n ): Promise<void> {\n if (!process.env.DISABLE_REPLICACHE_INDEXES && this.indexes.size > 0) {\n await updateIndexes(lc, this.map, this.indexes, key, value);\n }\n await this.map.put(key, value);\n }\n\n /**\n * Inserts or updates multiple entries in a single bulk operation. More\n * efficient than calling {@link put} in a loop because it uses\n * {@link BTreeWrite.putMany} to build or merge tree nodes in one pass.\n *\n * `entries` must be sorted in ascending key order.\n */\n async putMany(\n lc: LogContext,\n entries: ReadonlyArray<readonly [string, FrozenJSONValue]>,\n ): Promise<void> {\n if (entries.length === 0) {\n return;\n }\n\n // Update indexes for all entries\n if (!process.env.DISABLE_REPLICACHE_INDEXES && this.indexes.size > 0) {\n // TODO(arv): Indexes can use the optimizePatch pattern too.\n for (const [key, value] of entries) {\n await updateIndexes(lc, this.map, this.indexes, key, value);\n }\n }\n\n // BTreeWrite.putMany handles both empty and non-empty trees optimally\n await this.map.putMany(entries);\n }\n\n getMutationID(): Promise<number> {\n return getMutationID(this.#clientID, this.#dagWrite, this.#meta);\n }\n\n async del(lc: LogContext, key: string): Promise<boolean> {\n if (!process.env.DISABLE_REPLICACHE_INDEXES && this.indexes.size > 0) {\n await updateIndexes(lc, this.map, this.indexes, key, undefined);\n }\n return this.map.del(key);\n }\n\n async clear(): Promise<void> {\n await this.map.clear();\n const ps = [];\n for (const idx of this.indexes.values()) {\n ps.push(idx.clear());\n }\n await Promise.all(ps);\n }\n\n async putCommit(): Promise<Commit<CommitMeta>> {\n const valueHash = await this.map.flush();\n const indexRecords: IndexRecord[] = [];\n\n for (const index of this.indexes.values()) {\n const valueHash = await index.flush();\n const indexRecord: IndexRecord = {\n definition: index.meta.definition,\n valueHash,\n };\n indexRecords.push(indexRecord);\n }\n\n let commit: Commit<Meta>;\n const meta = this.#meta;\n switch (meta.type) {\n case MetaType.LocalDD31: {\n assert(\n this.#formatVersion >= FormatVersion.DD31,\n 'Expected formatVersion >= DD31 for LocalDD31 commit',\n );\n const {\n basisHash,\n mutationID,\n mutatorName,\n mutatorArgsJSON,\n originalHash,\n timestamp,\n } = meta;\n commit = commitNewLocalDD31(\n this.#dagWrite.createChunk,\n basisHash,\n await baseSnapshotHashFromHash(basisHash, this.#dagWrite),\n mutationID,\n mutatorName,\n mutatorArgsJSON,\n originalHash,\n valueHash,\n indexRecords,\n timestamp,\n this.#clientID,\n );\n break;\n }\n\n case MetaType.SnapshotDD31: {\n assert(\n this.#formatVersion > FormatVersion.DD31,\n 'Expected formatVersion > DD31 for SnapshotDD31 commit',\n );\n const {basisHash, lastMutationIDs, cookieJSON} = meta;\n commit = commitNewSnapshotDD31(\n this.#dagWrite.createChunk,\n basisHash,\n lastMutationIDs,\n cookieJSON,\n valueHash,\n indexRecords,\n );\n break;\n }\n }\n await this.#dagWrite.putChunk(commit.chunk);\n return commit;\n }\n\n // Return value is the hash of the new commit.\n async commit(headName: string): Promise<Hash> {\n const commit = await this.putCommit();\n const commitHash = commit.chunk.hash;\n await this.#dagWrite.setHead(headName, commitHash);\n await this.#dagWrite.commit();\n return commitHash;\n }\n\n async commitWithDiffs(\n headName: string,\n diffConfig: DiffComputationConfig,\n ): Promise<[Hash, DiffsMap]> {\n const commit = this.putCommit();\n const diffMap = await this.#generateDiffs(diffConfig);\n const commitHash = (await commit).chunk.hash;\n await this.#dagWrite.setHead(headName, commitHash);\n await this.#dagWrite.commit();\n return [commitHash, diffMap];\n }\n\n async #generateDiffs(diffConfig: DiffComputationConfig): Promise<DiffsMap> {\n const diffsMap = new DiffsMap();\n if (!diffConfig.shouldComputeDiffs()) {\n return diffsMap;\n }\n\n let valueDiff: InternalDiff = [];\n if (this.#basis) {\n const basisMap = new BTreeRead(\n this.#dagWrite,\n this.#formatVersion,\n this.#basis.valueHash,\n );\n valueDiff = await diff(basisMap, this.map);\n }\n diffsMap.set('', valueDiff);\n if (process.env.DISABLE_REPLICACHE_INDEXES) {\n return diffsMap;\n }\n let basisIndexes: Map<string, IndexRead>;\n if (this.#basis) {\n basisIndexes = readIndexesForRead(\n this.#basis,\n this.#dagWrite,\n this.#formatVersion,\n );\n } else {\n basisIndexes = new Map();\n }\n\n for (const [name, index] of this.indexes) {\n if (!diffConfig.shouldComputeDiffsForIndex(name)) {\n continue;\n }\n const basisIndex = basisIndexes.get(name);\n assert(index !== basisIndex, 'Expected index to differ from basisIndex');\n\n const indexDiffResult = await (basisIndex\n ? diff(basisIndex.map, index.map)\n : // No basis. All keys are new.\n allEntriesAsDiff(index.map, 'add'));\n diffsMap.set(name, indexDiffResult);\n }\n\n // Handle indexes in basisIndex but not in this.indexes. All keys are\n // deleted.\n for (const [name, basisIndex] of basisIndexes) {\n if (\n !this.indexes.has(name) &&\n diffConfig.shouldComputeDiffsForIndex(name)\n ) {\n const indexDiffResult = await allEntriesAsDiff(basisIndex.map, 'del');\n diffsMap.set(name, indexDiffResult);\n }\n }\n return diffsMap;\n }\n\n close(): void {\n this.#dagWrite.release();\n }\n}\n\nexport async function newWriteLocal(\n basisHash: Hash,\n mutatorName: string,\n mutatorArgsJSON: FrozenJSONValue,\n originalHash: Hash | null,\n dagWrite: DagWrite,\n timestamp: number,\n clientID: ClientID,\n formatVersion: FormatVersion,\n): Promise<Write> {\n const basis = await commitFromHash(basisHash, dagWrite);\n const bTreeWrite = new BTreeWrite(dagWrite, formatVersion, basis.valueHash);\n const mutationID = await basis.getNextMutationID(clientID, dagWrite);\n const indexes = readIndexesForWrite(basis, dagWrite, formatVersion);\n assert(\n formatVersion >= FormatVersion.DD31,\n 'Expected formatVersion >= DD31 for newWriteLocal',\n );\n return new Write(\n dagWrite,\n bTreeWrite,\n basis,\n\n {\n type: MetaType.LocalDD31,\n basisHash,\n baseSnapshotHash: await baseSnapshotHashFromHash(basisHash, dagWrite),\n mutatorName,\n mutatorArgsJSON,\n mutationID,\n originalHash,\n timestamp,\n clientID,\n },\n indexes,\n clientID,\n formatVersion,\n );\n}\n\nexport async function newWriteSnapshotDD31(\n basisHash: Hash,\n lastMutationIDs: Record<ClientID, number>,\n cookieJSON: FrozenCookie,\n dagWrite: DagWrite,\n clientID: ClientID,\n formatVersion: FormatVersion,\n): Promise<Write> {\n const basis = await commitFromHash(basisHash, dagWrite);\n const bTreeWrite = new BTreeWrite(dagWrite, formatVersion, basis.valueHash);\n return new Write(\n dagWrite,\n bTreeWrite,\n basis,\n {basisHash, type: MetaType.SnapshotDD31, lastMutationIDs, cookieJSON},\n readIndexesForWrite(basis, dagWrite, formatVersion),\n clientID,\n formatVersion,\n );\n}\n\nexport async function updateIndexes(\n lc: LogContext,\n map: BTreeRead,\n indexes: Map<string, IndexWrite>,\n key: string,\n newVal: FrozenJSONValue | undefined,\n): Promise<void> {\n let oldValPromise: Promise<FrozenJSONValue | undefined> | undefined;\n\n const ps: Promise<void>[] = [];\n for (const idx of indexes.values()) {\n const {keyPrefix} = idx.meta.definition;\n if (!keyPrefix || key.startsWith(keyPrefix)) {\n const oldVal = await (oldValPromise ??= map.get(key));\n if (oldVal !== undefined) {\n ps.push(\n indexValue(\n lc,\n idx.map,\n IndexOperation.Remove,\n key,\n oldVal,\n idx.meta.definition.jsonPointer,\n idx.meta.definition.allowEmpty ?? false,\n ),\n );\n }\n if (newVal !== undefined) {\n ps.push(\n indexValue(\n lc,\n idx.map,\n IndexOperation.Add,\n key,\n newVal,\n idx.meta.definition.jsonPointer,\n idx.meta.definition.allowEmpty ?? false,\n ),\n );\n }\n }\n }\n await Promise.all(ps);\n}\n\nexport function readIndexesForWrite(\n commit: Commit<CommitMeta>,\n dagWrite: DagWrite,\n formatVersion: FormatVersion,\n): Map<string, IndexWrite> {\n const m: Map<string, IndexWrite> = new Map();\n if (process.env.DISABLE_REPLICACHE_INDEXES) {\n return m;\n }\n for (const index of commit.indexes) {\n m.set(\n index.definition.name,\n new IndexWrite(\n index,\n new BTreeWrite(dagWrite, formatVersion, index.valueHash),\n ),\n );\n }\n return m;\n}\n\nexport async function createIndexBTree(\n lc: LogContext,\n dagWrite: DagWrite,\n valueMap: BTreeRead,\n prefix: string,\n jsonPointer: string,\n allowEmpty: boolean,\n formatVersion: FormatVersion,\n): Promise<BTreeWrite> {\n const indexMap = new BTreeWrite(dagWrite, formatVersion);\n for await (const entry of valueMap.scan(prefix, false)) {\n const key = entry[0];\n if (!key.startsWith(prefix)) {\n break;\n }\n await indexValue(\n lc,\n indexMap,\n IndexOperation.Add,\n key,\n entry[1],\n jsonPointer,\n allowEmpty,\n );\n }\n return indexMap;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AAExD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,CAAC,EAAE,sBAAsB,GAC5B,aAAa,CAMf"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AAExD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,CAAC,EAAE,sBAAsB,GAC5B,aAAa,CAMf"}
@@ -1,4 +1,5 @@
1
1
  import { SQLiteStore, dropStore } from "../sqlite-store.js";
2
+ import { Lock } from "@rocicorp/lock";
2
3
  import { deleteDatabaseSync, openDatabaseSync } from "expo-sqlite";
3
4
  //#region ../replicache/src/kv/expo-sqlite/store.ts
4
5
  function dropExpoSQLiteStore(name, opts) {
@@ -15,16 +16,38 @@ function expoSQLiteStoreProvider(opts) {
15
16
  drop: (name) => dropExpoSQLiteStore(name, opts)
16
17
  };
17
18
  }
19
+ /**
20
+ * An expo-sqlite prepared statement is a stateful cursor over a single
21
+ * `sqlite3_stmt`: `executeForRawResultAsync` resets, rebinds and steps the
22
+ * first row, and the returned result's `getAllAsync` then steps the *remaining*
23
+ * rows of whatever the statement is currently bound to. Both are separate
24
+ * native round trips.
25
+ *
26
+ * `SQLiteStore` shares one prepared statement per SQL across all concurrent
27
+ * readers, so without serialization reader B's `executeForRawResultAsync` can
28
+ * rebind the statement inside reader A's await gap, and A's `getAllAsync` then
29
+ * returns B's rows (and B is starved of them). That yields wrong or missing
30
+ * values and, when a ref count delta is computed from such a read and written,
31
+ * a persistently corrupted store.
32
+ *
33
+ * Every call on a statement is therefore run under a per-statement lock so the
34
+ * execute + fetch pair is atomic with respect to other users of that statement.
35
+ */
18
36
  var ExpoSQLitePreparedStatement = class {
19
37
  #statement;
38
+ #lock = new Lock();
20
39
  constructor(statement) {
21
40
  this.#statement = statement;
22
41
  }
23
- async exec(params) {
24
- await this.#statement.executeForRawResultAsync(params);
42
+ exec(params) {
43
+ return this.#lock.withLock(async () => {
44
+ await this.#statement.executeForRawResultAsync(params);
45
+ });
25
46
  }
26
- async all(params) {
27
- return (await this.#statement.executeForRawResultAsync(params)).getAllAsync();
47
+ all(params) {
48
+ return this.#lock.withLock(async () => {
49
+ return await (await this.#statement.executeForRawResultAsync(params)).getAllAsync();
50
+ });
28
51
  }
29
52
  };
30
53
  var ExpoSQLiteDatabase = class {
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":["#statement","#db","#filename","#statements"],"sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"sourcesContent":["import {\n deleteDatabaseSync,\n openDatabaseSync,\n type SQLiteDatabase as DB,\n type SQLiteStatement,\n} from 'expo-sqlite';\nimport type {\n PreparedStatement,\n SQLiteDatabase,\n SQLiteStoreOptions,\n} from '../sqlite-store.ts';\nimport {dropStore, SQLiteStore} from '../sqlite-store.ts';\nimport type {StoreProvider} from '../store.ts';\n\nexport type ExpoSQLiteStoreOptions = SQLiteStoreOptions;\n\nexport function dropExpoSQLiteStore(\n name: string,\n opts?: ExpoSQLiteStoreOptions,\n): Promise<void> {\n return dropStore(name, filename => new ExpoSQLiteDatabase(filename), opts);\n}\n\n/**\n * Creates a StoreProvider for SQLite-based stores using expo-sqlite.\n * Supports shared connections between multiple store instances with the same name,\n * providing efficient resource utilization and proper transaction isolation.\n */\nexport function expoSQLiteStoreProvider(\n opts?: ExpoSQLiteStoreOptions,\n): StoreProvider {\n return {\n create: name =>\n new SQLiteStore(name, name => new ExpoSQLiteDatabase(name), opts),\n drop: name => dropExpoSQLiteStore(name, opts),\n };\n}\n\nclass ExpoSQLitePreparedStatement implements PreparedStatement {\n readonly #statement: SQLiteStatement;\n\n constructor(statement: SQLiteStatement) {\n this.#statement = statement;\n }\n\n async exec(params: string[]): Promise<void> {\n await this.#statement.executeForRawResultAsync(params);\n }\n\n async all(params: string[]): Promise<unknown[][]> {\n const result = await this.#statement.executeForRawResultAsync(params);\n return result.getAllAsync() as Promise<unknown[][]>;\n }\n}\n\nclass ExpoSQLiteDatabase implements SQLiteDatabase {\n readonly #db: DB;\n readonly #filename: string;\n readonly #statements: Set<SQLiteStatement> = new Set();\n\n constructor(filename: string) {\n this.#filename = filename;\n this.#db = openDatabaseSync(filename);\n }\n\n close(): void {\n for (const stmt of this.#statements) {\n stmt.finalizeSync();\n }\n this.#db.closeSync();\n }\n\n destroy(): void {\n deleteDatabaseSync(this.#filename);\n }\n\n prepare(sql: string): PreparedStatement {\n const statement = this.#db.prepareSync(sql);\n this.#statements.add(statement);\n return new ExpoSQLitePreparedStatement(statement);\n }\n\n execSync(sql: string): void {\n this.#db.execSync(sql);\n }\n}\n"],"mappings":";;;AAgBA,SAAgB,oBACd,MACA,MACe;CACf,OAAO,UAAU,OAAM,aAAY,IAAI,mBAAmB,QAAQ,GAAG,IAAI;AAC3E;;;;;;AAOA,SAAgB,wBACd,MACe;CACf,OAAO;EACL,SAAQ,SACN,IAAI,YAAY,OAAM,SAAQ,IAAI,mBAAmB,IAAI,GAAG,IAAI;EAClE,OAAM,SAAQ,oBAAoB,MAAM,IAAI;CAC9C;AACF;AAEA,IAAM,8BAAN,MAA+D;CAC7D;CAEA,YAAY,WAA4B;EACtC,KAAKA,aAAa;CACpB;CAEA,MAAM,KAAK,QAAiC;EAC1C,MAAM,KAAKA,WAAW,yBAAyB,MAAM;CACvD;CAEA,MAAM,IAAI,QAAwC;EAEhD,QAAO,MADc,KAAKA,WAAW,yBAAyB,MAAM,GACtD,YAAY;CAC5B;AACF;AAEA,IAAM,qBAAN,MAAmD;CACjD;CACA;CACA,8BAA6C,IAAI,IAAI;CAErD,YAAY,UAAkB;EAC5B,KAAKE,YAAY;EACjB,KAAKD,MAAM,iBAAiB,QAAQ;CACtC;CAEA,QAAc;EACZ,KAAK,MAAM,QAAQ,KAAKE,aACtB,KAAK,aAAa;EAEpB,KAAKF,IAAI,UAAU;CACrB;CAEA,UAAgB;EACd,mBAAmB,KAAKC,SAAS;CACnC;CAEA,QAAQ,KAAgC;EACtC,MAAM,YAAY,KAAKD,IAAI,YAAY,GAAG;EAC1C,KAAKE,YAAY,IAAI,SAAS;EAC9B,OAAO,IAAI,4BAA4B,SAAS;CAClD;CAEA,SAAS,KAAmB;EAC1B,KAAKF,IAAI,SAAS,GAAG;CACvB;AACF"}
1
+ {"version":3,"file":"store.js","names":["#statement","#lock","#db","#filename","#statements"],"sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport {\n deleteDatabaseSync,\n openDatabaseSync,\n type SQLiteDatabase as DB,\n type SQLiteStatement,\n} from 'expo-sqlite';\nimport type {\n PreparedStatement,\n SQLiteDatabase,\n SQLiteStoreOptions,\n} from '../sqlite-store.ts';\nimport {dropStore, SQLiteStore} from '../sqlite-store.ts';\nimport type {StoreProvider} from '../store.ts';\n\nexport type ExpoSQLiteStoreOptions = SQLiteStoreOptions;\n\nexport function dropExpoSQLiteStore(\n name: string,\n opts?: ExpoSQLiteStoreOptions,\n): Promise<void> {\n return dropStore(name, filename => new ExpoSQLiteDatabase(filename), opts);\n}\n\n/**\n * Creates a StoreProvider for SQLite-based stores using expo-sqlite.\n * Supports shared connections between multiple store instances with the same name,\n * providing efficient resource utilization and proper transaction isolation.\n */\nexport function expoSQLiteStoreProvider(\n opts?: ExpoSQLiteStoreOptions,\n): StoreProvider {\n return {\n create: name =>\n new SQLiteStore(name, name => new ExpoSQLiteDatabase(name), opts),\n drop: name => dropExpoSQLiteStore(name, opts),\n };\n}\n\n/**\n * An expo-sqlite prepared statement is a stateful cursor over a single\n * `sqlite3_stmt`: `executeForRawResultAsync` resets, rebinds and steps the\n * first row, and the returned result's `getAllAsync` then steps the *remaining*\n * rows of whatever the statement is currently bound to. Both are separate\n * native round trips.\n *\n * `SQLiteStore` shares one prepared statement per SQL across all concurrent\n * readers, so without serialization reader B's `executeForRawResultAsync` can\n * rebind the statement inside reader A's await gap, and A's `getAllAsync` then\n * returns B's rows (and B is starved of them). That yields wrong or missing\n * values and, when a ref count delta is computed from such a read and written,\n * a persistently corrupted store.\n *\n * Every call on a statement is therefore run under a per-statement lock so the\n * execute + fetch pair is atomic with respect to other users of that statement.\n */\nclass ExpoSQLitePreparedStatement implements PreparedStatement {\n readonly #statement: SQLiteStatement;\n readonly #lock = new Lock();\n\n constructor(statement: SQLiteStatement) {\n this.#statement = statement;\n }\n\n exec(params: string[]): Promise<void> {\n return this.#lock.withLock(async () => {\n await this.#statement.executeForRawResultAsync(params);\n });\n }\n\n all(params: string[]): Promise<unknown[][]> {\n return this.#lock.withLock(async () => {\n const result = await this.#statement.executeForRawResultAsync(params);\n // Must be awaited inside the lock so that no other caller can reset the\n // statement before all rows have been stepped.\n return (await result.getAllAsync()) as unknown[][];\n });\n }\n}\n\nclass ExpoSQLiteDatabase implements SQLiteDatabase {\n readonly #db: DB;\n readonly #filename: string;\n readonly #statements: Set<SQLiteStatement> = new Set();\n\n constructor(filename: string) {\n this.#filename = filename;\n this.#db = openDatabaseSync(filename);\n }\n\n close(): void {\n for (const stmt of this.#statements) {\n stmt.finalizeSync();\n }\n this.#db.closeSync();\n }\n\n destroy(): void {\n deleteDatabaseSync(this.#filename);\n }\n\n prepare(sql: string): PreparedStatement {\n const statement = this.#db.prepareSync(sql);\n this.#statements.add(statement);\n return new ExpoSQLitePreparedStatement(statement);\n }\n\n execSync(sql: string): void {\n this.#db.execSync(sql);\n }\n}\n"],"mappings":";;;;AAiBA,SAAgB,oBACd,MACA,MACe;CACf,OAAO,UAAU,OAAM,aAAY,IAAI,mBAAmB,QAAQ,GAAG,IAAI;AAC3E;;;;;;AAOA,SAAgB,wBACd,MACe;CACf,OAAO;EACL,SAAQ,SACN,IAAI,YAAY,OAAM,SAAQ,IAAI,mBAAmB,IAAI,GAAG,IAAI;EAClE,OAAM,SAAQ,oBAAoB,MAAM,IAAI;CAC9C;AACF;;;;;;;;;;;;;;;;;;AAmBA,IAAM,8BAAN,MAA+D;CAC7D;CACA,QAAiB,IAAI,KAAK;CAE1B,YAAY,WAA4B;EACtC,KAAKA,aAAa;CACpB;CAEA,KAAK,QAAiC;EACpC,OAAO,KAAKC,MAAM,SAAS,YAAY;GACrC,MAAM,KAAKD,WAAW,yBAAyB,MAAM;EACvD,CAAC;CACH;CAEA,IAAI,QAAwC;EAC1C,OAAO,KAAKC,MAAM,SAAS,YAAY;GAIrC,OAAQ,OAAM,MAHO,KAAKD,WAAW,yBAAyB,MAAM,GAG/C,YAAY;EACnC,CAAC;CACH;AACF;AAEA,IAAM,qBAAN,MAAmD;CACjD;CACA;CACA,8BAA6C,IAAI,IAAI;CAErD,YAAY,UAAkB;EAC5B,KAAKG,YAAY;EACjB,KAAKD,MAAM,iBAAiB,QAAQ;CACtC;CAEA,QAAc;EACZ,KAAK,MAAM,QAAQ,KAAKE,aACtB,KAAK,aAAa;EAEpB,KAAKF,IAAI,UAAU;CACrB;CAEA,UAAgB;EACd,mBAAmB,KAAKC,SAAS;CACnC;CAEA,QAAQ,KAAgC;EACtC,MAAM,YAAY,KAAKD,IAAI,YAAY,GAAG;EAC1C,KAAKE,YAAY,IAAI,SAAS;EAC9B,OAAO,IAAI,4BAA4B,SAAS;CAClD;CAEA,SAAS,KAAmB;EAC1B,KAAKF,IAAI,SAAS,GAAG;CACvB;AACF"}
@@ -3,6 +3,13 @@ import type { Read, Store, Write } from './store.ts';
3
3
  import { WriteImplBase } from './write-impl-base.ts';
4
4
  /**
5
5
  * A SQLite prepared statement.
6
+ *
7
+ * `SQLiteStore` prepares one statement per SQL and shares it across all
8
+ * concurrent readers, so implementations must make each call atomic with
9
+ * respect to other callers of the same statement: `all()` must not let another
10
+ * `exec()`/`all()` rebind or reset the underlying statement between executing
11
+ * it and fetching its rows. Delegates whose native API splits execute and fetch
12
+ * into separate round trips (e.g. expo-sqlite) must serialize per statement.
6
13
  */
7
14
  export interface PreparedStatement {
8
15
  exec(params: string[]): Promise<void>;
@@ -47,6 +54,10 @@ export type PreparedStatements = {
47
54
  getMany: PreparedStatement;
48
55
  put: PreparedStatement;
49
56
  del: PreparedStatement;
57
+ /** Multi-row INSERT with the values bound as parameters, n rows wide. */
58
+ putN: (n: number) => PreparedStatement;
59
+ /** Multi-key DELETE with the keys bound as parameters, n keys wide. */
60
+ delN: (n: number) => PreparedStatement;
50
61
  };
51
62
  export interface SQLiteStoreOptions {
52
63
  busyTimeout?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/sqlite-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/sqlite-store.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { deepFreeze } from "../frozen-json.js";
2
2
  import { maybeTransactionIsClosedRejection, throwIfStoreClosed, transactionError } from "./throw-if-closed.js";
3
3
  import { WriteImplBase, deleteSentinel } from "./write-impl-base.js";
4
+ import { getOrInsertComputed } from "../../../shared/src/map.js";
4
5
  import { RWLock } from "@rocicorp/lock";
5
6
  //#region ../replicache/src/kv/sqlite-store.ts
6
7
  /**
@@ -58,6 +59,39 @@ function resolveFilename(name, opts) {
58
59
  return dir ? `${dir}/${safe}` : safe;
59
60
  }
60
61
  /**
62
+ * Widest batch we bind in one statement. SQLite's SQLITE_MAX_VARIABLE_NUMBER is
63
+ * 32766, and a put costs two parameters per row, so this is far below the cap;
64
+ * it exists to bound how many distinct statements we prepare and cache.
65
+ */
66
+ var MAX_BATCH = 128;
67
+ /** `repeatList('?', 3)` -> `'?,?,?'`. */
68
+ function repeatList(item, n) {
69
+ return `${item},`.repeat(n).slice(0, -1);
70
+ }
71
+ /**
72
+ * Prepares (and caches) a statement of each width on demand. Callers only ever
73
+ * ask for powers of two, so the cache holds at most log2(MAX_BATCH)+1 entries
74
+ * however many rows a commit turns out to have.
75
+ */
76
+ function batchStatements(delegate, sqlFor) {
77
+ const cache = /* @__PURE__ */ new Map();
78
+ return (n) => getOrInsertComputed(cache, n, () => delegate.prepare(sqlFor(n)));
79
+ }
80
+ /**
81
+ * Runs `items` through `getStatement` in power-of-two sized batches, so any
82
+ * length is covered by a handful of cached statement widths.
83
+ */
84
+ async function execInBatches(items, getStatement, toParams) {
85
+ for (let i = 0; i < items.length;) {
86
+ const remaining = Math.min(MAX_BATCH, items.length - i);
87
+ const n = 1 << 31 - Math.clz32(remaining);
88
+ const params = [];
89
+ for (let j = 0; j < n; j++) toParams(items[i + j], params);
90
+ await getStatement(n).exec(params);
91
+ i += n;
92
+ }
93
+ }
94
+ /**
61
95
  * Common database setup logic shared between expo-sqlite and op-sqlite implementations.
62
96
  * Configures SQLite pragmas, creates the entry table, and prepares common statements.
63
97
  */
@@ -79,7 +113,9 @@ function setupDatabase(delegate, opts) {
79
113
  getMany: delegate.prepare(`SELECT key, value FROM entry WHERE key IN (SELECT value FROM json_each(?))`),
80
114
  put: delegate.prepare(`INSERT OR REPLACE INTO entry (key, value)
81
115
  SELECT e.value->>0, e.value->1 FROM json_each(?) e`),
82
- del: delegate.prepare(`DELETE FROM entry WHERE key IN (SELECT value FROM json_each(?))`)
116
+ del: delegate.prepare(`DELETE FROM entry WHERE key IN (SELECT value FROM json_each(?))`),
117
+ putN: batchStatements(delegate, (n) => `INSERT OR REPLACE INTO entry (key, value) VALUES ${repeatList("(?,?)", n)}`),
118
+ delN: batchStatements(delegate, (n) => `DELETE FROM entry WHERE key IN (${repeatList("?", n)})`)
83
119
  };
84
120
  }
85
121
  var CB_STRIDE = 2;
@@ -209,8 +245,12 @@ var SQLiteWrite = class extends WriteImplBase {
209
245
  deleteKeys.push(entry[0]);
210
246
  this._pending.delete(entry[0]);
211
247
  }
212
- const delP = deleteKeys.length > 0 ? this.#preparedStatements.del.exec([JSON.stringify(deleteKeys)]) : void 0;
213
- const putP = this._pending.size > 0 ? this.#preparedStatements.put.exec([JSON.stringify([...this._pending])]) : void 0;
248
+ const putP = this._pending.size > 0 ? execInBatches([...this._pending], this.#preparedStatements.putN, ([key, value], out) => {
249
+ out.push(key, JSON.stringify(value));
250
+ }) : void 0;
251
+ const delP = deleteKeys.length > 0 ? execInBatches(deleteKeys, this.#preparedStatements.delN, (key, out) => {
252
+ out.push(key);
253
+ }) : void 0;
214
254
  if (putP) await putP;
215
255
  if (delP) await delP;
216
256
  this.#dbDelegate.execSync("COMMIT");