@rocicorp/zero 1.11.0-canary.8 → 1.11.0-canary.9
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.
- package/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/replicache/src/dag/gc.d.ts.map +1 -1
- package/out/replicache/src/dag/gc.js +2 -1
- package/out/replicache/src/dag/gc.js.map +1 -1
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts +12 -0
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts.map +1 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js +19 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js.map +1 -0
- package/out/replicache/src/dag/store-impl.d.ts +12 -2
- package/out/replicache/src/dag/store-impl.d.ts.map +1 -1
- package/out/replicache/src/dag/store-impl.js +22 -8
- package/out/replicache/src/dag/store-impl.js.map +1 -1
- package/out/replicache/src/db/rebase.d.ts +14 -2
- package/out/replicache/src/db/rebase.d.ts.map +1 -1
- package/out/replicache/src/db/rebase.js +27 -5
- package/out/replicache/src/db/rebase.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +10 -5
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +16 -9
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/persist.d.ts.map +1 -1
- package/out/replicache/src/persist/persist.js +12 -5
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/persist/refresh.js +6 -2
- package/out/replicache/src/persist/refresh.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +7 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +98 -11
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/replicache-options.d.ts +9 -0
- package/out/replicache/src/replicache-options.d.ts.map +1 -1
- package/out/shared/src/iterables.d.ts +2 -0
- package/out/shared/src/iterables.d.ts.map +1 -1
- package/out/shared/src/iterables.js +18 -1
- package/out/shared/src/iterables.js.map +1 -1
- package/out/shared/src/ring-buffer.d.ts +8 -0
- package/out/shared/src/ring-buffer.d.ts.map +1 -1
- package/out/shared/src/ring-buffer.js +22 -1
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/zero/package.js +2 -3
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +2 -2
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +1 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.d.ts +18 -2
- package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js +20 -7
- package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +156 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +15 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts +15 -0
- package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js +99 -0
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +14 -16
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +33 -126
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +16 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +8 -4
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts +16 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +113 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +1 -15
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +18 -21
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -3
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +8 -9
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +17 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +19 -3
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +1 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +1 -0
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +14 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +30 -4
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -2
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +6 -10
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +14 -4
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +1 -1
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/connection-manager.d.ts +20 -0
- package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-manager.js +32 -14
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.d.ts +2 -0
- package/out/zero-client/src/client/connection-status-enum.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.js +3 -1
- package/out/zero-client/src/client/connection-status-enum.js.map +1 -1
- package/out/zero-client/src/client/connection.d.ts +6 -0
- package/out/zero-client/src/client/connection.d.ts.map +1 -1
- package/out/zero-client/src/client/connection.js +2 -1
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +8 -11
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +11 -15
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/custom.js +2 -2
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/error.js +1 -1
- package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.js +2 -1
- package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +3 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/query-manager.js +42 -6
- package/out/zero-client/src/client/query-manager.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts +1 -0
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +7 -5
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts +9 -3
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +48 -11
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/types/query-result.d.ts +2 -0
- package/out/zero-client/src/types/query-result.d.ts.map +1 -1
- package/out/zero-react/src/use-query.d.ts +5 -4
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +12 -1
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-server/src/adapters/drizzle.d.ts +2 -1
- package/out/zero-server/src/adapters/drizzle.d.ts.map +1 -1
- package/out/zero-server/src/adapters/drizzle.js +12 -7
- package/out/zero-server/src/adapters/drizzle.js.map +1 -1
- package/out/zero-server/src/adapters/kysely.d.ts +2 -1
- package/out/zero-server/src/adapters/kysely.d.ts.map +1 -1
- package/out/zero-server/src/adapters/kysely.js +8 -2
- package/out/zero-server/src/adapters/kysely.js.map +1 -1
- package/out/zero-server/src/adapters/pg.d.ts +1 -0
- package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
- package/out/zero-server/src/adapters/pg.js +8 -2
- package/out/zero-server/src/adapters/pg.js.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.d.ts +1 -0
- package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.js +7 -1
- package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
- package/out/zero-server/src/adapters/prisma.d.ts +3 -2
- package/out/zero-server/src/adapters/prisma.d.ts.map +1 -1
- package/out/zero-server/src/adapters/prisma.js +8 -2
- package/out/zero-server/src/adapters/prisma.js.map +1 -1
- package/out/zero-server/src/custom.d.ts +5 -1
- package/out/zero-server/src/custom.d.ts.map +1 -1
- package/out/zero-server/src/custom.js +6 -3
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/mod.d.ts +1 -1
- package/out/zero-server/src/mod.d.ts.map +1 -1
- package/out/zero-server/src/pg-query-executor.d.ts +2 -2
- package/out/zero-server/src/pg-query-executor.d.ts.map +1 -1
- package/out/zero-server/src/pg-query-executor.js +2 -2
- package/out/zero-server/src/pg-query-executor.js.map +1 -1
- package/out/zero-server/src/schema.d.ts +2 -2
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +2 -2
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-server/src/zql-database.d.ts +21 -1
- package/out/zero-server/src/zql-database.d.ts.map +1 -1
- package/out/zero-server/src/zql-database.js +33 -2
- package/out/zero-server/src/zql-database.js.map +1 -1
- package/out/zero-solid/src/solid-view.d.ts +10 -0
- package/out/zero-solid/src/solid-view.d.ts.map +1 -1
- package/out/zero-solid/src/solid-view.js +27 -0
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts +10 -0
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +20 -8
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts.map +1 -1
- package/out/zql/src/ivm/cap.js +5 -3
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -1
- package/out/zql/src/ivm/deferred-input.js +3 -2
- package/out/zql/src/ivm/deferred-input.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts +1 -1
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.d.ts +2 -2
- package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-in.js +6 -5
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.d.ts +2 -2
- package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +6 -6
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +10 -8
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter.d.ts +2 -2
- package/out/zql/src/ivm/filter.d.ts.map +1 -1
- package/out/zql/src/ivm/filter.js +7 -4
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +2 -2
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +6 -4
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +2 -2
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +9 -9
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +3 -4
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +9 -7
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +3 -3
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/mutate/custom.d.ts +24 -1
- package/out/zql/src/mutate/custom.d.ts.map +1 -1
- package/out/zql/src/mutate/custom.js.map +1 -1
- package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
- package/out/zql/src/mutate/mutator-registry.js +2 -2
- package/out/zql/src/mutate/mutator-registry.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +2 -0
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +55 -28
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +11 -1
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +1 -0
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query.d.ts +7 -1
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.d.ts +1 -0
- package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zql/src/query/typed-view.d.ts +1 -1
- package/out/zql/src/query/typed-view.d.ts.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +7 -5
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +2 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assert } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { initBgIntervalProcess } from "../bg-interval.js";
|
|
3
|
+
import { InvalidRefCountError } from "../dag/invalid-ref-count-error.js";
|
|
3
4
|
import { assertHash, newRandomHash } from "../hash.js";
|
|
4
5
|
import { StoreImpl } from "../dag/store-impl.js";
|
|
5
6
|
import { withRead, withWrite } from "../with-transactions.js";
|
|
@@ -18,10 +19,10 @@ var COLLECT_IDB_INTERVAL = 720 * 60 * 1e3;
|
|
|
18
19
|
* We delay the initial collection to prevent doing it at startup.
|
|
19
20
|
*/
|
|
20
21
|
var INITIAL_COLLECT_IDB_DELAY = 300 * 1e3;
|
|
21
|
-
function initCollectIDBDatabases(idbDatabasesStore, kvStoreProvider, collectInterval, initialCollectDelay, maxAge, enableMutationRecovery, onClientsDeleted, lc, signal) {
|
|
22
|
+
function initCollectIDBDatabases(idbDatabasesStore, kvStoreProvider, collectInterval, initialCollectDelay, maxAge, enableMutationRecovery, onClientsDeleted, lc, signal, newDagStore = defaultNewDagStore) {
|
|
22
23
|
let initial = true;
|
|
23
24
|
initBgIntervalProcess("CollectIDBDatabases", async () => {
|
|
24
|
-
await collectIDBDatabases(idbDatabasesStore, Date.now(), maxAge, kvStoreProvider, enableMutationRecovery, onClientsDeleted);
|
|
25
|
+
await collectIDBDatabases(idbDatabasesStore, Date.now(), maxAge, kvStoreProvider, enableMutationRecovery, onClientsDeleted, newDagStore);
|
|
25
26
|
}, () => {
|
|
26
27
|
if (initial) {
|
|
27
28
|
initial = false;
|
|
@@ -47,12 +48,18 @@ async function collectIDBDatabases(idbDatabasesStore, now, maxAge, kvStoreProvid
|
|
|
47
48
|
const { errors } = await dropDatabases(idbDatabasesStore, dbNamesToRemove, kvStoreProvider.drop);
|
|
48
49
|
if (errors.length) throw errors[0];
|
|
49
50
|
if (deletedClientsToRemove.length > 0) {
|
|
50
|
-
let
|
|
51
|
-
for (const name of dbNamesToKeep)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
let newDeletedClients = deletedClientsToRemove;
|
|
52
|
+
for (const name of dbNamesToKeep) {
|
|
53
|
+
let dbDeletedClients;
|
|
54
|
+
try {
|
|
55
|
+
dbDeletedClients = await withWrite(newDagStore(name, kvStoreProvider.create), (dagWrite) => addDeletedClients(dagWrite, deletedClientsToRemove));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (!(e instanceof InvalidRefCountError)) throw e;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
newDeletedClients = mergeDeletedClients(newDeletedClients, dbDeletedClients);
|
|
61
|
+
}
|
|
62
|
+
await onClientsDeleted(normalizeDeletedClients(newDeletedClients));
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
async function dropDatabaseInternal(name, idbDatabasesStore, kvDropStore) {
|
|
@@ -128,6 +135,6 @@ function canDatabaseBeCollectedAndGetDeletedClientIDs(enableMutationRecovery, pe
|
|
|
128
135
|
});
|
|
129
136
|
}
|
|
130
137
|
//#endregion
|
|
131
|
-
export { COLLECT_IDB_INTERVAL, INITIAL_COLLECT_IDB_DELAY, dropAllDatabases, dropDatabase, initCollectIDBDatabases };
|
|
138
|
+
export { COLLECT_IDB_INTERVAL, INITIAL_COLLECT_IDB_DELAY, dropAllDatabases, dropDatabase, dropDatabaseInternal, initCollectIDBDatabases };
|
|
132
139
|
|
|
133
140
|
//# sourceMappingURL=collect-idb-databases.js.map
|
|
@@ -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 {InvalidRefCountError} from '../dag/invalid-ref-count-error.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 newDagStore: NewDagStore = defaultNewDagStore,\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 newDagStore,\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: 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 newDeletedClients: DeletedClients = deletedClientsToRemove;\n for (const name of dbNamesToKeep) {\n let dbDeletedClients: DeletedClients;\n try {\n dbDeletedClients = await withWrite(\n newDagStore(name, kvStoreProvider.create),\n dagWrite => addDeletedClients(dagWrite, deletedClientsToRemove),\n );\n } catch (e) {\n if (!(e instanceof InvalidRefCountError)) {\n throw e;\n }\n // This database is corrupt. Its own instance will find out and\n // recover the next time it writes to it; here, just skip it and keep\n // collecting into the others, instead of letting one corrupt\n // database break every collection run forever.\n continue;\n }\n newDeletedClients = mergeDeletedClients(\n newDeletedClients,\n dbDeletedClients,\n );\n }\n // normalize and dedupe\n const normalizedDeletedClients = normalizeDeletedClients(newDeletedClients);\n\n // Call the callback with the normalized deleted clients\n await onClientsDeleted(normalizedDeletedClients);\n }\n}\n\nexport async 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\n/**\n * Creates the dag store used to inspect and update a database during\n * collection. Callers can override it to install store hooks, for example so\n * a corrupt ref count found while writing deleted clients is recovered from.\n */\nexport type NewDagStore = (name: string, kvCreateStore: CreateStore) => Store;\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: NewDagStore,\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":";;;;;;;;;;;;;;;;AAiCA,IAAa,uBAAuB,MAAU,KAAK;;;;AAKnD,IAAa,4BAA4B,MAAS;AAElD,SAAgB,wBACd,mBACA,iBACA,iBACA,qBACA,QACA,wBACA,kBACA,IACA,QACA,cAA2B,oBACrB;CACN,IAAI,UAAU;CACd,sBACE,uBACA,YAAY;EACV,MAAM,oBACJ,mBACA,KAAK,IAAI,GACT,QACA,iBACA,wBACA,kBACA,WACF;CACF,SACM;EACJ,IAAI,SAAS;GACX,UAAU;GACV,OAAO;EACT;EACA,OAAO;CACT,GACA,IACA,MACF;AACF;;;;AAKA,eAAsB,oBACpB,mBACA,KACA,QACA,iBACA,wBACA,kBACA,cAA2B,oBACZ;CACf,MAAM,YAAY,MAAM,kBAAkB,aAAa;CAEvD,MAAM,MAAM,OAAO,OAAO,SAAS;CACnC,MAAM,iBAAiB,MAAM,QAAQ,IACnC,IAAI,IACF,OAAM,OACJ,CACE,GAAG,MACH,MAAM,6BACJ,IACA,KACA,QACA,wBACA,gBAAgB,QAChB,WACF,CACF,CACJ,CACF;CAEA,MAAM,kBAA4B,CAAC;CACnC,MAAM,gBAA0B,CAAC;CACjC,MAAM,yBAAiD,CAAC;CACxD,KAAK,MAAM,CAAC,QAAQ,CAAC,YAAY,oBAAoB,gBACnD,IAAI,YAAY;EACd,gBAAgB,KAAK,MAAM;EAC3B,uBAAuB,KAAK,GAAG,cAAc;CAC/C,OACE,cAAc,KAAK,MAAM;CAI7B,MAAM,EAAC,WAAU,MAAM,cACrB,mBACA,iBACA,gBAAgB,IAClB;CACA,IAAI,OAAO,QACT,MAAM,OAAO;CAGf,IAAI,uBAAuB,SAAS,GAAG;EAErC,IAAI,oBAAoC;EACxC,KAAK,MAAM,QAAQ,eAAe;GAChC,IAAI;GACJ,IAAI;IACF,mBAAmB,MAAM,UACvB,YAAY,MAAM,gBAAgB,MAAM,IACxC,aAAY,kBAAkB,UAAU,sBAAsB,CAChE;GACF,SAAS,GAAG;IACV,IAAI,EAAE,aAAa,uBACjB,MAAM;IAMR;GACF;GACA,oBAAoB,oBAClB,mBACA,gBACF;EACF;EAKA,MAAM,iBAH2B,wBAAwB,iBAGlC,CAAwB;CACjD;AACF;AAEA,eAAsB,qBACpB,MACA,mBACA,aACA;CACA,MAAM,YAAY,IAAI;CACtB,MAAM,kBAAkB,gBAAgB,CAAC,IAAI,CAAC;AAChD;AAEA,eAAe,cACb,mBACA,eACA,aACiD;CAGjD,MAAM,mBAAmB,MAAM,QAAQ,WACrC,cAAc,IAAI,OAAM,SAAQ;EAC9B,MAAM,qBAAqB,MAAM,mBAAmB,WAAW;EAC/D,OAAO;CACT,CAAC,CACH;CAEA,MAAM,UAAoB,CAAC;CAC3B,MAAM,SAAoB,CAAC;CAC3B,KAAK,MAAM,UAAU,kBACnB,IAAI,OAAO,WAAW,aACpB,QAAQ,KAAK,OAAO,KAAK;MAEzB,OAAO,KAAK,OAAO,MAAM;CAI7B,OAAO;EAAC;EAAS;CAAM;AACzB;AASA,SAAS,mBAAmB,MAAc,eAAmC;CAE3E,OAAO,IAAI,UADQ,cAAc,IACZ,GAAY,eAAe,UAAU;AAC5D;;;;;AAMA,SAAS,6BACP,IACA,KACA,QACA,wBACA,eACA,aAGA;CACA,IAAI,GAAG,0BAA0B,GAC/B,OAAO,CAAC,KAAK;CAKf,OACE,GAAG,4BAA4B,KAC7B,GAAG,4BAA4B,KAC/B,GAAG,4BAA4B,SAE/B,+DAA+D,GAAG,yBACtE;CACA,OAAO,6CACL,wBACA,YAAY,GAAG,MAAM,aAAa,GAClC,KACA,MACF;AACF;;;;;;AA0CA,eAAsB,aAAa,QAAgB,MAA4B;CAI7E,MAAM,kBAAkB,mBAHL,iBAAiB,MAAM,UAAU,MAAM,UAAU,EAClE,cAAc,KAAA,EAChB,CAC2C,GAAY,MAAM,OAAO;CACpE,MAAM,qBACJ,QACA,IAAI,kBAAkB,gBAAgB,MAAM,GAC5C,gBAAgB,IAClB;AACF;;;;;;;AAQA,eAAsB,iBAAiB,MAGpC;CAID,MAAM,kBAAkB,mBAHL,iBAAiB,MAAM,UAAU,MAAM,UAAU,EAClE,kBAAkB,KAAA,EACpB,CAC2C,GAAY,MAAM,OAAO;CACpE,MAAM,QAAQ,IAAI,kBAAkB,gBAAgB,MAAM;CAC1D,MAAM,YAAY,MAAM,MAAM,aAAa;CAE3C,OAAO,cAAc,OADL,OAAO,OAAO,SAAS,EAAE,KAAI,OAAM,GAAG,IAC1B,GAAS,gBAAgB,IAAI;AAC3D;;;;;;AAmBA,SAAS,6CACP,wBACA,QACA,KACA,QAGA;CACA,OAAO,SAAS,QAAQ,OAAM,SAAQ;EAGpC,IAAI,wBAAwB;GAC1B,MAAM,eAAe,MAAM,gBAAgB,IAAI;GAC/C,KAAK,MAAM,eAAe,aAAa,OAAO,GAC5C,IAAI,+BAA+B,WAAW,GAC5C,OAAO,CAAC,KAAK;EAGnB;EAEA,MAAM,UAAU,MAAM,WAAW,IAAI;EAKrC,KAAK,MAAM,GAAG,WAAW,SACvB,IAAI,MAAM,OAAO,uBAAuB,QACtC,OAAO,CAAC,KAAK;EAKjB,MAAM,iBAAyC,CAAC,GAAG,MADd,kBAAkB,IAAI,CACc;EAGzE,KAAK,MAAM,CAAC,UAAU,WAAW,SAC/B,eAAe,KAAK;GAClB;GACA,eAAe,OAAO;EACxB,CAAC;EAIH,OAAO,CAAC,MAAM,cAAc;CAC9B,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/persist/persist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAGtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAO,KAAK,EAAQ,MAAM,iBAAiB,CAAC;AAexD,OAAO,KAAK,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAEhE,OAAO,KAAK,EAAC,UAAU,EAAa,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAgB,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAgB7C,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,KAAK,EACb,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,MAAM,OAAO,EACrB,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,SAAS,EAChD,4BAA4B,sBAA0B,GACrD,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/persist/persist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAGtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAO,KAAK,EAAQ,MAAM,iBAAiB,CAAC;AAexD,OAAO,KAAK,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAEhE,OAAO,KAAK,EAAC,UAAU,EAAa,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAgB,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAgB7C,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,KAAK,EACb,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,MAAM,OAAO,EACrB,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,SAAS,EAChD,4BAA4B,sBAA0B,GACrD,OAAO,CAAC,IAAI,CAAC,CAmMf"}
|
|
@@ -63,7 +63,7 @@ async function persistDD31(lc, clientID, memdag, perdag, mutators, closed, forma
|
|
|
63
63
|
});
|
|
64
64
|
if (closed()) return;
|
|
65
65
|
let memdagBaseSnapshotPersisted = false;
|
|
66
|
-
|
|
66
|
+
let zeroDataForRebase = getZeroData && await getZeroData(memdagBaseSnapshot.chunk.hash);
|
|
67
67
|
await withWrite(perdag, async (perdagWrite) => {
|
|
68
68
|
const [mainClientGroup, latestPerdagMainClientGroupHeadCommit] = await getClientGroupInfo(perdagWrite, mainClientGroupID);
|
|
69
69
|
let newMainClientGroupHeadHash = latestPerdagMainClientGroupHeadCommit.chunk.hash;
|
|
@@ -85,12 +85,14 @@ async function persistDD31(lc, clientID, memdag, perdag, mutators, closed, forma
|
|
|
85
85
|
const mainClientGroupLocalMutations = await localMutationsDD31(mainClientGroup.headHash, perdagWrite);
|
|
86
86
|
lastServerAckdMutationIDs = memdagBaseSnapshot.meta.lastMutationIDs;
|
|
87
87
|
mutationIDs = { ...lastServerAckdMutationIDs };
|
|
88
|
-
|
|
88
|
+
const rebased = await rebase(mainClientGroupLocalMutations, newMainClientGroupHeadHash, perdagWrite, mutators, mutationIDs, lc, formatVersion, zeroDataForRebase);
|
|
89
|
+
newMainClientGroupHeadHash = rebased.hash;
|
|
90
|
+
zeroDataForRebase = rebased.zeroData;
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
let zeroDataForPerdagHeadCommit;
|
|
92
94
|
if (!memdagBaseSnapshotPersisted) zeroDataForPerdagHeadCommit = getZeroData && await getZeroData(newMainClientGroupHeadHash, { openLazySourceRead: perdagWrite });
|
|
93
|
-
newMainClientGroupHeadHash = await rebase(newMemdagMutations, newMainClientGroupHeadHash, perdagWrite, mutators, mutationIDs, lc, formatVersion, zeroDataForPerdagHeadCommit ??
|
|
95
|
+
newMainClientGroupHeadHash = (await rebase(newMemdagMutations, newMainClientGroupHeadHash, perdagWrite, mutators, mutationIDs, lc, formatVersion, zeroDataForPerdagHeadCommit ?? zeroDataForRebase)).hash;
|
|
94
96
|
await setClientGroup(mainClientGroupID, {
|
|
95
97
|
...mainClientGroup,
|
|
96
98
|
headHash: newMainClientGroupHeadHash,
|
|
@@ -113,10 +115,15 @@ async function rebase(mutations, basis, write, mutators, mutationIDs, lc, format
|
|
|
113
115
|
const { meta } = mutationCommit;
|
|
114
116
|
if (meta.mutationID > await getBasisMutationID(basis, meta.clientID)) {
|
|
115
117
|
mutationIDs[meta.clientID] = meta.mutationID;
|
|
116
|
-
|
|
118
|
+
const { result, zeroData: next } = await rebaseMutationAndPutCommit(mutationCommit, write, basis, mutators, lc, meta.clientID, formatVersion, zeroData);
|
|
119
|
+
basis = result.chunk.hash;
|
|
120
|
+
zeroData = next;
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
|
-
return
|
|
123
|
+
return {
|
|
124
|
+
hash: basis,
|
|
125
|
+
zeroData
|
|
126
|
+
};
|
|
120
127
|
}
|
|
121
128
|
//#endregion
|
|
122
129
|
export { persistDD31 };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persist.js","names":[],"sources":["../../../../../replicache/src/persist/persist.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {getOrInsert, getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type {Chunk} from '../dag/chunk.ts';\nimport type {LazyStore} from '../dag/lazy-store.ts';\nimport type {Read, Store, Write} from '../dag/store.ts';\nimport type {Commit} from '../db/commit.ts';\nimport {\n DEFAULT_HEAD_NAME,\n type LocalMetaDD31,\n type Meta,\n assertSnapshotCommitDD31,\n baseSnapshotFromCommit,\n commitFromHash,\n commitFromHead,\n compareCookiesForSnapshots,\n localMutationsDD31,\n localMutationsGreaterThan,\n} from '../db/commit.ts';\nimport {rebaseMutationAndPutCommit} from '../db/rebase.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {Hash} from '../hash.ts';\nimport type {ZeroOption, ZeroTxData} from '../replicache-options.ts';\nimport type {ClientGroupID, ClientID} from '../sync/ids.ts';\nimport type {MutatorDefs} from '../types.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {\n type ClientGroup,\n getClientGroup,\n setClientGroup,\n} from './client-groups.ts';\nimport {\n assertClientV6,\n assertHasClientState,\n getClientGroupIDForClient,\n mustGetClient,\n setClient,\n} from './clients.ts';\nimport {GatherMemoryOnlyVisitor} from './gather-mem-only-visitor.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\n/**\n * Persists the client's memdag state to the client's perdag client group.\n *\n * Persists the base snapshot from memdag to the client's perdag client group,\n * but only if it’s newer than the client's perdag client group’s base snapshot.\n * The base snapshot is persisted by gathering all memory-only chunks in the dag\n * subgraph rooted at the base snapshot's commit and writing them to the perdag.\n * Once the base snapshot is persisted, rebases onto this new base snapshot all\n * local commits from the client's perdag client group that are not already\n * reflected in the base snapshot.\n *\n * Whether or not the base snapshot is persisted, rebases onto the client's\n * perdag client group all memdag local commits not already in the client's\n * perdag client group's history.\n *\n * Also updates the `lastMutationIDs` and `lastServerAckdMutationIDs` properties\n * of the client's client group's entry in the `ClientGroupMap`.\n */\nexport async function persistDD31(\n lc: LogContext,\n clientID: ClientID,\n memdag: LazyStore,\n perdag: Store,\n mutators: MutatorDefs,\n closed: () => boolean,\n formatVersion: FormatVersion,\n getZeroData: ZeroOption['getTxData'] | undefined,\n onGatherMemOnlyChunksForTest = () => Promise.resolve(),\n): Promise<void> {\n if (closed()) {\n return;\n }\n\n const [perdagLMID, perdagBaseSnapshot, mainClientGroupID] = await withRead(\n perdag,\n async perdagRead => {\n await assertHasClientState(clientID, perdagRead);\n const mainClientGroupID = await getClientGroupIDForClient(\n clientID,\n perdagRead,\n );\n assert(\n mainClientGroupID,\n `No main client group for clientID: ${clientID}`,\n );\n const [, perdagMainClientGroupHeadCommit] = await getClientGroupInfo(\n perdagRead,\n mainClientGroupID,\n );\n const perdagLMID = await perdagMainClientGroupHeadCommit.getMutationID(\n clientID,\n perdagRead,\n );\n const perdagBaseSnapshot = await baseSnapshotFromCommit(\n perdagMainClientGroupHeadCommit,\n perdagRead,\n );\n assertSnapshotCommitDD31(perdagBaseSnapshot);\n return [perdagLMID, perdagBaseSnapshot, mainClientGroupID];\n },\n );\n\n if (closed()) {\n return;\n }\n const [newMemdagMutations, memdagBaseSnapshot, gatheredChunks] =\n await withRead(memdag, async memdagRead => {\n const memdagHeadCommit = await commitFromHead(\n DEFAULT_HEAD_NAME,\n memdagRead,\n );\n const newMutations = await localMutationsGreaterThan(\n memdagHeadCommit,\n {[clientID]: perdagLMID || 0},\n memdagRead,\n );\n const memdagBaseSnapshot = await baseSnapshotFromCommit(\n memdagHeadCommit,\n memdagRead,\n );\n assertSnapshotCommitDD31(memdagBaseSnapshot);\n\n let gatheredChunks: ReadonlyMap<Hash, Chunk> | undefined;\n if (\n compareCookiesForSnapshots(memdagBaseSnapshot, perdagBaseSnapshot) > 0\n ) {\n await onGatherMemOnlyChunksForTest();\n // Might need to persist snapshot, we will have to double check\n // after gathering the snapshot chunks from memdag\n const memdagBaseSnapshotHash = memdagBaseSnapshot.chunk.hash;\n // Gather all memory only chunks from base snapshot on the memdag.\n const visitor = new GatherMemoryOnlyVisitor(memdagRead);\n await visitor.visit(memdagBaseSnapshotHash);\n gatheredChunks = visitor.gatheredChunks;\n }\n\n return [newMutations, memdagBaseSnapshot, gatheredChunks];\n });\n\n if (closed()) {\n return;\n }\n\n let memdagBaseSnapshotPersisted = false;\n const zeroDataForMemdagBaseSnapshot =\n getZeroData && (await getZeroData(memdagBaseSnapshot.chunk.hash));\n\n await withWrite(perdag, async perdagWrite => {\n const [mainClientGroup, latestPerdagMainClientGroupHeadCommit] =\n await getClientGroupInfo(perdagWrite, mainClientGroupID);\n\n // These initial values for newMainClientGroupHeadHash, mutationIDs,\n // lastServerAckdMutationIDs are correct for the case where the memdag\n // snapshot is *not* persisted. If the memdag snapshot is persisted\n // these values are overwritten appropriately.\n let newMainClientGroupHeadHash: Hash =\n latestPerdagMainClientGroupHeadCommit.chunk.hash;\n let mutationIDs: Record<ClientID, number> = {\n ...mainClientGroup.mutationIDs,\n };\n let {lastServerAckdMutationIDs} = mainClientGroup;\n\n if (gatheredChunks) {\n // check if memdag snapshot still newer than perdag snapshot\n\n const client = await mustGetClient(clientID, perdagWrite);\n assertClientV6(client);\n\n const latestPerdagBaseSnapshot = await baseSnapshotFromCommit(\n latestPerdagMainClientGroupHeadCommit,\n perdagWrite,\n );\n assertSnapshotCommitDD31(latestPerdagBaseSnapshot);\n\n // check if memdag snapshot still newer than perdag snapshot\n if (\n compareCookiesForSnapshots(\n memdagBaseSnapshot,\n latestPerdagBaseSnapshot,\n ) > 0\n ) {\n // still newer, persist memdag snapshot by writing chunks\n memdagBaseSnapshotPersisted = true;\n await Promise.all(\n Array.from(gatheredChunks.values(), c => perdagWrite.putChunk(c)),\n );\n\n await setClient(\n clientID,\n {\n ...client,\n persistHash: memdagBaseSnapshot.chunk.hash,\n },\n perdagWrite,\n );\n // Rebase local mutations from perdag main client group onto new\n // snapshot\n newMainClientGroupHeadHash = memdagBaseSnapshot.chunk.hash;\n const mainClientGroupLocalMutations = await localMutationsDD31(\n mainClientGroup.headHash,\n perdagWrite,\n );\n\n lastServerAckdMutationIDs = memdagBaseSnapshot.meta.lastMutationIDs;\n mutationIDs = {...lastServerAckdMutationIDs};\n\n newMainClientGroupHeadHash = await rebase(\n mainClientGroupLocalMutations,\n newMainClientGroupHeadHash,\n perdagWrite,\n mutators,\n mutationIDs,\n lc,\n formatVersion,\n zeroDataForMemdagBaseSnapshot,\n );\n }\n }\n\n let zeroDataForPerdagHeadCommit: ZeroTxData | undefined;\n if (!memdagBaseSnapshotPersisted) {\n zeroDataForPerdagHeadCommit =\n getZeroData &&\n (await getZeroData(newMainClientGroupHeadHash, {\n openLazySourceRead: perdagWrite,\n }));\n }\n\n // rebase new memdag mutations onto perdag\n newMainClientGroupHeadHash = await rebase(\n newMemdagMutations,\n newMainClientGroupHeadHash,\n perdagWrite,\n mutators,\n mutationIDs,\n lc,\n formatVersion,\n zeroDataForPerdagHeadCommit ?? zeroDataForMemdagBaseSnapshot,\n );\n\n const newMainClientGroup = {\n ...mainClientGroup,\n headHash: newMainClientGroupHeadHash,\n mutationIDs,\n lastServerAckdMutationIDs,\n };\n\n await setClientGroup(mainClientGroupID, newMainClientGroup, perdagWrite);\n });\n\n if (gatheredChunks && memdagBaseSnapshotPersisted) {\n await withWrite(memdag, memdagWrite =>\n memdagWrite.chunksPersisted([...gatheredChunks.keys()]),\n );\n }\n}\n\nasync function getClientGroupInfo(\n perdagRead: Read,\n clientGroupID: ClientGroupID,\n): Promise<[ClientGroup, Commit<Meta>]> {\n const clientGroup = await getClientGroup(clientGroupID, perdagRead);\n assert(clientGroup, `No client group for clientGroupID: ${clientGroupID}`);\n return [clientGroup, await commitFromHash(clientGroup.headHash, perdagRead)];\n}\n\nasync function rebase(\n mutations: Commit<LocalMetaDD31>[],\n basis: Hash,\n write: Write,\n mutators: MutatorDefs,\n mutationIDs: Record<ClientID, number>,\n lc: LogContext,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<Hash> {\n const basisMutationIDs = new Map<Hash, Map<ClientID, Promise<number>>>();\n const getBasisMutationID = (\n basisHash: Hash,\n clientID: ClientID,\n ): Promise<number> =>\n getOrInsertComputed(\n getOrInsert(basisMutationIDs, basisHash, new Map()),\n clientID,\n () =>\n commitFromHash(basisHash, write).then(commit =>\n commit.getMutationID(clientID, write),\n ),\n );\n for (let i = mutations.length - 1; i >= 0; i--) {\n const mutationCommit = mutations[i];\n const {meta} = mutationCommit;\n if (meta.mutationID > (await getBasisMutationID(basis, meta.clientID))) {\n mutationIDs[meta.clientID] = meta.mutationID;\n basis = (\n await rebaseMutationAndPutCommit(\n mutationCommit,\n write,\n basis,\n mutators,\n lc,\n meta.clientID,\n formatVersion,\n zeroData,\n )\n ).chunk.hash;\n }\n }\n return basis;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"persist.js","names":[],"sources":["../../../../../replicache/src/persist/persist.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {getOrInsert, getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type {Chunk} from '../dag/chunk.ts';\nimport type {LazyStore} from '../dag/lazy-store.ts';\nimport type {Read, Store, Write} from '../dag/store.ts';\nimport type {Commit} from '../db/commit.ts';\nimport {\n DEFAULT_HEAD_NAME,\n type LocalMetaDD31,\n type Meta,\n assertSnapshotCommitDD31,\n baseSnapshotFromCommit,\n commitFromHash,\n commitFromHead,\n compareCookiesForSnapshots,\n localMutationsDD31,\n localMutationsGreaterThan,\n} from '../db/commit.ts';\nimport {rebaseMutationAndPutCommit} from '../db/rebase.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {Hash} from '../hash.ts';\nimport type {ZeroOption, ZeroTxData} from '../replicache-options.ts';\nimport type {ClientGroupID, ClientID} from '../sync/ids.ts';\nimport type {MutatorDefs} from '../types.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {\n type ClientGroup,\n getClientGroup,\n setClientGroup,\n} from './client-groups.ts';\nimport {\n assertClientV6,\n assertHasClientState,\n getClientGroupIDForClient,\n mustGetClient,\n setClient,\n} from './clients.ts';\nimport {GatherMemoryOnlyVisitor} from './gather-mem-only-visitor.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\n/**\n * Persists the client's memdag state to the client's perdag client group.\n *\n * Persists the base snapshot from memdag to the client's perdag client group,\n * but only if it’s newer than the client's perdag client group’s base snapshot.\n * The base snapshot is persisted by gathering all memory-only chunks in the dag\n * subgraph rooted at the base snapshot's commit and writing them to the perdag.\n * Once the base snapshot is persisted, rebases onto this new base snapshot all\n * local commits from the client's perdag client group that are not already\n * reflected in the base snapshot.\n *\n * Whether or not the base snapshot is persisted, rebases onto the client's\n * perdag client group all memdag local commits not already in the client's\n * perdag client group's history.\n *\n * Also updates the `lastMutationIDs` and `lastServerAckdMutationIDs` properties\n * of the client's client group's entry in the `ClientGroupMap`.\n */\nexport async function persistDD31(\n lc: LogContext,\n clientID: ClientID,\n memdag: LazyStore,\n perdag: Store,\n mutators: MutatorDefs,\n closed: () => boolean,\n formatVersion: FormatVersion,\n getZeroData: ZeroOption['getTxData'] | undefined,\n onGatherMemOnlyChunksForTest = () => Promise.resolve(),\n): Promise<void> {\n if (closed()) {\n return;\n }\n\n const [perdagLMID, perdagBaseSnapshot, mainClientGroupID] = await withRead(\n perdag,\n async perdagRead => {\n await assertHasClientState(clientID, perdagRead);\n const mainClientGroupID = await getClientGroupIDForClient(\n clientID,\n perdagRead,\n );\n assert(\n mainClientGroupID,\n `No main client group for clientID: ${clientID}`,\n );\n const [, perdagMainClientGroupHeadCommit] = await getClientGroupInfo(\n perdagRead,\n mainClientGroupID,\n );\n const perdagLMID = await perdagMainClientGroupHeadCommit.getMutationID(\n clientID,\n perdagRead,\n );\n const perdagBaseSnapshot = await baseSnapshotFromCommit(\n perdagMainClientGroupHeadCommit,\n perdagRead,\n );\n assertSnapshotCommitDD31(perdagBaseSnapshot);\n return [perdagLMID, perdagBaseSnapshot, mainClientGroupID];\n },\n );\n\n if (closed()) {\n return;\n }\n const [newMemdagMutations, memdagBaseSnapshot, gatheredChunks] =\n await withRead(memdag, async memdagRead => {\n const memdagHeadCommit = await commitFromHead(\n DEFAULT_HEAD_NAME,\n memdagRead,\n );\n const newMutations = await localMutationsGreaterThan(\n memdagHeadCommit,\n {[clientID]: perdagLMID || 0},\n memdagRead,\n );\n const memdagBaseSnapshot = await baseSnapshotFromCommit(\n memdagHeadCommit,\n memdagRead,\n );\n assertSnapshotCommitDD31(memdagBaseSnapshot);\n\n let gatheredChunks: ReadonlyMap<Hash, Chunk> | undefined;\n if (\n compareCookiesForSnapshots(memdagBaseSnapshot, perdagBaseSnapshot) > 0\n ) {\n await onGatherMemOnlyChunksForTest();\n // Might need to persist snapshot, we will have to double check\n // after gathering the snapshot chunks from memdag\n const memdagBaseSnapshotHash = memdagBaseSnapshot.chunk.hash;\n // Gather all memory only chunks from base snapshot on the memdag.\n const visitor = new GatherMemoryOnlyVisitor(memdagRead);\n await visitor.visit(memdagBaseSnapshotHash);\n gatheredChunks = visitor.gatheredChunks;\n }\n\n return [newMutations, memdagBaseSnapshot, gatheredChunks];\n });\n\n if (closed()) {\n return;\n }\n\n let memdagBaseSnapshotPersisted = false;\n // Carried across both rebase batches below: each batch continues from the\n // branch the previous one ended on.\n let zeroDataForRebase =\n getZeroData && (await getZeroData(memdagBaseSnapshot.chunk.hash));\n\n await withWrite(perdag, async perdagWrite => {\n const [mainClientGroup, latestPerdagMainClientGroupHeadCommit] =\n await getClientGroupInfo(perdagWrite, mainClientGroupID);\n\n // These initial values for newMainClientGroupHeadHash, mutationIDs,\n // lastServerAckdMutationIDs are correct for the case where the memdag\n // snapshot is *not* persisted. If the memdag snapshot is persisted\n // these values are overwritten appropriately.\n let newMainClientGroupHeadHash: Hash =\n latestPerdagMainClientGroupHeadCommit.chunk.hash;\n let mutationIDs: Record<ClientID, number> = {\n ...mainClientGroup.mutationIDs,\n };\n let {lastServerAckdMutationIDs} = mainClientGroup;\n\n if (gatheredChunks) {\n // check if memdag snapshot still newer than perdag snapshot\n\n const client = await mustGetClient(clientID, perdagWrite);\n assertClientV6(client);\n\n const latestPerdagBaseSnapshot = await baseSnapshotFromCommit(\n latestPerdagMainClientGroupHeadCommit,\n perdagWrite,\n );\n assertSnapshotCommitDD31(latestPerdagBaseSnapshot);\n\n // check if memdag snapshot still newer than perdag snapshot\n if (\n compareCookiesForSnapshots(\n memdagBaseSnapshot,\n latestPerdagBaseSnapshot,\n ) > 0\n ) {\n // still newer, persist memdag snapshot by writing chunks\n memdagBaseSnapshotPersisted = true;\n await Promise.all(\n Array.from(gatheredChunks.values(), c => perdagWrite.putChunk(c)),\n );\n\n await setClient(\n clientID,\n {\n ...client,\n persistHash: memdagBaseSnapshot.chunk.hash,\n },\n perdagWrite,\n );\n // Rebase local mutations from perdag main client group onto new\n // snapshot\n newMainClientGroupHeadHash = memdagBaseSnapshot.chunk.hash;\n const mainClientGroupLocalMutations = await localMutationsDD31(\n mainClientGroup.headHash,\n perdagWrite,\n );\n\n lastServerAckdMutationIDs = memdagBaseSnapshot.meta.lastMutationIDs;\n mutationIDs = {...lastServerAckdMutationIDs};\n\n const rebased = await rebase(\n mainClientGroupLocalMutations,\n newMainClientGroupHeadHash,\n perdagWrite,\n mutators,\n mutationIDs,\n lc,\n formatVersion,\n zeroDataForRebase,\n );\n newMainClientGroupHeadHash = rebased.hash;\n // The mutations rebased below run after these, so they have to see\n // what these wrote.\n zeroDataForRebase = rebased.zeroData;\n }\n }\n\n let zeroDataForPerdagHeadCommit: ZeroTxData | undefined;\n if (!memdagBaseSnapshotPersisted) {\n zeroDataForPerdagHeadCommit =\n getZeroData &&\n (await getZeroData(newMainClientGroupHeadHash, {\n openLazySourceRead: perdagWrite,\n }));\n }\n\n // rebase new memdag mutations onto perdag\n newMainClientGroupHeadHash = (\n await rebase(\n newMemdagMutations,\n newMainClientGroupHeadHash,\n perdagWrite,\n mutators,\n mutationIDs,\n lc,\n formatVersion,\n zeroDataForPerdagHeadCommit ?? zeroDataForRebase,\n )\n ).hash;\n\n const newMainClientGroup = {\n ...mainClientGroup,\n headHash: newMainClientGroupHeadHash,\n mutationIDs,\n lastServerAckdMutationIDs,\n };\n\n await setClientGroup(mainClientGroupID, newMainClientGroup, perdagWrite);\n });\n\n if (gatheredChunks && memdagBaseSnapshotPersisted) {\n await withWrite(memdag, memdagWrite =>\n memdagWrite.chunksPersisted([...gatheredChunks.keys()]),\n );\n }\n}\n\nasync function getClientGroupInfo(\n perdagRead: Read,\n clientGroupID: ClientGroupID,\n): Promise<[ClientGroup, Commit<Meta>]> {\n const clientGroup = await getClientGroup(clientGroupID, perdagRead);\n assert(clientGroup, `No client group for clientGroupID: ${clientGroupID}`);\n return [clientGroup, await commitFromHash(clientGroup.headHash, perdagRead)];\n}\n\ntype RebasedBatch = {\n hash: Hash;\n zeroData: ZeroTxData | undefined;\n};\n\nasync function rebase(\n mutations: Commit<LocalMetaDD31>[],\n basis: Hash,\n write: Write,\n mutators: MutatorDefs,\n mutationIDs: Record<ClientID, number>,\n lc: LogContext,\n formatVersion: FormatVersion,\n zeroData: ZeroTxData | undefined,\n): Promise<RebasedBatch> {\n const basisMutationIDs = new Map<Hash, Map<ClientID, Promise<number>>>();\n const getBasisMutationID = (\n basisHash: Hash,\n clientID: ClientID,\n ): Promise<number> =>\n getOrInsertComputed(\n getOrInsert(basisMutationIDs, basisHash, new Map()),\n clientID,\n () =>\n commitFromHash(basisHash, write).then(commit =>\n commit.getMutationID(clientID, write),\n ),\n );\n for (let i = mutations.length - 1; i >= 0; i--) {\n const mutationCommit = mutations[i];\n const {meta} = mutationCommit;\n if (meta.mutationID > (await getBasisMutationID(basis, meta.clientID))) {\n mutationIDs[meta.clientID] = meta.mutationID;\n const {result, zeroData: next} = await rebaseMutationAndPutCommit(\n mutationCommit,\n write,\n basis,\n mutators,\n lc,\n meta.clientID,\n formatVersion,\n zeroData,\n );\n basis = result.chunk.hash;\n zeroData = next;\n }\n }\n return {hash: basis, zeroData};\n}\n"],"mappings":"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"}
|
|
@@ -58,8 +58,12 @@ async function refresh(lc, memdag, perdag, clientID, mutators, diffConfig, close
|
|
|
58
58
|
await Promise.all(ps);
|
|
59
59
|
let newMemdagHeadHash = perdagClientGroupHeadHash;
|
|
60
60
|
if (newMemdagMutations.length > 0) {
|
|
61
|
-
|
|
62
|
-
for (let i = newMemdagMutations.length - 1; i >= 0; i--)
|
|
61
|
+
let zeroData = await zero?.getTxData?.(newMemdagHeadHash, { openLazyRead: memdagWrite });
|
|
62
|
+
for (let i = newMemdagMutations.length - 1; i >= 0; i--) {
|
|
63
|
+
const { result, zeroData: next } = await rebaseMutationAndPutCommit(newMemdagMutations[i], memdagWrite, newMemdagHeadHash, mutators, lc, newMemdagMutations[i].meta.clientID, formatVersion, zeroData);
|
|
64
|
+
newMemdagHeadHash = result.chunk.hash;
|
|
65
|
+
zeroData = next;
|
|
66
|
+
}
|
|
63
67
|
}
|
|
64
68
|
const diffs = await diffCommits(memdagHeadCommit, await commitFromHash(newMemdagHeadHash, memdagWrite), memdagWrite, diffConfig, formatVersion);
|
|
65
69
|
await memdagWrite.setHead(DEFAULT_HEAD_NAME, newMemdagHeadHash);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh.js","names":[],"sources":["../../../../../replicache/src/persist/refresh.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport type {LazyStore} from '../dag/lazy-store.ts';\nimport type {Store} from '../dag/store.ts';\nimport type {Commit} from '../db/commit.ts';\nimport {\n DEFAULT_HEAD_NAME,\n type SnapshotMetaDD31,\n assertSnapshotCommitDD31,\n baseSnapshotFromCommit,\n baseSnapshotFromHash,\n baseSnapshotFromHead,\n commitFromHash,\n commitFromHead,\n compareCookiesForSnapshots,\n localMutationsGreaterThan,\n} from '../db/commit.ts';\nimport {rebaseMutationAndPutCommit} from '../db/rebase.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {Hash} from '../hash.ts';\nimport type {ZeroOption} from '../replicache-options.ts';\nimport type {DiffsMap} from '../sync/diff.ts';\nimport {type DiffComputationConfig, diffCommits} from '../sync/diff.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport type {MutatorDefs} from '../types.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {\n ClientStateNotFoundError,\n type ClientV6,\n assertClientV6,\n getClientGroupForClient,\n mustGetClient,\n setClient,\n} from './clients.ts';\nimport {\n type ChunkWithSize,\n GatherNotCachedVisitor,\n} from './gather-not-cached-visitor.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nconst GATHER_SIZE_LIMIT = 5 * 2 ** 20; // 5 MB\n\ntype RefreshResult =\n | {\n type: 'aborted';\n refreshHashesForRevert?: readonly Hash[] | undefined;\n }\n | {\n type: 'complete';\n diffs: DiffsMap;\n newPerdagClientHeadHash: Hash;\n oldHead: Hash;\n newHead: Hash;\n };\n\n/**\n * This returns the diff between the state of the btree before and after\n * refresh. It returns `undefined` if the refresh was aborted.\n */\nexport async function refresh(\n lc: LogContext,\n memdag: LazyStore,\n perdag: Store,\n clientID: ClientID,\n mutators: MutatorDefs,\n diffConfig: DiffComputationConfig,\n closed: () => boolean,\n formatVersion: FormatVersion,\n zero: ZeroOption | undefined,\n): Promise<{oldHead: Hash; newHead: Hash; diffs: DiffsMap} | undefined> {\n if (closed()) {\n return;\n }\n const memdagBaseSnapshot = await withRead(memdag, memdagRead =>\n baseSnapshotFromHead(DEFAULT_HEAD_NAME, memdagRead),\n );\n assertSnapshotCommitDD31(memdagBaseSnapshot);\n\n type PerdagWriteResult = [\n perdagClientGroupHeadHash: Hash,\n perdagClientGroupBaseSnapshot: Commit<SnapshotMetaDD31>,\n perdagLmid: number,\n gatheredChunks: ReadonlyMap<Hash, ChunkWithSize>,\n refreshHashesForRevert: readonly Hash[],\n ];\n\n // Suspend eviction and deletion of chunks cached by the lazy store\n // to prevent cache misses. If eviction and deletion are not suspended\n // some chunks that are not gathered due to already being cached, may be\n // evicted or deleted by the time the write lock is acquired on the memdag,\n // which can lead to cache misses when performing the rebase and diff.\n // It is important to avoid these cache misses because they often create jank\n // because they block local mutations, pulls and queries on reading from idb.\n // Cache misses can still happen during the rebase and diff, but only\n // if the gather step hits its size limit.\n const result: RefreshResult =\n await memdag.withSuspendedSourceCacheEvictsAndDeletes(async () => {\n const perdagWriteResult: PerdagWriteResult | undefined = await withWrite(\n perdag,\n async perdagWrite => {\n const clientGroup = await getClientGroupForClient(\n clientID,\n perdagWrite,\n );\n if (!clientGroup) {\n throw new ClientStateNotFoundError(clientID);\n }\n\n const perdagClientGroupHeadHash = clientGroup.headHash;\n const perdagClientGroupHeadCommit = await commitFromHash(\n perdagClientGroupHeadHash,\n perdagWrite,\n );\n const perdagLmid = await perdagClientGroupHeadCommit.getMutationID(\n clientID,\n perdagWrite,\n );\n\n // Need to pull this head into memdag, but can't have it disappear if\n // perdag moves forward while we're rebasing in memdag. Can't change\n // client headHash until our rebase in memdag is complete, because if\n // rebase fails, then nothing is keeping client's chunks alive in\n // perdag.\n const client = await mustGetClient(clientID, perdagWrite);\n assertClientV6(client);\n const perdagClientGroupBaseSnapshot = await baseSnapshotFromHash(\n perdagClientGroupHeadHash,\n perdagWrite,\n );\n assertSnapshotCommitDD31(perdagClientGroupBaseSnapshot);\n if (\n shouldAbortRefresh(\n memdagBaseSnapshot,\n perdagClientGroupBaseSnapshot,\n perdagClientGroupHeadHash,\n )\n ) {\n return undefined;\n }\n\n // To avoid pulling the entire perdag graph into the memdag\n // the amount of chunk data gathered is limited by size.\n const visitor = new GatherNotCachedVisitor(\n perdagWrite,\n memdag,\n GATHER_SIZE_LIMIT,\n );\n await visitor.visit(perdagClientGroupHeadHash);\n const {gatheredChunks} = visitor;\n\n const refreshHashesSet = new Set(client.refreshHashes);\n refreshHashesSet.add(perdagClientGroupHeadHash);\n\n const newClient: ClientV6 = {\n ...client,\n refreshHashes: [...refreshHashesSet],\n };\n\n await setClient(clientID, newClient, perdagWrite);\n return [\n perdagClientGroupHeadHash,\n perdagClientGroupBaseSnapshot,\n perdagLmid,\n gatheredChunks,\n client.refreshHashes,\n ];\n },\n );\n\n if (closed() || !perdagWriteResult) {\n return {\n type: 'aborted',\n } as const;\n }\n\n const [\n perdagClientGroupHeadHash,\n perdagClientGroupBaseSnapshot,\n perdagLmid,\n gatheredChunks,\n refreshHashesForRevert,\n ] = perdagWriteResult;\n return withWrite(memdag, async memdagWrite => {\n const memdagHeadCommit = await commitFromHead(\n DEFAULT_HEAD_NAME,\n memdagWrite,\n );\n const memdagBaseSnapshot = await baseSnapshotFromCommit(\n memdagHeadCommit,\n memdagWrite,\n );\n assertSnapshotCommitDD31(memdagBaseSnapshot);\n if (\n shouldAbortRefresh(\n memdagBaseSnapshot,\n perdagClientGroupBaseSnapshot,\n perdagClientGroupHeadHash,\n )\n ) {\n return {\n type: 'aborted',\n refreshHashesForRevert,\n };\n }\n\n const newMemdagMutations = await localMutationsGreaterThan(\n memdagHeadCommit,\n {[clientID]: perdagLmid},\n memdagWrite,\n );\n const ps = [];\n for (const {chunk, size} of gatheredChunks.values()) {\n ps.push(memdagWrite.putChunk(chunk, size));\n }\n await Promise.all(ps);\n\n let newMemdagHeadHash = perdagClientGroupHeadHash;\n if (newMemdagMutations.length > 0) {\n const zeroData = await zero?.getTxData?.(newMemdagHeadHash, {\n openLazyRead: memdagWrite,\n });\n for (let i = newMemdagMutations.length - 1; i >= 0; i--) {\n newMemdagHeadHash = (\n await rebaseMutationAndPutCommit(\n newMemdagMutations[i],\n memdagWrite,\n newMemdagHeadHash,\n mutators,\n lc,\n newMemdagMutations[i].meta.clientID,\n formatVersion,\n zeroData,\n )\n ).chunk.hash;\n }\n }\n\n const newMemdagHeadCommit = await commitFromHash(\n newMemdagHeadHash,\n memdagWrite,\n );\n const diffs = await diffCommits(\n memdagHeadCommit,\n newMemdagHeadCommit,\n memdagWrite,\n diffConfig,\n formatVersion,\n );\n\n await memdagWrite.setHead(DEFAULT_HEAD_NAME, newMemdagHeadHash);\n return {\n type: 'complete',\n diffs,\n oldHead: memdagHeadCommit.chunk.hash,\n newHead: newMemdagHeadHash,\n newPerdagClientHeadHash: perdagClientGroupHeadHash,\n } as const;\n });\n });\n\n if (closed()) {\n return;\n }\n\n const setRefreshHashes = (refreshHashes: readonly Hash[]) =>\n withWrite(perdag, async perdagWrite => {\n const client = await mustGetClient(clientID, perdagWrite);\n const newClient = {\n ...client,\n refreshHashes,\n };\n\n // If this cleanup never happens, it's no big deal, some data will stay\n // alive longer but next refresh will fix it.\n await setClient(clientID, newClient, perdagWrite);\n });\n\n if (result.type === 'aborted') {\n if (result.refreshHashesForRevert) {\n await setRefreshHashes(result.refreshHashesForRevert);\n }\n return undefined;\n }\n\n // Advance zero here before setting refresh hashes\n // since we must advance before delegating control of the microtask\n // loop.\n zero?.advance(result.oldHead, result.newHead, result.diffs.get('') ?? []);\n await setRefreshHashes([result.newPerdagClientHeadHash]);\n return {\n oldHead: result.oldHead,\n newHead: result.newHead,\n diffs: result.diffs,\n };\n}\n\nfunction shouldAbortRefresh(\n memdagBaseSnapshot: Commit<SnapshotMetaDD31>,\n perdagClientGroupBaseSnapshot: Commit<SnapshotMetaDD31>,\n perdagClientGroupHeadHash: Hash,\n): boolean {\n const baseSnapshotCookieCompareResult = compareCookiesForSnapshots(\n memdagBaseSnapshot,\n perdagClientGroupBaseSnapshot,\n );\n return (\n baseSnapshotCookieCompareResult > 0 ||\n (baseSnapshotCookieCompareResult === 0 &&\n perdagClientGroupHeadHash === perdagClientGroupBaseSnapshot.chunk.hash)\n );\n}\n"],"mappings":";;;;;;;AAyCA,IAAM,oBAAoB,IAAI,KAAK;;;;;AAmBnC,eAAsB,QACpB,IACA,QACA,QACA,UACA,UACA,YACA,QACA,eACA,MACsE;CACtE,IAAI,OAAO,GACT;CAEF,MAAM,qBAAqB,MAAM,SAAS,SAAQ,eAChD,qBAAqB,mBAAmB,UAAU,CACpD;CACA,yBAAyB,kBAAkB;CAmB3C,MAAM,SACJ,MAAM,OAAO,yCAAyC,YAAY;EAChE,MAAM,oBAAmD,MAAM,UAC7D,QACA,OAAM,gBAAe;GACnB,MAAM,cAAc,MAAM,wBACxB,UACA,WACF;GACA,IAAI,CAAC,aACH,MAAM,IAAI,yBAAyB,QAAQ;GAG7C,MAAM,4BAA4B,YAAY;GAK9C,MAAM,aAAa,OAAM,MAJiB,eACxC,2BACA,WACF,GACqD,cACnD,UACA,WACF;GAOA,MAAM,SAAS,MAAM,cAAc,UAAU,WAAW;GACxD,eAAe,MAAM;GACrB,MAAM,gCAAgC,MAAM,qBAC1C,2BACA,WACF;GACA,yBAAyB,6BAA6B;GACtD,IACE,mBACE,oBACA,+BACA,yBACF,GAEA;GAKF,MAAM,UAAU,IAAI,uBAClB,aACA,QACA,iBACF;GACA,MAAM,QAAQ,MAAM,yBAAyB;GAC7C,MAAM,EAAC,mBAAkB;GAEzB,MAAM,mBAAmB,IAAI,IAAI,OAAO,aAAa;GACrD,iBAAiB,IAAI,yBAAyB;GAO9C,MAAM,UAAU,UAAU;IAJxB,GAAG;IACH,eAAe,CAAC,GAAG,gBAAgB;GAGX,GAAW,WAAW;GAChD,OAAO;IACL;IACA;IACA;IACA;IACA,OAAO;GACT;EACF,CACF;EAEA,IAAI,OAAO,KAAK,CAAC,mBACf,OAAO,EACL,MAAM,UACR;EAGF,MAAM,CACJ,2BACA,+BACA,YACA,gBACA,0BACE;EACJ,OAAO,UAAU,QAAQ,OAAM,gBAAe;GAC5C,MAAM,mBAAmB,MAAM,eAC7B,mBACA,WACF;GACA,MAAM,qBAAqB,MAAM,uBAC/B,kBACA,WACF;GACA,yBAAyB,kBAAkB;GAC3C,IACE,mBACE,oBACA,+BACA,yBACF,GAEA,OAAO;IACL,MAAM;IACN;GACF;GAGF,MAAM,qBAAqB,MAAM,0BAC/B,kBACA,GAAE,WAAW,WAAU,GACvB,WACF;GACA,MAAM,KAAK,CAAC;GACZ,KAAK,MAAM,EAAC,OAAO,UAAS,eAAe,OAAO,GAChD,GAAG,KAAK,YAAY,SAAS,OAAO,IAAI,CAAC;GAE3C,MAAM,QAAQ,IAAI,EAAE;GAEpB,IAAI,oBAAoB;GACxB,IAAI,mBAAmB,SAAS,GAAG;IACjC,MAAM,WAAW,MAAM,MAAM,YAAY,mBAAmB,EAC1D,cAAc,YAChB,CAAC;IACD,KAAK,IAAI,IAAI,mBAAmB,SAAS,GAAG,KAAK,GAAG,KAClD,qBACE,MAAM,2BACJ,mBAAmB,IACnB,aACA,mBACA,UACA,IACA,mBAAmB,GAAG,KAAK,UAC3B,eACA,QACF,GACA,MAAM;GAEZ;GAMA,MAAM,QAAQ,MAAM,YAClB,kBACA,MANgC,eAChC,mBACA,WACF,GAIE,aACA,YACA,aACF;GAEA,MAAM,YAAY,QAAQ,mBAAmB,iBAAiB;GAC9D,OAAO;IACL,MAAM;IACN;IACA,SAAS,iBAAiB,MAAM;IAChC,SAAS;IACT,yBAAyB;GAC3B;EACF,CAAC;CACH,CAAC;CAEH,IAAI,OAAO,GACT;CAGF,MAAM,oBAAoB,kBACxB,UAAU,QAAQ,OAAM,gBAAe;EASrC,MAAM,UAAU,UAAU;GANxB,GAAG,MAFgB,cAAc,UAAU,WAAW;GAGtD;EAKwB,GAAW,WAAW;CAClD,CAAC;CAEH,IAAI,OAAO,SAAS,WAAW;EAC7B,IAAI,OAAO,wBACT,MAAM,iBAAiB,OAAO,sBAAsB;EAEtD;CACF;CAKA,MAAM,QAAQ,OAAO,SAAS,OAAO,SAAS,OAAO,MAAM,IAAI,EAAE,KAAK,CAAC,CAAC;CACxE,MAAM,iBAAiB,CAAC,OAAO,uBAAuB,CAAC;CACvD,OAAO;EACL,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,OAAO,OAAO;CAChB;AACF;AAEA,SAAS,mBACP,oBACA,+BACA,2BACS;CACT,MAAM,kCAAkC,2BACtC,oBACA,6BACF;CACA,OACE,kCAAkC,KACjC,oCAAoC,KACnC,8BAA8B,8BAA8B,MAAM;AAExE"}
|
|
1
|
+
{"version":3,"file":"refresh.js","names":[],"sources":["../../../../../replicache/src/persist/refresh.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport type {LazyStore} from '../dag/lazy-store.ts';\nimport type {Store} from '../dag/store.ts';\nimport type {Commit} from '../db/commit.ts';\nimport {\n DEFAULT_HEAD_NAME,\n type SnapshotMetaDD31,\n assertSnapshotCommitDD31,\n baseSnapshotFromCommit,\n baseSnapshotFromHash,\n baseSnapshotFromHead,\n commitFromHash,\n commitFromHead,\n compareCookiesForSnapshots,\n localMutationsGreaterThan,\n} from '../db/commit.ts';\nimport {rebaseMutationAndPutCommit} from '../db/rebase.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {Hash} from '../hash.ts';\nimport type {ZeroOption} from '../replicache-options.ts';\nimport type {DiffsMap} from '../sync/diff.ts';\nimport {type DiffComputationConfig, diffCommits} from '../sync/diff.ts';\nimport type {ClientID} from '../sync/ids.ts';\nimport type {MutatorDefs} from '../types.ts';\nimport {withRead, withWrite} from '../with-transactions.ts';\nimport {\n ClientStateNotFoundError,\n type ClientV6,\n assertClientV6,\n getClientGroupForClient,\n mustGetClient,\n setClient,\n} from './clients.ts';\nimport {\n type ChunkWithSize,\n GatherNotCachedVisitor,\n} from './gather-not-cached-visitor.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nconst GATHER_SIZE_LIMIT = 5 * 2 ** 20; // 5 MB\n\ntype RefreshResult =\n | {\n type: 'aborted';\n refreshHashesForRevert?: readonly Hash[] | undefined;\n }\n | {\n type: 'complete';\n diffs: DiffsMap;\n newPerdagClientHeadHash: Hash;\n oldHead: Hash;\n newHead: Hash;\n };\n\n/**\n * This returns the diff between the state of the btree before and after\n * refresh. It returns `undefined` if the refresh was aborted.\n */\nexport async function refresh(\n lc: LogContext,\n memdag: LazyStore,\n perdag: Store,\n clientID: ClientID,\n mutators: MutatorDefs,\n diffConfig: DiffComputationConfig,\n closed: () => boolean,\n formatVersion: FormatVersion,\n zero: ZeroOption | undefined,\n): Promise<{oldHead: Hash; newHead: Hash; diffs: DiffsMap} | undefined> {\n if (closed()) {\n return;\n }\n const memdagBaseSnapshot = await withRead(memdag, memdagRead =>\n baseSnapshotFromHead(DEFAULT_HEAD_NAME, memdagRead),\n );\n assertSnapshotCommitDD31(memdagBaseSnapshot);\n\n type PerdagWriteResult = [\n perdagClientGroupHeadHash: Hash,\n perdagClientGroupBaseSnapshot: Commit<SnapshotMetaDD31>,\n perdagLmid: number,\n gatheredChunks: ReadonlyMap<Hash, ChunkWithSize>,\n refreshHashesForRevert: readonly Hash[],\n ];\n\n // Suspend eviction and deletion of chunks cached by the lazy store\n // to prevent cache misses. If eviction and deletion are not suspended\n // some chunks that are not gathered due to already being cached, may be\n // evicted or deleted by the time the write lock is acquired on the memdag,\n // which can lead to cache misses when performing the rebase and diff.\n // It is important to avoid these cache misses because they often create jank\n // because they block local mutations, pulls and queries on reading from idb.\n // Cache misses can still happen during the rebase and diff, but only\n // if the gather step hits its size limit.\n const result: RefreshResult =\n await memdag.withSuspendedSourceCacheEvictsAndDeletes(async () => {\n const perdagWriteResult: PerdagWriteResult | undefined = await withWrite(\n perdag,\n async perdagWrite => {\n const clientGroup = await getClientGroupForClient(\n clientID,\n perdagWrite,\n );\n if (!clientGroup) {\n throw new ClientStateNotFoundError(clientID);\n }\n\n const perdagClientGroupHeadHash = clientGroup.headHash;\n const perdagClientGroupHeadCommit = await commitFromHash(\n perdagClientGroupHeadHash,\n perdagWrite,\n );\n const perdagLmid = await perdagClientGroupHeadCommit.getMutationID(\n clientID,\n perdagWrite,\n );\n\n // Need to pull this head into memdag, but can't have it disappear if\n // perdag moves forward while we're rebasing in memdag. Can't change\n // client headHash until our rebase in memdag is complete, because if\n // rebase fails, then nothing is keeping client's chunks alive in\n // perdag.\n const client = await mustGetClient(clientID, perdagWrite);\n assertClientV6(client);\n const perdagClientGroupBaseSnapshot = await baseSnapshotFromHash(\n perdagClientGroupHeadHash,\n perdagWrite,\n );\n assertSnapshotCommitDD31(perdagClientGroupBaseSnapshot);\n if (\n shouldAbortRefresh(\n memdagBaseSnapshot,\n perdagClientGroupBaseSnapshot,\n perdagClientGroupHeadHash,\n )\n ) {\n return undefined;\n }\n\n // To avoid pulling the entire perdag graph into the memdag\n // the amount of chunk data gathered is limited by size.\n const visitor = new GatherNotCachedVisitor(\n perdagWrite,\n memdag,\n GATHER_SIZE_LIMIT,\n );\n await visitor.visit(perdagClientGroupHeadHash);\n const {gatheredChunks} = visitor;\n\n const refreshHashesSet = new Set(client.refreshHashes);\n refreshHashesSet.add(perdagClientGroupHeadHash);\n\n const newClient: ClientV6 = {\n ...client,\n refreshHashes: [...refreshHashesSet],\n };\n\n await setClient(clientID, newClient, perdagWrite);\n return [\n perdagClientGroupHeadHash,\n perdagClientGroupBaseSnapshot,\n perdagLmid,\n gatheredChunks,\n client.refreshHashes,\n ];\n },\n );\n\n if (closed() || !perdagWriteResult) {\n return {\n type: 'aborted',\n } as const;\n }\n\n const [\n perdagClientGroupHeadHash,\n perdagClientGroupBaseSnapshot,\n perdagLmid,\n gatheredChunks,\n refreshHashesForRevert,\n ] = perdagWriteResult;\n return withWrite(memdag, async memdagWrite => {\n const memdagHeadCommit = await commitFromHead(\n DEFAULT_HEAD_NAME,\n memdagWrite,\n );\n const memdagBaseSnapshot = await baseSnapshotFromCommit(\n memdagHeadCommit,\n memdagWrite,\n );\n assertSnapshotCommitDD31(memdagBaseSnapshot);\n if (\n shouldAbortRefresh(\n memdagBaseSnapshot,\n perdagClientGroupBaseSnapshot,\n perdagClientGroupHeadHash,\n )\n ) {\n return {\n type: 'aborted',\n refreshHashesForRevert,\n };\n }\n\n const newMemdagMutations = await localMutationsGreaterThan(\n memdagHeadCommit,\n {[clientID]: perdagLmid},\n memdagWrite,\n );\n const ps = [];\n for (const {chunk, size} of gatheredChunks.values()) {\n ps.push(memdagWrite.putChunk(chunk, size));\n }\n await Promise.all(ps);\n\n let newMemdagHeadHash = perdagClientGroupHeadHash;\n if (newMemdagMutations.length > 0) {\n let zeroData = await zero?.getTxData?.(newMemdagHeadHash, {\n openLazyRead: memdagWrite,\n });\n for (let i = newMemdagMutations.length - 1; i >= 0; i--) {\n const {result, zeroData: next} = await rebaseMutationAndPutCommit(\n newMemdagMutations[i],\n memdagWrite,\n newMemdagHeadHash,\n mutators,\n lc,\n newMemdagMutations[i].meta.clientID,\n formatVersion,\n zeroData,\n );\n newMemdagHeadHash = result.chunk.hash;\n zeroData = next;\n }\n }\n\n const newMemdagHeadCommit = await commitFromHash(\n newMemdagHeadHash,\n memdagWrite,\n );\n const diffs = await diffCommits(\n memdagHeadCommit,\n newMemdagHeadCommit,\n memdagWrite,\n diffConfig,\n formatVersion,\n );\n\n await memdagWrite.setHead(DEFAULT_HEAD_NAME, newMemdagHeadHash);\n return {\n type: 'complete',\n diffs,\n oldHead: memdagHeadCommit.chunk.hash,\n newHead: newMemdagHeadHash,\n newPerdagClientHeadHash: perdagClientGroupHeadHash,\n } as const;\n });\n });\n\n if (closed()) {\n return;\n }\n\n const setRefreshHashes = (refreshHashes: readonly Hash[]) =>\n withWrite(perdag, async perdagWrite => {\n const client = await mustGetClient(clientID, perdagWrite);\n const newClient = {\n ...client,\n refreshHashes,\n };\n\n // If this cleanup never happens, it's no big deal, some data will stay\n // alive longer but next refresh will fix it.\n await setClient(clientID, newClient, perdagWrite);\n });\n\n if (result.type === 'aborted') {\n if (result.refreshHashesForRevert) {\n await setRefreshHashes(result.refreshHashesForRevert);\n }\n return undefined;\n }\n\n // Advance zero here before setting refresh hashes\n // since we must advance before delegating control of the microtask\n // loop.\n zero?.advance(result.oldHead, result.newHead, result.diffs.get('') ?? []);\n await setRefreshHashes([result.newPerdagClientHeadHash]);\n return {\n oldHead: result.oldHead,\n newHead: result.newHead,\n diffs: result.diffs,\n };\n}\n\nfunction shouldAbortRefresh(\n memdagBaseSnapshot: Commit<SnapshotMetaDD31>,\n perdagClientGroupBaseSnapshot: Commit<SnapshotMetaDD31>,\n perdagClientGroupHeadHash: Hash,\n): boolean {\n const baseSnapshotCookieCompareResult = compareCookiesForSnapshots(\n memdagBaseSnapshot,\n perdagClientGroupBaseSnapshot,\n );\n return (\n baseSnapshotCookieCompareResult > 0 ||\n (baseSnapshotCookieCompareResult === 0 &&\n perdagClientGroupHeadHash === perdagClientGroupBaseSnapshot.chunk.hash)\n );\n}\n"],"mappings":"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"}
|
|
@@ -128,9 +128,13 @@ export declare class ReplicacheImpl<MD extends MutatorDefs = {}> {
|
|
|
128
128
|
*/
|
|
129
129
|
onSync: ((syncing: boolean) => void) | null;
|
|
130
130
|
/**
|
|
131
|
-
* `onClientStateNotFound` is called when the persistent client
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* `onClientStateNotFound` is called when the persistent client state can no
|
|
132
|
+
* longer be used. This happens when:
|
|
133
|
+
* - the persistent client has been garbage collected. This can happen if the
|
|
134
|
+
* client has no pending mutations and has not been used for a while.
|
|
135
|
+
* - the persistent store was found to be corrupt. Replicache then tries to
|
|
136
|
+
* drop the database, including any pending mutations, so that a fresh one
|
|
137
|
+
* is created on reload.
|
|
134
138
|
*
|
|
135
139
|
* The default behavior is to reload the page (using `location.reload()`). Set
|
|
136
140
|
* this to `null` or provide your own function to prevent the page from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replicache-impl.d.ts","sourceRoot":"","sources":["../../../../replicache/src/replicache-impl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAUjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,2BAA2B,CAAC;AAG5D,OAAO,EAAe,KAAK,MAAM,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"replicache-impl.d.ts","sourceRoot":"","sources":["../../../../replicache/src/replicache-impl.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import { promiseVoid } from "../../shared/src/resolved-promises.js";
|
|
|
7
7
|
import { deepFreeze } from "./frozen-json.js";
|
|
8
8
|
import { makeIDBName } from "./make-idb-name.js";
|
|
9
9
|
import { AbortError } from "../../shared/src/abort-error.js";
|
|
10
|
+
import { InvalidRefCountError } from "./dag/invalid-ref-count-error.js";
|
|
10
11
|
import { assertHash, emptyHash, newRandomHash } from "./hash.js";
|
|
11
12
|
import { ChunkNotFoundError, mustGetHeadHash } from "./dag/store.js";
|
|
12
13
|
import { StoreImpl } from "./dag/store-impl.js";
|
|
@@ -20,7 +21,7 @@ import { ClientStateNotFoundError, hasClientState, initClientV6 } from "./persis
|
|
|
20
21
|
import { getKVStoreProvider } from "./get-kv-store-provider.js";
|
|
21
22
|
import { createLogContext } from "./log-options.js";
|
|
22
23
|
import { IDBDatabasesStore } from "./persist/idb-databases-store.js";
|
|
23
|
-
import { COLLECT_IDB_INTERVAL, INITIAL_COLLECT_IDB_DELAY, initCollectIDBDatabases } from "./persist/collect-idb-databases.js";
|
|
24
|
+
import { COLLECT_IDB_INTERVAL, INITIAL_COLLECT_IDB_DELAY, dropDatabaseInternal, initCollectIDBDatabases } from "./persist/collect-idb-databases.js";
|
|
24
25
|
import { throwIfClosed } from "./transaction-closed-error.js";
|
|
25
26
|
import { getDocumentVisibilityWatcher } from "../../shared/src/document-visible.js";
|
|
26
27
|
import { PullDelegate, PushDelegate } from "./connection-loop-delegates.js";
|
|
@@ -118,6 +119,12 @@ var ReplicacheImpl = class {
|
|
|
118
119
|
#online = true;
|
|
119
120
|
#clientID = makeClientID();
|
|
120
121
|
#ready;
|
|
122
|
+
/**
|
|
123
|
+
* Resolves if open fails, for whatever reason. `#ready` never resolves in
|
|
124
|
+
* that case, so `close()` waits on whichever of the two settles first
|
|
125
|
+
* instead of hanging forever.
|
|
126
|
+
*/
|
|
127
|
+
#openFailed = resolver();
|
|
121
128
|
#profileIDPromise;
|
|
122
129
|
#clientGroupIDPromise;
|
|
123
130
|
#mutatorRegistry = {};
|
|
@@ -187,9 +194,13 @@ var ReplicacheImpl = class {
|
|
|
187
194
|
*/
|
|
188
195
|
onSync = null;
|
|
189
196
|
/**
|
|
190
|
-
* `onClientStateNotFound` is called when the persistent client
|
|
191
|
-
*
|
|
192
|
-
*
|
|
197
|
+
* `onClientStateNotFound` is called when the persistent client state can no
|
|
198
|
+
* longer be used. This happens when:
|
|
199
|
+
* - the persistent client has been garbage collected. This can happen if the
|
|
200
|
+
* client has no pending mutations and has not been used for a while.
|
|
201
|
+
* - the persistent store was found to be corrupt. Replicache then tries to
|
|
202
|
+
* drop the database, including any pending mutations, so that a fresh one
|
|
203
|
+
* is created on reload.
|
|
193
204
|
*
|
|
194
205
|
* The default behavior is to reload the page (using `location.reload()`). Set
|
|
195
206
|
* this to `null` or provide your own function to prevent the page from
|
|
@@ -253,7 +264,7 @@ var ReplicacheImpl = class {
|
|
|
253
264
|
this.#kvStoreProvider = kvStoreProvider;
|
|
254
265
|
const perKVStore = kvStoreProvider.create(this.idbName);
|
|
255
266
|
this.#idbDatabases = new IDBDatabasesStore(kvStoreProvider.create);
|
|
256
|
-
this.perdag = new StoreImpl(perKVStore, newRandomHash, assertHash);
|
|
267
|
+
this.perdag = new StoreImpl(perKVStore, newRandomHash, assertHash, this.#onInvalidRefCount);
|
|
257
268
|
this.memdag = new LazyStore(this.perdag, LAZY_STORE_SOURCE_CHUNK_CACHE_SIZE_LIMIT, newRandomHash, assertHash);
|
|
258
269
|
const readyResolver = resolver();
|
|
259
270
|
this.#ready = readyResolver.promise;
|
|
@@ -273,7 +284,14 @@ var ReplicacheImpl = class {
|
|
|
273
284
|
this.#onPersist = initOnPersistChannel(this.name, this.#closeAbortController.signal, (persistInfo) => {
|
|
274
285
|
this.#handlePersist(persistInfo);
|
|
275
286
|
});
|
|
276
|
-
this.#open(indexes, enableClientGroupForking, enableMutationRecovery, clientMaxAgeMs, profileIDResolver.resolve, clientGroupIDResolver.resolve, readyResolver.resolve, onClientsDeleted)
|
|
287
|
+
this.#open(indexes, enableClientGroupForking, enableMutationRecovery, clientMaxAgeMs, profileIDResolver.resolve, clientGroupIDResolver.resolve, readyResolver.resolve, onClientsDeleted).catch((e) => {
|
|
288
|
+
this.#openFailed.resolve();
|
|
289
|
+
if (e instanceof InvalidRefCountError) {
|
|
290
|
+
this.#lc.debug?.("Open failed because the persistent store is corrupt");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
throw e;
|
|
294
|
+
});
|
|
277
295
|
}
|
|
278
296
|
async #open(indexes, enableClientGroupForking, enableMutationRecovery, clientMaxAgeMs, profileIDResolver, resolveClientGroupID, resolveReady, onClientsDeleted) {
|
|
279
297
|
const { clientID } = this;
|
|
@@ -298,7 +316,7 @@ var ReplicacheImpl = class {
|
|
|
298
316
|
this.#clientStateNotFoundOnClient(clientID);
|
|
299
317
|
}, HEARTBEAT_INTERVAL, this.#lc, signal);
|
|
300
318
|
initClientGC(clientID, this.perdag, clientMaxAgeMs, GC_INTERVAL, onClientsDeleted, this.#lc, signal);
|
|
301
|
-
initCollectIDBDatabases(this.#idbDatabases, this.#kvStoreProvider, COLLECT_IDB_INTERVAL, INITIAL_COLLECT_IDB_DELAY, 2 * clientMaxAgeMs, enableMutationRecovery, onClientsDeleted, this.#lc, signal);
|
|
319
|
+
initCollectIDBDatabases(this.#idbDatabases, this.#kvStoreProvider, COLLECT_IDB_INTERVAL, INITIAL_COLLECT_IDB_DELAY, 2 * clientMaxAgeMs, enableMutationRecovery, onClientsDeleted, this.#lc, signal, (name, createKVStore) => new StoreImpl(createKVStore(name), newRandomHash, assertHash, name === this.idbName ? this.#onInvalidRefCount : void 0));
|
|
302
320
|
initClientGroupGC(this.perdag, enableMutationRecovery, this.#lc, signal);
|
|
303
321
|
initNewClientChannel(this.name, this.idbName, signal, client.clientGroupID, isNewClientGroup, () => {
|
|
304
322
|
this.#fireOnUpdateNeeded(updateNeededReasonNewClientGroup);
|
|
@@ -375,7 +393,7 @@ var ReplicacheImpl = class {
|
|
|
375
393
|
closingInstances.set(this.name, promise);
|
|
376
394
|
this.#closeAbortController.abort();
|
|
377
395
|
getBrowserGlobal("document")?.removeEventListener("visibilitychange", this.#onVisibilityChange);
|
|
378
|
-
await this.#ready;
|
|
396
|
+
await Promise.race([this.#ready, this.#openFailed.promise]);
|
|
379
397
|
const closingPromises = [
|
|
380
398
|
this.memdag.close(),
|
|
381
399
|
this.perdag.close(),
|
|
@@ -401,11 +419,13 @@ var ReplicacheImpl = class {
|
|
|
401
419
|
this.#schedulePersist();
|
|
402
420
|
return;
|
|
403
421
|
}
|
|
404
|
-
|
|
422
|
+
let zeroData = await this.#zero?.getTxData?.(syncHead);
|
|
405
423
|
for (const mutation of replayMutations) {
|
|
406
424
|
if (this.#subscriptions.hasPendingSubscriptionRuns) await Promise.resolve();
|
|
407
425
|
const { meta } = mutation;
|
|
408
|
-
|
|
426
|
+
const { result, zeroData: next } = await withWriteNoImplicitCommit(this.memdag, (dagWrite) => rebaseMutationAndCommit(mutation, dagWrite, syncHead, SYNC_HEAD_NAME, this.#mutatorRegistry, lc, isLocalMetaDD31(meta) ? meta.clientID : clientID, 7, zeroData));
|
|
427
|
+
syncHead = result;
|
|
428
|
+
zeroData = next;
|
|
409
429
|
}
|
|
410
430
|
}
|
|
411
431
|
}
|
|
@@ -628,7 +648,10 @@ var ReplicacheImpl = class {
|
|
|
628
648
|
await persistDD31(this.#lc, clientID, this.memdag, this.perdag, this.#mutatorRegistry, () => this.#closed, 7, this.#zero?.getTxData);
|
|
629
649
|
} catch (e) {
|
|
630
650
|
if (e instanceof ClientStateNotFoundError) this.#clientStateNotFoundOnClient(clientID);
|
|
631
|
-
else if (
|
|
651
|
+
else if (e instanceof InvalidRefCountError) {
|
|
652
|
+
await this.#handleInvalidRefCount(e);
|
|
653
|
+
return;
|
|
654
|
+
} else if (this.#closed) this.#lc.debug?.("Exception persisting during close", e);
|
|
632
655
|
else throw e;
|
|
633
656
|
}
|
|
634
657
|
const clientGroupID = await this.#clientGroupIDPromise;
|
|
@@ -648,6 +671,7 @@ var ReplicacheImpl = class {
|
|
|
648
671
|
refreshResult = await refresh(this.#lc, this.memdag, this.perdag, clientID, this.#mutatorRegistry, this.#subscriptions, () => this.closed, 7, this.#zero);
|
|
649
672
|
} catch (e) {
|
|
650
673
|
if (e instanceof ClientStateNotFoundError) this.#clientStateNotFoundOnClient(clientID);
|
|
674
|
+
else if (e instanceof InvalidRefCountError) await this.#handleInvalidRefCount(e);
|
|
651
675
|
else if (this.#closed) this.#lc.debug?.("Exception refreshing during close", e);
|
|
652
676
|
else throw e;
|
|
653
677
|
}
|
|
@@ -660,6 +684,69 @@ var ReplicacheImpl = class {
|
|
|
660
684
|
this.#lc.error?.(`Client state not found on client, clientID: ${clientID}`);
|
|
661
685
|
this.#fireOnClientStateNotFound();
|
|
662
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Set once recovery from a corrupt database has started. Every write path
|
|
689
|
+
* into the perdag (open, persist, refresh, heartbeat, GC, ...) can trip on
|
|
690
|
+
* the same corrupt key, so recovery runs once and later reports await it.
|
|
691
|
+
*/
|
|
692
|
+
#corruptDatabaseRecovery;
|
|
693
|
+
/**
|
|
694
|
+
* Installed on the dag store that writes to our database. The store calls
|
|
695
|
+
* it after the failing transaction has been released, so dropping the
|
|
696
|
+
* database from here does not race with the rollback.
|
|
697
|
+
*/
|
|
698
|
+
#onInvalidRefCount = (e) => {
|
|
699
|
+
this.#handleInvalidRefCount(e);
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* The persistent dag store contains an invalid ref count. This means the
|
|
703
|
+
* store is corrupt (due to some unknown bug) and every subsequent write that
|
|
704
|
+
* touches that chunk would fail the same way. There is no way to repair it,
|
|
705
|
+
* so we treat it like the client state was lost and fire
|
|
706
|
+
* `onClientStateNotFound`.
|
|
707
|
+
*
|
|
708
|
+
* Disabling the client group is not enough: the corrupt ref count stays in
|
|
709
|
+
* the underlying kv store and any later write that touches that chunk (for
|
|
710
|
+
* example rewriting the `clients` chunk when a new client starts) would hit
|
|
711
|
+
* it again. Instead we drop the whole database so that the reload starts
|
|
712
|
+
* from a fresh store. Pending local mutations in this database are lost.
|
|
713
|
+
*
|
|
714
|
+
* This is expected to be exceedingly rare. Other instances sharing the
|
|
715
|
+
* database are not told: they simply hit the same corrupt key (or, once
|
|
716
|
+
* this instance drops the database, a generic storage error) on their own
|
|
717
|
+
* next write and recover the same way then. That's an acceptable delay for
|
|
718
|
+
* something we don't expect to ever happen.
|
|
719
|
+
*/
|
|
720
|
+
#handleInvalidRefCount(e) {
|
|
721
|
+
if (this.#corruptDatabaseRecovery === void 0) {
|
|
722
|
+
this.#lc.error?.(`Client state is corrupt on client, clientID: ${this.clientID}. Dropping database ${this.idbName}`, e);
|
|
723
|
+
this.#corruptDatabaseRecovery = this.#dropDatabaseAndFireOnClientStateNotFound();
|
|
724
|
+
}
|
|
725
|
+
return this.#corruptDatabaseRecovery;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Drops our database, then fires `onClientStateNotFound` so the app can
|
|
729
|
+
* recover, whether or not the drop itself succeeded.
|
|
730
|
+
*
|
|
731
|
+
* Uses its own handle on the databases registry rather than `#idbDatabases`
|
|
732
|
+
* so it works even when `close()` is already closing this instance.
|
|
733
|
+
*/
|
|
734
|
+
async #dropDatabaseAndFireOnClientStateNotFound() {
|
|
735
|
+
const { idbName, clientID } = this;
|
|
736
|
+
let idbDatabases;
|
|
737
|
+
try {
|
|
738
|
+
idbDatabases = new IDBDatabasesStore(this.#kvStoreProvider.create);
|
|
739
|
+
await dropDatabaseInternal(idbName, idbDatabases, this.#kvStoreProvider.drop);
|
|
740
|
+
} catch (dropError) {
|
|
741
|
+
this.#lc.error?.(`Failed to drop database ${idbName}, clientID: ${clientID}`, dropError);
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
await idbDatabases?.close();
|
|
745
|
+
} catch (closeError) {
|
|
746
|
+
this.#lc.debug?.(`Failed to close the databases registry after dropping ${idbName}, clientID: ${clientID}`, closeError);
|
|
747
|
+
}
|
|
748
|
+
this.#fireOnClientStateNotFound();
|
|
749
|
+
}
|
|
663
750
|
async #clientStateNotFoundOnServer() {
|
|
664
751
|
const clientGroupID = await this.#clientGroupIDPromise;
|
|
665
752
|
this.#lc.error?.(`Client state not found on server, clientGroupID: ${clientGroupID}`);
|