@rocicorp/zero 1.6.0-canary.12 → 1.6.0-canary.13

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 (551) hide show
  1. package/README.md +3 -28
  2. package/out/_virtual/{_@oxc-project_runtime@0.130.0 → _@oxc-project_runtime@0.122.0}/helpers/usingCtx.js +1 -1
  3. package/out/analyze-query/src/analyze-cli.js +3 -3
  4. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  5. package/out/analyze-query/src/bin-analyze.js +1 -6
  6. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  7. package/out/analyze-query/src/bin-transform.js.map +1 -1
  8. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  9. package/out/ast-to-zql/src/bin.js.map +1 -1
  10. package/out/ast-to-zql/src/format.js.map +1 -1
  11. package/out/datadog/src/datadog-log-sink.js.map +1 -1
  12. package/out/otel/src/enabled.js.map +1 -1
  13. package/out/otel/src/log-options.js.map +1 -1
  14. package/out/otel/src/maybe-time.js.map +1 -1
  15. package/out/otel/src/span.js.map +1 -1
  16. package/out/replicache/src/async-iterable-to-array.js.map +1 -1
  17. package/out/replicache/src/bg-interval.js.map +1 -1
  18. package/out/replicache/src/btree/diff.js.map +1 -1
  19. package/out/replicache/src/btree/node.js.map +1 -1
  20. package/out/replicache/src/btree/read.js.map +1 -1
  21. package/out/replicache/src/btree/splice.js.map +1 -1
  22. package/out/replicache/src/btree/write.js +3 -6
  23. package/out/replicache/src/btree/write.js.map +1 -1
  24. package/out/replicache/src/call-default-fetch.js.map +1 -1
  25. package/out/replicache/src/connection-loop-delegates.js.map +1 -1
  26. package/out/replicache/src/connection-loop.js.map +1 -1
  27. package/out/replicache/src/cookies.js.map +1 -1
  28. package/out/replicache/src/dag/chunk.js.map +1 -1
  29. package/out/replicache/src/dag/gc.js.map +1 -1
  30. package/out/replicache/src/dag/key.js.map +1 -1
  31. package/out/replicache/src/dag/lazy-store.js.map +1 -1
  32. package/out/replicache/src/dag/store-impl.js.map +1 -1
  33. package/out/replicache/src/dag/store.js.map +1 -1
  34. package/out/replicache/src/dag/visitor.js.map +1 -1
  35. package/out/replicache/src/db/commit.js.map +1 -1
  36. package/out/replicache/src/db/index.js.map +1 -1
  37. package/out/replicache/src/db/read.js.map +1 -1
  38. package/out/replicache/src/db/rebase.js.map +1 -1
  39. package/out/replicache/src/db/write.js.map +1 -1
  40. package/out/replicache/src/deleted-clients.js.map +1 -1
  41. package/out/replicache/src/error-responses.js.map +1 -1
  42. package/out/replicache/src/frozen-json.js.map +1 -1
  43. package/out/replicache/src/get-default-puller.js.map +1 -1
  44. package/out/replicache/src/get-default-pusher.js.map +1 -1
  45. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  46. package/out/replicache/src/hash.js.map +1 -1
  47. package/out/replicache/src/http-request-info.js.map +1 -1
  48. package/out/replicache/src/index-defs.js.map +1 -1
  49. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  50. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +1 -1
  51. package/out/replicache/src/kv/idb-store.js.map +1 -1
  52. package/out/replicache/src/kv/mem-store.js.map +1 -1
  53. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  54. package/out/replicache/src/kv/read-impl.js.map +1 -1
  55. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  56. package/out/replicache/src/kv/sqlite-store.js +1 -4
  57. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  58. package/out/replicache/src/kv/throw-if-closed.js.map +1 -1
  59. package/out/replicache/src/kv/write-impl-base.js.map +1 -1
  60. package/out/replicache/src/kv/write-impl.js.map +1 -1
  61. package/out/replicache/src/lazy.js.map +1 -1
  62. package/out/replicache/src/log-options.js.map +1 -1
  63. package/out/replicache/src/make-idb-name.js.map +1 -1
  64. package/out/replicache/src/new-client-channel.js.map +1 -1
  65. package/out/replicache/src/on-persist-channel.js.map +1 -1
  66. package/out/replicache/src/patch-operation.js.map +1 -1
  67. package/out/replicache/src/pending-mutations.js.map +1 -1
  68. package/out/replicache/src/persist/client-gc.js.map +1 -1
  69. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  70. package/out/replicache/src/persist/client-groups.js +0 -40
  71. package/out/replicache/src/persist/client-groups.js.map +1 -1
  72. package/out/replicache/src/persist/clients.js +0 -28
  73. package/out/replicache/src/persist/clients.js.map +1 -1
  74. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  75. package/out/replicache/src/persist/gather-mem-only-visitor.js.map +1 -1
  76. package/out/replicache/src/persist/gather-not-cached-visitor.js.map +1 -1
  77. package/out/replicache/src/persist/heartbeat.js.map +1 -1
  78. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  79. package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
  80. package/out/replicache/src/persist/make-client-id.js.map +1 -1
  81. package/out/replicache/src/persist/persist.js.map +1 -1
  82. package/out/replicache/src/persist/refresh.js.map +1 -1
  83. package/out/replicache/src/process-scheduler.js.map +1 -1
  84. package/out/replicache/src/pusher.js.map +1 -1
  85. package/out/replicache/src/replicache-impl.js.map +1 -1
  86. package/out/replicache/src/report-error.js.map +1 -1
  87. package/out/replicache/src/request-idle.js.map +1 -1
  88. package/out/replicache/src/scan-iterator.js.map +1 -1
  89. package/out/replicache/src/scan-options.js.map +1 -1
  90. package/out/replicache/src/set-interval-with-signal.js.map +1 -1
  91. package/out/replicache/src/subscriptions.js.map +1 -1
  92. package/out/replicache/src/sync/diff.js.map +1 -1
  93. package/out/replicache/src/sync/ids.js.map +1 -1
  94. package/out/replicache/src/sync/patch.js.map +1 -1
  95. package/out/replicache/src/sync/pull-error.js.map +1 -1
  96. package/out/replicache/src/sync/pull.js.map +1 -1
  97. package/out/replicache/src/sync/push.js.map +1 -1
  98. package/out/replicache/src/sync/request-id.js.map +1 -1
  99. package/out/replicache/src/to-error.js.map +1 -1
  100. package/out/replicache/src/transaction-closed-error.js.map +1 -1
  101. package/out/replicache/src/transactions.js.map +1 -1
  102. package/out/replicache/src/with-transactions.js.map +1 -1
  103. package/out/shared/src/abort-error.js.map +1 -1
  104. package/out/shared/src/arrays.js.map +1 -1
  105. package/out/shared/src/asserts.js.map +1 -1
  106. package/out/shared/src/bigint-json.js.map +1 -1
  107. package/out/shared/src/binary-search.js.map +1 -1
  108. package/out/shared/src/broadcast-channel.js.map +1 -1
  109. package/out/shared/src/browser-env.js.map +1 -1
  110. package/out/shared/src/btree-set.js.map +1 -1
  111. package/out/shared/src/cache.js.map +1 -1
  112. package/out/shared/src/centroid.js.map +1 -1
  113. package/out/shared/src/custom-key-map.js.map +1 -1
  114. package/out/shared/src/custom-key-set.js.map +1 -1
  115. package/out/shared/src/deep-clone.js.map +1 -1
  116. package/out/shared/src/deep-merge.js.map +1 -1
  117. package/out/shared/src/document-visible.js.map +1 -1
  118. package/out/shared/src/dotenv.js.map +1 -1
  119. package/out/shared/src/error.js.map +1 -1
  120. package/out/shared/src/hash.js.map +1 -1
  121. package/out/shared/src/iterables.d.ts +0 -2
  122. package/out/shared/src/iterables.d.ts.map +1 -1
  123. package/out/shared/src/iterables.js +1 -9
  124. package/out/shared/src/iterables.js.map +1 -1
  125. package/out/shared/src/json-schema.js.map +1 -1
  126. package/out/shared/src/json.js.map +1 -1
  127. package/out/shared/src/logging-test-utils.js.map +1 -1
  128. package/out/shared/src/logging.js.map +1 -1
  129. package/out/shared/src/map.js.map +1 -1
  130. package/out/shared/src/must.js.map +1 -1
  131. package/out/shared/src/object-traversal.js.map +1 -1
  132. package/out/shared/src/objects.js.map +1 -1
  133. package/out/shared/src/options.js.map +1 -1
  134. package/out/shared/src/parse-big-int.js.map +1 -1
  135. package/out/shared/src/promise-race.js.map +1 -1
  136. package/out/shared/src/queue.d.ts.map +1 -1
  137. package/out/shared/src/queue.js +21 -15
  138. package/out/shared/src/queue.js.map +1 -1
  139. package/out/shared/src/rand.js.map +1 -1
  140. package/out/shared/src/random-uint64.js.map +1 -1
  141. package/out/shared/src/random-values.js.map +1 -1
  142. package/out/shared/src/record-proxy.js.map +1 -1
  143. package/out/shared/src/resolved-promises.js.map +1 -1
  144. package/out/shared/src/sentinels.js.map +1 -1
  145. package/out/shared/src/set-utils.js.map +1 -1
  146. package/out/shared/src/size-of-value.js.map +1 -1
  147. package/out/shared/src/sleep.js.map +1 -1
  148. package/out/shared/src/sorted-entries.js.map +1 -1
  149. package/out/shared/src/string-compare.js.map +1 -1
  150. package/out/shared/src/subscribable.js.map +1 -1
  151. package/out/shared/src/tdigest-schema.js.map +1 -1
  152. package/out/shared/src/tdigest.js.map +1 -1
  153. package/out/shared/src/valita.js.map +1 -1
  154. package/out/z2s/src/compiler.js.map +1 -1
  155. package/out/z2s/src/sql.js.map +1 -1
  156. package/out/zero/package.js +26 -34
  157. package/out/zero/package.js.map +1 -1
  158. package/out/zero/src/build-schema.js.map +1 -1
  159. package/out/zero/src/zero-cache-dev.js.map +1 -1
  160. package/out/zero/src/zero-out.js.map +1 -1
  161. package/out/zero-cache/src/auth/auth.js.map +1 -1
  162. package/out/zero-cache/src/auth/jwt.js.map +1 -1
  163. package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
  164. package/out/zero-cache/src/auth/read-authorizer.js.map +1 -1
  165. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  166. package/out/zero-cache/src/config/network.js.map +1 -1
  167. package/out/zero-cache/src/config/normalize.js.map +1 -1
  168. package/out/zero-cache/src/config/server-context.js.map +1 -1
  169. package/out/zero-cache/src/config/zero-config.js +0 -5
  170. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  171. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  172. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  173. package/out/zero-cache/src/db/create.js.map +1 -1
  174. package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
  175. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  176. package/out/zero-cache/src/db/migration-lite.js +0 -19
  177. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  178. package/out/zero-cache/src/db/migration.js +0 -19
  179. package/out/zero-cache/src/db/migration.js.map +1 -1
  180. package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
  181. package/out/zero-cache/src/db/pg-copy.js.map +1 -1
  182. package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
  183. package/out/zero-cache/src/db/pg-type-parser.js.map +1 -1
  184. package/out/zero-cache/src/db/run-transaction.js.map +1 -1
  185. package/out/zero-cache/src/db/specs.js.map +1 -1
  186. package/out/zero-cache/src/db/statements.js.map +1 -1
  187. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  188. package/out/zero-cache/src/db/warmup.js.map +1 -1
  189. package/out/zero-cache/src/observability/events.js.map +1 -1
  190. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  191. package/out/zero-cache/src/scripts/decommission.js.map +1 -1
  192. package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
  193. package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
  194. package/out/zero-cache/src/scripts/permissions.js +2 -1
  195. package/out/zero-cache/src/scripts/permissions.js.map +1 -1
  196. package/out/zero-cache/src/server/anonymous-otel-start.js +7 -8
  197. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  198. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  199. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  200. package/out/zero-cache/src/server/logging.js.map +1 -1
  201. package/out/zero-cache/src/server/main.js.map +1 -1
  202. package/out/zero-cache/src/server/mutator.js.map +1 -1
  203. package/out/zero-cache/src/server/otel-diag-logger.js.map +1 -1
  204. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  205. package/out/zero-cache/src/server/otel-start.js.map +1 -1
  206. package/out/zero-cache/src/server/priority-op.js.map +1 -1
  207. package/out/zero-cache/src/server/reaper.js.map +1 -1
  208. package/out/zero-cache/src/server/replicator.js.map +1 -1
  209. package/out/zero-cache/src/server/runner/main.js.map +1 -1
  210. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  211. package/out/zero-cache/src/server/runner/runtime.js.map +1 -1
  212. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  213. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
  214. package/out/zero-cache/src/server/syncer.js.map +1 -1
  215. package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
  216. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  217. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  218. package/out/zero-cache/src/services/analyze.js +2 -5
  219. package/out/zero-cache/src/services/analyze.js.map +1 -1
  220. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  221. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  222. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  223. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  224. package/out/zero-cache/src/services/change-source/pg/backfill-metadata.js.map +1 -1
  225. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  226. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  227. package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
  228. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  229. package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.js.map +1 -1
  230. package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js.map +1 -1
  231. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  232. package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
  233. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  234. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  235. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  236. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  237. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  238. package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
  239. package/out/zero-cache/src/services/change-source/protocol/current/control.js.map +1 -1
  240. package/out/zero-cache/src/services/change-source/protocol/current/data.js +0 -2
  241. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  242. package/out/zero-cache/src/services/change-source/protocol/current/downstream.js.map +1 -1
  243. package/out/zero-cache/src/services/change-source/protocol/current/json.js.map +1 -1
  244. package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
  245. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  246. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  247. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  248. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  249. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  250. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  251. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  252. package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +1 -1
  253. package/out/zero-cache/src/services/change-streamer/schema/init.js +25 -21
  254. package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
  255. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  256. package/out/zero-cache/src/services/change-streamer/snapshot.js +0 -15
  257. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  258. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  259. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  260. package/out/zero-cache/src/services/heapz.js.map +1 -1
  261. package/out/zero-cache/src/services/http-service.js.map +1 -1
  262. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  263. package/out/zero-cache/src/services/limiter/sliding-window-limiter.js.map +1 -1
  264. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  265. package/out/zero-cache/src/services/mutagen/error.js.map +1 -1
  266. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  267. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  268. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  269. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  270. package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
  271. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  272. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  273. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  274. package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
  275. package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
  276. package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
  277. package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
  278. package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
  279. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  280. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  281. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  282. package/out/zero-cache/src/services/run-ast.js +0 -1
  283. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  284. package/out/zero-cache/src/services/runner.js.map +1 -1
  285. package/out/zero-cache/src/services/running-state.js.map +1 -1
  286. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  287. package/out/zero-cache/src/services/statz.js.map +1 -1
  288. package/out/zero-cache/src/services/view-syncer/active-users-gauge.js.map +1 -1
  289. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  290. package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
  291. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  292. package/out/zero-cache/src/services/view-syncer/cvr-purger.d.ts.map +1 -1
  293. package/out/zero-cache/src/services/view-syncer/cvr-purger.js +1 -2
  294. package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
  295. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  296. package/out/zero-cache/src/services/view-syncer/cvr-store.js +1 -2
  297. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  298. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  299. package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
  300. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +14 -0
  301. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  302. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +25 -2
  303. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  304. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  305. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  306. package/out/zero-cache/src/services/view-syncer/row-set-signature.js.map +1 -1
  307. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  308. package/out/zero-cache/src/services/view-syncer/schema/init.js +113 -97
  309. package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
  310. package/out/zero-cache/src/services/view-syncer/schema/types.js +1 -103
  311. package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
  312. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  313. package/out/zero-cache/src/services/view-syncer/tracer.js.map +1 -1
  314. package/out/zero-cache/src/services/view-syncer/ttl-clock.js.map +1 -1
  315. package/out/zero-cache/src/services/view-syncer/view-syncer.js +1 -4
  316. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  317. package/out/zero-cache/src/types/configuration-error.js.map +1 -1
  318. package/out/zero-cache/src/types/error-with-level.js.map +1 -1
  319. package/out/zero-cache/src/types/http.js.map +1 -1
  320. package/out/zero-cache/src/types/lexi-version.js.map +1 -1
  321. package/out/zero-cache/src/types/lite.js.map +1 -1
  322. package/out/zero-cache/src/types/names.js.map +1 -1
  323. package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
  324. package/out/zero-cache/src/types/pg.js.map +1 -1
  325. package/out/zero-cache/src/types/processes.js.map +1 -1
  326. package/out/zero-cache/src/types/profiler.js.map +1 -1
  327. package/out/zero-cache/src/types/row-key.js.map +1 -1
  328. package/out/zero-cache/src/types/shards.js.map +1 -1
  329. package/out/zero-cache/src/types/sql.js.map +1 -1
  330. package/out/zero-cache/src/types/state-version.js.map +1 -1
  331. package/out/zero-cache/src/types/streams.js.map +1 -1
  332. package/out/zero-cache/src/types/strings.js.map +1 -1
  333. package/out/zero-cache/src/types/subscription.js.map +1 -1
  334. package/out/zero-cache/src/types/timeout.js.map +1 -1
  335. package/out/zero-cache/src/types/url-params.js.map +1 -1
  336. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  337. package/out/zero-cache/src/types/ws.js.map +1 -1
  338. package/out/zero-cache/src/workers/connect-params.js.map +1 -1
  339. package/out/zero-cache/src/workers/connection.js.map +1 -1
  340. package/out/zero-cache/src/workers/mutator.js.map +1 -1
  341. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  342. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  343. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  344. package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
  345. package/out/zero-client/src/client/connection-manager.js +1 -2
  346. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  347. package/out/zero-client/src/client/connection.js.map +1 -1
  348. package/out/zero-client/src/client/context.js.map +1 -1
  349. package/out/zero-client/src/client/crud-impl.js.map +1 -1
  350. package/out/zero-client/src/client/crud.js.map +1 -1
  351. package/out/zero-client/src/client/custom.js +1 -2
  352. package/out/zero-client/src/client/custom.js.map +1 -1
  353. package/out/zero-client/src/client/delete-clients-manager.js.map +1 -1
  354. package/out/zero-client/src/client/enable-analytics.js.map +1 -1
  355. package/out/zero-client/src/client/error.js.map +1 -1
  356. package/out/zero-client/src/client/http-string.js.map +1 -1
  357. package/out/zero-client/src/client/inspector/client-group.js.map +1 -1
  358. package/out/zero-client/src/client/inspector/client.js.map +1 -1
  359. package/out/zero-client/src/client/inspector/html-dialog-prompt.js.map +1 -1
  360. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  361. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  362. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  363. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  364. package/out/zero-client/src/client/keys.js.map +1 -1
  365. package/out/zero-client/src/client/log-options.js.map +1 -1
  366. package/out/zero-client/src/client/make-mutate-property.js.map +1 -1
  367. package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
  368. package/out/zero-client/src/client/metrics.js.map +1 -1
  369. package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
  370. package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
  371. package/out/zero-client/src/client/options.js.map +1 -1
  372. package/out/zero-client/src/client/query-manager.js.map +1 -1
  373. package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
  374. package/out/zero-client/src/client/server-option.js.map +1 -1
  375. package/out/zero-client/src/client/version.js +1 -1
  376. package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
  377. package/out/zero-client/src/client/zero-rep.js.map +1 -1
  378. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  379. package/out/zero-client/src/client/zero.js +32 -58
  380. package/out/zero-client/src/client/zero.js.map +1 -1
  381. package/out/zero-client/src/util/nanoid.js.map +1 -1
  382. package/out/zero-client/src/util/socket.d.ts +3 -0
  383. package/out/zero-client/src/util/socket.d.ts.map +1 -0
  384. package/out/zero-client/src/util/socket.js +8 -0
  385. package/out/zero-client/src/util/socket.js.map +1 -0
  386. package/out/zero-protocol/src/analyze-query-result.js +0 -3
  387. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  388. package/out/zero-protocol/src/application-error.js.map +1 -1
  389. package/out/zero-protocol/src/ast.js.map +1 -1
  390. package/out/zero-protocol/src/change-desired-queries.js +0 -1
  391. package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
  392. package/out/zero-protocol/src/client-schema.js.map +1 -1
  393. package/out/zero-protocol/src/close-connection.js.map +1 -1
  394. package/out/zero-protocol/src/connect.js +0 -7
  395. package/out/zero-protocol/src/connect.js.map +1 -1
  396. package/out/zero-protocol/src/custom-queries.js.map +1 -1
  397. package/out/zero-protocol/src/data.js.map +1 -1
  398. package/out/zero-protocol/src/delete-clients.js.map +1 -1
  399. package/out/zero-protocol/src/down.js.map +1 -1
  400. package/out/zero-protocol/src/error.js +0 -7
  401. package/out/zero-protocol/src/error.js.map +1 -1
  402. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  403. package/out/zero-protocol/src/inspect-up.js +0 -1
  404. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  405. package/out/zero-protocol/src/mutate-server.js.map +1 -1
  406. package/out/zero-protocol/src/mutation-id.js.map +1 -1
  407. package/out/zero-protocol/src/mutation.js.map +1 -1
  408. package/out/zero-protocol/src/mutations-patch.js.map +1 -1
  409. package/out/zero-protocol/src/ping.js.map +1 -1
  410. package/out/zero-protocol/src/poke.js +0 -4
  411. package/out/zero-protocol/src/poke.js.map +1 -1
  412. package/out/zero-protocol/src/pong.js.map +1 -1
  413. package/out/zero-protocol/src/primary-key.js.map +1 -1
  414. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  415. package/out/zero-protocol/src/pull.js.map +1 -1
  416. package/out/zero-protocol/src/push.js +0 -16
  417. package/out/zero-protocol/src/push.js.map +1 -1
  418. package/out/zero-protocol/src/queries-patch.js.map +1 -1
  419. package/out/zero-protocol/src/query-hash.js.map +1 -1
  420. package/out/zero-protocol/src/query-server.js.map +1 -1
  421. package/out/zero-protocol/src/row-patch.js.map +1 -1
  422. package/out/zero-protocol/src/up.js.map +1 -1
  423. package/out/zero-protocol/src/update-auth.js.map +1 -1
  424. package/out/zero-protocol/src/version.js.map +1 -1
  425. package/out/zero-react/src/use-connection-state.js.map +1 -1
  426. package/out/zero-react/src/use-query.js.map +1 -1
  427. package/out/zero-react/src/use-zero-online.js.map +1 -1
  428. package/out/zero-react/src/zero-provider.js.map +1 -1
  429. package/out/zero-schema/src/builder/relationship-builder.js.map +1 -1
  430. package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
  431. package/out/zero-schema/src/builder/table-builder.js.map +1 -1
  432. package/out/zero-schema/src/compiled-permissions.js.map +1 -1
  433. package/out/zero-schema/src/name-mapper.js.map +1 -1
  434. package/out/zero-schema/src/permissions.js.map +1 -1
  435. package/out/zero-schema/src/schema-config.js.map +1 -1
  436. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  437. package/out/zero-server/src/adapters/kysely.js.map +1 -1
  438. package/out/zero-server/src/adapters/pg.js.map +1 -1
  439. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  440. package/out/zero-server/src/adapters/prisma.js.map +1 -1
  441. package/out/zero-server/src/custom.js +1 -2
  442. package/out/zero-server/src/custom.js.map +1 -1
  443. package/out/zero-server/src/logging.js.map +1 -1
  444. package/out/zero-server/src/pg-query-executor.js.map +1 -1
  445. package/out/zero-server/src/process-mutations.js.map +1 -1
  446. package/out/zero-server/src/push-processor.js.map +1 -1
  447. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  448. package/out/zero-server/src/schema.js.map +1 -1
  449. package/out/zero-server/src/zql-database.js.map +1 -1
  450. package/out/zero-solid/src/solid-view.js.map +1 -1
  451. package/out/zero-solid/src/use-connection-state.js.map +1 -1
  452. package/out/zero-solid/src/use-query.js.map +1 -1
  453. package/out/zero-solid/src/use-zero-online.js.map +1 -1
  454. package/out/zero-solid/src/use-zero.js.map +1 -1
  455. package/out/zero-types/src/format.js.map +1 -1
  456. package/out/zero-types/src/name-mapper.js.map +1 -1
  457. package/out/zql/src/builder/builder.js.map +1 -1
  458. package/out/zql/src/builder/debug-delegate.d.ts +0 -5
  459. package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
  460. package/out/zql/src/builder/debug-delegate.js +1 -10
  461. package/out/zql/src/builder/debug-delegate.js.map +1 -1
  462. package/out/zql/src/builder/filter.js.map +1 -1
  463. package/out/zql/src/builder/like.js.map +1 -1
  464. package/out/zql/src/error.js.map +1 -1
  465. package/out/zql/src/ivm/array-view.js.map +1 -1
  466. package/out/zql/src/ivm/cap.js.map +1 -1
  467. package/out/zql/src/ivm/change.js.map +1 -1
  468. package/out/zql/src/ivm/constraint.js +1 -1
  469. package/out/zql/src/ivm/constraint.js.map +1 -1
  470. package/out/zql/src/ivm/data.js.map +1 -1
  471. package/out/zql/src/ivm/exists.js.map +1 -1
  472. package/out/zql/src/ivm/fan-in.js.map +1 -1
  473. package/out/zql/src/ivm/fan-out.js.map +1 -1
  474. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  475. package/out/zql/src/ivm/filter-push.js.map +1 -1
  476. package/out/zql/src/ivm/filter.js.map +1 -1
  477. package/out/zql/src/ivm/flipped-join.d.ts +8 -4
  478. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  479. package/out/zql/src/ivm/flipped-join.js +63 -59
  480. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  481. package/out/zql/src/ivm/join-utils.js.map +1 -1
  482. package/out/zql/src/ivm/join.js.map +1 -1
  483. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  484. package/out/zql/src/ivm/memory-source.js.map +1 -1
  485. package/out/zql/src/ivm/memory-storage.js.map +1 -1
  486. package/out/zql/src/ivm/operator.d.ts +1 -1
  487. package/out/zql/src/ivm/operator.js.map +1 -1
  488. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  489. package/out/zql/src/ivm/schema.d.ts +8 -0
  490. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  491. package/out/zql/src/ivm/skip-yields.js.map +1 -1
  492. package/out/zql/src/ivm/skip.js.map +1 -1
  493. package/out/zql/src/ivm/source.js.map +1 -1
  494. package/out/zql/src/ivm/stream.js.map +1 -1
  495. package/out/zql/src/ivm/take.js.map +1 -1
  496. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  497. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  498. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  499. package/out/zql/src/mutate/crud.js.map +1 -1
  500. package/out/zql/src/mutate/custom.js.map +1 -1
  501. package/out/zql/src/mutate/mutator-registry.js.map +1 -1
  502. package/out/zql/src/mutate/mutator.js.map +1 -1
  503. package/out/zql/src/planner/planner-builder.js.map +1 -1
  504. package/out/zql/src/planner/planner-connection.js.map +1 -1
  505. package/out/zql/src/planner/planner-constraint.js.map +1 -1
  506. package/out/zql/src/planner/planner-debug.js.map +1 -1
  507. package/out/zql/src/planner/planner-fan-in.js.map +1 -1
  508. package/out/zql/src/planner/planner-fan-out.js.map +1 -1
  509. package/out/zql/src/planner/planner-graph.js.map +1 -1
  510. package/out/zql/src/planner/planner-join.d.ts.map +1 -1
  511. package/out/zql/src/planner/planner-join.js +1 -2
  512. package/out/zql/src/planner/planner-join.js.map +1 -1
  513. package/out/zql/src/planner/planner-node.js.map +1 -1
  514. package/out/zql/src/planner/planner-source.js.map +1 -1
  515. package/out/zql/src/planner/planner-terminus.js.map +1 -1
  516. package/out/zql/src/query/complete-ordering.js.map +1 -1
  517. package/out/zql/src/query/create-builder.js.map +1 -1
  518. package/out/zql/src/query/error.js.map +1 -1
  519. package/out/zql/src/query/escape-like.js.map +1 -1
  520. package/out/zql/src/query/expression.js.map +1 -1
  521. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  522. package/out/zql/src/query/metrics-delegate.js.map +1 -1
  523. package/out/zql/src/query/named.js.map +1 -1
  524. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  525. package/out/zql/src/query/query-impl.js +1 -1
  526. package/out/zql/src/query/query-impl.js.map +1 -1
  527. package/out/zql/src/query/query-internals.js.map +1 -1
  528. package/out/zql/src/query/query-registry.js.map +1 -1
  529. package/out/zql/src/query/runnable-query-impl.js.map +1 -1
  530. package/out/zql/src/query/static-query.js.map +1 -1
  531. package/out/zql/src/query/ttl.js.map +1 -1
  532. package/out/zql/src/query/validate-input.js.map +1 -1
  533. package/out/zqlite/src/database-storage.js.map +1 -1
  534. package/out/zqlite/src/db.js.map +1 -1
  535. package/out/zqlite/src/explain-queries.js.map +1 -1
  536. package/out/zqlite/src/internal/sql-inline.js.map +1 -1
  537. package/out/zqlite/src/internal/sql.js.map +1 -1
  538. package/out/zqlite/src/internal/statement-cache.js.map +1 -1
  539. package/out/zqlite/src/query-builder.js.map +1 -1
  540. package/out/zqlite/src/query-delegate.js.map +1 -1
  541. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  542. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  543. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  544. package/out/zqlite/src/table-source.d.ts.map +1 -1
  545. package/out/zqlite/src/table-source.js +6 -6
  546. package/out/zqlite/src/table-source.js.map +1 -1
  547. package/package.json +26 -42
  548. package/out/shared/src/ring-buffer.d.ts +0 -32
  549. package/out/shared/src/ring-buffer.d.ts.map +0 -1
  550. package/out/shared/src/ring-buffer.js +0 -109
  551. package/out/shared/src/ring-buffer.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"make-idb-name.js","names":[],"sources":["../../../../replicache/src/make-idb-name.ts"],"sourcesContent":["import * as FormatVersion from './format-version-enum.ts';\n\n/**\n * Returns the name of the IDB database that will be used for a particular Replicache instance.\n * @param name The name of the Replicache instance (i.e., the `name` field of `ReplicacheOptions`).\n * @param schemaVersion The schema version of the database (i.e., the `schemaVersion` field of `ReplicacheOptions`).\n * @returns\n */\n\nexport function makeIDBName(name: string, schemaVersion?: string): string {\n return makeIDBNameInternal(name, schemaVersion, FormatVersion.Latest);\n}\n\nfunction makeIDBNameInternal(\n name: string,\n schemaVersion: string | undefined,\n formatVersion: number,\n): string {\n const n = `rep:${name}:${formatVersion}`;\n return schemaVersion ? `${n}:${schemaVersion}` : n;\n}\n\nexport {makeIDBNameInternal as makeIDBNameForTesting};\n"],"mappings":";;;;;;;AASA,SAAgB,YAAY,MAAc,eAAgC;CACxE,OAAO,oBAAoB,MAAM,eAAe,CAAoB;AACtE;AAEA,SAAS,oBACP,MACA,eACA,eACQ;CACR,MAAM,IAAI,OAAO,KAAK,GAAG;CACzB,OAAO,gBAAgB,GAAG,EAAE,GAAG,kBAAkB;AACnD"}
