@verdant-web/store 3.12.0 → 4.0.0-next.0
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/dist/bundle/index.js +11 -13
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/batching.test.js +5 -5
- package/dist/esm/__tests__/batching.test.js.map +1 -1
- package/dist/esm/__tests__/entities.test.js +1 -1
- package/dist/esm/__tests__/entities.test.js.map +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -3
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/backup.d.ts +3 -4
- package/dist/esm/backup.js.map +1 -1
- package/dist/esm/client/Client.d.ts +28 -33
- package/dist/esm/client/Client.js +50 -161
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +8 -11
- package/dist/esm/client/ClientDescriptor.js +39 -141
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/context/Time.d.ts +13 -0
- package/dist/esm/context/Time.js +27 -0
- package/dist/esm/context/Time.js.map +1 -0
- package/dist/esm/context/context.d.ts +170 -0
- package/dist/esm/{context.js.map → context/context.js.map} +1 -1
- package/dist/esm/entities/DocumentManager.js.map +1 -1
- package/dist/esm/entities/Entity.d.ts +4 -5
- package/dist/esm/entities/Entity.js +5 -3
- package/dist/esm/entities/Entity.js.map +1 -1
- package/dist/esm/entities/Entity.test.js +4 -3
- package/dist/esm/entities/Entity.test.js.map +1 -1
- package/dist/esm/entities/EntityCache.d.ts +0 -3
- package/dist/esm/entities/EntityCache.js +0 -9
- package/dist/esm/entities/EntityCache.js.map +1 -1
- package/dist/esm/entities/EntityMetadata.d.ts +1 -1
- package/dist/esm/entities/EntityMetadata.js +6 -5
- package/dist/esm/entities/EntityMetadata.js.map +1 -1
- package/dist/esm/entities/EntityStore.d.ts +2 -6
- package/dist/esm/entities/EntityStore.js +22 -16
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/entities/OperationBatcher.d.ts +2 -5
- package/dist/esm/entities/OperationBatcher.js +9 -7
- package/dist/esm/entities/OperationBatcher.js.map +1 -1
- package/dist/esm/entities/types.d.ts +1 -1
- package/dist/esm/errors.d.ts +8 -0
- package/dist/esm/errors.js +12 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/files/EntityFile.d.ts +6 -3
- package/dist/esm/files/EntityFile.js +22 -19
- package/dist/esm/files/EntityFile.js.map +1 -1
- package/dist/esm/files/FileManager.d.ts +8 -39
- package/dist/esm/files/FileManager.js +15 -170
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/files/utils.d.ts +0 -1
- package/dist/esm/files/utils.js +0 -14
- package/dist/esm/files/utils.js.map +1 -1
- package/dist/esm/index.d.ts +1 -2
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/{metadata → persistence}/MessageCreator.d.ts +5 -6
- package/dist/esm/{metadata → persistence}/MessageCreator.js +31 -38
- package/dist/esm/persistence/MessageCreator.js.map +1 -0
- package/dist/esm/persistence/PersistenceFiles.d.ts +48 -0
- package/dist/esm/persistence/PersistenceFiles.js +160 -0
- package/dist/esm/persistence/PersistenceFiles.js.map +1 -0
- package/dist/esm/persistence/PersistenceMetadata.d.ts +69 -0
- package/dist/esm/persistence/PersistenceMetadata.js +302 -0
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -0
- package/dist/esm/persistence/PersistenceQueries.d.ts +34 -0
- package/dist/esm/persistence/PersistenceQueries.js +15 -0
- package/dist/esm/persistence/PersistenceQueries.js.map +1 -0
- package/dist/esm/persistence/PersistenceRebaser.d.ts +32 -0
- package/dist/esm/persistence/PersistenceRebaser.js +120 -0
- package/dist/esm/persistence/PersistenceRebaser.js.map +1 -0
- package/dist/esm/{IDBService.d.ts → persistence/idb/IdbService.d.ts} +9 -7
- package/dist/esm/{IDBService.js → persistence/idb/IdbService.js} +29 -8
- package/dist/esm/persistence/idb/IdbService.js.map +1 -0
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +58 -0
- package/dist/esm/{files/FileStorage.js → persistence/idb/files/IdbPersistenceFileDb.js} +85 -50
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -0
- package/dist/esm/persistence/idb/idbPersistence.d.ts +19 -0
- package/dist/esm/persistence/idb/idbPersistence.js +80 -0
- package/dist/esm/persistence/idb/idbPersistence.js.map +1 -0
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +72 -0
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +235 -0
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -0
- package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.d.ts +3 -1
- package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.js +12 -3
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -0
- package/dist/esm/persistence/idb/queries/IdbQueryDb.d.ts +41 -0
- package/dist/esm/persistence/idb/queries/IdbQueryDb.js +174 -0
- package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +1 -0
- package/dist/esm/{migration → persistence/idb/queries/migration}/db.d.ts +1 -1
- package/dist/esm/{migration → persistence/idb/queries/migration}/db.js +10 -48
- package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -0
- package/dist/esm/persistence/idb/queries/migration/engine.d.ts +12 -0
- package/dist/esm/{migration → persistence/idb/queries/migration}/engine.js +29 -46
- package/dist/esm/persistence/idb/queries/migration/engine.js.map +1 -0
- package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.d.ts +1 -3
- package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.js +11 -10
- package/dist/esm/persistence/idb/queries/migration/migrations.js.map +1 -0
- package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.d.ts +1 -3
- package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.js +4 -7
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +1 -0
- package/dist/esm/{migration → persistence/idb/queries/migration}/paths.js +2 -2
- package/dist/esm/persistence/idb/queries/migration/paths.js.map +1 -0
- package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +1 -0
- package/dist/esm/persistence/idb/queries/migration/types.d.ts +6 -0
- package/dist/esm/persistence/idb/queries/migration/types.js.map +1 -0
- package/dist/esm/persistence/idb/queries/ranges.d.ts +2 -0
- package/dist/esm/persistence/idb/queries/ranges.js +66 -0
- package/dist/esm/persistence/idb/queries/ranges.js.map +1 -0
- package/dist/esm/{idb.d.ts → persistence/idb/util.d.ts} +11 -0
- package/dist/esm/{idb.js → persistence/idb/util.js} +58 -1
- package/dist/esm/persistence/idb/util.js.map +1 -0
- package/dist/esm/persistence/interfaces.d.ts +181 -0
- package/dist/esm/persistence/interfaces.js +2 -0
- package/dist/esm/persistence/interfaces.js.map +1 -0
- package/dist/esm/persistence/persistence.d.ts +4 -0
- package/dist/esm/persistence/persistence.js +126 -0
- package/dist/esm/persistence/persistence.js.map +1 -0
- package/dist/esm/queries/BaseQuery.d.ts +2 -1
- package/dist/esm/queries/BaseQuery.js +3 -0
- package/dist/esm/queries/BaseQuery.js.map +1 -1
- package/dist/esm/queries/CollectionQueries.d.ts +1 -1
- package/dist/esm/queries/FindAllQuery.js +1 -3
- package/dist/esm/queries/FindAllQuery.js.map +1 -1
- package/dist/esm/queries/FindInfiniteQuery.js +2 -5
- package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
- package/dist/esm/queries/FindOneQuery.js +1 -3
- package/dist/esm/queries/FindOneQuery.js.map +1 -1
- package/dist/esm/queries/FindPageQuery.js +1 -3
- package/dist/esm/queries/FindPageQuery.js.map +1 -1
- package/dist/esm/queries/QueryCache.d.ts +1 -1
- package/dist/esm/queries/QueryCache.js +4 -0
- package/dist/esm/queries/QueryCache.js.map +1 -1
- package/dist/esm/sync/FileSync.d.ts +23 -8
- package/dist/esm/sync/FileSync.js +76 -28
- package/dist/esm/sync/FileSync.js.map +1 -1
- package/dist/esm/sync/PresenceManager.d.ts +4 -3
- package/dist/esm/sync/PresenceManager.js +2 -2
- package/dist/esm/sync/PresenceManager.js.map +1 -1
- package/dist/esm/sync/PushPullSync.d.ts +4 -6
- package/dist/esm/sync/PushPullSync.js +13 -12
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +9 -11
- package/dist/esm/sync/Sync.js +34 -29
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.d.ts +4 -6
- package/dist/esm/sync/WebSocketSync.js +20 -22
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/utils/Disposable.d.ts +5 -2
- package/dist/esm/utils/Disposable.js +3 -2
- package/dist/esm/utils/Disposable.js.map +1 -1
- package/dist/esm/utils/wip.d.ts +2 -0
- package/dist/esm/utils/wip.js +5 -0
- package/dist/esm/utils/wip.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/batching.test.ts +6 -6
- package/src/__tests__/entities.test.ts +1 -1
- package/src/__tests__/fixtures/testStorage.ts +2 -10
- package/src/__tests__/queries.test.ts +1 -1
- package/src/backup.ts +3 -4
- package/src/client/Client.ts +69 -226
- package/src/client/ClientDescriptor.ts +53 -184
- package/src/context/Time.ts +35 -0
- package/src/context/context.ts +200 -0
- package/src/entities/DocumentManager.ts +0 -3
- package/src/entities/Entity.test.ts +9 -9
- package/src/entities/Entity.ts +6 -12
- package/src/entities/EntityCache.ts +0 -9
- package/src/entities/EntityMetadata.ts +4 -4
- package/src/entities/EntityStore.ts +26 -29
- package/src/entities/OperationBatcher.ts +9 -11
- package/src/entities/types.ts +1 -1
- package/src/errors.ts +13 -0
- package/src/files/EntityFile.ts +16 -5
- package/src/files/FileManager.ts +18 -245
- package/src/files/utils.ts +0 -15
- package/src/index.ts +2 -1
- package/src/{metadata → persistence}/MessageCreator.ts +46 -36
- package/src/persistence/PersistenceFiles.ts +227 -0
- package/src/persistence/PersistenceMetadata.ts +425 -0
- package/src/persistence/PersistenceQueries.ts +22 -0
- package/src/persistence/PersistenceRebaser.ts +171 -0
- package/src/{IDBService.ts → persistence/idb/IdbService.ts} +45 -12
- package/src/{files/FileStorage.ts → persistence/idb/files/IdbPersistenceFileDb.ts} +128 -86
- package/src/persistence/idb/idbPersistence.ts +116 -0
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +460 -0
- package/src/{metadata → persistence/idb/metadata}/openMetadataDatabase.ts +21 -4
- package/src/persistence/idb/queries/IdbQueryDb.ts +251 -0
- package/src/{migration → persistence/idb/queries/migration}/db.ts +18 -72
- package/src/{migration → persistence/idb/queries/migration}/engine.ts +39 -62
- package/src/{migration → persistence/idb/queries/migration}/migrations.ts +13 -18
- package/src/{migration → persistence/idb/queries/migration}/openQueryDatabase.ts +5 -14
- package/src/{migration → persistence/idb/queries/migration}/paths.ts +4 -3
- package/src/persistence/idb/queries/migration/types.ts +8 -0
- package/src/persistence/idb/queries/ranges.ts +107 -0
- package/src/{idb.ts → persistence/idb/util.ts} +75 -0
- package/src/persistence/interfaces.ts +240 -0
- package/src/persistence/persistence.ts +223 -0
- package/src/queries/BaseQuery.ts +5 -1
- package/src/queries/CollectionQueries.ts +2 -2
- package/src/queries/FindAllQuery.ts +1 -3
- package/src/queries/FindInfiniteQuery.ts +2 -5
- package/src/queries/FindOneQuery.ts +1 -3
- package/src/queries/FindPageQuery.ts +1 -3
- package/src/queries/QueryCache.ts +20 -1
- package/src/sync/FileSync.ts +93 -30
- package/src/sync/PresenceManager.ts +5 -7
- package/src/sync/PushPullSync.ts +23 -19
- package/src/sync/Sync.ts +45 -36
- package/src/sync/WebSocketSync.ts +41 -27
- package/src/utils/Disposable.ts +7 -4
- package/src/utils/wip.ts +5 -0
- package/dist/esm/IDBService.js.map +0 -1
- package/dist/esm/__tests__/legacyOids.test.d.ts +0 -1
- package/dist/esm/__tests__/legacyOids.test.js +0 -352
- package/dist/esm/__tests__/legacyOids.test.js.map +0 -1
- package/dist/esm/context.d.ts +0 -45
- package/dist/esm/files/FileStorage.d.ts +0 -47
- package/dist/esm/files/FileStorage.js.map +0 -1
- package/dist/esm/idb.js.map +0 -1
- package/dist/esm/metadata/AckInfoStore.d.ts +0 -10
- package/dist/esm/metadata/AckInfoStore.js +0 -22
- package/dist/esm/metadata/AckInfoStore.js.map +0 -1
- package/dist/esm/metadata/BaselinesStore.d.ts +0 -40
- package/dist/esm/metadata/BaselinesStore.js +0 -102
- package/dist/esm/metadata/BaselinesStore.js.map +0 -1
- package/dist/esm/metadata/LocalReplicaStore.d.ts +0 -19
- package/dist/esm/metadata/LocalReplicaStore.js +0 -56
- package/dist/esm/metadata/LocalReplicaStore.js.map +0 -1
- package/dist/esm/metadata/MessageCreator.js.map +0 -1
- package/dist/esm/metadata/Metadata.d.ts +0 -146
- package/dist/esm/metadata/Metadata.js +0 -452
- package/dist/esm/metadata/Metadata.js.map +0 -1
- package/dist/esm/metadata/OperationsStore.d.ts +0 -62
- package/dist/esm/metadata/OperationsStore.js +0 -175
- package/dist/esm/metadata/OperationsStore.js.map +0 -1
- package/dist/esm/metadata/SchemaStore.d.ts +0 -9
- package/dist/esm/metadata/SchemaStore.js +0 -35
- package/dist/esm/metadata/SchemaStore.js.map +0 -1
- package/dist/esm/metadata/openMetadataDatabase.js.map +0 -1
- package/dist/esm/migration/db.js.map +0 -1
- package/dist/esm/migration/engine.d.ts +0 -15
- package/dist/esm/migration/engine.js.map +0 -1
- package/dist/esm/migration/errors.d.ts +0 -5
- package/dist/esm/migration/errors.js +0 -8
- package/dist/esm/migration/errors.js.map +0 -1
- package/dist/esm/migration/migrations.js.map +0 -1
- package/dist/esm/migration/openQueryDatabase.js.map +0 -1
- package/dist/esm/migration/openWIPDatabase.d.ts +0 -11
- package/dist/esm/migration/openWIPDatabase.js +0 -65
- package/dist/esm/migration/openWIPDatabase.js.map +0 -1
- package/dist/esm/migration/paths.js.map +0 -1
- package/dist/esm/migration/paths.test.js.map +0 -1
- package/dist/esm/migration/types.d.ts +0 -3
- package/dist/esm/migration/types.js.map +0 -1
- package/dist/esm/queries/QueryableStorage.d.ts +0 -20
- package/dist/esm/queries/QueryableStorage.js +0 -90
- package/dist/esm/queries/QueryableStorage.js.map +0 -1
- package/dist/esm/queries/dbQueries.d.ts +0 -22
- package/dist/esm/queries/dbQueries.js +0 -130
- package/dist/esm/queries/dbQueries.js.map +0 -1
- package/src/__tests__/legacyOids.test.ts +0 -375
- package/src/context.ts +0 -55
- package/src/metadata/AckInfoStore.ts +0 -30
- package/src/metadata/BaselinesStore.ts +0 -188
- package/src/metadata/LocalReplicaStore.ts +0 -79
- package/src/metadata/Metadata.ts +0 -685
- package/src/metadata/OperationsStore.ts +0 -332
- package/src/metadata/SchemaStore.ts +0 -47
- package/src/migration/errors.ts +0 -7
- package/src/migration/openWIPDatabase.ts +0 -97
- package/src/migration/types.ts +0 -4
- package/src/queries/QueryableStorage.ts +0 -122
- package/src/queries/dbQueries.ts +0 -161
- /package/dist/esm/{context.js → context/context.js} +0 -0
- /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.d.ts +0 -0
- /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.d.ts +0 -0
- /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.js +0 -0
- /package/dist/esm/{migration → persistence/idb/queries/migration}/types.js +0 -0
- /package/src/{migration → persistence/idb/queries/migration}/paths.test.ts +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyPatch,
|
|
3
|
+
assignOid,
|
|
4
|
+
isFileRef,
|
|
5
|
+
ObjectIdentifier,
|
|
6
|
+
Ref,
|
|
7
|
+
} from '@verdant-web/common';
|
|
8
|
+
import { Context } from '../context/context.js';
|
|
9
|
+
import { AbstractTransaction, PersistenceMetadataDb } from './interfaces.js';
|
|
10
|
+
|
|
11
|
+
export class PersistenceRebaser {
|
|
12
|
+
constructor(
|
|
13
|
+
private db: PersistenceMetadataDb,
|
|
14
|
+
private ctx: Pick<
|
|
15
|
+
Context,
|
|
16
|
+
'closing' | 'log' | 'time' | 'internalEvents' | 'globalEvents' | 'config'
|
|
17
|
+
>,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Autonomous rebases are only allowed for clients who have never synced. They
|
|
22
|
+
* keep storage clean for non-syncing clients by compressing history.
|
|
23
|
+
*/
|
|
24
|
+
tryAutonomousRebase = async () => {
|
|
25
|
+
const localReplicaInfo = await this.db.getLocalReplica();
|
|
26
|
+
if (localReplicaInfo.lastSyncedLogicalTime) return; // cannot autonomously rebase if we've synced
|
|
27
|
+
// but if we have never synced... we can rebase everything!
|
|
28
|
+
await this.runRebase(this.ctx.time.now);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Attempt to autonomously rebase local documents without server intervention.
|
|
33
|
+
* This can currently only happen for a client who has never synced before.
|
|
34
|
+
* The goal is to allow local-only clients to compress their history to exactly
|
|
35
|
+
* their undo stack.
|
|
36
|
+
*/
|
|
37
|
+
private runRebase = async (globalAckTimestamp: string) => {
|
|
38
|
+
if (this.ctx.closing) return;
|
|
39
|
+
|
|
40
|
+
// find all operations before the global ack
|
|
41
|
+
let lastTimestamp;
|
|
42
|
+
const toRebase = new Set<ObjectIdentifier>();
|
|
43
|
+
const transaction = this.db.transaction({
|
|
44
|
+
storeNames: ['baselines', 'operations'],
|
|
45
|
+
mode: 'readwrite',
|
|
46
|
+
});
|
|
47
|
+
let operationCount = 0;
|
|
48
|
+
await this.db.iterateAllOperations(
|
|
49
|
+
(patch) => {
|
|
50
|
+
toRebase.add(patch.oid);
|
|
51
|
+
lastTimestamp = patch.timestamp;
|
|
52
|
+
operationCount++;
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
before: globalAckTimestamp,
|
|
56
|
+
transaction,
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!toRebase.size) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.ctx.closing) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// rebase each affected document
|
|
69
|
+
let newBaselines = [];
|
|
70
|
+
for (const oid of toRebase) {
|
|
71
|
+
newBaselines.push(
|
|
72
|
+
await this.rebase(
|
|
73
|
+
oid,
|
|
74
|
+
lastTimestamp || globalAckTimestamp,
|
|
75
|
+
transaction,
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
this.ctx.globalEvents.emit('rebase');
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Debounces rebase attempts to avoid thrashing the database with
|
|
84
|
+
* rebase operations.
|
|
85
|
+
*/
|
|
86
|
+
scheduleRebase = async (timestamp: string) => {
|
|
87
|
+
if (this.rebaseTimeout) {
|
|
88
|
+
clearTimeout(this.rebaseTimeout);
|
|
89
|
+
}
|
|
90
|
+
this.rebaseTimeout = setTimeout(
|
|
91
|
+
this.runRebase,
|
|
92
|
+
this.ctx.config.persistence?.rebaseTimeout ?? 10000,
|
|
93
|
+
timestamp,
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
private rebaseTimeout: NodeJS.Timeout | null = null;
|
|
97
|
+
|
|
98
|
+
rebase = async (
|
|
99
|
+
oid: ObjectIdentifier,
|
|
100
|
+
upTo: string,
|
|
101
|
+
providedTx?: AbstractTransaction,
|
|
102
|
+
) => {
|
|
103
|
+
const transaction =
|
|
104
|
+
providedTx ||
|
|
105
|
+
this.db.transaction({
|
|
106
|
+
storeNames: ['operations', 'baselines'],
|
|
107
|
+
mode: 'readwrite',
|
|
108
|
+
});
|
|
109
|
+
const baseline = await this.db.getBaseline(oid, { transaction });
|
|
110
|
+
let current: any = baseline?.snapshot || undefined;
|
|
111
|
+
let operationsApplied = 0;
|
|
112
|
+
let authz = baseline?.authz;
|
|
113
|
+
const deletedRefs: Ref[] = [];
|
|
114
|
+
await this.db.consumeEntityOperations(
|
|
115
|
+
oid,
|
|
116
|
+
(patch) => {
|
|
117
|
+
// FIXME: this seems like the wrong place to do this
|
|
118
|
+
// but it's here as a safety measure...
|
|
119
|
+
if (!baseline || patch.timestamp > baseline.timestamp) {
|
|
120
|
+
current = applyPatch(current, patch.data, deletedRefs);
|
|
121
|
+
if (patch.data.op === 'initialize') {
|
|
122
|
+
authz = patch.authz;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// delete all prior operations to the baseline
|
|
126
|
+
operationsApplied++;
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
to: upTo,
|
|
130
|
+
transaction,
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
if (current) {
|
|
134
|
+
assignOid(current, oid);
|
|
135
|
+
}
|
|
136
|
+
const newBaseline = {
|
|
137
|
+
oid,
|
|
138
|
+
snapshot: current,
|
|
139
|
+
timestamp: upTo,
|
|
140
|
+
authz,
|
|
141
|
+
};
|
|
142
|
+
if (newBaseline.snapshot) {
|
|
143
|
+
await this.db.setBaselines([newBaseline], { transaction });
|
|
144
|
+
} else {
|
|
145
|
+
await this.db.deleteBaseline(oid, { transaction });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.ctx.log(
|
|
149
|
+
'debug',
|
|
150
|
+
'rebased',
|
|
151
|
+
oid,
|
|
152
|
+
'up to',
|
|
153
|
+
upTo,
|
|
154
|
+
':',
|
|
155
|
+
current,
|
|
156
|
+
'and deleted',
|
|
157
|
+
operationsApplied,
|
|
158
|
+
'operations',
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// cleanup deleted refs
|
|
162
|
+
if (deletedRefs.length) {
|
|
163
|
+
const fileRefs = deletedRefs.filter(isFileRef);
|
|
164
|
+
if (fileRefs.length) {
|
|
165
|
+
this.ctx.internalEvents.emit('filesDeleted', fileRefs);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return newBaseline;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Context } from '
|
|
1
|
+
import { Context } from '../../context/context.js';
|
|
2
2
|
import {
|
|
3
|
+
copyDatabase,
|
|
3
4
|
createAbortableTransaction,
|
|
4
5
|
isAbortError,
|
|
5
6
|
storeRequestPromise,
|
|
6
|
-
} from './
|
|
7
|
-
import { Disposable } from '
|
|
7
|
+
} from './util.js';
|
|
8
|
+
import { Disposable } from '../../utils/Disposable.js';
|
|
8
9
|
|
|
9
|
-
export class
|
|
10
|
+
export class IdbService extends Disposable {
|
|
10
11
|
protected log?: Context['log'];
|
|
11
12
|
private globalAbortController = new AbortController();
|
|
12
13
|
|
|
@@ -19,6 +20,10 @@ export class IDBService extends Disposable {
|
|
|
19
20
|
this.addDispose(() => {
|
|
20
21
|
this.globalAbortController.abort();
|
|
21
22
|
});
|
|
23
|
+
this.db.addEventListener('versionchange', this.onVersionChange);
|
|
24
|
+
this.addDispose(() => {
|
|
25
|
+
this.db.removeEventListener('versionchange', this.onVersionChange);
|
|
26
|
+
});
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
createTransaction = (
|
|
@@ -45,7 +50,7 @@ export class IDBService extends Disposable {
|
|
|
45
50
|
return tx;
|
|
46
51
|
};
|
|
47
52
|
|
|
48
|
-
run = async <T>(
|
|
53
|
+
run = async <T = any>(
|
|
49
54
|
storeName: string,
|
|
50
55
|
getRequest: (store: IDBObjectStore) => IDBRequest<T>,
|
|
51
56
|
opts?: {
|
|
@@ -66,7 +71,7 @@ export class IDBService extends Disposable {
|
|
|
66
71
|
storeName: string,
|
|
67
72
|
getRequests: (store: IDBObjectStore) => IDBRequest<T>[],
|
|
68
73
|
opts?: {
|
|
69
|
-
mode
|
|
74
|
+
mode?: 'readonly' | 'readwrite';
|
|
70
75
|
transaction?: IDBTransaction;
|
|
71
76
|
abort?: AbortSignal;
|
|
72
77
|
},
|
|
@@ -80,8 +85,16 @@ export class IDBService extends Disposable {
|
|
|
80
85
|
|
|
81
86
|
iterate = async <T>(
|
|
82
87
|
storeName: string,
|
|
83
|
-
getRequest: (
|
|
84
|
-
|
|
88
|
+
getRequest: (
|
|
89
|
+
store: IDBObjectStore,
|
|
90
|
+
) =>
|
|
91
|
+
| IDBRequest<IDBCursorWithValue | null>
|
|
92
|
+
| IDBRequest<IDBCursorWithValue | null>[],
|
|
93
|
+
iterator: (
|
|
94
|
+
value: T,
|
|
95
|
+
store: IDBObjectStore,
|
|
96
|
+
cursor: IDBCursorWithValue,
|
|
97
|
+
) => boolean | void,
|
|
85
98
|
opts?: {
|
|
86
99
|
mode?: 'readonly' | 'readwrite';
|
|
87
100
|
transaction?: IDBTransaction;
|
|
@@ -98,8 +111,12 @@ export class IDBService extends Disposable {
|
|
|
98
111
|
req.onsuccess = () => {
|
|
99
112
|
const cursor = req.result;
|
|
100
113
|
if (cursor) {
|
|
101
|
-
iterator(cursor.value, store);
|
|
102
|
-
|
|
114
|
+
const stop = iterator(cursor.value, store, cursor);
|
|
115
|
+
if (stop) {
|
|
116
|
+
resolve();
|
|
117
|
+
} else {
|
|
118
|
+
cursor.continue();
|
|
119
|
+
}
|
|
103
120
|
} else {
|
|
104
121
|
resolve();
|
|
105
122
|
}
|
|
@@ -119,7 +136,7 @@ export class IDBService extends Disposable {
|
|
|
119
136
|
request.onsuccess = () => {
|
|
120
137
|
const cursor = request.result as IDBCursorWithValue | null;
|
|
121
138
|
if (cursor) {
|
|
122
|
-
const stop = iterator(cursor.value, store);
|
|
139
|
+
const stop = iterator(cursor.value, store, cursor);
|
|
123
140
|
if (stop) {
|
|
124
141
|
resolve();
|
|
125
142
|
} else {
|
|
@@ -139,9 +156,25 @@ export class IDBService extends Disposable {
|
|
|
139
156
|
});
|
|
140
157
|
};
|
|
141
158
|
|
|
142
|
-
clear = (storeName: string) => {
|
|
159
|
+
clear = (storeName: string, transaction?: IDBTransaction) => {
|
|
143
160
|
return this.run<undefined>(storeName, (store) => store.clear(), {
|
|
144
161
|
mode: 'readwrite',
|
|
162
|
+
transaction,
|
|
145
163
|
});
|
|
146
164
|
};
|
|
165
|
+
|
|
166
|
+
cloneTo = async (otherDb: IDBDatabase) => {
|
|
167
|
+
await copyDatabase(this.db, otherDb);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
private onVersionChange = () => {
|
|
171
|
+
this.log?.(
|
|
172
|
+
'warn',
|
|
173
|
+
`Another tab has requested a version change for ${this.db.name}`,
|
|
174
|
+
);
|
|
175
|
+
this.db.close();
|
|
176
|
+
if (typeof window !== 'undefined') {
|
|
177
|
+
window.location.reload();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
147
180
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { FileData } from '@verdant-web/common';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
AbstractTransaction,
|
|
4
|
+
PersistedFileData,
|
|
5
|
+
PersistenceFileDb,
|
|
6
|
+
QueryMode,
|
|
7
|
+
} from '../../interfaces.js';
|
|
8
|
+
import { IdbService } from '../IdbService.js';
|
|
9
|
+
import { getAllFromObjectStores, getSizeOfObjectStore } from '../util.js';
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* When stored in IDB, replace the file blob with an array buffer
|
|
@@ -15,20 +20,27 @@ export interface StoredFileData extends Omit<FileData, 'remote' | 'file'> {
|
|
|
15
20
|
timestamp?: string;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
export class IdbPersistenceFileDb
|
|
24
|
+
extends IdbService
|
|
25
|
+
implements PersistenceFileDb
|
|
26
|
+
{
|
|
27
|
+
transaction = (opts: {
|
|
28
|
+
mode?: QueryMode;
|
|
29
|
+
storeNames: string[];
|
|
30
|
+
abort?: AbortSignal;
|
|
31
|
+
}): AbstractTransaction => {
|
|
32
|
+
return this.createTransaction(opts.storeNames, {
|
|
33
|
+
mode: opts.mode,
|
|
34
|
+
abort: opts.abort,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
21
37
|
|
|
22
|
-
|
|
23
|
-
addFile = async (
|
|
38
|
+
add = async (
|
|
24
39
|
file: FileData,
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
downloadRemote = false,
|
|
28
|
-
}: { transaction?: IDBTransaction; downloadRemote?: boolean } = {},
|
|
29
|
-
) => {
|
|
40
|
+
options?: { transaction?: AbstractTransaction; downloadRemote?: boolean },
|
|
41
|
+
): Promise<void> => {
|
|
30
42
|
let buffer = file.file ? await fileToArrayBuffer(file.file) : undefined;
|
|
31
|
-
if (!buffer && downloadRemote && file.url) {
|
|
43
|
+
if (!buffer && options?.downloadRemote && file.url) {
|
|
32
44
|
try {
|
|
33
45
|
buffer = await fetch(file.url, {
|
|
34
46
|
method: 'GET',
|
|
@@ -41,7 +53,7 @@ export class FileStorage extends IDBService {
|
|
|
41
53
|
);
|
|
42
54
|
}
|
|
43
55
|
}
|
|
44
|
-
|
|
56
|
+
await this.run(
|
|
45
57
|
'files',
|
|
46
58
|
(store) => {
|
|
47
59
|
return store.put({
|
|
@@ -53,33 +65,25 @@ export class FileStorage extends IDBService {
|
|
|
53
65
|
type: file.type,
|
|
54
66
|
url: file.url,
|
|
55
67
|
buffer,
|
|
56
|
-
}
|
|
68
|
+
} satisfies StoredFileData);
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
mode: 'readwrite',
|
|
72
|
+
transaction: options?.transaction as IDBTransaction,
|
|
57
73
|
},
|
|
58
|
-
{ mode: 'readwrite', transaction },
|
|
59
74
|
);
|
|
60
75
|
};
|
|
61
|
-
|
|
62
|
-
private hydrateFileData = (raw: StoredFileData): ReturnedFileData => {
|
|
63
|
-
(raw as any).remote = raw.remote === 'true';
|
|
64
|
-
const buffer = raw.buffer;
|
|
65
|
-
delete raw.buffer;
|
|
66
|
-
(raw as unknown as FileData).file = buffer
|
|
67
|
-
? arrayBufferToBlob(buffer, raw.type)
|
|
68
|
-
: undefined;
|
|
69
|
-
return raw as unknown as ReturnedFileData;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
76
|
markUploaded = async (
|
|
73
77
|
id: string,
|
|
74
|
-
|
|
75
|
-
) => {
|
|
76
|
-
const current = await this.getFileRaw(id,
|
|
78
|
+
options?: { transaction?: AbstractTransaction },
|
|
79
|
+
): Promise<void> => {
|
|
80
|
+
const current = await this.getFileRaw(id, options);
|
|
77
81
|
|
|
78
82
|
if (!current) {
|
|
79
83
|
throw new Error('File is not in local database');
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
await this.run(
|
|
83
87
|
'files',
|
|
84
88
|
(store) => {
|
|
85
89
|
return store.put({
|
|
@@ -87,62 +91,48 @@ export class FileStorage extends IDBService {
|
|
|
87
91
|
remote: 'true',
|
|
88
92
|
} as StoredFileData);
|
|
89
93
|
},
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
private getFileRaw = async (
|
|
95
|
-
id: string,
|
|
96
|
-
{ transaction }: { transaction?: IDBTransaction } = {},
|
|
97
|
-
): Promise<StoredFileData | undefined> => {
|
|
98
|
-
const raw = await this.run<StoredFileData>(
|
|
99
|
-
'files',
|
|
100
|
-
(store) => {
|
|
101
|
-
return store.get(id);
|
|
94
|
+
{
|
|
95
|
+
mode: 'readwrite',
|
|
96
|
+
transaction: options?.transaction as IDBTransaction,
|
|
102
97
|
},
|
|
103
|
-
{ mode: 'readonly', transaction },
|
|
104
98
|
);
|
|
105
|
-
if (!raw) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
return raw;
|
|
109
99
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const raw = await this.getFileRaw(id, { transaction });
|
|
100
|
+
get = async (
|
|
101
|
+
fileId: string,
|
|
102
|
+
options?: { transaction?: AbstractTransaction },
|
|
103
|
+
): Promise<PersistedFileData | null> => {
|
|
104
|
+
const raw = await this.getFileRaw(fileId, options);
|
|
116
105
|
if (!raw) {
|
|
117
|
-
return
|
|
106
|
+
return null;
|
|
118
107
|
}
|
|
119
108
|
return this.hydrateFileData(raw);
|
|
120
109
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
) {
|
|
110
|
+
delete = (
|
|
111
|
+
fileId: string,
|
|
112
|
+
options?: { transaction?: AbstractTransaction },
|
|
113
|
+
): Promise<void> => {
|
|
126
114
|
return this.run<undefined>(
|
|
127
115
|
'files',
|
|
128
116
|
(store) => {
|
|
129
|
-
return store.delete(
|
|
117
|
+
return store.delete(fileId);
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
mode: 'readwrite',
|
|
121
|
+
transaction: options?.transaction as IDBTransaction,
|
|
130
122
|
},
|
|
131
|
-
{ mode: 'readwrite', transaction },
|
|
132
123
|
);
|
|
133
|
-
}
|
|
134
|
-
|
|
124
|
+
};
|
|
135
125
|
markPendingDelete = async (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
) => {
|
|
139
|
-
const current = await this.getFileRaw(
|
|
126
|
+
fileId: string,
|
|
127
|
+
options?: { transaction?: AbstractTransaction },
|
|
128
|
+
): Promise<void> => {
|
|
129
|
+
const current = await this.getFileRaw(fileId, options);
|
|
140
130
|
|
|
141
131
|
if (!current) {
|
|
142
132
|
throw new Error('File is not in local database');
|
|
143
133
|
}
|
|
144
134
|
|
|
145
|
-
|
|
135
|
+
await this.run(
|
|
146
136
|
'files',
|
|
147
137
|
(store) => {
|
|
148
138
|
return store.put({
|
|
@@ -150,23 +140,31 @@ export class FileStorage extends IDBService {
|
|
|
150
140
|
deletedAt: Date.now(),
|
|
151
141
|
} as StoredFileData);
|
|
152
142
|
},
|
|
153
|
-
{
|
|
143
|
+
{
|
|
144
|
+
mode: 'readwrite',
|
|
145
|
+
transaction: options?.transaction as IDBTransaction,
|
|
146
|
+
},
|
|
154
147
|
);
|
|
155
148
|
};
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
listUnsynced = async (options?: {
|
|
150
|
+
transaction?: AbstractTransaction;
|
|
151
|
+
}): Promise<PersistedFileData[]> => {
|
|
158
152
|
const raw = await this.run<StoredFileData[]>(
|
|
159
153
|
'files',
|
|
160
154
|
(store) => {
|
|
161
155
|
return store.index('remote').getAll('false');
|
|
162
156
|
},
|
|
163
|
-
{ mode: 'readonly' },
|
|
157
|
+
{ mode: 'readonly', transaction: options?.transaction as IDBTransaction },
|
|
164
158
|
);
|
|
165
159
|
return raw.map(this.hydrateFileData);
|
|
166
160
|
};
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
resetSyncedStatusSince = async (
|
|
162
|
+
since: string | null,
|
|
163
|
+
options?: { transaction?: AbstractTransaction },
|
|
164
|
+
): Promise<void> => {
|
|
165
|
+
const tx: IDBTransaction =
|
|
166
|
+
(options?.transaction as any) ??
|
|
167
|
+
this.createTransaction(['files'], { mode: 'readwrite' });
|
|
170
168
|
const raw = await this.run<StoredFileData[]>(
|
|
171
169
|
'files',
|
|
172
170
|
(store) => {
|
|
@@ -194,11 +192,10 @@ export class FileStorage extends IDBService {
|
|
|
194
192
|
}),
|
|
195
193
|
);
|
|
196
194
|
};
|
|
197
|
-
|
|
198
195
|
iterateOverPendingDelete = (
|
|
199
|
-
iterator: (file:
|
|
200
|
-
transaction?: IDBTransaction,
|
|
201
|
-
) => {
|
|
196
|
+
iterator: (file: PersistedFileData, store: IDBObjectStore) => void,
|
|
197
|
+
options?: { transaction?: IDBTransaction },
|
|
198
|
+
): Promise<void> => {
|
|
202
199
|
return this.iterate<StoredFileData>(
|
|
203
200
|
'files',
|
|
204
201
|
(store) => {
|
|
@@ -209,22 +206,67 @@ export class FileStorage extends IDBService {
|
|
|
209
206
|
(value, store) => {
|
|
210
207
|
iterator(this.hydrateFileData(value), store);
|
|
211
208
|
},
|
|
212
|
-
{
|
|
209
|
+
{
|
|
210
|
+
mode: 'readwrite',
|
|
211
|
+
transaction: options?.transaction as IDBTransaction,
|
|
212
|
+
},
|
|
213
213
|
);
|
|
214
214
|
};
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
getAll = async (options?: {
|
|
216
|
+
transaction?: AbstractTransaction;
|
|
217
|
+
}): Promise<PersistedFileData[]> => {
|
|
217
218
|
const [files] = await getAllFromObjectStores(this.db, ['files']);
|
|
218
219
|
return files.map(this.hydrateFileData);
|
|
219
220
|
};
|
|
220
|
-
|
|
221
|
-
stats = async () => {
|
|
221
|
+
stats = async (): Promise<{ size: { count: number; size: number } }> => {
|
|
222
222
|
return {
|
|
223
223
|
size: await getSizeOfObjectStore(this.db, 'files'),
|
|
224
224
|
};
|
|
225
225
|
};
|
|
226
|
+
|
|
227
|
+
private hydrateFileData = (raw: StoredFileData): PersistedFileData => {
|
|
228
|
+
(raw as any).remote = raw.remote === 'true';
|
|
229
|
+
const buffer = raw.buffer;
|
|
230
|
+
delete raw.buffer;
|
|
231
|
+
(raw as unknown as FileData).file = buffer
|
|
232
|
+
? arrayBufferToBlob(buffer, raw.type)
|
|
233
|
+
: undefined;
|
|
234
|
+
return raw as unknown as PersistedFileData;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
private getFileRaw = async (
|
|
238
|
+
id: string,
|
|
239
|
+
{ transaction }: { transaction?: AbstractTransaction } = {},
|
|
240
|
+
): Promise<StoredFileData | undefined> => {
|
|
241
|
+
const raw = await this.run<StoredFileData>(
|
|
242
|
+
'files',
|
|
243
|
+
(store) => {
|
|
244
|
+
return store.get(id);
|
|
245
|
+
},
|
|
246
|
+
{ mode: 'readonly', transaction: transaction as IDBTransaction },
|
|
247
|
+
);
|
|
248
|
+
if (!raw) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
return raw;
|
|
252
|
+
};
|
|
226
253
|
}
|
|
227
254
|
|
|
228
255
|
export function arrayBufferToBlob(buffer: ArrayBuffer, type: string) {
|
|
229
256
|
return new Blob([buffer], { type });
|
|
230
257
|
}
|
|
258
|
+
|
|
259
|
+
function fileToArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
|
|
260
|
+
// special case for testing...
|
|
261
|
+
if ('__testReadBuffer' in file) {
|
|
262
|
+
return Promise.resolve<any>(file.__testReadBuffer);
|
|
263
|
+
}
|
|
264
|
+
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
265
|
+
const reader = new FileReader();
|
|
266
|
+
reader.onload = () => {
|
|
267
|
+
resolve(reader.result as ArrayBuffer);
|
|
268
|
+
};
|
|
269
|
+
reader.onerror = reject;
|
|
270
|
+
reader.readAsArrayBuffer(file);
|
|
271
|
+
});
|
|
272
|
+
}
|