1
+ {"version":3,"file":"make-idb-name.js","names":[],"sources":["../../../../replicache/src/make-idb-name.ts"],"sourcesContent":["import * as FormatVersion from './format-version-enum.ts';\n\n/**\n * Returns the name of the IDB database that will be used for a particular Replicache instance.\n * @param name The name of the Replicache instance (i.e., the `name` field of `ReplicacheOptions`).\n * @param schemaVersion The schema version of the database (i.e., the `schemaVersion` field of `ReplicacheOptions`).\n * @returns\n */\n\nexport function makeIDBName(name: string, schemaVersion?: string): string {\n return makeIDBNameInternal(name, schemaVersion, FormatVersion.Latest);\n}\n\nfunction makeIDBNameInternal(\n name: string,\n schemaVersion: string | undefined,\n formatVersion: number,\n): string {\n const n = `rep:${name}:${formatVersion}`;\n return schemaVersion ? `${n}:${schemaVersion}` : n;\n}\n\nexport {makeIDBNameInternal as makeIDBNameForTesting};\n"],"mappings":";;;;;;;AASA,SAAgB,YAAY,MAAc,eAAgC;AACxE,QAAO,oBAAoB,MAAM,eAAe,EAAqB;;AAGvE,SAAS,oBACP,MACA,eACA,eACQ;CACR,MAAM,IAAI,OAAO,KAAK,GAAG;AACzB,QAAO,gBAAgB,GAAG,EAAE,GAAG,kBAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"new-client-channel.js","names":[],"sources":["../../../../replicache/src/new-client-channel.ts"],"sourcesContent":["import {BroadcastChannel} from '../../shared/src/broadcast-channel.ts';\nimport type {Read, Store} from './dag/store.ts';\nimport {getClientGroup} from './persist/client-groups.ts';\nimport {withRead} from './with-transactions.ts';\n\n// Older clients (<= replicache@13.0.1), listened on this channel name\n// and *asserted* that the messages received were an array containing exactly\n// one string.\nfunction makeChannelNameV0(replicacheName: string): string {\n return `replicache-new-client-group:${replicacheName}`;\n}\n\n// This channel name was introduced when we first needed to change the message\n// format. The design of the messages sent on this channel allows for\n// the message content to be extended in the future in a way that is\n// forward and backwards compatible. The message format can be extended\n// by adding new *optional* fields.\nfunction makeChannelNameV1(replicacheName: string): string {\n return `replicache-new-client-group-v1:${replicacheName}`;\n}\n\nexport {\n makeChannelNameV0 as makeChannelNameV0ForTesting,\n makeChannelNameV1 as makeChannelNameV1ForTesting,\n};\n\n// This message type can be extended with optional properties.\ntype NewClientChannelMessageV1 = {clientGroupID: string; idbName: string};\n\nfunction isNewClientChannelMessageV1(\n message: unknown,\n): message is NewClientChannelMessageV1 {\n return (\n typeof message === 'object' &&\n typeof (message as {clientGroupID: unknown}).clientGroupID === 'string' &&\n typeof (message as {idbName: unknown}).idbName === 'string'\n );\n}\n\nexport function initNewClientChannel(\n replicacheName: string,\n idbName: string,\n signal: AbortSignal,\n clientGroupID: string,\n isNewClientGroup: boolean,\n onUpdateNeeded: () => void,\n perdag: Store,\n) {\n if (signal.aborted) {\n return;\n }\n\n const channelV1 = new BroadcastChannel(makeChannelNameV1(replicacheName));\n if (isNewClientGroup) {\n channelV1.postMessage({clientGroupID, idbName});\n // Send expected format to V0 channel for old clients.\n const channelV0 = new BroadcastChannel(makeChannelNameV0(replicacheName));\n channelV0.postMessage([clientGroupID]);\n channelV0.close();\n }\n\n channelV1.onmessage = async (e: MessageEvent) => {\n const {data} = e;\n if (isNewClientChannelMessageV1(data)) {\n const {clientGroupID: newClientGroupID, idbName: newClientIDBName} = data;\n if (newClientGroupID !== clientGroupID) {\n if (newClientIDBName === idbName) {\n // Check if this client can see the new client's newClientGroupID in its\n // perdag. It should be able to if the clients share persistent\n // storage. However, with `ReplicacheOption.kvStore`\n // and `IDBStoreWithMemFallback` clients may not actually share\n // persistent storage. If storage is not shared, then there is no point\n // in updating, since clients cannot sync locally. If clients do update\n // in this case, they can continually cause each other to update, since\n // on each update the clients get assigned a new client group.\n const updateNeeded = await withRead(\n perdag,\n async (perdagRead: Read) =>\n (await getClientGroup(newClientGroupID, perdagRead)) !==\n undefined,\n );\n if (updateNeeded) {\n onUpdateNeeded();\n }\n } else {\n // Idb name is different, indicating new schema or format version.\n // Update to get assigned to newClientIDBName, and hopefully\n // newClientGroupID.\n // If storage is not actually shared (i.e. due to\n // `ReplicacheOption.kvStore`\n // or `IDBStoreWithMemFallback`) the new client will not\n // get assigned to newClientGroupID, but should get the\n // newClientIDBName.\n // Note: we don't try to read from newClientIDBName to see\n // if this client shares storage with the new client, because\n // the newClientIDBName may have a format version this client\n // cannot read.\n onUpdateNeeded();\n return;\n }\n }\n }\n };\n\n signal.addEventListener('abort', () => channelV1.close(), {once: true});\n}\n"],"mappings":";;;;AAQA,SAAS,kBAAkB,gBAAgC;CACzD,OAAO,+BAA+B;AACxC;AAOA,SAAS,kBAAkB,gBAAgC;CACzD,OAAO,kCAAkC;AAC3C;AAUA,SAAS,4BACP,SACsC;CACtC,OACE,OAAO,YAAY,YACnB,OAAQ,QAAqC,kBAAkB,YAC/D,OAAQ,QAA+B,YAAY;AAEvD;AAEA,SAAgB,qBACd,gBACA,SACA,QACA,eACA,kBACA,gBACA,QACA;CACA,IAAI,OAAO,SACT;CAGF,MAAM,YAAY,IAAI,GAAiB,kBAAkB,cAAc,CAAC;CACxE,IAAI,kBAAkB;EACpB,UAAU,YAAY;GAAC;GAAe;EAAO,CAAC;EAE9C,MAAM,YAAY,IAAI,GAAiB,kBAAkB,cAAc,CAAC;EACxE,UAAU,YAAY,CAAC,aAAa,CAAC;EACrC,UAAU,MAAM;CAClB;CAEA,UAAU,YAAY,OAAO,MAAoB;EAC/C,MAAM,EAAC,SAAQ;EACf,IAAI,4BAA4B,IAAI,GAAG;GACrC,MAAM,EAAC,eAAe,kBAAkB,SAAS,qBAAoB;GACrE,IAAI,qBAAqB,eACvB,IAAI,qBAAqB;QAenB,MANuB,SACzB,QACA,OAAO,eACJ,MAAM,eAAe,kBAAkB,UAAU,MAClD,KAAA,CACJ,GAEE,eAAe;GAAA,OAEZ;IAaL,eAAe;IACf;GACF;EAEJ;CACF;CAEA,OAAO,iBAAiB,eAAe,UAAU,MAAM,GAAG,EAAC,MAAM,KAAI,CAAC;AACxE"}
1
+ {"version":3,"file":"new-client-channel.js","names":[],"sources":["../../../../replicache/src/new-client-channel.ts"],"sourcesContent":["import {BroadcastChannel} from '../../shared/src/broadcast-channel.ts';\nimport type {Read, Store} from './dag/store.ts';\nimport {getClientGroup} from './persist/client-groups.ts';\nimport {withRead} from './with-transactions.ts';\n\n// Older clients (<= replicache@13.0.1), listened on this channel name\n// and *asserted* that the messages received were an array containing exactly\n// one string.\nfunction makeChannelNameV0(replicacheName: string): string {\n return `replicache-new-client-group:${replicacheName}`;\n}\n\n// This channel name was introduced when we first needed to change the message\n// format. The design of the messages sent on this channel allows for\n// the message content to be extended in the future in a way that is\n// forward and backwards compatible. The message format can be extended\n// by adding new *optional* fields.\nfunction makeChannelNameV1(replicacheName: string): string {\n return `replicache-new-client-group-v1:${replicacheName}`;\n}\n\nexport {\n makeChannelNameV0 as makeChannelNameV0ForTesting,\n makeChannelNameV1 as makeChannelNameV1ForTesting,\n};\n\n// This message type can be extended with optional properties.\ntype NewClientChannelMessageV1 = {clientGroupID: string; idbName: string};\n\nfunction isNewClientChannelMessageV1(\n message: unknown,\n): message is NewClientChannelMessageV1 {\n return (\n typeof message === 'object' &&\n typeof (message as {clientGroupID: unknown}).clientGroupID === 'string' &&\n typeof (message as {idbName: unknown}).idbName === 'string'\n );\n}\n\nexport function initNewClientChannel(\n replicacheName: string,\n idbName: string,\n signal: AbortSignal,\n clientGroupID: string,\n isNewClientGroup: boolean,\n onUpdateNeeded: () => void,\n perdag: Store,\n) {\n if (signal.aborted) {\n return;\n }\n\n const channelV1 = new BroadcastChannel(makeChannelNameV1(replicacheName));\n if (isNewClientGroup) {\n channelV1.postMessage({clientGroupID, idbName});\n // Send expected format to V0 channel for old clients.\n const channelV0 = new BroadcastChannel(makeChannelNameV0(replicacheName));\n channelV0.postMessage([clientGroupID]);\n channelV0.close();\n }\n\n channelV1.onmessage = async (e: MessageEvent) => {\n const {data} = e;\n if (isNewClientChannelMessageV1(data)) {\n const {clientGroupID: newClientGroupID, idbName: newClientIDBName} = data;\n if (newClientGroupID !== clientGroupID) {\n if (newClientIDBName === idbName) {\n // Check if this client can see the new client's newClientGroupID in its\n // perdag. It should be able to if the clients share persistent\n // storage. However, with `ReplicacheOption.kvStore`\n // and `IDBStoreWithMemFallback` clients may not actually share\n // persistent storage. If storage is not shared, then there is no point\n // in updating, since clients cannot sync locally. If clients do update\n // in this case, they can continually cause each other to update, since\n // on each update the clients get assigned a new client group.\n const updateNeeded = await withRead(\n perdag,\n async (perdagRead: Read) =>\n (await getClientGroup(newClientGroupID, perdagRead)) !==\n undefined,\n );\n if (updateNeeded) {\n onUpdateNeeded();\n }\n } else {\n // Idb name is different, indicating new schema or format version.\n // Update to get assigned to newClientIDBName, and hopefully\n // newClientGroupID.\n // If storage is not actually shared (i.e. due to\n // `ReplicacheOption.kvStore`\n // or `IDBStoreWithMemFallback`) the new client will not\n // get assigned to newClientGroupID, but should get the\n // newClientIDBName.\n // Note: we don't try to read from newClientIDBName to see\n // if this client shares storage with the new client, because\n // the newClientIDBName may have a format version this client\n // cannot read.\n onUpdateNeeded();\n return;\n }\n }\n }\n };\n\n signal.addEventListener('abort', () => channelV1.close(), {once: true});\n}\n"],"mappings":";;;;AAQA,SAAS,kBAAkB,gBAAgC;AACzD,QAAO,+BAA+B;;AAQxC,SAAS,kBAAkB,gBAAgC;AACzD,QAAO,kCAAkC;;AAW3C,SAAS,4BACP,SACsC;AACtC,QACE,OAAO,YAAY,YACnB,OAAQ,QAAqC,kBAAkB,YAC/D,OAAQ,QAA+B,YAAY;;AAIvD,SAAgB,qBACd,gBACA,SACA,QACA,eACA,kBACA,gBACA,QACA;AACA,KAAI,OAAO,QACT;CAGF,MAAM,YAAY,IAAI,GAAiB,kBAAkB,eAAe,CAAC;AACzE,KAAI,kBAAkB;AACpB,YAAU,YAAY;GAAC;GAAe;GAAQ,CAAC;EAE/C,MAAM,YAAY,IAAI,GAAiB,kBAAkB,eAAe,CAAC;AACzE,YAAU,YAAY,CAAC,cAAc,CAAC;AACtC,YAAU,OAAO;;AAGnB,WAAU,YAAY,OAAO,MAAoB;EAC/C,MAAM,EAAC,SAAQ;AACf,MAAI,4BAA4B,KAAK,EAAE;GACrC,MAAM,EAAC,eAAe,kBAAkB,SAAS,qBAAoB;AACrE,OAAI,qBAAqB,cACvB,KAAI,qBAAqB;QASF,MAAM,SACzB,QACA,OAAO,eACJ,MAAM,eAAe,kBAAkB,WAAW,KACnD,KAAA,EACH,CAEC,iBAAgB;UAEb;AAaL,oBAAgB;AAChB;;;;AAMR,QAAO,iBAAiB,eAAe,UAAU,OAAO,EAAE,EAAC,MAAM,MAAK,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"on-persist-channel.js","names":[],"sources":["../../../../replicache/src/on-persist-channel.ts"],"sourcesContent":["import {assertObject, assertString} from '../../shared/src/asserts.ts';\nimport {BroadcastChannel} from '../../shared/src/broadcast-channel.ts';\nimport type {ClientGroupID, ClientID} from './sync/ids.ts';\n\nfunction makeChannelName(replicacheName: string): string {\n return `replicache-on-persist:${replicacheName}`;\n}\n\nexport type PersistInfo = {\n clientGroupID: ClientGroupID;\n clientID: ClientID;\n};\n\nexport type OnPersist = (persistInfo: PersistInfo) => void;\n\ntype HandlePersist = OnPersist;\n\nfunction assertPersistInfo(value: unknown): asserts value is PersistInfo {\n assertObject(value);\n assertString(value.clientGroupID);\n assertString(value.clientID);\n}\n\nexport function initOnPersistChannel(\n replicacheName: string,\n signal: AbortSignal,\n handlePersist: HandlePersist,\n): OnPersist {\n if (signal.aborted) {\n return () => undefined;\n }\n const channel = new BroadcastChannel(makeChannelName(replicacheName));\n\n channel.onmessage = e => {\n const {data} = e;\n assertPersistInfo(data);\n handlePersist({\n clientGroupID: data.clientGroupID,\n clientID: data.clientID,\n });\n };\n\n signal.addEventListener('abort', () => channel.close(), {once: true});\n\n return (persistInfo: PersistInfo) => {\n if (signal.aborted) {\n return;\n }\n channel.postMessage(persistInfo);\n handlePersist(persistInfo);\n };\n}\n"],"mappings":";;;AAIA,SAAS,gBAAgB,gBAAgC;CACvD,OAAO,yBAAyB;AAClC;AAWA,SAAS,kBAAkB,OAA8C;CACvE,aAAa,KAAK;CAClB,aAAa,MAAM,aAAa;CAChC,aAAa,MAAM,QAAQ;AAC7B;AAEA,SAAgB,qBACd,gBACA,QACA,eACW;CACX,IAAI,OAAO,SACT,aAAa,KAAA;CAEf,MAAM,UAAU,IAAI,GAAiB,gBAAgB,cAAc,CAAC;CAEpE,QAAQ,aAAY,MAAK;EACvB,MAAM,EAAC,SAAQ;EACf,kBAAkB,IAAI;EACtB,cAAc;GACZ,eAAe,KAAK;GACpB,UAAU,KAAK;EACjB,CAAC;CACH;CAEA,OAAO,iBAAiB,eAAe,QAAQ,MAAM,GAAG,EAAC,MAAM,KAAI,CAAC;CAEpE,QAAQ,gBAA6B;EACnC,IAAI,OAAO,SACT;EAEF,QAAQ,YAAY,WAAW;EAC/B,cAAc,WAAW;CAC3B;AACF"}
1
+ {"version":3,"file":"on-persist-channel.js","names":[],"sources":["../../../../replicache/src/on-persist-channel.ts"],"sourcesContent":["import {assertObject, assertString} from '../../shared/src/asserts.ts';\nimport {BroadcastChannel} from '../../shared/src/broadcast-channel.ts';\nimport type {ClientGroupID, ClientID} from './sync/ids.ts';\n\nfunction makeChannelName(replicacheName: string): string {\n return `replicache-on-persist:${replicacheName}`;\n}\n\nexport type PersistInfo = {\n clientGroupID: ClientGroupID;\n clientID: ClientID;\n};\n\nexport type OnPersist = (persistInfo: PersistInfo) => void;\n\ntype HandlePersist = OnPersist;\n\nfunction assertPersistInfo(value: unknown): asserts value is PersistInfo {\n assertObject(value);\n assertString(value.clientGroupID);\n assertString(value.clientID);\n}\n\nexport function initOnPersistChannel(\n replicacheName: string,\n signal: AbortSignal,\n handlePersist: HandlePersist,\n): OnPersist {\n if (signal.aborted) {\n return () => undefined;\n }\n const channel = new BroadcastChannel(makeChannelName(replicacheName));\n\n channel.onmessage = e => {\n const {data} = e;\n assertPersistInfo(data);\n handlePersist({\n clientGroupID: data.clientGroupID,\n clientID: data.clientID,\n });\n };\n\n signal.addEventListener('abort', () => channel.close(), {once: true});\n\n return (persistInfo: PersistInfo) => {\n if (signal.aborted) {\n return;\n }\n channel.postMessage(persistInfo);\n handlePersist(persistInfo);\n };\n}\n"],"mappings":";;;AAIA,SAAS,gBAAgB,gBAAgC;AACvD,QAAO,yBAAyB;;AAYlC,SAAS,kBAAkB,OAA8C;AACvE,cAAa,MAAM;AACnB,cAAa,MAAM,cAAc;AACjC,cAAa,MAAM,SAAS;;AAG9B,SAAgB,qBACd,gBACA,QACA,eACW;AACX,KAAI,OAAO,QACT,cAAa,KAAA;CAEf,MAAM,UAAU,IAAI,GAAiB,gBAAgB,eAAe,CAAC;AAErE,SAAQ,aAAY,MAAK;EACvB,MAAM,EAAC,SAAQ;AACf,oBAAkB,KAAK;AACvB,gBAAc;GACZ,eAAe,KAAK;GACpB,UAAU,KAAK;GAChB,CAAC;;AAGJ,QAAO,iBAAiB,eAAe,QAAQ,OAAO,EAAE,EAAC,MAAM,MAAK,CAAC;AAErE,SAAQ,gBAA6B;AACnC,MAAI,OAAO,QACT;AAEF,UAAQ,YAAY,YAAY;AAChC,gBAAc,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"patch-operation.js","names":[],"sources":["../../../../replicache/src/patch-operation.ts"],"sourcesContent":["import {\n assertArray,\n assertObject,\n assertString,\n} from '../../shared/src/asserts.ts';\nimport {\n type ReadonlyJSONObject,\n type ReadonlyJSONValue,\n assertJSONObject,\n assertJSONValue,\n} from '../../shared/src/json.ts';\n\nexport type PatchOperationInternal =\n | {\n readonly op: 'put';\n readonly key: string;\n readonly value: ReadonlyJSONValue;\n }\n | {\n readonly op: 'update';\n readonly key: string;\n readonly merge?: ReadonlyJSONObject | undefined;\n readonly constrain?: readonly string[] | undefined;\n }\n | {\n readonly op: 'del';\n readonly key: string;\n }\n | {\n readonly op: 'clear';\n };\n\n/**\n * This type describes the patch field in a {@link PullResponse} and it is used\n * to describe how to update the Replicache key-value store.\n */\nexport type PatchOperation =\n | {\n readonly op: 'put';\n readonly key: string;\n readonly value: ReadonlyJSONValue;\n }\n | {\n readonly op: 'del';\n readonly key: string;\n }\n | {\n readonly op: 'clear';\n };\n\nexport function assertPatchOperations(\n p: unknown,\n): asserts p is PatchOperationInternal[] {\n assertArray(p);\n for (const item of p) {\n assertPatchOperation(item);\n }\n}\n\nfunction assertPatchOperation(p: unknown): asserts p is PatchOperationInternal {\n assertObject(p);\n switch (p.op) {\n case 'put':\n assertString(p.key);\n assertJSONValue(p.value);\n break;\n case 'update':\n assertString(p.key);\n if (p.merge !== undefined) {\n assertJSONObject(p.merge);\n }\n if (p.constrain !== undefined) {\n assertArray(p.constrain);\n for (const key of p.constrain) {\n assertString(key);\n }\n }\n break;\n case 'del':\n assertString(p.key);\n break;\n case 'clear':\n break;\n default:\n throw new Error(\n `unknown patch op \\`${p.op}\\`, expected one of \\`put\\`, \\`del\\`, \\`clear\\``,\n );\n }\n}\n"],"mappings":";;;AAkDA,SAAgB,sBACd,GACuC;CACvC,YAAY,CAAC;CACb,KAAK,MAAM,QAAQ,GACjB,qBAAqB,IAAI;AAE7B;AAEA,SAAS,qBAAqB,GAAiD;CAC7E,aAAa,CAAC;CACd,QAAQ,EAAE,IAAV;EACE,KAAK;GACH,aAAa,EAAE,GAAG;GAClB,gBAAgB,EAAE,KAAK;GACvB;EACF,KAAK;GACH,aAAa,EAAE,GAAG;GAClB,IAAI,EAAE,UAAU,KAAA,GACd,iBAAiB,EAAE,KAAK;GAE1B,IAAI,EAAE,cAAc,KAAA,GAAW;IAC7B,YAAY,EAAE,SAAS;IACvB,KAAK,MAAM,OAAO,EAAE,WAClB,aAAa,GAAG;GAEpB;GACA;EACF,KAAK;GACH,aAAa,EAAE,GAAG;GAClB;EACF,KAAK,SACH;EACF,SACE,MAAM,IAAI,MACR,sBAAsB,EAAE,GAAG,gDAC7B;CACJ;AACF"}
1
+ {"version":3,"file":"patch-operation.js","names":[],"sources":["../../../../replicache/src/patch-operation.ts"],"sourcesContent":["import {\n assertArray,\n assertObject,\n assertString,\n} from '../../shared/src/asserts.ts';\nimport {\n type ReadonlyJSONObject,\n type ReadonlyJSONValue,\n assertJSONObject,\n assertJSONValue,\n} from '../../shared/src/json.ts';\n\nexport type PatchOperationInternal =\n | {\n readonly op: 'put';\n readonly key: string;\n readonly value: ReadonlyJSONValue;\n }\n | {\n readonly op: 'update';\n readonly key: string;\n readonly merge?: ReadonlyJSONObject | undefined;\n readonly constrain?: readonly string[] | undefined;\n }\n | {\n readonly op: 'del';\n readonly key: string;\n }\n | {\n readonly op: 'clear';\n };\n\n/**\n * This type describes the patch field in a {@link PullResponse} and it is used\n * to describe how to update the Replicache key-value store.\n */\nexport type PatchOperation =\n | {\n readonly op: 'put';\n readonly key: string;\n readonly value: ReadonlyJSONValue;\n }\n | {\n readonly op: 'del';\n readonly key: string;\n }\n | {\n readonly op: 'clear';\n };\n\nexport function assertPatchOperations(\n p: unknown,\n): asserts p is PatchOperationInternal[] {\n assertArray(p);\n for (const item of p) {\n assertPatchOperation(item);\n }\n}\n\nfunction assertPatchOperation(p: unknown): asserts p is PatchOperationInternal {\n assertObject(p);\n switch (p.op) {\n case 'put':\n assertString(p.key);\n assertJSONValue(p.value);\n break;\n case 'update':\n assertString(p.key);\n if (p.merge !== undefined) {\n assertJSONObject(p.merge);\n }\n if (p.constrain !== undefined) {\n assertArray(p.constrain);\n for (const key of p.constrain) {\n assertString(key);\n }\n }\n break;\n case 'del':\n assertString(p.key);\n break;\n case 'clear':\n break;\n default:\n throw new Error(\n `unknown patch op \\`${p.op}\\`, expected one of \\`put\\`, \\`del\\`, \\`clear\\``,\n );\n }\n}\n"],"mappings":";;;AAkDA,SAAgB,sBACd,GACuC;AACvC,aAAY,EAAE;AACd,MAAK,MAAM,QAAQ,EACjB,sBAAqB,KAAK;;AAI9B,SAAS,qBAAqB,GAAiD;AAC7E,cAAa,EAAE;AACf,SAAQ,EAAE,IAAV;EACE,KAAK;AACH,gBAAa,EAAE,IAAI;AACnB,mBAAgB,EAAE,MAAM;AACxB;EACF,KAAK;AACH,gBAAa,EAAE,IAAI;AACnB,OAAI,EAAE,UAAU,KAAA,EACd,kBAAiB,EAAE,MAAM;AAE3B,OAAI,EAAE,cAAc,KAAA,GAAW;AAC7B,gBAAY,EAAE,UAAU;AACxB,SAAK,MAAM,OAAO,EAAE,UAClB,cAAa,IAAI;;AAGrB;EACF,KAAK;AACH,gBAAa,EAAE,IAAI;AACnB;EACF,KAAK,QACH;EACF,QACE,OAAM,IAAI,MACR,sBAAsB,EAAE,GAAG,iDAC5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"pending-mutations.js","names":[],"sources":["../../../../replicache/src/pending-mutations.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport {mustGetHeadHash, type Read} from './dag/store.ts';\nimport {DEFAULT_HEAD_NAME, localMutationsDD31} from './db/commit.ts';\nimport type {ClientID} from './sync/ids.ts';\n\nexport type PendingMutation = {\n readonly name: string;\n readonly id: number;\n readonly args: ReadonlyJSONValue;\n readonly clientID: ClientID;\n};\n\n/**\n * This returns the pending changes with the oldest mutations first.\n */\nexport async function pendingMutationsForAPI(\n dagRead: Read,\n): Promise<readonly PendingMutation[]> {\n const mainHeadHash = await mustGetHeadHash(DEFAULT_HEAD_NAME, dagRead);\n const pending = await localMutationsDD31(mainHeadHash, dagRead);\n return pending\n .map(p => ({\n id: p.meta.mutationID,\n name: p.meta.mutatorName,\n args: p.meta.mutatorArgsJSON,\n clientID: p.meta.clientID,\n }))\n .reverse();\n}\n"],"mappings":";;;;;;AAeA,eAAsB,uBACpB,SACqC;CAGrC,QAAO,MADe,mBAAmB,MADd,gBAAgB,mBAAmB,OAAO,GACd,OAAO,GAE3D,KAAI,OAAM;EACT,IAAI,EAAE,KAAK;EACX,MAAM,EAAE,KAAK;EACb,MAAM,EAAE,KAAK;EACb,UAAU,EAAE,KAAK;CACnB,EAAE,EACD,QAAQ;AACb"}
1
+ {"version":3,"file":"pending-mutations.js","names":[],"sources":["../../../../replicache/src/pending-mutations.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport {mustGetHeadHash, type Read} from './dag/store.ts';\nimport {DEFAULT_HEAD_NAME, localMutationsDD31} from './db/commit.ts';\nimport type {ClientID} from './sync/ids.ts';\n\nexport type PendingMutation = {\n readonly name: string;\n readonly id: number;\n readonly args: ReadonlyJSONValue;\n readonly clientID: ClientID;\n};\n\n/**\n * This returns the pending changes with the oldest mutations first.\n */\nexport async function pendingMutationsForAPI(\n dagRead: Read,\n): Promise<readonly PendingMutation[]> {\n const mainHeadHash = await mustGetHeadHash(DEFAULT_HEAD_NAME, dagRead);\n const pending = await localMutationsDD31(mainHeadHash, dagRead);\n return pending\n .map(p => ({\n id: p.meta.mutationID,\n name: p.meta.mutatorName,\n args: p.meta.mutatorArgsJSON,\n clientID: p.meta.clientID,\n }))\n .reverse();\n}\n"],"mappings":";;;;;;AAeA,eAAsB,uBACpB,SACqC;AAGrC,SADgB,MAAM,mBADD,MAAM,gBAAgB,mBAAmB,QAAQ,EACf,QAAQ,EAE5D,KAAI,OAAM;EACT,IAAI,EAAE,KAAK;EACX,MAAM,EAAE,KAAK;EACb,MAAM,EAAE,KAAK;EACb,UAAU,EAAE,KAAK;EAClB,EAAE,CACF,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-gc.js","names":[],"sources":["../../../../../replicache/src/persist/client-gc.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport type {Store} from '../dag/store.ts';\nimport {\n addDeletedClients,\n type WritableDeletedClients,\n} from '../deleted-clients.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport type {Client, OnClientsDeleted} from './clients.ts';\nimport {type ClientMap, getClients, setClients} from './clients.ts';\n\n/**\n * The maximum time a client can be inactive before it is garbage collected.\n * This means that this is the maximum time a tab can be in the background\n * (frozen) and still be able to sync when it comes back to the foreground.\n */\nexport const CLIENT_MAX_INACTIVE_TIME = 24 * 60 * 60 * 1000; // 24 hours\n\n/**\n * How frequently to try to garbage collect clients.\n */\nexport const GC_INTERVAL = 5 * 60 * 1000; // 5 minutes\n\nlet latestGCUpdate: Promise<ClientMap> | undefined;\nexport function getLatestGCUpdate(): Promise<ClientMap> | undefined {\n return latestGCUpdate;\n}\n\nexport function initClientGC(\n clientID: ClientID,\n dagStore: Store,\n clientMaxInactiveTime: number,\n gcInterval: number,\n onClientsDeleted: OnClientsDeleted,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n initBgIntervalProcess(\n 'ClientGC',\n () => {\n latestGCUpdate = gcClients(\n clientID,\n dagStore,\n clientMaxInactiveTime,\n onClientsDeleted,\n );\n return latestGCUpdate;\n },\n () => gcInterval,\n lc,\n signal,\n );\n}\n\nfunction gcClients(\n clientID: ClientID,\n dagStore: Store,\n clientMaxInactiveTime: number,\n onClientsDeleted: OnClientsDeleted,\n): Promise<ClientMap> {\n return withWrite(dagStore, async dagWrite => {\n const now = Date.now();\n const clients = await getClients(dagWrite);\n const deletedClients: WritableDeletedClients = [];\n const newClients: Map<ClientID, Client> = new Map();\n for (const [id, client] of clients) {\n if (\n id === clientID /* never collect ourself */ ||\n now - client.heartbeatTimestampMs <= clientMaxInactiveTime\n ) {\n newClients.set(id, client);\n } else {\n deletedClients.push({\n clientGroupID: client.clientGroupID,\n clientID: id,\n });\n }\n }\n\n if (newClients.size === clients.size) {\n return clients;\n }\n await setClients(newClients, dagWrite);\n const normalizedDeletedClients = await addDeletedClients(\n dagWrite,\n deletedClients,\n );\n await onClientsDeleted(normalizedDeletedClients);\n return newClients;\n });\n}\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,2BAA2B,OAAU,KAAK;;;;AAKvD,IAAa,cAAc,MAAS;AAEpC,IAAI;AAKJ,SAAgB,aACd,UACA,UACA,uBACA,YACA,kBACA,IACA,QACM;CACN,sBACE,kBACM;EACJ,iBAAiB,UACf,UACA,UACA,uBACA,gBACF;EACA,OAAO;CACT,SACM,YACN,IACA,MACF;AACF;AAEA,SAAS,UACP,UACA,UACA,uBACA,kBACoB;CACpB,OAAO,UAAU,UAAU,OAAM,aAAY;EAC3C,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,UAAU,MAAM,WAAW,QAAQ;EACzC,MAAM,iBAAyC,CAAC;EAChD,MAAM,6BAAoC,IAAI,IAAI;EAClD,KAAK,MAAM,CAAC,IAAI,WAAW,SACzB,IACE,OAAO,YACP,MAAM,OAAO,wBAAwB,uBAErC,WAAW,IAAI,IAAI,MAAM;OAEzB,eAAe,KAAK;GAClB,eAAe,OAAO;GACtB,UAAU;EACZ,CAAC;EAIL,IAAI,WAAW,SAAS,QAAQ,MAC9B,OAAO;EAET,MAAM,WAAW,YAAY,QAAQ;EAKrC,MAAM,iBAAiB,MAJgB,kBACrC,UACA,cACF,CAC+C;EAC/C,OAAO;CACT,CAAC;AACH"}
1
+ {"version":3,"file":"client-gc.js","names":[],"sources":["../../../../../replicache/src/persist/client-gc.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport type {Store} from '../dag/store.ts';\nimport {\n addDeletedClients,\n type WritableDeletedClients,\n} from '../deleted-clients.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport type {Client, OnClientsDeleted} from './clients.ts';\nimport {type ClientMap, getClients, setClients} from './clients.ts';\n\n/**\n * The maximum time a client can be inactive before it is garbage collected.\n * This means that this is the maximum time a tab can be in the background\n * (frozen) and still be able to sync when it comes back to the foreground.\n */\nexport const CLIENT_MAX_INACTIVE_TIME = 24 * 60 * 60 * 1000; // 24 hours\n\n/**\n * How frequently to try to garbage collect clients.\n */\nexport const GC_INTERVAL = 5 * 60 * 1000; // 5 minutes\n\nlet latestGCUpdate: Promise<ClientMap> | undefined;\nexport function getLatestGCUpdate(): Promise<ClientMap> | undefined {\n return latestGCUpdate;\n}\n\nexport function initClientGC(\n clientID: ClientID,\n dagStore: Store,\n clientMaxInactiveTime: number,\n gcInterval: number,\n onClientsDeleted: OnClientsDeleted,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n initBgIntervalProcess(\n 'ClientGC',\n () => {\n latestGCUpdate = gcClients(\n clientID,\n dagStore,\n clientMaxInactiveTime,\n onClientsDeleted,\n );\n return latestGCUpdate;\n },\n () => gcInterval,\n lc,\n signal,\n );\n}\n\nfunction gcClients(\n clientID: ClientID,\n dagStore: Store,\n clientMaxInactiveTime: number,\n onClientsDeleted: OnClientsDeleted,\n): Promise<ClientMap> {\n return withWrite(dagStore, async dagWrite => {\n const now = Date.now();\n const clients = await getClients(dagWrite);\n const deletedClients: WritableDeletedClients = [];\n const newClients: Map<ClientID, Client> = new Map();\n for (const [id, client] of clients) {\n if (\n id === clientID /* never collect ourself */ ||\n now - client.heartbeatTimestampMs <= clientMaxInactiveTime\n ) {\n newClients.set(id, client);\n } else {\n deletedClients.push({\n clientGroupID: client.clientGroupID,\n clientID: id,\n });\n }\n }\n\n if (newClients.size === clients.size) {\n return clients;\n }\n await setClients(newClients, dagWrite);\n const normalizedDeletedClients = await addDeletedClients(\n dagWrite,\n deletedClients,\n );\n await onClientsDeleted(normalizedDeletedClients);\n return newClients;\n });\n}\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,2BAA2B,OAAU,KAAK;;;;AAKvD,IAAa,cAAc,MAAS;AAEpC,IAAI;AAKJ,SAAgB,aACd,UACA,UACA,uBACA,YACA,kBACA,IACA,QACM;AACN,uBACE,kBACM;AACJ,mBAAiB,UACf,UACA,UACA,uBACA,iBACD;AACD,SAAO;UAEH,YACN,IACA,OACD;;AAGH,SAAS,UACP,UACA,UACA,uBACA,kBACoB;AACpB,QAAO,UAAU,UAAU,OAAM,aAAY;EAC3C,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,UAAU,MAAM,WAAW,SAAS;EAC1C,MAAM,iBAAyC,EAAE;EACjD,MAAM,6BAAoC,IAAI,KAAK;AACnD,OAAK,MAAM,CAAC,IAAI,WAAW,QACzB,KACE,OAAO,YACP,MAAM,OAAO,wBAAwB,sBAErC,YAAW,IAAI,IAAI,OAAO;MAE1B,gBAAe,KAAK;GAClB,eAAe,OAAO;GACtB,UAAU;GACX,CAAC;AAIN,MAAI,WAAW,SAAS,QAAQ,KAC9B,QAAO;AAET,QAAM,WAAW,YAAY,SAAS;AAKtC,QAAM,iBAJ2B,MAAM,kBACrC,UACA,eACD,CAC+C;AAChD,SAAO;GACP"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-group-gc.js","names":[],"sources":["../../../../../replicache/src/persist/client-group-gc.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport type {Store} from '../dag/store.ts';\nimport type {ClientGroupID} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport {\n clientGroupHasPendingMutations,\n getClientGroups,\n setClientGroups,\n type ClientGroupMap,\n} from './client-groups.ts';\nimport {getClients} from './clients.ts';\n\nconst GC_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n\nlet latestGCUpdate: Promise<ClientGroupMap> | undefined;\nexport function getLatestGCUpdate(): Promise<ClientGroupMap> | undefined {\n return latestGCUpdate;\n}\n\nexport function initClientGroupGC(\n dagStore: Store,\n enableMutationRecovery: boolean,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n initBgIntervalProcess(\n 'ClientGroupGC',\n () => {\n latestGCUpdate = gcClientGroups(dagStore, enableMutationRecovery);\n return latestGCUpdate;\n },\n () => GC_INTERVAL_MS,\n lc,\n signal,\n );\n}\n\n/**\n * This removes client groups that have no clients and no pending mutations.\n * If {@linkcode enableMutationRecovery} is true, it will keep client groups with\n * pending mutations. If it is false, it will remove client groups even when they\n * have pending mutations.\n */\nexport function gcClientGroups(\n dagStore: Store,\n enableMutationRecovery: boolean,\n): Promise<ClientGroupMap> {\n return withWrite(dagStore, async tx => {\n const clients = await getClients(tx);\n const clientGroupIDs = new Set();\n for (const client of clients.values()) {\n clientGroupIDs.add(client.clientGroupID);\n }\n const clientGroups = new Map();\n const removeClientGroups: Set<ClientGroupID> = new Set();\n for (const [clientGroupID, clientGroup] of await getClientGroups(tx)) {\n if (\n clientGroupIDs.has(clientGroupID) ||\n (enableMutationRecovery && clientGroupHasPendingMutations(clientGroup))\n ) {\n clientGroups.set(clientGroupID, clientGroup);\n } else {\n removeClientGroups.add(clientGroupID);\n }\n }\n await setClientGroups(clientGroups, tx);\n // Client group GC doesn't delete individual clients, so we don't call onClientsDeleted\n return clientGroups;\n });\n}\n"],"mappings":";;;;;AAaA,IAAM,iBAAiB,MAAS;AAEhC,IAAI;AAKJ,SAAgB,kBACd,UACA,wBACA,IACA,QACM;CACN,sBACE,uBACM;EACJ,iBAAiB,eAAe,UAAU,sBAAsB;EAChE,OAAO;CACT,SACM,gBACN,IACA,MACF;AACF;;;;;;;AAQA,SAAgB,eACd,UACA,wBACyB;CACzB,OAAO,UAAU,UAAU,OAAM,OAAM;EACrC,MAAM,UAAU,MAAM,WAAW,EAAE;EACnC,MAAM,iCAAiB,IAAI,IAAI;EAC/B,KAAK,MAAM,UAAU,QAAQ,OAAO,GAClC,eAAe,IAAI,OAAO,aAAa;EAEzC,MAAM,+BAAe,IAAI,IAAI;EAC7B,MAAM,qCAAyC,IAAI,IAAI;EACvD,KAAK,MAAM,CAAC,eAAe,gBAAgB,MAAM,gBAAgB,EAAE,GACjE,IACE,eAAe,IAAI,aAAa,KAC/B,0BAA0B,+BAA+B,WAAW,GAErE,aAAa,IAAI,eAAe,WAAW;OAE3C,mBAAmB,IAAI,aAAa;EAGxC,MAAM,gBAAgB,cAAc,EAAE;EAEtC,OAAO;CACT,CAAC;AACH"}
1
+ {"version":3,"file":"client-group-gc.js","names":[],"sources":["../../../../../replicache/src/persist/client-group-gc.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport type {Store} from '../dag/store.ts';\nimport type {ClientGroupID} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport {\n clientGroupHasPendingMutations,\n getClientGroups,\n setClientGroups,\n type ClientGroupMap,\n} from './client-groups.ts';\nimport {getClients} from './clients.ts';\n\nconst GC_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n\nlet latestGCUpdate: Promise<ClientGroupMap> | undefined;\nexport function getLatestGCUpdate(): Promise<ClientGroupMap> | undefined {\n return latestGCUpdate;\n}\n\nexport function initClientGroupGC(\n dagStore: Store,\n enableMutationRecovery: boolean,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n initBgIntervalProcess(\n 'ClientGroupGC',\n () => {\n latestGCUpdate = gcClientGroups(dagStore, enableMutationRecovery);\n return latestGCUpdate;\n },\n () => GC_INTERVAL_MS,\n lc,\n signal,\n );\n}\n\n/**\n * This removes client groups that have no clients and no pending mutations.\n * If {@linkcode enableMutationRecovery} is true, it will keep client groups with\n * pending mutations. If it is false, it will remove client groups even when they\n * have pending mutations.\n */\nexport function gcClientGroups(\n dagStore: Store,\n enableMutationRecovery: boolean,\n): Promise<ClientGroupMap> {\n return withWrite(dagStore, async tx => {\n const clients = await getClients(tx);\n const clientGroupIDs = new Set();\n for (const client of clients.values()) {\n clientGroupIDs.add(client.clientGroupID);\n }\n const clientGroups = new Map();\n const removeClientGroups: Set<ClientGroupID> = new Set();\n for (const [clientGroupID, clientGroup] of await getClientGroups(tx)) {\n if (\n clientGroupIDs.has(clientGroupID) ||\n (enableMutationRecovery && clientGroupHasPendingMutations(clientGroup))\n ) {\n clientGroups.set(clientGroupID, clientGroup);\n } else {\n removeClientGroups.add(clientGroupID);\n }\n }\n await setClientGroups(clientGroups, tx);\n // Client group GC doesn't delete individual clients, so we don't call onClientsDeleted\n return clientGroups;\n });\n}\n"],"mappings":";;;;;AAaA,IAAM,iBAAiB,MAAS;AAEhC,IAAI;AAKJ,SAAgB,kBACd,UACA,wBACA,IACA,QACM;AACN,uBACE,uBACM;AACJ,mBAAiB,eAAe,UAAU,uBAAuB;AACjE,SAAO;UAEH,gBACN,IACA,OACD;;;;;;;;AASH,SAAgB,eACd,UACA,wBACyB;AACzB,QAAO,UAAU,UAAU,OAAM,OAAM;EACrC,MAAM,UAAU,MAAM,WAAW,GAAG;EACpC,MAAM,iCAAiB,IAAI,KAAK;AAChC,OAAK,MAAM,UAAU,QAAQ,QAAQ,CACnC,gBAAe,IAAI,OAAO,cAAc;EAE1C,MAAM,+BAAe,IAAI,KAAK;EAC9B,MAAM,qCAAyC,IAAI,KAAK;AACxD,OAAK,MAAM,CAAC,eAAe,gBAAgB,MAAM,gBAAgB,GAAG,CAClE,KACE,eAAe,IAAI,cAAc,IAChC,0BAA0B,+BAA+B,YAAY,CAEtE,cAAa,IAAI,eAAe,YAAY;MAE5C,oBAAmB,IAAI,cAAc;AAGzC,QAAM,gBAAgB,cAAc,GAAG;AAEvC,SAAO;GACP"}
@@ -6,51 +6,11 @@ import { toRefs } from "../dag/chunk.js";
6
6
  import { indexDefinitionsEqual, indexDefinitionsSchema } from "../index-defs.js";
7
7
  //#region ../replicache/src/persist/client-groups.ts
8
8
  var clientGroupSchema = readonlyObject({
9
- /**
10
- * The hash of the commit in the perdag last persisted to this client group.
11
- * Should only be updated by clients assigned to this client group.
12
- */
13
9
  headHash: hashSchema,
14
- /**
15
- * Set of mutator names common to all clients assigned to this client group.
16
- */
17
10
  mutatorNames: readonlyArray(valita_exports.string()),
18
- /**
19
- * Index definitions common to all clients assigned to this client group.
20
- */
21
11
  indexes: indexDefinitionsSchema,
22
- /**
23
- * The highest mutation ID of every client assigned to this client group.
24
- * Should only be updated by clients assigned to this client group. Read by
25
- * other clients to determine if there are unacknowledged pending mutations
26
- * for them to try to recover. This is redundant with information in the
27
- * commit graph at `headHash`, but allows other clients to determine if there
28
- * are unacknowledged pending mutations without having to load the commit
29
- * graph.
30
- */
31
12
  mutationIDs: readonlyRecord(valita_exports.number()),
32
- /**
33
- * The highest lastMutationID received from the server for every client
34
- * assigned to this client group.
35
- *
36
- * Should be updated by the clients assigned to this client group whenever
37
- * they persist to this client group. Read by other clients to determine if
38
- * there are unacknowledged pending mutations for them to recover and
39
- * *updated* by other clients upon successfully recovering pending mutations
40
- * to avoid redundant pushes of pending mutations.
41
- *
42
- * Note: This will be the same as the `lastMutationIDs` of the base snapshot
43
- * of the client group's commit graph when written by clients assigned to this
44
- * client group. However, when written by another client recovering mutations
45
- * it may be different because the other client does not update the commit
46
- * graph.
47
- */
48
13
  lastServerAckdMutationIDs: valita_exports.record(valita_exports.number()),
49
- /**
50
- * If the server deletes this client group it can signal that the client group
51
- * was deleted. If that happens we mark this client group as disabled so that
52
- * we do not use it again when creating new clients.
53
- */
54
14
  disabled: valita_exports.boolean()
55
15
  });
56
16
  var CLIENT_GROUPS_HEAD_NAME = "client-groups";
@@ -1 +1 @@
1
- {"version":3,"file":"client-groups.js","names":[],"sources":["../../../../../replicache/src/persist/client-groups.ts"],"sourcesContent":["import {assert, assertObject} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport {toRefs} from '../dag/chunk.ts';\nimport type {Read, Write} from '../dag/store.ts';\nimport {deepFreeze, type FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, hashSchema} from '../hash.ts';\nimport {indexDefinitionsEqual, indexDefinitionsSchema} from '../index-defs.ts';\nimport type {ClientGroupID} from '../sync/ids.ts';\n\nexport type ClientGroupMap = ReadonlyMap<ClientGroupID, ClientGroup>;\n\nconst clientGroupSchema = valita.readonlyObject({\n /**\n * The hash of the commit in the perdag last persisted to this client group.\n * Should only be updated by clients assigned to this client group.\n */\n headHash: hashSchema,\n\n /**\n * Set of mutator names common to all clients assigned to this client group.\n */\n mutatorNames: valita.readonlyArray(valita.string()),\n\n /**\n * Index definitions common to all clients assigned to this client group.\n */\n indexes: indexDefinitionsSchema,\n\n /**\n * The highest mutation ID of every client assigned to this client group.\n * Should only be updated by clients assigned to this client group. Read by\n * other clients to determine if there are unacknowledged pending mutations\n * for them to try to recover. This is redundant with information in the\n * commit graph at `headHash`, but allows other clients to determine if there\n * are unacknowledged pending mutations without having to load the commit\n * graph.\n */\n mutationIDs: valita.readonlyRecord(valita.number()),\n\n /**\n * The highest lastMutationID received from the server for every client\n * assigned to this client group.\n *\n * Should be updated by the clients assigned to this client group whenever\n * they persist to this client group. Read by other clients to determine if\n * there are unacknowledged pending mutations for them to recover and\n * *updated* by other clients upon successfully recovering pending mutations\n * to avoid redundant pushes of pending mutations.\n *\n * Note: This will be the same as the `lastMutationIDs` of the base snapshot\n * of the client group's commit graph when written by clients assigned to this\n * client group. However, when written by another client recovering mutations\n * it may be different because the other client does not update the commit\n * graph.\n */\n lastServerAckdMutationIDs: valita.record(valita.number()),\n\n /**\n * If the server deletes this client group it can signal that the client group\n * was deleted. If that happens we mark this client group as disabled so that\n * we do not use it again when creating new clients.\n */\n disabled: valita.boolean(),\n});\n\nexport type ClientGroup = valita.Infer<typeof clientGroupSchema>;\n\nexport const CLIENT_GROUPS_HEAD_NAME = 'client-groups';\n\nfunction assertClientGroup(value: unknown): asserts value is ClientGroup {\n valita.assert(value, clientGroupSchema);\n}\n\nfunction chunkDataToClientGroupMap(chunkData: unknown): ClientGroupMap {\n assertObject(chunkData);\n const clientGroups = new Map<ClientGroupID, ClientGroup>();\n for (const [key, value] of Object.entries(chunkData)) {\n if (value !== undefined) {\n assertClientGroup(value);\n clientGroups.set(key, value);\n }\n }\n return clientGroups;\n}\n\nfunction clientGroupMapToChunkData(\n clientGroups: ClientGroupMap,\n dagWrite: Write,\n): FrozenJSONValue {\n const chunkData: {[id: ClientGroupID]: ClientGroup} = {};\n for (const [clientGroupID, clientGroup] of clientGroups.entries()) {\n dagWrite.assertValidHash(clientGroup.headHash);\n chunkData[clientGroupID] = {\n ...clientGroup,\n mutatorNames: [...clientGroup.mutatorNames.values()],\n };\n }\n return deepFreeze(chunkData);\n}\n\nasync function getClientGroupsAtHash(\n hash: Hash,\n dagRead: Read,\n): Promise<ClientGroupMap> {\n const chunk = await dagRead.getChunk(hash);\n return chunkDataToClientGroupMap(chunk?.data);\n}\n\nexport async function getClientGroups(dagRead: Read): Promise<ClientGroupMap> {\n const hash = await dagRead.getHead(CLIENT_GROUPS_HEAD_NAME);\n if (!hash) {\n return new Map();\n }\n return getClientGroupsAtHash(hash, dagRead);\n}\n\nexport async function setClientGroups(\n clientGroups: ClientGroupMap,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const currClientGroups = await getClientGroups(dagWrite);\n for (const [clientGroupID, clientGroup] of clientGroups) {\n const currClientGroup = currClientGroups.get(clientGroupID);\n validateClientGroupUpdate(clientGroup, currClientGroup);\n }\n return setValidatedClientGroups(clientGroups, dagWrite);\n}\n\nexport async function setClientGroup(\n clientGroupID: ClientGroupID,\n clientGroup: ClientGroup,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const currClientGroups = await getClientGroups(dagWrite);\n const currClientGroup = currClientGroups.get(clientGroupID);\n validateClientGroupUpdate(clientGroup, currClientGroup);\n const newClientGroups = new Map(currClientGroups);\n newClientGroups.set(clientGroupID, clientGroup);\n return setValidatedClientGroups(newClientGroups, dagWrite);\n}\n\nexport async function deleteClientGroup(\n clientGroupID: ClientGroupID,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const currClientGroups = await getClientGroups(dagWrite);\n if (!currClientGroups.has(clientGroupID)) {\n return currClientGroups;\n }\n const newClientGroups = new Map(currClientGroups.entries());\n newClientGroups.delete(clientGroupID);\n return setValidatedClientGroups(newClientGroups, dagWrite);\n}\n\nfunction validateClientGroupUpdate(\n clientGroup: ClientGroup,\n currClientGroup: ClientGroup | undefined,\n) {\n const mutatorNamesSet = new Set(clientGroup.mutatorNames);\n assert(\n mutatorNamesSet.size === clientGroup.mutatorNames.length,\n \"A client group's mutatorNames must be a set.\",\n );\n if (currClientGroup !== undefined) {\n assert(\n indexDefinitionsEqual(currClientGroup.indexes, clientGroup.indexes),\n \"A client group's index definitions must never change.\",\n );\n assert(\n mutatorNamesEqual(mutatorNamesSet, currClientGroup.mutatorNames),\n \"A client group's mutatorNames must never change.\",\n );\n }\n}\n\nasync function setValidatedClientGroups(\n clientGroups: ClientGroupMap,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const chunkData = clientGroupMapToChunkData(clientGroups, dagWrite);\n const refs: Set<Hash> = new Set();\n for (const clientGroup of clientGroups.values()) {\n refs.add(clientGroup.headHash);\n }\n const chunk = dagWrite.createChunk(chunkData, toRefs(refs));\n await dagWrite.putChunk(chunk);\n await dagWrite.setHead(CLIENT_GROUPS_HEAD_NAME, chunk.hash);\n return clientGroups;\n}\n\nexport function mutatorNamesEqual(\n mutatorNamesSet: ReadonlySet<string>,\n mutatorNames: readonly string[],\n): boolean {\n if (mutatorNames.length !== mutatorNamesSet.size) {\n return false;\n }\n for (const mutatorName of mutatorNames) {\n if (!mutatorNamesSet.has(mutatorName)) {\n return false;\n }\n }\n return true;\n}\n\nexport async function getClientGroup(\n id: ClientGroupID,\n dagRead: Read,\n): Promise<ClientGroup | undefined> {\n const clientGroups = await getClientGroups(dagRead);\n return clientGroups.get(id);\n}\n\nexport function clientGroupHasPendingMutations(clientGroup: ClientGroup) {\n for (const [clientID, mutationID] of Object.entries(\n clientGroup.mutationIDs,\n )) {\n const lastServerAckdMutationID =\n clientGroup.lastServerAckdMutationIDs[clientID];\n if (\n (lastServerAckdMutationID === undefined && mutationID !== 0) ||\n lastServerAckdMutationID < mutationID\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Marks a client group as disabled. This can happen if the server deletes the\n * client group (servers should not delete clients or client groups but it often\n * happens in practice when developing).\n *\n * A disabled client group prevents pulls and pushes from happening.\n */\nexport async function disableClientGroup(\n clientGroupID: string,\n dagWrite: Write,\n): Promise<void> {\n const clientGroup = await getClientGroup(clientGroupID, dagWrite);\n if (!clientGroup) {\n // No client group matching in the database, so nothing to do.\n return;\n }\n const disabledClientGroup = {\n ...clientGroup,\n disabled: true,\n };\n await setClientGroup(clientGroupID, disabledClientGroup, dagWrite);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-groups.js","names":[],"sources":["../../../../../replicache/src/persist/client-groups.ts"],"sourcesContent":["import {assert, assertObject} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport {toRefs} from '../dag/chunk.ts';\nimport type {Read, Write} from '../dag/store.ts';\nimport {deepFreeze, type FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, hashSchema} from '../hash.ts';\nimport {indexDefinitionsEqual, indexDefinitionsSchema} from '../index-defs.ts';\nimport type {ClientGroupID} from '../sync/ids.ts';\n\nexport type ClientGroupMap = ReadonlyMap<ClientGroupID, ClientGroup>;\n\nconst clientGroupSchema = valita.readonlyObject({\n /**\n * The hash of the commit in the perdag last persisted to this client group.\n * Should only be updated by clients assigned to this client group.\n */\n headHash: hashSchema,\n\n /**\n * Set of mutator names common to all clients assigned to this client group.\n */\n mutatorNames: valita.readonlyArray(valita.string()),\n\n /**\n * Index definitions common to all clients assigned to this client group.\n */\n indexes: indexDefinitionsSchema,\n\n /**\n * The highest mutation ID of every client assigned to this client group.\n * Should only be updated by clients assigned to this client group. Read by\n * other clients to determine if there are unacknowledged pending mutations\n * for them to try to recover. This is redundant with information in the\n * commit graph at `headHash`, but allows other clients to determine if there\n * are unacknowledged pending mutations without having to load the commit\n * graph.\n */\n mutationIDs: valita.readonlyRecord(valita.number()),\n\n /**\n * The highest lastMutationID received from the server for every client\n * assigned to this client group.\n *\n * Should be updated by the clients assigned to this client group whenever\n * they persist to this client group. Read by other clients to determine if\n * there are unacknowledged pending mutations for them to recover and\n * *updated* by other clients upon successfully recovering pending mutations\n * to avoid redundant pushes of pending mutations.\n *\n * Note: This will be the same as the `lastMutationIDs` of the base snapshot\n * of the client group's commit graph when written by clients assigned to this\n * client group. However, when written by another client recovering mutations\n * it may be different because the other client does not update the commit\n * graph.\n */\n lastServerAckdMutationIDs: valita.record(valita.number()),\n\n /**\n * If the server deletes this client group it can signal that the client group\n * was deleted. If that happens we mark this client group as disabled so that\n * we do not use it again when creating new clients.\n */\n disabled: valita.boolean(),\n});\n\nexport type ClientGroup = valita.Infer<typeof clientGroupSchema>;\n\nexport const CLIENT_GROUPS_HEAD_NAME = 'client-groups';\n\nfunction assertClientGroup(value: unknown): asserts value is ClientGroup {\n valita.assert(value, clientGroupSchema);\n}\n\nfunction chunkDataToClientGroupMap(chunkData: unknown): ClientGroupMap {\n assertObject(chunkData);\n const clientGroups = new Map<ClientGroupID, ClientGroup>();\n for (const [key, value] of Object.entries(chunkData)) {\n if (value !== undefined) {\n assertClientGroup(value);\n clientGroups.set(key, value);\n }\n }\n return clientGroups;\n}\n\nfunction clientGroupMapToChunkData(\n clientGroups: ClientGroupMap,\n dagWrite: Write,\n): FrozenJSONValue {\n const chunkData: {[id: ClientGroupID]: ClientGroup} = {};\n for (const [clientGroupID, clientGroup] of clientGroups.entries()) {\n dagWrite.assertValidHash(clientGroup.headHash);\n chunkData[clientGroupID] = {\n ...clientGroup,\n mutatorNames: [...clientGroup.mutatorNames.values()],\n };\n }\n return deepFreeze(chunkData);\n}\n\nasync function getClientGroupsAtHash(\n hash: Hash,\n dagRead: Read,\n): Promise<ClientGroupMap> {\n const chunk = await dagRead.getChunk(hash);\n return chunkDataToClientGroupMap(chunk?.data);\n}\n\nexport async function getClientGroups(dagRead: Read): Promise<ClientGroupMap> {\n const hash = await dagRead.getHead(CLIENT_GROUPS_HEAD_NAME);\n if (!hash) {\n return new Map();\n }\n return getClientGroupsAtHash(hash, dagRead);\n}\n\nexport async function setClientGroups(\n clientGroups: ClientGroupMap,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const currClientGroups = await getClientGroups(dagWrite);\n for (const [clientGroupID, clientGroup] of clientGroups) {\n const currClientGroup = currClientGroups.get(clientGroupID);\n validateClientGroupUpdate(clientGroup, currClientGroup);\n }\n return setValidatedClientGroups(clientGroups, dagWrite);\n}\n\nexport async function setClientGroup(\n clientGroupID: ClientGroupID,\n clientGroup: ClientGroup,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const currClientGroups = await getClientGroups(dagWrite);\n const currClientGroup = currClientGroups.get(clientGroupID);\n validateClientGroupUpdate(clientGroup, currClientGroup);\n const newClientGroups = new Map(currClientGroups);\n newClientGroups.set(clientGroupID, clientGroup);\n return setValidatedClientGroups(newClientGroups, dagWrite);\n}\n\nexport async function deleteClientGroup(\n clientGroupID: ClientGroupID,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const currClientGroups = await getClientGroups(dagWrite);\n if (!currClientGroups.has(clientGroupID)) {\n return currClientGroups;\n }\n const newClientGroups = new Map(currClientGroups.entries());\n newClientGroups.delete(clientGroupID);\n return setValidatedClientGroups(newClientGroups, dagWrite);\n}\n\nfunction validateClientGroupUpdate(\n clientGroup: ClientGroup,\n currClientGroup: ClientGroup | undefined,\n) {\n const mutatorNamesSet = new Set(clientGroup.mutatorNames);\n assert(\n mutatorNamesSet.size === clientGroup.mutatorNames.length,\n \"A client group's mutatorNames must be a set.\",\n );\n if (currClientGroup !== undefined) {\n assert(\n indexDefinitionsEqual(currClientGroup.indexes, clientGroup.indexes),\n \"A client group's index definitions must never change.\",\n );\n assert(\n mutatorNamesEqual(mutatorNamesSet, currClientGroup.mutatorNames),\n \"A client group's mutatorNames must never change.\",\n );\n }\n}\n\nasync function setValidatedClientGroups(\n clientGroups: ClientGroupMap,\n dagWrite: Write,\n): Promise<ClientGroupMap> {\n const chunkData = clientGroupMapToChunkData(clientGroups, dagWrite);\n const refs: Set<Hash> = new Set();\n for (const clientGroup of clientGroups.values()) {\n refs.add(clientGroup.headHash);\n }\n const chunk = dagWrite.createChunk(chunkData, toRefs(refs));\n await dagWrite.putChunk(chunk);\n await dagWrite.setHead(CLIENT_GROUPS_HEAD_NAME, chunk.hash);\n return clientGroups;\n}\n\nexport function mutatorNamesEqual(\n mutatorNamesSet: ReadonlySet<string>,\n mutatorNames: readonly string[],\n): boolean {\n if (mutatorNames.length !== mutatorNamesSet.size) {\n return false;\n }\n for (const mutatorName of mutatorNames) {\n if (!mutatorNamesSet.has(mutatorName)) {\n return false;\n }\n }\n return true;\n}\n\nexport async function getClientGroup(\n id: ClientGroupID,\n dagRead: Read,\n): Promise<ClientGroup | undefined> {\n const clientGroups = await getClientGroups(dagRead);\n return clientGroups.get(id);\n}\n\nexport function clientGroupHasPendingMutations(clientGroup: ClientGroup) {\n for (const [clientID, mutationID] of Object.entries(\n clientGroup.mutationIDs,\n )) {\n const lastServerAckdMutationID =\n clientGroup.lastServerAckdMutationIDs[clientID];\n if (\n (lastServerAckdMutationID === undefined && mutationID !== 0) ||\n lastServerAckdMutationID < mutationID\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Marks a client group as disabled. This can happen if the server deletes the\n * client group (servers should not delete clients or client groups but it often\n * happens in practice when developing).\n *\n * A disabled client group prevents pulls and pushes from happening.\n */\nexport async function disableClientGroup(\n clientGroupID: string,\n dagWrite: Write,\n): Promise<void> {\n const clientGroup = await getClientGroup(clientGroupID, dagWrite);\n if (!clientGroup) {\n // No client group matching in the database, so nothing to do.\n return;\n }\n const disabledClientGroup = {\n ...clientGroup,\n disabled: true,\n };\n await setClientGroup(clientGroupID, disabledClientGroup, dagWrite);\n}\n"],"mappings":";;;;;;;AAWA,IAAM,oBAAoB,eAAsB;CAK9C,UAAU;CAKV,cAAc,cAAqB,eAAO,QAAQ,CAAC;CAKnD,SAAS;CAWT,aAAa,eAAsB,eAAO,QAAQ,CAAC;CAkBnD,2BAA2B,eAAO,OAAO,eAAO,QAAQ,CAAC;CAOzD,UAAU,eAAO,SAAS;CAC3B,CAAC;AAIF,IAAa,0BAA0B;AAEvC,SAAS,kBAAkB,OAA8C;AACvE,UAAc,OAAO,kBAAkB;;AAGzC,SAAS,0BAA0B,WAAoC;AACrE,cAAa,UAAU;CACvB,MAAM,+BAAe,IAAI,KAAiC;AAC1D,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,CAClD,KAAI,UAAU,KAAA,GAAW;AACvB,oBAAkB,MAAM;AACxB,eAAa,IAAI,KAAK,MAAM;;AAGhC,QAAO;;AAGT,SAAS,0BACP,cACA,UACiB;CACjB,MAAM,YAAgD,EAAE;AACxD,MAAK,MAAM,CAAC,eAAe,gBAAgB,aAAa,SAAS,EAAE;AACjE,WAAS,gBAAgB,YAAY,SAAS;AAC9C,YAAU,iBAAiB;GACzB,GAAG;GACH,cAAc,CAAC,GAAG,YAAY,aAAa,QAAQ,CAAC;GACrD;;AAEH,QAAO,WAAW,UAAU;;AAG9B,eAAe,sBACb,MACA,SACyB;AAEzB,QAAO,2BADO,MAAM,QAAQ,SAAS,KAAK,GACF,KAAK;;AAG/C,eAAsB,gBAAgB,SAAwC;CAC5E,MAAM,OAAO,MAAM,QAAQ,QAAQ,wBAAwB;AAC3D,KAAI,CAAC,KACH,wBAAO,IAAI,KAAK;AAElB,QAAO,sBAAsB,MAAM,QAAQ;;AAG7C,eAAsB,gBACpB,cACA,UACyB;CACzB,MAAM,mBAAmB,MAAM,gBAAgB,SAAS;AACxD,MAAK,MAAM,CAAC,eAAe,gBAAgB,aAEzC,2BAA0B,aADF,iBAAiB,IAAI,cAAc,CACJ;AAEzD,QAAO,yBAAyB,cAAc,SAAS;;AAGzD,eAAsB,eACpB,eACA,aACA,UACyB;CACzB,MAAM,mBAAmB,MAAM,gBAAgB,SAAS;AAExD,2BAA0B,aADF,iBAAiB,IAAI,cAAc,CACJ;CACvD,MAAM,kBAAkB,IAAI,IAAI,iBAAiB;AACjD,iBAAgB,IAAI,eAAe,YAAY;AAC/C,QAAO,yBAAyB,iBAAiB,SAAS;;AAgB5D,SAAS,0BACP,aACA,iBACA;CACA,MAAM,kBAAkB,IAAI,IAAI,YAAY,aAAa;AACzD,QACE,gBAAgB,SAAS,YAAY,aAAa,QAClD,+CACD;AACD,KAAI,oBAAoB,KAAA,GAAW;AACjC,SACE,sBAAsB,gBAAgB,SAAS,YAAY,QAAQ,EACnE,wDACD;AACD,SACE,kBAAkB,iBAAiB,gBAAgB,aAAa,EAChE,mDACD;;;AAIL,eAAe,yBACb,cACA,UACyB;CACzB,MAAM,YAAY,0BAA0B,cAAc,SAAS;CACnE,MAAM,uBAAkB,IAAI,KAAK;AACjC,MAAK,MAAM,eAAe,aAAa,QAAQ,CAC7C,MAAK,IAAI,YAAY,SAAS;CAEhC,MAAM,QAAQ,SAAS,YAAY,WAAW,OAAO,KAAK,CAAC;AAC3D,OAAM,SAAS,SAAS,MAAM;AAC9B,OAAM,SAAS,QAAQ,yBAAyB,MAAM,KAAK;AAC3D,QAAO;;AAGT,SAAgB,kBACd,iBACA,cACS;AACT,KAAI,aAAa,WAAW,gBAAgB,KAC1C,QAAO;AAET,MAAK,MAAM,eAAe,aACxB,KAAI,CAAC,gBAAgB,IAAI,YAAY,CACnC,QAAO;AAGX,QAAO;;AAGT,eAAsB,eACpB,IACA,SACkC;AAElC,SADqB,MAAM,gBAAgB,QAAQ,EAC/B,IAAI,GAAG;;AAG7B,SAAgB,+BAA+B,aAA0B;AACvE,MAAK,MAAM,CAAC,UAAU,eAAe,OAAO,QAC1C,YAAY,YACb,EAAE;EACD,MAAM,2BACJ,YAAY,0BAA0B;AACxC,MACG,6BAA6B,KAAA,KAAa,eAAe,KAC1D,2BAA2B,WAE3B,QAAO;;AAGX,QAAO;;;;;;;;;AAUT,eAAsB,mBACpB,eACA,UACe;CACf,MAAM,cAAc,MAAM,eAAe,eAAe,SAAS;AACjE,KAAI,CAAC,YAEH;AAMF,OAAM,eAAe,eAJO;EAC1B,GAAG;EACH,UAAU;EACX,EACwD,SAAS"}
@@ -18,41 +18,13 @@ import { makeClientID } from "./make-client-id.js";
18
18
  var clientV5Schema = readonlyObject({
19
19
  heartbeatTimestampMs: valita_exports.number(),
20
20
  headHash: hashSchema,
21
- /**
22
- * The hash of a commit we are in the middle of refreshing into this client's
23
- * memdag.
24
- */
25
21
  tempRefreshHash: hashSchema.nullable(),
26
- /**
27
- * ID of this client's perdag client group. This needs to be sent in pull
28
- * request (to enable syncing all last mutation ids in the client group).
29
- */
30
22
  clientGroupID: clientGroupIDSchema
31
23
  });
32
24
  var clientV6Schema = readonlyObject({
33
25
  heartbeatTimestampMs: valita_exports.number(),
34
- /**
35
- * A set of hashes, which contains:
36
- * 1. The hash of the last commit this client refreshed from its client group
37
- * (this is the commit it bootstrapped from until it completes its first
38
- * refresh).
39
- * 2. One or more hashes that were added to retain chunks of a commit while it
40
- * was being refreshed into this client's memdag. (This can be one or more
41
- * because refresh's cleanup step is a separate transaction and can fail).
42
- * Upon refresh completing and successfully running its clean up step, this
43
- * set will contain a single hash: the hash of the last commit this client
44
- * refreshed.
45
- */
46
26
  refreshHashes: readonlyArray(hashSchema),
47
- /**
48
- * The hash of the last snapshot commit persisted by this client to this
49
- * client's client group, or null if has never persisted a snapshot.
50
- */
51
27
  persistHash: hashSchema.nullable(),
52
- /**
53
- * ID of this client's perdag client group. This needs to be sent in pull
54
- * request (to enable syncing all last mutation ids in the client group).
55
- */
56
28
  clientGroupID: clientGroupIDSchema
57
29
  });
58
30
  function isClientV6(client) {
@@ -1 +1 @@
1
- {"version":3,"file":"clients.js","names":[],"sources":["../../../../../replicache/src/persist/clients.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, assertObject} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport {emptyDataNode} from '../btree/node.ts';\nimport {BTreeRead} from '../btree/read.ts';\nimport {type FrozenCookie, compareCookies} from '../cookies.ts';\nimport {type Refs, toRefs} from '../dag/chunk.ts';\nimport type {Read, Store, Write} from '../dag/store.ts';\nimport type {Commit} from '../db/commit.ts';\nimport {\n type ChunkIndexDefinition,\n type IndexRecord,\n type SnapshotMetaDD31,\n assertSnapshotCommitDD31,\n baseSnapshotFromHash,\n chunkIndexDefinitionEqualIgnoreName,\n getRefs,\n newSnapshotCommitDataDD31,\n toChunkIndexDefinition,\n} from '../db/commit.ts';\nimport {createIndexBTree} from '../db/write.ts';\nimport type {DeletedClients} from '../deleted-clients.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport {type FrozenJSONValue, deepFreeze} from '../frozen-json.ts';\nimport {type Hash, hashSchema} from '../hash.ts';\nimport {type IndexDefinitions, indexDefinitionsEqual} from '../index-defs.ts';\nimport {\n type ClientGroupID,\n type ClientID,\n clientGroupIDSchema,\n} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport {\n type ClientGroup,\n getClientGroup,\n getClientGroups,\n mutatorNamesEqual,\n setClientGroup,\n} from './client-groups.ts';\nimport {makeClientID} from './make-client-id.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport type ClientMap = ReadonlyMap<ClientID, ClientV5 | ClientV6>;\n\nconst clientV5Schema = valita.readonlyObject({\n heartbeatTimestampMs: valita.number(),\n\n headHash: hashSchema,\n\n /**\n * The hash of a commit we are in the middle of refreshing into this client's\n * memdag.\n */\n tempRefreshHash: hashSchema.nullable(),\n\n /**\n * ID of this client's perdag client group. This needs to be sent in pull\n * request (to enable syncing all last mutation ids in the client group).\n */\n clientGroupID: clientGroupIDSchema,\n});\n\nexport type ClientV5 = valita.Infer<typeof clientV5Schema>;\n\nconst clientV6Schema = valita.readonlyObject({\n heartbeatTimestampMs: valita.number(),\n\n /**\n * A set of hashes, which contains:\n * 1. The hash of the last commit this client refreshed from its client group\n * (this is the commit it bootstrapped from until it completes its first\n * refresh).\n * 2. One or more hashes that were added to retain chunks of a commit while it\n * was being refreshed into this client's memdag. (This can be one or more\n * because refresh's cleanup step is a separate transaction and can fail).\n * Upon refresh completing and successfully running its clean up step, this\n * set will contain a single hash: the hash of the last commit this client\n * refreshed.\n */\n refreshHashes: valita.readonlyArray(hashSchema),\n\n /**\n * The hash of the last snapshot commit persisted by this client to this\n * client's client group, or null if has never persisted a snapshot.\n */\n persistHash: hashSchema.nullable(),\n\n /**\n * ID of this client's perdag client group. This needs to be sent in pull\n * request (to enable syncing all last mutation ids in the client group).\n */\n clientGroupID: clientGroupIDSchema,\n});\n\nexport type ClientV6 = valita.Infer<typeof clientV6Schema>;\n\nexport type Client = ClientV5 | ClientV6;\n\nfunction isClientV6(client: Client): client is ClientV6 {\n return (client as ClientV6).refreshHashes !== undefined;\n}\n\nexport const CLIENTS_HEAD_NAME = 'clients';\n\nconst clientSchema = valita.union(clientV5Schema, clientV6Schema);\n\nfunction assertClient(value: unknown): asserts value is Client {\n valita.assert(value, clientSchema);\n}\n\nexport function assertClientV6(value: unknown): asserts value is ClientV6 {\n valita.assert(value, clientV6Schema);\n}\n\nfunction chunkDataToClientMap(chunkData: unknown): ClientMap {\n assertObject(chunkData);\n const clients = new Map();\n for (const key in chunkData) {\n if (hasOwn(chunkData, key)) {\n const value = chunkData[key];\n if (value !== undefined) {\n assertClient(value);\n clients.set(key, value);\n }\n }\n }\n return clients;\n}\n\nfunction clientMapToChunkData(\n clients: ClientMap,\n dagWrite: Write,\n): FrozenJSONValue {\n for (const client of clients.values()) {\n if (isClientV6(client)) {\n client.refreshHashes.forEach(dagWrite.assertValidHash);\n if (client.persistHash) {\n dagWrite.assertValidHash(client.persistHash);\n }\n } else {\n dagWrite.assertValidHash(client.headHash);\n if (client.tempRefreshHash) {\n dagWrite.assertValidHash(client.tempRefreshHash);\n }\n }\n }\n return deepFreeze(Object.fromEntries(clients));\n}\n\nexport async function getClients(dagRead: Read): Promise<ClientMap> {\n const hash = await dagRead.getHead(CLIENTS_HEAD_NAME);\n return getClientsAtHash(hash, dagRead);\n}\n\nasync function getClientsAtHash(\n hash: Hash | undefined,\n dagRead: Read,\n): Promise<ClientMap> {\n if (!hash) {\n return new Map();\n }\n const chunk = await dagRead.getChunk(hash);\n return chunkDataToClientMap(chunk?.data);\n}\n\n/**\n * Used to signal that a client does not exist. Maybe it was garbage collected?\n */\nexport class ClientStateNotFoundError extends Error {\n name = 'ClientStateNotFoundError';\n readonly id: string;\n constructor(id: ClientID) {\n super(`Client state not found, id: ${id}`);\n this.id = id;\n }\n}\n\n/**\n * Throws a `ClientStateNotFoundError` if the client does not exist.\n */\nexport async function assertHasClientState(\n id: ClientID,\n dagRead: Read,\n): Promise<void> {\n if (!(await hasClientState(id, dagRead))) {\n throw new ClientStateNotFoundError(id);\n }\n}\n\nexport async function hasClientState(\n id: ClientID,\n dagRead: Read,\n): Promise<boolean> {\n return !!(await getClient(id, dagRead));\n}\n\nexport async function getClient(\n id: ClientID,\n dagRead: Read,\n): Promise<Client | undefined> {\n const clients = await getClients(dagRead);\n return clients.get(id);\n}\n\nexport async function mustGetClient(\n id: ClientID,\n dagRead: Read,\n): Promise<Client> {\n const client = await getClient(id, dagRead);\n if (!client) {\n throw new ClientStateNotFoundError(id);\n }\n return client;\n}\n\ntype InitClientV6Result = [\n client: ClientV6,\n hash: Hash,\n clientMap: ClientMap,\n newClientGroup: boolean,\n];\n\nexport function initClientV6(\n newClientID: ClientID,\n lc: LogContext,\n perdag: Store,\n mutatorNames: string[],\n indexes: IndexDefinitions,\n formatVersion: FormatVersion,\n enableClientGroupForking: boolean,\n): Promise<InitClientV6Result> {\n return withWrite(perdag, async dagWrite => {\n async function setClientsAndClientGroupAndCommit(\n basisHash: Hash | null,\n cookieJSON: FrozenCookie,\n valueHash: Hash,\n indexRecords: readonly IndexRecord[],\n ): Promise<InitClientV6Result> {\n const newSnapshotData = newSnapshotCommitDataDD31(\n basisHash,\n {},\n cookieJSON,\n valueHash,\n indexRecords,\n );\n const chunk = dagWrite.createChunk(\n newSnapshotData,\n getRefs(newSnapshotData),\n );\n\n const newClientGroupID = makeClientID();\n\n const newClient: ClientV6 = {\n heartbeatTimestampMs: Date.now(),\n refreshHashes: [chunk.hash],\n persistHash: null,\n clientGroupID: newClientGroupID,\n };\n\n const newClients = new Map(clients).set(newClientID, newClient);\n\n const clientGroup: ClientGroup = {\n headHash: chunk.hash,\n mutatorNames,\n indexes,\n mutationIDs: {},\n lastServerAckdMutationIDs: {},\n disabled: false,\n };\n\n await Promise.all([\n dagWrite.putChunk(chunk),\n setClients(newClients, dagWrite),\n setClientGroup(newClientGroupID, clientGroup, dagWrite),\n ]);\n\n return [newClient, chunk.hash, newClients, true];\n }\n\n const clients = await getClients(dagWrite);\n\n const res = await findMatchingClient(dagWrite, mutatorNames, indexes);\n if (res.type === FIND_MATCHING_CLIENT_TYPE_HEAD) {\n // We found a client group with matching mutators and indexes. We can\n // reuse it.\n const {clientGroupID, headHash} = res;\n\n const newClient: ClientV6 = {\n clientGroupID,\n refreshHashes: [headHash],\n heartbeatTimestampMs: Date.now(),\n persistHash: null,\n };\n const newClients = new Map(clients).set(newClientID, newClient);\n await setClients(newClients, dagWrite);\n\n return [newClient, headHash, newClients, false];\n }\n\n if (\n !enableClientGroupForking ||\n res.type === FIND_MATCHING_CLIENT_TYPE_NEW\n ) {\n // No client group to fork from. Create empty snapshot.\n const emptyBTreeChunk = dagWrite.createChunk(emptyDataNode, []);\n await dagWrite.putChunk(emptyBTreeChunk);\n\n // Create indexes\n const indexRecords: IndexRecord[] = [];\n\n // At this point the value of replicache is the empty tree so all index\n // maps will also be the empty tree.\n for (const [name, indexDefinition] of Object.entries(indexes)) {\n const chunkIndexDefinition = toChunkIndexDefinition(\n name,\n indexDefinition,\n );\n indexRecords.push({\n definition: chunkIndexDefinition,\n valueHash: emptyBTreeChunk.hash,\n });\n }\n\n return setClientsAndClientGroupAndCommit(\n null,\n null,\n emptyBTreeChunk.hash,\n indexRecords,\n );\n }\n\n // Now we create a new client and client group that we fork from the found\n // snapshot.\n assert(\n res.type === FIND_MATCHING_CLIENT_TYPE_FORK,\n 'Expected result type to be FORK',\n );\n\n const {snapshot} = res;\n\n // Create indexes\n const indexRecords: IndexRecord[] = [];\n const {valueHash, indexes: oldIndexes} = snapshot;\n const map = new BTreeRead(dagWrite, formatVersion, valueHash);\n\n for (const [name, indexDefinition] of Object.entries(indexes)) {\n const {prefix = '', jsonPointer, allowEmpty = false} = indexDefinition;\n const chunkIndexDefinition: ChunkIndexDefinition = {\n name,\n keyPrefix: prefix,\n jsonPointer,\n allowEmpty,\n };\n\n const oldIndex = findMatchingOldIndex(oldIndexes, chunkIndexDefinition);\n if (oldIndex) {\n indexRecords.push({\n definition: chunkIndexDefinition,\n valueHash: oldIndex.valueHash,\n });\n } else {\n const indexBTree = await createIndexBTree(\n lc,\n dagWrite,\n map,\n prefix,\n jsonPointer,\n allowEmpty,\n formatVersion,\n );\n indexRecords.push({\n definition: chunkIndexDefinition,\n valueHash: await indexBTree.flush(),\n });\n }\n }\n\n return setClientsAndClientGroupAndCommit(\n snapshot.meta.basisHash,\n snapshot.meta.cookieJSON,\n snapshot.valueHash,\n indexRecords,\n );\n });\n}\n\nfunction findMatchingOldIndex(\n oldIndexes: readonly IndexRecord[],\n chunkIndexDefinition: ChunkIndexDefinition,\n) {\n return oldIndexes.find(index =>\n chunkIndexDefinitionEqualIgnoreName(index.definition, chunkIndexDefinition),\n );\n}\n\nexport const FIND_MATCHING_CLIENT_TYPE_NEW = 0;\nexport const FIND_MATCHING_CLIENT_TYPE_FORK = 1;\nexport const FIND_MATCHING_CLIENT_TYPE_HEAD = 2;\n\nexport type FindMatchingClientResult =\n | {\n type: typeof FIND_MATCHING_CLIENT_TYPE_NEW;\n }\n | {\n type: typeof FIND_MATCHING_CLIENT_TYPE_FORK;\n snapshot: Commit<SnapshotMetaDD31>;\n }\n | {\n type: typeof FIND_MATCHING_CLIENT_TYPE_HEAD;\n clientGroupID: ClientGroupID;\n headHash: Hash;\n };\n\nexport async function findMatchingClient(\n dagRead: Read,\n mutatorNames: string[],\n indexes: IndexDefinitions,\n): Promise<FindMatchingClientResult> {\n let newestCookie: FrozenCookie | undefined;\n let bestSnapshot: Commit<SnapshotMetaDD31> | undefined;\n const mutatorNamesSet = new Set(mutatorNames);\n\n const clientGroups = await getClientGroups(dagRead);\n for (const [clientGroupID, clientGroup] of clientGroups) {\n if (\n !clientGroup.disabled &&\n mutatorNamesEqual(mutatorNamesSet, clientGroup.mutatorNames) &&\n indexDefinitionsEqual(indexes, clientGroup.indexes)\n ) {\n // exact match\n return {\n type: FIND_MATCHING_CLIENT_TYPE_HEAD,\n clientGroupID,\n headHash: clientGroup.headHash,\n };\n }\n\n const clientGroupSnapshotCommit = await baseSnapshotFromHash(\n clientGroup.headHash,\n dagRead,\n );\n assertSnapshotCommitDD31(clientGroupSnapshotCommit);\n\n const {cookieJSON} = clientGroupSnapshotCommit.meta;\n if (\n newestCookie === undefined ||\n compareCookies(cookieJSON, newestCookie) > 0\n ) {\n newestCookie = cookieJSON;\n bestSnapshot = clientGroupSnapshotCommit;\n }\n }\n\n if (bestSnapshot) {\n return {\n type: FIND_MATCHING_CLIENT_TYPE_FORK,\n snapshot: bestSnapshot,\n };\n }\n\n return {type: FIND_MATCHING_CLIENT_TYPE_NEW};\n}\n\nfunction getRefsForClients(clients: ClientMap): Refs {\n const refs: Set<Hash> = new Set();\n for (const client of clients.values()) {\n if (isClientV6(client)) {\n for (const hash of client.refreshHashes) {\n refs.add(hash);\n }\n if (client.persistHash) {\n refs.add(client.persistHash);\n }\n } else {\n refs.add(client.headHash);\n if (client.tempRefreshHash) {\n refs.add(client.tempRefreshHash);\n }\n }\n }\n return toRefs(refs);\n}\n\nexport async function getClientGroupForClient(\n clientID: ClientID,\n read: Read,\n): Promise<ClientGroup | undefined> {\n const clientGroupID = await getClientGroupIDForClient(clientID, read);\n if (!clientGroupID) {\n return undefined;\n }\n return getClientGroup(clientGroupID, read);\n}\n\nexport async function getClientGroupIDForClient(\n clientID: ClientID,\n read: Read,\n): Promise<ClientGroupID | undefined> {\n const client = await getClient(clientID, read);\n return client?.clientGroupID;\n}\n\n/**\n * Adds a Client to the ClientMap and updates the 'clients' head to point at\n * the updated clients.\n */\nexport async function setClient(\n clientID: ClientID,\n client: Client,\n dagWrite: Write,\n): Promise<Hash> {\n const clients = await getClients(dagWrite);\n const newClients = new Map(clients).set(clientID, client);\n return setClients(newClients, dagWrite);\n}\n\n/**\n * Sets the ClientMap and updates the 'clients' head top point at the new\n * clients.\n */\nexport async function setClients(\n clients: ClientMap,\n dagWrite: Write,\n): Promise<Hash> {\n const chunkData = clientMapToChunkData(clients, dagWrite);\n const chunk = dagWrite.createChunk(chunkData, getRefsForClients(clients));\n await dagWrite.putChunk(chunk);\n await dagWrite.setHead(CLIENTS_HEAD_NAME, chunk.hash);\n return chunk.hash;\n}\n\n/**\n * Callback function for when Replicache has deleted one or more clients.\n */\nexport type OnClientsDeleted = (\n deletedClients: DeletedClients,\n) => Promise<void>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"clients.js","names":[],"sources":["../../../../../replicache/src/persist/clients.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert, assertObject} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport {emptyDataNode} from '../btree/node.ts';\nimport {BTreeRead} from '../btree/read.ts';\nimport {type FrozenCookie, compareCookies} from '../cookies.ts';\nimport {type Refs, toRefs} from '../dag/chunk.ts';\nimport type {Read, Store, Write} from '../dag/store.ts';\nimport type {Commit} from '../db/commit.ts';\nimport {\n type ChunkIndexDefinition,\n type IndexRecord,\n type SnapshotMetaDD31,\n assertSnapshotCommitDD31,\n baseSnapshotFromHash,\n chunkIndexDefinitionEqualIgnoreName,\n getRefs,\n newSnapshotCommitDataDD31,\n toChunkIndexDefinition,\n} from '../db/commit.ts';\nimport {createIndexBTree} from '../db/write.ts';\nimport type {DeletedClients} from '../deleted-clients.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport {type FrozenJSONValue, deepFreeze} from '../frozen-json.ts';\nimport {type Hash, hashSchema} from '../hash.ts';\nimport {type IndexDefinitions, indexDefinitionsEqual} from '../index-defs.ts';\nimport {\n type ClientGroupID,\n type ClientID,\n clientGroupIDSchema,\n} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport {\n type ClientGroup,\n getClientGroup,\n getClientGroups,\n mutatorNamesEqual,\n setClientGroup,\n} from './client-groups.ts';\nimport {makeClientID} from './make-client-id.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport type ClientMap = ReadonlyMap<ClientID, ClientV5 | ClientV6>;\n\nconst clientV5Schema = valita.readonlyObject({\n heartbeatTimestampMs: valita.number(),\n\n headHash: hashSchema,\n\n /**\n * The hash of a commit we are in the middle of refreshing into this client's\n * memdag.\n */\n tempRefreshHash: hashSchema.nullable(),\n\n /**\n * ID of this client's perdag client group. This needs to be sent in pull\n * request (to enable syncing all last mutation ids in the client group).\n */\n clientGroupID: clientGroupIDSchema,\n});\n\nexport type ClientV5 = valita.Infer<typeof clientV5Schema>;\n\nconst clientV6Schema = valita.readonlyObject({\n heartbeatTimestampMs: valita.number(),\n\n /**\n * A set of hashes, which contains:\n * 1. The hash of the last commit this client refreshed from its client group\n * (this is the commit it bootstrapped from until it completes its first\n * refresh).\n * 2. One or more hashes that were added to retain chunks of a commit while it\n * was being refreshed into this client's memdag. (This can be one or more\n * because refresh's cleanup step is a separate transaction and can fail).\n * Upon refresh completing and successfully running its clean up step, this\n * set will contain a single hash: the hash of the last commit this client\n * refreshed.\n */\n refreshHashes: valita.readonlyArray(hashSchema),\n\n /**\n * The hash of the last snapshot commit persisted by this client to this\n * client's client group, or null if has never persisted a snapshot.\n */\n persistHash: hashSchema.nullable(),\n\n /**\n * ID of this client's perdag client group. This needs to be sent in pull\n * request (to enable syncing all last mutation ids in the client group).\n */\n clientGroupID: clientGroupIDSchema,\n});\n\nexport type ClientV6 = valita.Infer<typeof clientV6Schema>;\n\nexport type Client = ClientV5 | ClientV6;\n\nfunction isClientV6(client: Client): client is ClientV6 {\n return (client as ClientV6).refreshHashes !== undefined;\n}\n\nexport const CLIENTS_HEAD_NAME = 'clients';\n\nconst clientSchema = valita.union(clientV5Schema, clientV6Schema);\n\nfunction assertClient(value: unknown): asserts value is Client {\n valita.assert(value, clientSchema);\n}\n\nexport function assertClientV6(value: unknown): asserts value is ClientV6 {\n valita.assert(value, clientV6Schema);\n}\n\nfunction chunkDataToClientMap(chunkData: unknown): ClientMap {\n assertObject(chunkData);\n const clients = new Map();\n for (const key in chunkData) {\n if (hasOwn(chunkData, key)) {\n const value = chunkData[key];\n if (value !== undefined) {\n assertClient(value);\n clients.set(key, value);\n }\n }\n }\n return clients;\n}\n\nfunction clientMapToChunkData(\n clients: ClientMap,\n dagWrite: Write,\n): FrozenJSONValue {\n for (const client of clients.values()) {\n if (isClientV6(client)) {\n client.refreshHashes.forEach(dagWrite.assertValidHash);\n if (client.persistHash) {\n dagWrite.assertValidHash(client.persistHash);\n }\n } else {\n dagWrite.assertValidHash(client.headHash);\n if (client.tempRefreshHash) {\n dagWrite.assertValidHash(client.tempRefreshHash);\n }\n }\n }\n return deepFreeze(Object.fromEntries(clients));\n}\n\nexport async function getClients(dagRead: Read): Promise<ClientMap> {\n const hash = await dagRead.getHead(CLIENTS_HEAD_NAME);\n return getClientsAtHash(hash, dagRead);\n}\n\nasync function getClientsAtHash(\n hash: Hash | undefined,\n dagRead: Read,\n): Promise<ClientMap> {\n if (!hash) {\n return new Map();\n }\n const chunk = await dagRead.getChunk(hash);\n return chunkDataToClientMap(chunk?.data);\n}\n\n/**\n * Used to signal that a client does not exist. Maybe it was garbage collected?\n */\nexport class ClientStateNotFoundError extends Error {\n name = 'ClientStateNotFoundError';\n readonly id: string;\n constructor(id: ClientID) {\n super(`Client state not found, id: ${id}`);\n this.id = id;\n }\n}\n\n/**\n * Throws a `ClientStateNotFoundError` if the client does not exist.\n */\nexport async function assertHasClientState(\n id: ClientID,\n dagRead: Read,\n): Promise<void> {\n if (!(await hasClientState(id, dagRead))) {\n throw new ClientStateNotFoundError(id);\n }\n}\n\nexport async function hasClientState(\n id: ClientID,\n dagRead: Read,\n): Promise<boolean> {\n return !!(await getClient(id, dagRead));\n}\n\nexport async function getClient(\n id: ClientID,\n dagRead: Read,\n): Promise<Client | undefined> {\n const clients = await getClients(dagRead);\n return clients.get(id);\n}\n\nexport async function mustGetClient(\n id: ClientID,\n dagRead: Read,\n): Promise<Client> {\n const client = await getClient(id, dagRead);\n if (!client) {\n throw new ClientStateNotFoundError(id);\n }\n return client;\n}\n\ntype InitClientV6Result = [\n client: ClientV6,\n hash: Hash,\n clientMap: ClientMap,\n newClientGroup: boolean,\n];\n\nexport function initClientV6(\n newClientID: ClientID,\n lc: LogContext,\n perdag: Store,\n mutatorNames: string[],\n indexes: IndexDefinitions,\n formatVersion: FormatVersion,\n enableClientGroupForking: boolean,\n): Promise<InitClientV6Result> {\n return withWrite(perdag, async dagWrite => {\n async function setClientsAndClientGroupAndCommit(\n basisHash: Hash | null,\n cookieJSON: FrozenCookie,\n valueHash: Hash,\n indexRecords: readonly IndexRecord[],\n ): Promise<InitClientV6Result> {\n const newSnapshotData = newSnapshotCommitDataDD31(\n basisHash,\n {},\n cookieJSON,\n valueHash,\n indexRecords,\n );\n const chunk = dagWrite.createChunk(\n newSnapshotData,\n getRefs(newSnapshotData),\n );\n\n const newClientGroupID = makeClientID();\n\n const newClient: ClientV6 = {\n heartbeatTimestampMs: Date.now(),\n refreshHashes: [chunk.hash],\n persistHash: null,\n clientGroupID: newClientGroupID,\n };\n\n const newClients = new Map(clients).set(newClientID, newClient);\n\n const clientGroup: ClientGroup = {\n headHash: chunk.hash,\n mutatorNames,\n indexes,\n mutationIDs: {},\n lastServerAckdMutationIDs: {},\n disabled: false,\n };\n\n await Promise.all([\n dagWrite.putChunk(chunk),\n setClients(newClients, dagWrite),\n setClientGroup(newClientGroupID, clientGroup, dagWrite),\n ]);\n\n return [newClient, chunk.hash, newClients, true];\n }\n\n const clients = await getClients(dagWrite);\n\n const res = await findMatchingClient(dagWrite, mutatorNames, indexes);\n if (res.type === FIND_MATCHING_CLIENT_TYPE_HEAD) {\n // We found a client group with matching mutators and indexes. We can\n // reuse it.\n const {clientGroupID, headHash} = res;\n\n const newClient: ClientV6 = {\n clientGroupID,\n refreshHashes: [headHash],\n heartbeatTimestampMs: Date.now(),\n persistHash: null,\n };\n const newClients = new Map(clients).set(newClientID, newClient);\n await setClients(newClients, dagWrite);\n\n return [newClient, headHash, newClients, false];\n }\n\n if (\n !enableClientGroupForking ||\n res.type === FIND_MATCHING_CLIENT_TYPE_NEW\n ) {\n // No client group to fork from. Create empty snapshot.\n const emptyBTreeChunk = dagWrite.createChunk(emptyDataNode, []);\n await dagWrite.putChunk(emptyBTreeChunk);\n\n // Create indexes\n const indexRecords: IndexRecord[] = [];\n\n // At this point the value of replicache is the empty tree so all index\n // maps will also be the empty tree.\n for (const [name, indexDefinition] of Object.entries(indexes)) {\n const chunkIndexDefinition = toChunkIndexDefinition(\n name,\n indexDefinition,\n );\n indexRecords.push({\n definition: chunkIndexDefinition,\n valueHash: emptyBTreeChunk.hash,\n });\n }\n\n return setClientsAndClientGroupAndCommit(\n null,\n null,\n emptyBTreeChunk.hash,\n indexRecords,\n );\n }\n\n // Now we create a new client and client group that we fork from the found\n // snapshot.\n assert(\n res.type === FIND_MATCHING_CLIENT_TYPE_FORK,\n 'Expected result type to be FORK',\n );\n\n const {snapshot} = res;\n\n // Create indexes\n const indexRecords: IndexRecord[] = [];\n const {valueHash, indexes: oldIndexes} = snapshot;\n const map = new BTreeRead(dagWrite, formatVersion, valueHash);\n\n for (const [name, indexDefinition] of Object.entries(indexes)) {\n const {prefix = '', jsonPointer, allowEmpty = false} = indexDefinition;\n const chunkIndexDefinition: ChunkIndexDefinition = {\n name,\n keyPrefix: prefix,\n jsonPointer,\n allowEmpty,\n };\n\n const oldIndex = findMatchingOldIndex(oldIndexes, chunkIndexDefinition);\n if (oldIndex) {\n indexRecords.push({\n definition: chunkIndexDefinition,\n valueHash: oldIndex.valueHash,\n });\n } else {\n const indexBTree = await createIndexBTree(\n lc,\n dagWrite,\n map,\n prefix,\n jsonPointer,\n allowEmpty,\n formatVersion,\n );\n indexRecords.push({\n definition: chunkIndexDefinition,\n valueHash: await indexBTree.flush(),\n });\n }\n }\n\n return setClientsAndClientGroupAndCommit(\n snapshot.meta.basisHash,\n snapshot.meta.cookieJSON,\n snapshot.valueHash,\n indexRecords,\n );\n });\n}\n\nfunction findMatchingOldIndex(\n oldIndexes: readonly IndexRecord[],\n chunkIndexDefinition: ChunkIndexDefinition,\n) {\n return oldIndexes.find(index =>\n chunkIndexDefinitionEqualIgnoreName(index.definition, chunkIndexDefinition),\n );\n}\n\nexport const FIND_MATCHING_CLIENT_TYPE_NEW = 0;\nexport const FIND_MATCHING_CLIENT_TYPE_FORK = 1;\nexport const FIND_MATCHING_CLIENT_TYPE_HEAD = 2;\n\nexport type FindMatchingClientResult =\n | {\n type: typeof FIND_MATCHING_CLIENT_TYPE_NEW;\n }\n | {\n type: typeof FIND_MATCHING_CLIENT_TYPE_FORK;\n snapshot: Commit<SnapshotMetaDD31>;\n }\n | {\n type: typeof FIND_MATCHING_CLIENT_TYPE_HEAD;\n clientGroupID: ClientGroupID;\n headHash: Hash;\n };\n\nexport async function findMatchingClient(\n dagRead: Read,\n mutatorNames: string[],\n indexes: IndexDefinitions,\n): Promise<FindMatchingClientResult> {\n let newestCookie: FrozenCookie | undefined;\n let bestSnapshot: Commit<SnapshotMetaDD31> | undefined;\n const mutatorNamesSet = new Set(mutatorNames);\n\n const clientGroups = await getClientGroups(dagRead);\n for (const [clientGroupID, clientGroup] of clientGroups) {\n if (\n !clientGroup.disabled &&\n mutatorNamesEqual(mutatorNamesSet, clientGroup.mutatorNames) &&\n indexDefinitionsEqual(indexes, clientGroup.indexes)\n ) {\n // exact match\n return {\n type: FIND_MATCHING_CLIENT_TYPE_HEAD,\n clientGroupID,\n headHash: clientGroup.headHash,\n };\n }\n\n const clientGroupSnapshotCommit = await baseSnapshotFromHash(\n clientGroup.headHash,\n dagRead,\n );\n assertSnapshotCommitDD31(clientGroupSnapshotCommit);\n\n const {cookieJSON} = clientGroupSnapshotCommit.meta;\n if (\n newestCookie === undefined ||\n compareCookies(cookieJSON, newestCookie) > 0\n ) {\n newestCookie = cookieJSON;\n bestSnapshot = clientGroupSnapshotCommit;\n }\n }\n\n if (bestSnapshot) {\n return {\n type: FIND_MATCHING_CLIENT_TYPE_FORK,\n snapshot: bestSnapshot,\n };\n }\n\n return {type: FIND_MATCHING_CLIENT_TYPE_NEW};\n}\n\nfunction getRefsForClients(clients: ClientMap): Refs {\n const refs: Set<Hash> = new Set();\n for (const client of clients.values()) {\n if (isClientV6(client)) {\n for (const hash of client.refreshHashes) {\n refs.add(hash);\n }\n if (client.persistHash) {\n refs.add(client.persistHash);\n }\n } else {\n refs.add(client.headHash);\n if (client.tempRefreshHash) {\n refs.add(client.tempRefreshHash);\n }\n }\n }\n return toRefs(refs);\n}\n\nexport async function getClientGroupForClient(\n clientID: ClientID,\n read: Read,\n): Promise<ClientGroup | undefined> {\n const clientGroupID = await getClientGroupIDForClient(clientID, read);\n if (!clientGroupID) {\n return undefined;\n }\n return getClientGroup(clientGroupID, read);\n}\n\nexport async function getClientGroupIDForClient(\n clientID: ClientID,\n read: Read,\n): Promise<ClientGroupID | undefined> {\n const client = await getClient(clientID, read);\n return client?.clientGroupID;\n}\n\n/**\n * Adds a Client to the ClientMap and updates the 'clients' head to point at\n * the updated clients.\n */\nexport async function setClient(\n clientID: ClientID,\n client: Client,\n dagWrite: Write,\n): Promise<Hash> {\n const clients = await getClients(dagWrite);\n const newClients = new Map(clients).set(clientID, client);\n return setClients(newClients, dagWrite);\n}\n\n/**\n * Sets the ClientMap and updates the 'clients' head top point at the new\n * clients.\n */\nexport async function setClients(\n clients: ClientMap,\n dagWrite: Write,\n): Promise<Hash> {\n const chunkData = clientMapToChunkData(clients, dagWrite);\n const chunk = dagWrite.createChunk(chunkData, getRefsForClients(clients));\n await dagWrite.putChunk(chunk);\n await dagWrite.setHead(CLIENTS_HEAD_NAME, chunk.hash);\n return chunk.hash;\n}\n\n/**\n * Callback function for when Replicache has deleted one or more clients.\n */\nexport type OnClientsDeleted = (\n deletedClients: DeletedClients,\n) => Promise<void>;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"collect-idb-databases.js","names":[],"sources":["../../../../../replicache/src/persist/collect-idb-databases.ts"],"sourcesContent":["import type {LogContext, LogLevel, LogSink} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport {StoreImpl} from '../dag/store-impl.ts';\nimport type {Store} from '../dag/store.ts';\nimport {\n addDeletedClients,\n getDeletedClients,\n mergeDeletedClients,\n normalizeDeletedClients,\n type DeletedClients,\n type WritableDeletedClients,\n} from '../deleted-clients.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport {getKVStoreProvider} from '../get-kv-store-provider.ts';\nimport {assertHash, newRandomHash} from '../hash.ts';\nimport type {CreateStore, DropStore, StoreProvider} from '../kv/store.ts';\nimport {createLogContext} from '../log-options.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {\n clientGroupHasPendingMutations,\n getClientGroups,\n} from './client-groups.ts';\nimport type {OnClientsDeleted} from './clients.ts';\nimport {getClients} from './clients.ts';\nimport type {IndexedDBDatabase} from './idb-databases-store.ts';\nimport {IDBDatabasesStore} from './idb-databases-store.ts';\n\n/**\n * How frequently to try to collect\n */\nexport const COLLECT_IDB_INTERVAL = 12 * 60 * 60 * 1000; // 12 hours\n\n/**\n * We delay the initial collection to prevent doing it at startup.\n */\nexport const INITIAL_COLLECT_IDB_DELAY = 5 * 60 * 1000; // 5 minutes\n\nexport function initCollectIDBDatabases(\n idbDatabasesStore: IDBDatabasesStore,\n kvStoreProvider: StoreProvider,\n collectInterval: number,\n initialCollectDelay: number,\n maxAge: number,\n enableMutationRecovery: boolean,\n onClientsDeleted: OnClientsDeleted,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n let initial = true;\n initBgIntervalProcess(\n 'CollectIDBDatabases',\n async () => {\n await collectIDBDatabases(\n idbDatabasesStore,\n Date.now(),\n maxAge,\n kvStoreProvider,\n enableMutationRecovery,\n onClientsDeleted,\n );\n },\n () => {\n if (initial) {\n initial = false;\n return initialCollectDelay;\n }\n return collectInterval;\n },\n lc,\n signal,\n );\n}\n\n/**\n * Collects IDB databases that are no longer needed.\n */\nexport async function collectIDBDatabases(\n idbDatabasesStore: IDBDatabasesStore,\n now: number,\n maxAge: number,\n kvStoreProvider: StoreProvider,\n enableMutationRecovery: boolean,\n onClientsDeleted: OnClientsDeleted,\n newDagStore = defaultNewDagStore,\n): Promise<void> {\n const databases = await idbDatabasesStore.getDatabases();\n\n const dbs = Object.values(databases);\n const collectResults = await Promise.all(\n dbs.map(\n async db =>\n [\n db.name,\n await gatherDatabaseInfoForCollect(\n db,\n now,\n maxAge,\n enableMutationRecovery,\n kvStoreProvider.create,\n newDagStore,\n ),\n ] as const,\n ),\n );\n\n const dbNamesToRemove: string[] = [];\n const dbNamesToKeep: string[] = [];\n const deletedClientsToRemove: WritableDeletedClients = [];\n for (const [dbName, [canCollect, deletedClients]] of collectResults) {\n if (canCollect) {\n dbNamesToRemove.push(dbName);\n deletedClientsToRemove.push(...deletedClients);\n } else {\n dbNamesToKeep.push(dbName);\n }\n }\n\n const {errors} = await dropDatabases(\n idbDatabasesStore,\n dbNamesToRemove,\n kvStoreProvider.drop,\n );\n if (errors.length) {\n throw errors[0];\n }\n\n if (deletedClientsToRemove.length > 0) {\n // Add the deleted clients to all the dbs that survived the collection.\n let allDeletedClients: DeletedClients = deletedClientsToRemove;\n for (const name of dbNamesToKeep) {\n await withWrite(\n newDagStore(name, kvStoreProvider.create),\n async dagWrite => {\n const newDeletedClients = await addDeletedClients(\n dagWrite,\n deletedClientsToRemove,\n );\n\n allDeletedClients = mergeDeletedClients(\n allDeletedClients,\n newDeletedClients,\n );\n },\n );\n }\n // normalize and dedupe\n const normalizedDeletedClients = normalizeDeletedClients(allDeletedClients);\n\n // Call the callback with the normalized deleted clients\n await onClientsDeleted(normalizedDeletedClients);\n }\n}\n\nasync function dropDatabaseInternal(\n name: string,\n idbDatabasesStore: IDBDatabasesStore,\n kvDropStore: DropStore,\n) {\n await kvDropStore(name);\n await idbDatabasesStore.deleteDatabases([name]);\n}\n\nasync function dropDatabases(\n idbDatabasesStore: IDBDatabasesStore,\n namesToRemove: string[],\n kvDropStore: DropStore,\n): Promise<{dropped: string[]; errors: unknown[]}> {\n // Try to remove the databases in parallel. Don't let a single reject fail the\n // other ones. We will check for failures afterwards.\n const dropStoreResults = await Promise.allSettled(\n namesToRemove.map(async name => {\n await dropDatabaseInternal(name, idbDatabasesStore, kvDropStore);\n return name;\n }),\n );\n\n const dropped: string[] = [];\n const errors: unknown[] = [];\n for (const result of dropStoreResults) {\n if (result.status === 'fulfilled') {\n dropped.push(result.value);\n } else {\n errors.push(result.reason);\n }\n }\n\n return {dropped, errors};\n}\n\nfunction defaultNewDagStore(name: string, kvCreateStore: CreateStore): Store {\n const perKvStore = kvCreateStore(name);\n return new StoreImpl(perKvStore, newRandomHash, assertHash);\n}\n\n/**\n * If any client has a recent heartbeat or there are pending mutations we\n * return `[false]`. Otherwise we return `[true, deletedClients]`.\n */\nfunction gatherDatabaseInfoForCollect(\n db: IndexedDBDatabase,\n now: number,\n maxAge: number,\n enableMutationRecovery: boolean,\n kvCreateStore: CreateStore,\n newDagStore: typeof defaultNewDagStore,\n): MaybePromise<\n [canCollect: false] | [canCollect: true, deletedClients: DeletedClients]\n> {\n if (db.replicacheFormatVersion > FormatVersion.Latest) {\n return [false];\n }\n\n // If increase the format version we need to decide how to deal with this\n // logic.\n assert(\n db.replicacheFormatVersion === FormatVersion.DD31 ||\n db.replicacheFormatVersion === FormatVersion.V6 ||\n db.replicacheFormatVersion === FormatVersion.V7,\n () =>\n `Expected replicacheFormatVersion to be DD31, V6, or V7, got ${db.replicacheFormatVersion}`,\n );\n return canDatabaseBeCollectedAndGetDeletedClientIDs(\n enableMutationRecovery,\n newDagStore(db.name, kvCreateStore),\n now,\n maxAge,\n );\n}\n\n/**\n * Options for `dropDatabase` and `dropAllDatabases`.\n */\nexport type DropDatabaseOptions = {\n /**\n * Allows providing a custom implementation of the underlying storage layer.\n * Default is `'idb'`.\n */\n kvStore?: 'idb' | 'mem' | StoreProvider | undefined;\n /**\n * Determines how much logging to do. When this is set to `'debug'`,\n * Replicache will also log `'info'` and `'error'` messages. When set to\n * `'info'` we log `'info'` and `'error'` but not `'debug'`. When set to\n * `'error'` we only log `'error'` messages.\n * Default is `'info'`.\n */\n logLevel?: LogLevel | undefined;\n /**\n * Enables custom handling of logs.\n *\n * By default logs are logged to the console. If you would like logs to be\n * sent elsewhere (e.g. to a cloud logging service like DataDog) you can\n * provide an array of {@link LogSink}s. Logs at or above\n * {@link DropDatabaseOptions.logLevel} are sent to each of these {@link LogSink}s.\n * If you would still like logs to go to the console, include\n * `consoleLogSink` in the array.\n *\n * ```ts\n * logSinks: [consoleLogSink, myCloudLogSink],\n * ```\n * Default is `[consoleLogSink]`.\n */\n logSinks?: LogSink[] | undefined;\n};\n\n/**\n * Drops the specified database.\n * @param dbName The name of the database to drop.\n * @param opts Options for dropping the database.\n */\nexport async function dropDatabase(dbName: string, opts?: DropDatabaseOptions) {\n const logContext = createLogContext(opts?.logLevel, opts?.logSinks, {\n dropDatabase: undefined,\n });\n const kvStoreProvider = getKVStoreProvider(logContext, opts?.kvStore);\n await dropDatabaseInternal(\n dbName,\n new IDBDatabasesStore(kvStoreProvider.create),\n kvStoreProvider.drop,\n );\n}\n\n/**\n * Deletes all IndexedDB data associated with Replicache.\n *\n * Returns an object with the names of the successfully dropped databases\n * and any errors encountered while dropping.\n */\nexport async function dropAllDatabases(opts?: DropDatabaseOptions): Promise<{\n dropped: string[];\n errors: unknown[];\n}> {\n const logContext = createLogContext(opts?.logLevel, opts?.logSinks, {\n dropAllDatabases: undefined,\n });\n const kvStoreProvider = getKVStoreProvider(logContext, opts?.kvStore);\n const store = new IDBDatabasesStore(kvStoreProvider.create);\n const databases = await store.getDatabases();\n const dbNames = Object.values(databases).map(db => db.name);\n return dropDatabases(store, dbNames, kvStoreProvider.drop);\n}\n\n/**\n * Deletes all IndexedDB data associated with Replicache.\n *\n * Returns an object with the names of the successfully dropped databases\n * and any errors encountered while dropping.\n *\n * @deprecated Use `dropAllDatabases` instead.\n */\nexport function deleteAllReplicacheData(opts?: DropDatabaseOptions) {\n return dropAllDatabases(opts);\n}\n\n/**\n * If there are pending mutations in any of the clients in this db we return\n * `[false]`. If any client has a recent heartbeat we also return `[false]`.\n * Otherwise we return `[true, deletedClients]`.\n */\nfunction canDatabaseBeCollectedAndGetDeletedClientIDs(\n enableMutationRecovery: boolean,\n perdag: Store,\n now: number,\n maxAge: number,\n): Promise<\n [canCollect: false] | [canCollect: true, deletedClients: DeletedClients]\n> {\n return withRead(perdag, async read => {\n // If mutation recovery is disabled we do not care if there are pending\n // mutations when we decide if we can collect the database.\n if (enableMutationRecovery) {\n const clientGroups = await getClientGroups(read);\n for (const clientGroup of clientGroups.values()) {\n if (clientGroupHasPendingMutations(clientGroup)) {\n return [false];\n }\n }\n }\n\n const clients = await getClients(read);\n\n // Don't collect if any client has a recent heartbeat (is still active).\n // This is defense in depth - normally lastOpenedTimestampMS is kept fresh\n // by the heartbeat, but this check protects against edge cases.\n for (const [, client] of clients) {\n if (now - client.heartbeatTimestampMs < maxAge) {\n return [false];\n }\n }\n\n const existingDeletedClients = await getDeletedClients(read);\n const deletedClients: WritableDeletedClients = [...existingDeletedClients];\n\n // Add all current clients to the deleted clients list\n for (const [clientID, client] of clients) {\n deletedClients.push({\n clientID,\n clientGroupID: client.clientGroupID,\n });\n }\n\n // The normalization (deduping and sorting) will be done when storing\n return [true, deletedClients];\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"collect-idb-databases.js","names":[],"sources":["../../../../../replicache/src/persist/collect-idb-databases.ts"],"sourcesContent":["import type {LogContext, LogLevel, LogSink} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport {StoreImpl} from '../dag/store-impl.ts';\nimport type {Store} from '../dag/store.ts';\nimport {\n addDeletedClients,\n getDeletedClients,\n mergeDeletedClients,\n normalizeDeletedClients,\n type DeletedClients,\n type WritableDeletedClients,\n} from '../deleted-clients.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport {getKVStoreProvider} from '../get-kv-store-provider.ts';\nimport {assertHash, newRandomHash} from '../hash.ts';\nimport type {CreateStore, DropStore, StoreProvider} from '../kv/store.ts';\nimport {createLogContext} from '../log-options.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {\n clientGroupHasPendingMutations,\n getClientGroups,\n} from './client-groups.ts';\nimport type {OnClientsDeleted} from './clients.ts';\nimport {getClients} from './clients.ts';\nimport type {IndexedDBDatabase} from './idb-databases-store.ts';\nimport {IDBDatabasesStore} from './idb-databases-store.ts';\n\n/**\n * How frequently to try to collect\n */\nexport const COLLECT_IDB_INTERVAL = 12 * 60 * 60 * 1000; // 12 hours\n\n/**\n * We delay the initial collection to prevent doing it at startup.\n */\nexport const INITIAL_COLLECT_IDB_DELAY = 5 * 60 * 1000; // 5 minutes\n\nexport function initCollectIDBDatabases(\n idbDatabasesStore: IDBDatabasesStore,\n kvStoreProvider: StoreProvider,\n collectInterval: number,\n initialCollectDelay: number,\n maxAge: number,\n enableMutationRecovery: boolean,\n onClientsDeleted: OnClientsDeleted,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n let initial = true;\n initBgIntervalProcess(\n 'CollectIDBDatabases',\n async () => {\n await collectIDBDatabases(\n idbDatabasesStore,\n Date.now(),\n maxAge,\n kvStoreProvider,\n enableMutationRecovery,\n onClientsDeleted,\n );\n },\n () => {\n if (initial) {\n initial = false;\n return initialCollectDelay;\n }\n return collectInterval;\n },\n lc,\n signal,\n );\n}\n\n/**\n * Collects IDB databases that are no longer needed.\n */\nexport async function collectIDBDatabases(\n idbDatabasesStore: IDBDatabasesStore,\n now: number,\n maxAge: number,\n kvStoreProvider: StoreProvider,\n enableMutationRecovery: boolean,\n onClientsDeleted: OnClientsDeleted,\n newDagStore = defaultNewDagStore,\n): Promise<void> {\n const databases = await idbDatabasesStore.getDatabases();\n\n const dbs = Object.values(databases);\n const collectResults = await Promise.all(\n dbs.map(\n async db =>\n [\n db.name,\n await gatherDatabaseInfoForCollect(\n db,\n now,\n maxAge,\n enableMutationRecovery,\n kvStoreProvider.create,\n newDagStore,\n ),\n ] as const,\n ),\n );\n\n const dbNamesToRemove: string[] = [];\n const dbNamesToKeep: string[] = [];\n const deletedClientsToRemove: WritableDeletedClients = [];\n for (const [dbName, [canCollect, deletedClients]] of collectResults) {\n if (canCollect) {\n dbNamesToRemove.push(dbName);\n deletedClientsToRemove.push(...deletedClients);\n } else {\n dbNamesToKeep.push(dbName);\n }\n }\n\n const {errors} = await dropDatabases(\n idbDatabasesStore,\n dbNamesToRemove,\n kvStoreProvider.drop,\n );\n if (errors.length) {\n throw errors[0];\n }\n\n if (deletedClientsToRemove.length > 0) {\n // Add the deleted clients to all the dbs that survived the collection.\n let allDeletedClients: DeletedClients = deletedClientsToRemove;\n for (const name of dbNamesToKeep) {\n await withWrite(\n newDagStore(name, kvStoreProvider.create),\n async dagWrite => {\n const newDeletedClients = await addDeletedClients(\n dagWrite,\n deletedClientsToRemove,\n );\n\n allDeletedClients = mergeDeletedClients(\n allDeletedClients,\n newDeletedClients,\n );\n },\n );\n }\n // normalize and dedupe\n const normalizedDeletedClients = normalizeDeletedClients(allDeletedClients);\n\n // Call the callback with the normalized deleted clients\n await onClientsDeleted(normalizedDeletedClients);\n }\n}\n\nasync function dropDatabaseInternal(\n name: string,\n idbDatabasesStore: IDBDatabasesStore,\n kvDropStore: DropStore,\n) {\n await kvDropStore(name);\n await idbDatabasesStore.deleteDatabases([name]);\n}\n\nasync function dropDatabases(\n idbDatabasesStore: IDBDatabasesStore,\n namesToRemove: string[],\n kvDropStore: DropStore,\n): Promise<{dropped: string[]; errors: unknown[]}> {\n // Try to remove the databases in parallel. Don't let a single reject fail the\n // other ones. We will check for failures afterwards.\n const dropStoreResults = await Promise.allSettled(\n namesToRemove.map(async name => {\n await dropDatabaseInternal(name, idbDatabasesStore, kvDropStore);\n return name;\n }),\n );\n\n const dropped: string[] = [];\n const errors: unknown[] = [];\n for (const result of dropStoreResults) {\n if (result.status === 'fulfilled') {\n dropped.push(result.value);\n } else {\n errors.push(result.reason);\n }\n }\n\n return {dropped, errors};\n}\n\nfunction defaultNewDagStore(name: string, kvCreateStore: CreateStore): Store {\n const perKvStore = kvCreateStore(name);\n return new StoreImpl(perKvStore, newRandomHash, assertHash);\n}\n\n/**\n * If any client has a recent heartbeat or there are pending mutations we\n * return `[false]`. Otherwise we return `[true, deletedClients]`.\n */\nfunction gatherDatabaseInfoForCollect(\n db: IndexedDBDatabase,\n now: number,\n maxAge: number,\n enableMutationRecovery: boolean,\n kvCreateStore: CreateStore,\n newDagStore: typeof defaultNewDagStore,\n): MaybePromise<\n [canCollect: false] | [canCollect: true, deletedClients: DeletedClients]\n> {\n if (db.replicacheFormatVersion > FormatVersion.Latest) {\n return [false];\n }\n\n // If increase the format version we need to decide how to deal with this\n // logic.\n assert(\n db.replicacheFormatVersion === FormatVersion.DD31 ||\n db.replicacheFormatVersion === FormatVersion.V6 ||\n db.replicacheFormatVersion === FormatVersion.V7,\n () =>\n `Expected replicacheFormatVersion to be DD31, V6, or V7, got ${db.replicacheFormatVersion}`,\n );\n return canDatabaseBeCollectedAndGetDeletedClientIDs(\n enableMutationRecovery,\n newDagStore(db.name, kvCreateStore),\n now,\n maxAge,\n );\n}\n\n/**\n * Options for `dropDatabase` and `dropAllDatabases`.\n */\nexport type DropDatabaseOptions = {\n /**\n * Allows providing a custom implementation of the underlying storage layer.\n * Default is `'idb'`.\n */\n kvStore?: 'idb' | 'mem' | StoreProvider | undefined;\n /**\n * Determines how much logging to do. When this is set to `'debug'`,\n * Replicache will also log `'info'` and `'error'` messages. When set to\n * `'info'` we log `'info'` and `'error'` but not `'debug'`. When set to\n * `'error'` we only log `'error'` messages.\n * Default is `'info'`.\n */\n logLevel?: LogLevel | undefined;\n /**\n * Enables custom handling of logs.\n *\n * By default logs are logged to the console. If you would like logs to be\n * sent elsewhere (e.g. to a cloud logging service like DataDog) you can\n * provide an array of {@link LogSink}s. Logs at or above\n * {@link DropDatabaseOptions.logLevel} are sent to each of these {@link LogSink}s.\n * If you would still like logs to go to the console, include\n * `consoleLogSink` in the array.\n *\n * ```ts\n * logSinks: [consoleLogSink, myCloudLogSink],\n * ```\n * Default is `[consoleLogSink]`.\n */\n logSinks?: LogSink[] | undefined;\n};\n\n/**\n * Drops the specified database.\n * @param dbName The name of the database to drop.\n * @param opts Options for dropping the database.\n */\nexport async function dropDatabase(dbName: string, opts?: DropDatabaseOptions) {\n const logContext = createLogContext(opts?.logLevel, opts?.logSinks, {\n dropDatabase: undefined,\n });\n const kvStoreProvider = getKVStoreProvider(logContext, opts?.kvStore);\n await dropDatabaseInternal(\n dbName,\n new IDBDatabasesStore(kvStoreProvider.create),\n kvStoreProvider.drop,\n );\n}\n\n/**\n * Deletes all IndexedDB data associated with Replicache.\n *\n * Returns an object with the names of the successfully dropped databases\n * and any errors encountered while dropping.\n */\nexport async function dropAllDatabases(opts?: DropDatabaseOptions): Promise<{\n dropped: string[];\n errors: unknown[];\n}> {\n const logContext = createLogContext(opts?.logLevel, opts?.logSinks, {\n dropAllDatabases: undefined,\n });\n const kvStoreProvider = getKVStoreProvider(logContext, opts?.kvStore);\n const store = new IDBDatabasesStore(kvStoreProvider.create);\n const databases = await store.getDatabases();\n const dbNames = Object.values(databases).map(db => db.name);\n return dropDatabases(store, dbNames, kvStoreProvider.drop);\n}\n\n/**\n * Deletes all IndexedDB data associated with Replicache.\n *\n * Returns an object with the names of the successfully dropped databases\n * and any errors encountered while dropping.\n *\n * @deprecated Use `dropAllDatabases` instead.\n */\nexport function deleteAllReplicacheData(opts?: DropDatabaseOptions) {\n return dropAllDatabases(opts);\n}\n\n/**\n * If there are pending mutations in any of the clients in this db we return\n * `[false]`. If any client has a recent heartbeat we also return `[false]`.\n * Otherwise we return `[true, deletedClients]`.\n */\nfunction canDatabaseBeCollectedAndGetDeletedClientIDs(\n enableMutationRecovery: boolean,\n perdag: Store,\n now: number,\n maxAge: number,\n): Promise<\n [canCollect: false] | [canCollect: true, deletedClients: DeletedClients]\n> {\n return withRead(perdag, async read => {\n // If mutation recovery is disabled we do not care if there are pending\n // mutations when we decide if we can collect the database.\n if (enableMutationRecovery) {\n const clientGroups = await getClientGroups(read);\n for (const clientGroup of clientGroups.values()) {\n if (clientGroupHasPendingMutations(clientGroup)) {\n return [false];\n }\n }\n }\n\n const clients = await getClients(read);\n\n // Don't collect if any client has a recent heartbeat (is still active).\n // This is defense in depth - normally lastOpenedTimestampMS is kept fresh\n // by the heartbeat, but this check protects against edge cases.\n for (const [, client] of clients) {\n if (now - client.heartbeatTimestampMs < maxAge) {\n return [false];\n }\n }\n\n const existingDeletedClients = await getDeletedClients(read);\n const deletedClients: WritableDeletedClients = [...existingDeletedClients];\n\n // Add all current clients to the deleted clients list\n for (const [clientID, client] of clients) {\n deletedClients.push({\n clientID,\n clientGroupID: client.clientGroupID,\n });\n }\n\n // The normalization (deduping and sorting) will be done when storing\n return [true, deletedClients];\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gather-mem-only-visitor.js","names":["#gatheredChunks","#lazyRead"],"sources":["../../../../../replicache/src/persist/gather-mem-only-visitor.ts"],"sourcesContent":["import {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport type {Chunk} from '../dag/chunk.ts';\nimport type {LazyRead} from '../dag/lazy-store.ts';\nimport {Visitor} from '../dag/visitor.ts';\nimport type {Hash} from '../hash.ts';\n\nexport class GatherMemoryOnlyVisitor extends Visitor {\n readonly #gatheredChunks: Map<Hash, Chunk> = new Map();\n readonly #lazyRead: LazyRead;\n\n constructor(dagRead: LazyRead) {\n super(dagRead);\n this.#lazyRead = dagRead;\n }\n\n get gatheredChunks(): ReadonlyMap<Hash, Chunk> {\n return this.#gatheredChunks;\n }\n\n override visit(h: Hash): Promise<void> {\n if (!this.#lazyRead.isMemOnlyChunkHash(h)) {\n // Not a memory-only hash, no need to visit anything else.\n return promiseVoid;\n }\n return super.visit(h);\n }\n\n override visitChunk(chunk: Chunk): Promise<void> {\n this.#gatheredChunks.set(chunk.hash, chunk);\n return super.visitChunk(chunk);\n }\n}\n"],"mappings":";;;AAMA,IAAa,0BAAb,cAA6C,QAAQ;CACnD,kCAA6C,IAAI,IAAI;CACrD;CAEA,YAAY,SAAmB;EAC7B,MAAM,OAAO;EACb,KAAKC,YAAY;CACnB;CAEA,IAAI,iBAA2C;EAC7C,OAAO,KAAKD;CACd;CAEA,MAAe,GAAwB;EACrC,IAAI,CAAC,KAAKC,UAAU,mBAAmB,CAAC,GAEtC,OAAO;EAET,OAAO,MAAM,MAAM,CAAC;CACtB;CAEA,WAAoB,OAA6B;EAC/C,KAAKD,gBAAgB,IAAI,MAAM,MAAM,KAAK;EAC1C,OAAO,MAAM,WAAW,KAAK;CAC/B;AACF"}
1
+ {"version":3,"file":"gather-mem-only-visitor.js","names":["#gatheredChunks","#lazyRead"],"sources":["../../../../../replicache/src/persist/gather-mem-only-visitor.ts"],"sourcesContent":["import {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport type {Chunk} from '../dag/chunk.ts';\nimport type {LazyRead} from '../dag/lazy-store.ts';\nimport {Visitor} from '../dag/visitor.ts';\nimport type {Hash} from '../hash.ts';\n\nexport class GatherMemoryOnlyVisitor extends Visitor {\n readonly #gatheredChunks: Map<Hash, Chunk> = new Map();\n readonly #lazyRead: LazyRead;\n\n constructor(dagRead: LazyRead) {\n super(dagRead);\n this.#lazyRead = dagRead;\n }\n\n get gatheredChunks(): ReadonlyMap<Hash, Chunk> {\n return this.#gatheredChunks;\n }\n\n override visit(h: Hash): Promise<void> {\n if (!this.#lazyRead.isMemOnlyChunkHash(h)) {\n // Not a memory-only hash, no need to visit anything else.\n return promiseVoid;\n }\n return super.visit(h);\n }\n\n override visitChunk(chunk: Chunk): Promise<void> {\n this.#gatheredChunks.set(chunk.hash, chunk);\n return super.visitChunk(chunk);\n }\n}\n"],"mappings":";;;AAMA,IAAa,0BAAb,cAA6C,QAAQ;CACnD,kCAA6C,IAAI,KAAK;CACtD;CAEA,YAAY,SAAmB;AAC7B,QAAM,QAAQ;AACd,QAAA,WAAiB;;CAGnB,IAAI,iBAA2C;AAC7C,SAAO,MAAA;;CAGT,MAAe,GAAwB;AACrC,MAAI,CAAC,MAAA,SAAe,mBAAmB,EAAE,CAEvC,QAAO;AAET,SAAO,MAAM,MAAM,EAAE;;CAGvB,WAAoB,OAA6B;AAC/C,QAAA,eAAqB,IAAI,MAAM,MAAM,MAAM;AAC3C,SAAO,MAAM,WAAW,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"gather-not-cached-visitor.js","names":["#gatheredChunks","#lazyStore","#gatherSizeLimit","#getSizeOfChunk","#gatheredChunksTotalSize"],"sources":["../../../../../replicache/src/persist/gather-not-cached-visitor.ts"],"sourcesContent":["import {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {getSizeOfValue} from '../../../shared/src/size-of-value.ts';\nimport type {Chunk} from '../dag/chunk.ts';\nimport type {LazyStore} from '../dag/lazy-store.ts';\nimport type {Read} from '../dag/store.ts';\nimport {Visitor} from '../dag/visitor.ts';\nimport type {Hash} from '../hash.ts';\n\nexport type ChunkWithSize = {chunk: Chunk; size: number};\n\nexport class GatherNotCachedVisitor extends Visitor {\n readonly #gatheredChunks: Map<Hash, ChunkWithSize> = new Map();\n #gatheredChunksTotalSize = 0;\n readonly #lazyStore: LazyStore;\n readonly #gatherSizeLimit: number;\n readonly #getSizeOfChunk: (chunk: Chunk) => number;\n\n constructor(\n dagRead: Read,\n lazyStore: LazyStore,\n gatherSizeLimit: number,\n getSizeOfChunk: (chunk: Chunk) => number = getSizeOfValue,\n ) {\n super(dagRead);\n this.#lazyStore = lazyStore;\n this.#gatherSizeLimit = gatherSizeLimit;\n this.#getSizeOfChunk = getSizeOfChunk;\n }\n\n get gatheredChunks(): ReadonlyMap<Hash, ChunkWithSize> {\n return this.#gatheredChunks;\n }\n\n override visit(h: Hash): Promise<void> {\n if (\n this.#gatheredChunksTotalSize >= this.#gatherSizeLimit ||\n this.#lazyStore.isCached(h)\n ) {\n return promiseVoid;\n }\n return super.visit(h);\n }\n\n override visitChunk(chunk: Chunk): Promise<void> {\n if (this.#gatheredChunksTotalSize < this.#gatherSizeLimit) {\n const size = this.#getSizeOfChunk(chunk);\n this.#gatheredChunks.set(chunk.hash, {chunk, size});\n this.#gatheredChunksTotalSize += size;\n }\n\n return super.visitChunk(chunk);\n }\n}\n"],"mappings":";;;;AAUA,IAAa,yBAAb,cAA4C,QAAQ;CAClD,kCAAqD,IAAI,IAAI;CAC7D,2BAA2B;CAC3B;CACA;CACA;CAEA,YACE,SACA,WACA,iBACA,iBAA2C,gBAC3C;EACA,MAAM,OAAO;EACb,KAAKC,aAAa;EAClB,KAAKC,mBAAmB;EACxB,KAAKC,kBAAkB;CACzB;CAEA,IAAI,iBAAmD;EACrD,OAAO,KAAKH;CACd;CAEA,MAAe,GAAwB;EACrC,IACE,KAAKI,4BAA4B,KAAKF,oBACtC,KAAKD,WAAW,SAAS,CAAC,GAE1B,OAAO;EAET,OAAO,MAAM,MAAM,CAAC;CACtB;CAEA,WAAoB,OAA6B;EAC/C,IAAI,KAAKG,2BAA2B,KAAKF,kBAAkB;GACzD,MAAM,OAAO,KAAKC,gBAAgB,KAAK;GACvC,KAAKH,gBAAgB,IAAI,MAAM,MAAM;IAAC;IAAO;GAAI,CAAC;GAClD,KAAKI,4BAA4B;EACnC;EAEA,OAAO,MAAM,WAAW,KAAK;CAC/B;AACF"}
1
+ {"version":3,"file":"gather-not-cached-visitor.js","names":["#gatheredChunks","#lazyStore","#gatherSizeLimit","#getSizeOfChunk","#gatheredChunksTotalSize"],"sources":["../../../../../replicache/src/persist/gather-not-cached-visitor.ts"],"sourcesContent":["import {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {getSizeOfValue} from '../../../shared/src/size-of-value.ts';\nimport type {Chunk} from '../dag/chunk.ts';\nimport type {LazyStore} from '../dag/lazy-store.ts';\nimport type {Read} from '../dag/store.ts';\nimport {Visitor} from '../dag/visitor.ts';\nimport type {Hash} from '../hash.ts';\n\nexport type ChunkWithSize = {chunk: Chunk; size: number};\n\nexport class GatherNotCachedVisitor extends Visitor {\n readonly #gatheredChunks: Map<Hash, ChunkWithSize> = new Map();\n #gatheredChunksTotalSize = 0;\n readonly #lazyStore: LazyStore;\n readonly #gatherSizeLimit: number;\n readonly #getSizeOfChunk: (chunk: Chunk) => number;\n\n constructor(\n dagRead: Read,\n lazyStore: LazyStore,\n gatherSizeLimit: number,\n getSizeOfChunk: (chunk: Chunk) => number = getSizeOfValue,\n ) {\n super(dagRead);\n this.#lazyStore = lazyStore;\n this.#gatherSizeLimit = gatherSizeLimit;\n this.#getSizeOfChunk = getSizeOfChunk;\n }\n\n get gatheredChunks(): ReadonlyMap<Hash, ChunkWithSize> {\n return this.#gatheredChunks;\n }\n\n override visit(h: Hash): Promise<void> {\n if (\n this.#gatheredChunksTotalSize >= this.#gatherSizeLimit ||\n this.#lazyStore.isCached(h)\n ) {\n return promiseVoid;\n }\n return super.visit(h);\n }\n\n override visitChunk(chunk: Chunk): Promise<void> {\n if (this.#gatheredChunksTotalSize < this.#gatherSizeLimit) {\n const size = this.#getSizeOfChunk(chunk);\n this.#gatheredChunks.set(chunk.hash, {chunk, size});\n this.#gatheredChunksTotalSize += size;\n }\n\n return super.visitChunk(chunk);\n }\n}\n"],"mappings":";;;;AAUA,IAAa,yBAAb,cAA4C,QAAQ;CAClD,kCAAqD,IAAI,KAAK;CAC9D,2BAA2B;CAC3B;CACA;CACA;CAEA,YACE,SACA,WACA,iBACA,iBAA2C,gBAC3C;AACA,QAAM,QAAQ;AACd,QAAA,YAAkB;AAClB,QAAA,kBAAwB;AACxB,QAAA,iBAAuB;;CAGzB,IAAI,iBAAmD;AACrD,SAAO,MAAA;;CAGT,MAAe,GAAwB;AACrC,MACE,MAAA,2BAAiC,MAAA,mBACjC,MAAA,UAAgB,SAAS,EAAE,CAE3B,QAAO;AAET,SAAO,MAAM,MAAM,EAAE;;CAGvB,WAAoB,OAA6B;AAC/C,MAAI,MAAA,0BAAgC,MAAA,iBAAuB;GACzD,MAAM,OAAO,MAAA,eAAqB,MAAM;AACxC,SAAA,eAAqB,IAAI,MAAM,MAAM;IAAC;IAAO;IAAK,CAAC;AACnD,SAAA,2BAAiC;;AAGnC,SAAO,MAAM,WAAW,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat.js","names":[],"sources":["../../../../../replicache/src/persist/heartbeat.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport type {Store} from '../dag/store.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport {\n type ClientMap,\n ClientStateNotFoundError,\n getClients,\n setClients,\n} from './clients.ts';\n\nexport const HEARTBEAT_INTERVAL = 60 * 1000;\n\nexport let latestHeartbeatUpdate: Promise<ClientMap> | undefined;\n\nexport function startHeartbeats(\n clientID: ClientID,\n dagStore: Store,\n onClientStateNotFound: () => void,\n heartbeatIntervalMs: number,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n initBgIntervalProcess(\n 'Heartbeat',\n async () => {\n latestHeartbeatUpdate = writeHeartbeat(clientID, dagStore);\n try {\n return await latestHeartbeatUpdate;\n } catch (e) {\n if (e instanceof ClientStateNotFoundError) {\n onClientStateNotFound();\n return;\n }\n throw e;\n }\n },\n () => heartbeatIntervalMs,\n lc,\n signal,\n );\n}\n\nexport function writeHeartbeat(\n clientID: ClientID,\n dagStore: Store,\n): Promise<ClientMap> {\n return withWrite(dagStore, async dagWrite => {\n const clients = await getClients(dagWrite);\n const client = clients.get(clientID);\n if (!client) {\n throw new ClientStateNotFoundError(clientID);\n }\n\n const newClient = {\n ...client,\n heartbeatTimestampMs: Date.now(),\n };\n const newClients = new Map(clients).set(clientID, newClient);\n\n await setClients(newClients, dagWrite);\n return newClients;\n });\n}\n"],"mappings":";;;;AAYA,IAAa,qBAAqB,KAAK;AAEvC,IAAW;AAEX,SAAgB,gBACd,UACA,UACA,uBACA,qBACA,IACA,QACM;CACN,sBACE,aACA,YAAY;EACV,wBAAwB,eAAe,UAAU,QAAQ;EACzD,IAAI;GACF,OAAO,MAAM;EACf,SAAS,GAAG;GACV,IAAI,aAAa,0BAA0B;IACzC,sBAAsB;IACtB;GACF;GACA,MAAM;EACR;CACF,SACM,qBACN,IACA,MACF;AACF;AAEA,SAAgB,eACd,UACA,UACoB;CACpB,OAAO,UAAU,UAAU,OAAM,aAAY;EAC3C,MAAM,UAAU,MAAM,WAAW,QAAQ;EACzC,MAAM,SAAS,QAAQ,IAAI,QAAQ;EACnC,IAAI,CAAC,QACH,MAAM,IAAI,yBAAyB,QAAQ;EAG7C,MAAM,YAAY;GAChB,GAAG;GACH,sBAAsB,KAAK,IAAI;EACjC;EACA,MAAM,aAAa,IAAI,IAAI,OAAO,EAAE,IAAI,UAAU,SAAS;EAE3D,MAAM,WAAW,YAAY,QAAQ;EACrC,OAAO;CACT,CAAC;AACH"}
1
+ {"version":3,"file":"heartbeat.js","names":[],"sources":["../../../../../replicache/src/persist/heartbeat.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {initBgIntervalProcess} from '../bg-interval.ts';\nimport type {Store} from '../dag/store.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport {withWrite} from '../with-transactions.ts';\nimport {\n type ClientMap,\n ClientStateNotFoundError,\n getClients,\n setClients,\n} from './clients.ts';\n\nexport const HEARTBEAT_INTERVAL = 60 * 1000;\n\nexport let latestHeartbeatUpdate: Promise<ClientMap> | undefined;\n\nexport function startHeartbeats(\n clientID: ClientID,\n dagStore: Store,\n onClientStateNotFound: () => void,\n heartbeatIntervalMs: number,\n lc: LogContext,\n signal: AbortSignal,\n): void {\n initBgIntervalProcess(\n 'Heartbeat',\n async () => {\n latestHeartbeatUpdate = writeHeartbeat(clientID, dagStore);\n try {\n return await latestHeartbeatUpdate;\n } catch (e) {\n if (e instanceof ClientStateNotFoundError) {\n onClientStateNotFound();\n return;\n }\n throw e;\n }\n },\n () => heartbeatIntervalMs,\n lc,\n signal,\n );\n}\n\nexport function writeHeartbeat(\n clientID: ClientID,\n dagStore: Store,\n): Promise<ClientMap> {\n return withWrite(dagStore, async dagWrite => {\n const clients = await getClients(dagWrite);\n const client = clients.get(clientID);\n if (!client) {\n throw new ClientStateNotFoundError(clientID);\n }\n\n const newClient = {\n ...client,\n heartbeatTimestampMs: Date.now(),\n };\n const newClients = new Map(clients).set(clientID, newClient);\n\n await setClients(newClients, dagWrite);\n return newClients;\n });\n}\n"],"mappings":";;;;AAYA,IAAa,qBAAqB,KAAK;AAEvC,IAAW;AAEX,SAAgB,gBACd,UACA,UACA,uBACA,qBACA,IACA,QACM;AACN,uBACE,aACA,YAAY;AACV,0BAAwB,eAAe,UAAU,SAAS;AAC1D,MAAI;AACF,UAAO,MAAM;WACN,GAAG;AACV,OAAI,aAAa,0BAA0B;AACzC,2BAAuB;AACvB;;AAEF,SAAM;;UAGJ,qBACN,IACA,OACD;;AAGH,SAAgB,eACd,UACA,UACoB;AACpB,QAAO,UAAU,UAAU,OAAM,aAAY;EAC3C,MAAM,UAAU,MAAM,WAAW,SAAS;EAC1C,MAAM,SAAS,QAAQ,IAAI,SAAS;AACpC,MAAI,CAAC,OACH,OAAM,IAAI,yBAAyB,SAAS;EAG9C,MAAM,YAAY;GAChB,GAAG;GACH,sBAAsB,KAAK,KAAK;GACjC;EACD,MAAM,aAAa,IAAI,IAAI,QAAQ,CAAC,IAAI,UAAU,UAAU;AAE5D,QAAM,WAAW,YAAY,SAAS;AACtC,SAAO;GACP"}
@@ -1 +1 @@
1
- {"version":3,"file":"idb-databases-store-db-name.js","names":[],"sources":["../../../../../replicache/src/persist/idb-databases-store-db-name.ts"],"sourcesContent":["import {randomUint64} from '../../../shared/src/random-uint64.ts';\nimport {dropIDBStoreWithMemFallback} from '../kv/idb-store-with-mem-fallback.ts';\n\nconst IDB_DATABASES_VERSION = 0;\nconst IDB_DATABASES_DB_NAME = 'replicache-dbs-v' + IDB_DATABASES_VERSION;\n\nlet testNamespace = '';\n\n/** Namespace db name in test to isolate tests' indexeddb state. */\nexport function setupForTest(): void {\n testNamespace = randomUint64().toString(36);\n}\n\nexport function teardownForTest(): Promise<void> {\n const idbDatabasesDBName = getIDBDatabasesDBName();\n testNamespace = '';\n return dropIDBStoreWithMemFallback(idbDatabasesDBName);\n}\n\nexport function getIDBDatabasesDBName(): string {\n return testNamespace + IDB_DATABASES_DB_NAME;\n}\n"],"mappings":";;AAIA,IAAM,wBAAwB;AAE9B,IAAI,gBAAgB;AAapB,SAAgB,wBAAgC;CAC9C,OAAO,gBAAgB;AACzB"}
1
+ {"version":3,"file":"idb-databases-store-db-name.js","names":[],"sources":["../../../../../replicache/src/persist/idb-databases-store-db-name.ts"],"sourcesContent":["import {randomUint64} from '../../../shared/src/random-uint64.ts';\nimport {dropIDBStoreWithMemFallback} from '../kv/idb-store-with-mem-fallback.ts';\n\nconst IDB_DATABASES_VERSION = 0;\nconst IDB_DATABASES_DB_NAME = 'replicache-dbs-v' + IDB_DATABASES_VERSION;\n\nlet testNamespace = '';\n\n/** Namespace db name in test to isolate tests' indexeddb state. */\nexport function setupForTest(): void {\n testNamespace = randomUint64().toString(36);\n}\n\nexport function teardownForTest(): Promise<void> {\n const idbDatabasesDBName = getIDBDatabasesDBName();\n testNamespace = '';\n return dropIDBStoreWithMemFallback(idbDatabasesDBName);\n}\n\nexport function getIDBDatabasesDBName(): string {\n return testNamespace + IDB_DATABASES_DB_NAME;\n}\n"],"mappings":";;AAIA,IAAM,wBAAwB;AAE9B,IAAI,gBAAgB;AAapB,SAAgB,wBAAgC;AAC9C,QAAO,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"idb-databases-store.js","names":["#kvStore","#putDatabase"],"sources":["../../../../../replicache/src/persist/idb-databases-store.ts"],"sourcesContent":["import {\n assert,\n assertNumber,\n assertObject,\n assertString,\n} from '../../../shared/src/asserts.ts';\nimport {getBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport {deepFreeze} from '../frozen-json.ts';\nimport type {CreateStore, Read, Store} from '../kv/store.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {getIDBDatabasesDBName} from './idb-databases-store-db-name.ts';\nimport {makeClientID} from './make-client-id.ts';\n\nconst DBS_KEY = 'dbs';\nexport const PROFILE_ID_KEY = 'profileId';\n\n// TODO: make an opaque type\nexport type IndexedDBName = string;\n\nexport type IndexedDBDatabase = {\n readonly name: IndexedDBName;\n readonly replicacheName: string;\n readonly replicacheFormatVersion: number;\n readonly schemaVersion: string;\n /** @deprecated No longer used. Kept for backwards compatibility when reading old data. */\n readonly lastOpenedTimestampMS?: number | undefined;\n};\n\nexport type IndexedDBDatabaseRecord = {\n readonly [name: IndexedDBName]: IndexedDBDatabase;\n};\n\nfunction assertIndexedDBDatabaseRecord(\n value: unknown,\n): asserts value is IndexedDBDatabaseRecord {\n assertObject(value);\n for (const [name, db] of Object.entries(value)) {\n assertString(name);\n assertIndexedDBDatabase(db);\n assert(\n name === db.name,\n () => `Expected record key \"${name}\" to match db.name \"${db.name}\"`,\n );\n }\n}\n\nfunction assertIndexedDBDatabase(\n value: unknown,\n): asserts value is IndexedDBDatabase {\n assertObject(value);\n assertString(value.name);\n assertString(value.replicacheName);\n assertNumber(value.replicacheFormatVersion);\n assertString(value.schemaVersion);\n if (value.lastOpenedTimestampMS !== undefined) {\n assertNumber(value.lastOpenedTimestampMS);\n }\n}\n\nexport class IDBDatabasesStore {\n readonly #kvStore: Store;\n\n constructor(createKVStore: CreateStore) {\n this.#kvStore = createKVStore(getIDBDatabasesDBName());\n }\n\n putDatabase(db: IndexedDBDatabase): Promise<IndexedDBDatabaseRecord> {\n return this.#putDatabase(db);\n }\n\n putDatabaseForTesting(\n db: IndexedDBDatabase,\n ): Promise<IndexedDBDatabaseRecord> {\n return this.#putDatabase(db);\n }\n\n #putDatabase(db: IndexedDBDatabase): Promise<IndexedDBDatabaseRecord> {\n return withWrite(this.#kvStore, async write => {\n const oldDbRecord = await getDatabases(write);\n const dbRecord = {\n ...oldDbRecord,\n [db.name]: db,\n };\n await write.put(DBS_KEY, dbRecord);\n return dbRecord;\n });\n }\n\n clearDatabases(): Promise<void> {\n return withWrite(this.#kvStore, write => write.del(DBS_KEY));\n }\n\n deleteDatabases(names: Iterable<IndexedDBName>): Promise<void> {\n return withWrite(this.#kvStore, async write => {\n const oldDbRecord = await getDatabases(write);\n const dbRecord = {\n ...oldDbRecord,\n };\n for (const name of names) {\n delete dbRecord[name];\n }\n await write.put(DBS_KEY, dbRecord);\n });\n }\n\n getDatabases(): Promise<IndexedDBDatabaseRecord> {\n return withRead(this.#kvStore, getDatabases);\n }\n\n close(): Promise<void> {\n return this.#kvStore.close();\n }\n\n getProfileID(): Promise<string> {\n return withWrite(this.#kvStore, async write => {\n let profileId = await write.get(PROFILE_ID_KEY);\n if (profileId === undefined) {\n // Not in the kv store. Try localStorage in case we are using a non persistent kv store.\n const maybeLocalStorage = getBrowserGlobal('localStorage');\n if (maybeLocalStorage) {\n profileId = maybeLocalStorage.getItem(PROFILE_ID_KEY) ?? undefined;\n }\n\n if (profileId === undefined) {\n // Profile id is 'p' followed by a random number.\n profileId = `p${makeClientID()}`;\n }\n\n await write.put(PROFILE_ID_KEY, profileId);\n if (maybeLocalStorage) {\n maybeLocalStorage.setItem(PROFILE_ID_KEY, profileId);\n }\n }\n assertString(profileId);\n return profileId;\n });\n }\n}\n\nasync function getDatabases(read: Read): Promise<IndexedDBDatabaseRecord> {\n let dbRecord = await read.get(DBS_KEY);\n if (!dbRecord) {\n dbRecord = deepFreeze({});\n }\n assertIndexedDBDatabaseRecord(dbRecord);\n return dbRecord;\n}\n"],"mappings":";;;;;;;AAaA,IAAM,UAAU;AAChB,IAAa,iBAAiB;AAkB9B,SAAS,8BACP,OAC0C;CAC1C,aAAa,KAAK;CAClB,KAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,KAAK,GAAG;EAC9C,aAAa,IAAI;EACjB,wBAAwB,EAAE;EAC1B,OACE,SAAS,GAAG,YACN,wBAAwB,KAAK,sBAAsB,GAAG,KAAK,EACnE;CACF;AACF;AAEA,SAAS,wBACP,OACoC;CACpC,aAAa,KAAK;CAClB,aAAa,MAAM,IAAI;CACvB,aAAa,MAAM,cAAc;CACjC,aAAa,MAAM,uBAAuB;CAC1C,aAAa,MAAM,aAAa;CAChC,IAAI,MAAM,0BAA0B,KAAA,GAClC,aAAa,MAAM,qBAAqB;AAE5C;AAEA,IAAa,oBAAb,MAA+B;CAC7B;CAEA,YAAY,eAA4B;EACtC,KAAKA,WAAW,cAAc,sBAAsB,CAAC;CACvD;CAEA,YAAY,IAAyD;EACnE,OAAO,KAAKC,aAAa,EAAE;CAC7B;CAEA,sBACE,IACkC;EAClC,OAAO,KAAKA,aAAa,EAAE;CAC7B;CAEA,aAAa,IAAyD;EACpE,OAAO,UAAU,KAAKD,UAAU,OAAM,UAAS;GAE7C,MAAM,WAAW;IACf,GAAG,MAFqB,aAAa,KAAK;KAGzC,GAAG,OAAO;GACb;GACA,MAAM,MAAM,IAAI,SAAS,QAAQ;GACjC,OAAO;EACT,CAAC;CACH;CAEA,iBAAgC;EAC9B,OAAO,UAAU,KAAKA,WAAU,UAAS,MAAM,IAAI,OAAO,CAAC;CAC7D;CAEA,gBAAgB,OAA+C;EAC7D,OAAO,UAAU,KAAKA,UAAU,OAAM,UAAS;GAE7C,MAAM,WAAW,EACf,GAAG,MAFqB,aAAa,KAAK,EAG5C;GACA,KAAK,MAAM,QAAQ,OACjB,OAAO,SAAS;GAElB,MAAM,MAAM,IAAI,SAAS,QAAQ;EACnC,CAAC;CACH;CAEA,eAAiD;EAC/C,OAAO,SAAS,KAAKA,UAAU,YAAY;CAC7C;CAEA,QAAuB;EACrB,OAAO,KAAKA,SAAS,MAAM;CAC7B;CAEA,eAAgC;EAC9B,OAAO,UAAU,KAAKA,UAAU,OAAM,UAAS;GAC7C,IAAI,YAAY,MAAM,MAAM,IAAI,cAAc;GAC9C,IAAI,cAAc,KAAA,GAAW;IAE3B,MAAM,oBAAoB,iBAAiB,cAAc;IACzD,IAAI,mBACF,YAAY,kBAAkB,QAAA,WAAsB,KAAK,KAAA;IAG3D,IAAI,cAAc,KAAA,GAEhB,YAAY,IAAI,aAAa;IAG/B,MAAM,MAAM,IAAI,gBAAgB,SAAS;IACzC,IAAI,mBACF,kBAAkB,QAAQ,gBAAgB,SAAS;GAEvD;GACA,aAAa,SAAS;GACtB,OAAO;EACT,CAAC;CACH;AACF;AAEA,eAAe,aAAa,MAA8C;CACxE,IAAI,WAAW,MAAM,KAAK,IAAI,OAAO;CACrC,IAAI,CAAC,UACH,WAAW,WAAW,CAAC,CAAC;CAE1B,8BAA8B,QAAQ;CACtC,OAAO;AACT"}
1
+ {"version":3,"file":"idb-databases-store.js","names":["#kvStore","#putDatabase"],"sources":["../../../../../replicache/src/persist/idb-databases-store.ts"],"sourcesContent":["import {\n assert,\n assertNumber,\n assertObject,\n assertString,\n} from '../../../shared/src/asserts.ts';\nimport {getBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport {deepFreeze} from '../frozen-json.ts';\nimport type {CreateStore, Read, Store} from '../kv/store.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {getIDBDatabasesDBName} from './idb-databases-store-db-name.ts';\nimport {makeClientID} from './make-client-id.ts';\n\nconst DBS_KEY = 'dbs';\nexport const PROFILE_ID_KEY = 'profileId';\n\n// TODO: make an opaque type\nexport type IndexedDBName = string;\n\nexport type IndexedDBDatabase = {\n readonly name: IndexedDBName;\n readonly replicacheName: string;\n readonly replicacheFormatVersion: number;\n readonly schemaVersion: string;\n /** @deprecated No longer used. Kept for backwards compatibility when reading old data. */\n readonly lastOpenedTimestampMS?: number | undefined;\n};\n\nexport type IndexedDBDatabaseRecord = {\n readonly [name: IndexedDBName]: IndexedDBDatabase;\n};\n\nfunction assertIndexedDBDatabaseRecord(\n value: unknown,\n): asserts value is IndexedDBDatabaseRecord {\n assertObject(value);\n for (const [name, db] of Object.entries(value)) {\n assertString(name);\n assertIndexedDBDatabase(db);\n assert(\n name === db.name,\n () => `Expected record key \"${name}\" to match db.name \"${db.name}\"`,\n );\n }\n}\n\nfunction assertIndexedDBDatabase(\n value: unknown,\n): asserts value is IndexedDBDatabase {\n assertObject(value);\n assertString(value.name);\n assertString(value.replicacheName);\n assertNumber(value.replicacheFormatVersion);\n assertString(value.schemaVersion);\n if (value.lastOpenedTimestampMS !== undefined) {\n assertNumber(value.lastOpenedTimestampMS);\n }\n}\n\nexport class IDBDatabasesStore {\n readonly #kvStore: Store;\n\n constructor(createKVStore: CreateStore) {\n this.#kvStore = createKVStore(getIDBDatabasesDBName());\n }\n\n putDatabase(db: IndexedDBDatabase): Promise<IndexedDBDatabaseRecord> {\n return this.#putDatabase(db);\n }\n\n putDatabaseForTesting(\n db: IndexedDBDatabase,\n ): Promise<IndexedDBDatabaseRecord> {\n return this.#putDatabase(db);\n }\n\n #putDatabase(db: IndexedDBDatabase): Promise<IndexedDBDatabaseRecord> {\n return withWrite(this.#kvStore, async write => {\n const oldDbRecord = await getDatabases(write);\n const dbRecord = {\n ...oldDbRecord,\n [db.name]: db,\n };\n await write.put(DBS_KEY, dbRecord);\n return dbRecord;\n });\n }\n\n clearDatabases(): Promise<void> {\n return withWrite(this.#kvStore, write => write.del(DBS_KEY));\n }\n\n deleteDatabases(names: Iterable<IndexedDBName>): Promise<void> {\n return withWrite(this.#kvStore, async write => {\n const oldDbRecord = await getDatabases(write);\n const dbRecord = {\n ...oldDbRecord,\n };\n for (const name of names) {\n delete dbRecord[name];\n }\n await write.put(DBS_KEY, dbRecord);\n });\n }\n\n getDatabases(): Promise<IndexedDBDatabaseRecord> {\n return withRead(this.#kvStore, getDatabases);\n }\n\n close(): Promise<void> {\n return this.#kvStore.close();\n }\n\n getProfileID(): Promise<string> {\n return withWrite(this.#kvStore, async write => {\n let profileId = await write.get(PROFILE_ID_KEY);\n if (profileId === undefined) {\n // Not in the kv store. Try localStorage in case we are using a non persistent kv store.\n const maybeLocalStorage = getBrowserGlobal('localStorage');\n if (maybeLocalStorage) {\n profileId = maybeLocalStorage.getItem(PROFILE_ID_KEY) ?? undefined;\n }\n\n if (profileId === undefined) {\n // Profile id is 'p' followed by a random number.\n profileId = `p${makeClientID()}`;\n }\n\n await write.put(PROFILE_ID_KEY, profileId);\n if (maybeLocalStorage) {\n maybeLocalStorage.setItem(PROFILE_ID_KEY, profileId);\n }\n }\n assertString(profileId);\n return profileId;\n });\n }\n}\n\nasync function getDatabases(read: Read): Promise<IndexedDBDatabaseRecord> {\n let dbRecord = await read.get(DBS_KEY);\n if (!dbRecord) {\n dbRecord = deepFreeze({});\n }\n assertIndexedDBDatabaseRecord(dbRecord);\n return dbRecord;\n}\n"],"mappings":";;;;;;;AAaA,IAAM,UAAU;AAChB,IAAa,iBAAiB;AAkB9B,SAAS,8BACP,OAC0C;AAC1C,cAAa,MAAM;AACnB,MAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,MAAM,EAAE;AAC9C,eAAa,KAAK;AAClB,0BAAwB,GAAG;AAC3B,SACE,SAAS,GAAG,YACN,wBAAwB,KAAK,sBAAsB,GAAG,KAAK,GAClE;;;AAIL,SAAS,wBACP,OACoC;AACpC,cAAa,MAAM;AACnB,cAAa,MAAM,KAAK;AACxB,cAAa,MAAM,eAAe;AAClC,cAAa,MAAM,wBAAwB;AAC3C,cAAa,MAAM,cAAc;AACjC,KAAI,MAAM,0BAA0B,KAAA,EAClC,cAAa,MAAM,sBAAsB;;AAI7C,IAAa,oBAAb,MAA+B;CAC7B;CAEA,YAAY,eAA4B;AACtC,QAAA,UAAgB,cAAc,uBAAuB,CAAC;;CAGxD,YAAY,IAAyD;AACnE,SAAO,MAAA,YAAkB,GAAG;;CAG9B,sBACE,IACkC;AAClC,SAAO,MAAA,YAAkB,GAAG;;CAG9B,aAAa,IAAyD;AACpE,SAAO,UAAU,MAAA,SAAe,OAAM,UAAS;GAE7C,MAAM,WAAW;IACf,GAFkB,MAAM,aAAa,MAAM;KAG1C,GAAG,OAAO;IACZ;AACD,SAAM,MAAM,IAAI,SAAS,SAAS;AAClC,UAAO;IACP;;CAGJ,iBAAgC;AAC9B,SAAO,UAAU,MAAA,UAAe,UAAS,MAAM,IAAI,QAAQ,CAAC;;CAG9D,gBAAgB,OAA+C;AAC7D,SAAO,UAAU,MAAA,SAAe,OAAM,UAAS;GAE7C,MAAM,WAAW,EACf,GAFkB,MAAM,aAAa,MAAM,EAG5C;AACD,QAAK,MAAM,QAAQ,MACjB,QAAO,SAAS;AAElB,SAAM,MAAM,IAAI,SAAS,SAAS;IAClC;;CAGJ,eAAiD;AAC/C,SAAO,SAAS,MAAA,SAAe,aAAa;;CAG9C,QAAuB;AACrB,SAAO,MAAA,QAAc,OAAO;;CAG9B,eAAgC;AAC9B,SAAO,UAAU,MAAA,SAAe,OAAM,UAAS;GAC7C,IAAI,YAAY,MAAM,MAAM,IAAI,eAAe;AAC/C,OAAI,cAAc,KAAA,GAAW;IAE3B,MAAM,oBAAoB,iBAAiB,eAAe;AAC1D,QAAI,kBACF,aAAY,kBAAkB,QAAA,YAAuB,IAAI,KAAA;AAG3D,QAAI,cAAc,KAAA,EAEhB,aAAY,IAAI,cAAc;AAGhC,UAAM,MAAM,IAAI,gBAAgB,UAAU;AAC1C,QAAI,kBACF,mBAAkB,QAAQ,gBAAgB,UAAU;;AAGxD,gBAAa,UAAU;AACvB,UAAO;IACP;;;AAIN,eAAe,aAAa,MAA8C;CACxE,IAAI,WAAW,MAAM,KAAK,IAAI,QAAQ;AACtC,KAAI,CAAC,SACH,YAAW,WAAW,EAAE,CAAC;AAE3B,+BAA8B,SAAS;AACvC,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"make-client-id.js","names":[],"sources":["../../../../../replicache/src/persist/make-client-id.ts"],"sourcesContent":["import {randomUint64} from '../../../shared/src/random-uint64.ts';\n\n/**\n * Returns a random 18 character string encoded in base32 suitable as a client\n * ID.\n */\nexport function makeClientID(): string {\n const length = 18;\n const high = randomUint64();\n const low = randomUint64();\n const combined = (high << 64n) | low;\n return combined.toString(32).slice(-length).padStart(length, '0');\n}\n"],"mappings":";;;;;;AAMA,SAAgB,eAAuB;CACrC,MAAM,SAAS;CACf,MAAM,OAAO,aAAa;CAC1B,MAAM,MAAM,aAAa;CAEzB,QADkB,QAAQ,MAAO,KACjB,SAAS,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,QAAQ,GAAG;AAClE"}
1
+ {"version":3,"file":"make-client-id.js","names":[],"sources":["../../../../../replicache/src/persist/make-client-id.ts"],"sourcesContent":["import {randomUint64} from '../../../shared/src/random-uint64.ts';\n\n/**\n * Returns a random 18 character string encoded in base32 suitable as a client\n * ID.\n */\nexport function makeClientID(): string {\n const length = 18;\n const high = randomUint64();\n const low = randomUint64();\n const combined = (high << 64n) | low;\n return combined.toString(32).slice(-length).padStart(length, '0');\n}\n"],"mappings":";;;;;;AAMA,SAAgB,eAAuB;CACrC,MAAM,SAAS;CACf,MAAM,OAAO,cAAc;CAC3B,MAAM,MAAM,cAAc;AAE1B,SADkB,QAAQ,MAAO,KACjB,SAAS,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,QAAQ,IAAI"}