@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,227 @@
|
|
|
1
|
+
import { FileData, FileRef } from '@verdant-web/common';
|
|
2
|
+
import { Context, FileConfig } from '../context/context.js';
|
|
3
|
+
import { PersistedFileData, PersistenceFileDb } from './interfaces.js';
|
|
4
|
+
import { Disposable } from '../utils/Disposable.js';
|
|
5
|
+
|
|
6
|
+
export class PersistenceFiles extends Disposable {
|
|
7
|
+
constructor(
|
|
8
|
+
private db: PersistenceFileDb,
|
|
9
|
+
private context: Omit<Context, 'queries'>,
|
|
10
|
+
) {
|
|
11
|
+
super();
|
|
12
|
+
context.internalEvents.subscribe('filesDeleted', this.onFileRefsDeleted);
|
|
13
|
+
this.compose(this.db);
|
|
14
|
+
// on startup, try deleting old files.
|
|
15
|
+
this.cleanupDeletedFiles();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private get config(): Required<FileConfig> {
|
|
19
|
+
return {
|
|
20
|
+
canCleanupDeletedFile(fileData) {
|
|
21
|
+
return (
|
|
22
|
+
fileData.deletedAt !== null &&
|
|
23
|
+
fileData.deletedAt < Date.now() - 1000 * 60 * 24 * 3
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
...this.context.config.files,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onServerReset = (since: string | null) =>
|
|
31
|
+
this.db.resetSyncedStatusSince(since);
|
|
32
|
+
add = async (file: FileData, options?: { downloadRemote?: boolean }) => {
|
|
33
|
+
// this method accepts a FileData which refers to a remote
|
|
34
|
+
// file, as well as local files. in the case of a remote file,
|
|
35
|
+
// we actually re-download and upload the file again. this powers
|
|
36
|
+
// the cloning of documents with files; we clone their filedata
|
|
37
|
+
// and re-upload to a new file ID. otherwise, when the cloned
|
|
38
|
+
// filedata was marked deleted, the original file would be deleted
|
|
39
|
+
// and the clone would refer to a missing file.
|
|
40
|
+
if (file.url && !file.file) {
|
|
41
|
+
this.context.log(
|
|
42
|
+
'debug',
|
|
43
|
+
'Remote file added to an entity. This usually means an entity was cloned. Downloading remote file...',
|
|
44
|
+
file.id,
|
|
45
|
+
);
|
|
46
|
+
const blob = await this.context.files.downloadRemoteFile(file.url, 0, 3);
|
|
47
|
+
// convert blob to file with name and type
|
|
48
|
+
file.file = new File([blob], file.name, { type: file.type });
|
|
49
|
+
} else if (!file.file) {
|
|
50
|
+
this.context.log(
|
|
51
|
+
'warn',
|
|
52
|
+
'File added without a file or URL. This file will not be available for use.',
|
|
53
|
+
file.id,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
file.remote = false;
|
|
58
|
+
// fire event for processing immediately
|
|
59
|
+
this.context.internalEvents.emit('fileAdded', file);
|
|
60
|
+
// store in persistence db
|
|
61
|
+
await this.db.add(file, options);
|
|
62
|
+
this.context.log(
|
|
63
|
+
'debug',
|
|
64
|
+
'File added',
|
|
65
|
+
file.id,
|
|
66
|
+
file.name,
|
|
67
|
+
file.type,
|
|
68
|
+
file.file ? 'with binary file' : file.url ? 'with url' : 'with no data',
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
onUploaded = this.db.markUploaded.bind(this.db);
|
|
72
|
+
get = this.db.get.bind(this.db);
|
|
73
|
+
getAll = this.db.getAll.bind(this.db);
|
|
74
|
+
listUnsynced = this.db.listUnsynced.bind(this.db);
|
|
75
|
+
iterateOverPendingDelete = this.db.iterateOverPendingDelete.bind(this.db);
|
|
76
|
+
stats = this.db.stats.bind(this.db);
|
|
77
|
+
|
|
78
|
+
private getFileExportName = (originalFileName: string, id: string) => {
|
|
79
|
+
return `${id}___${originalFileName}`;
|
|
80
|
+
};
|
|
81
|
+
export = async (downloadRemote = false) => {
|
|
82
|
+
const storedFiles = await this.getAll();
|
|
83
|
+
if (downloadRemote) {
|
|
84
|
+
for (const storedFile of storedFiles) {
|
|
85
|
+
// if it doesn't have a buffer, we need to read one from the server
|
|
86
|
+
if (!storedFile.file && storedFile.url) {
|
|
87
|
+
try {
|
|
88
|
+
const blob = await this.downloadRemoteFile(storedFile.url);
|
|
89
|
+
storedFile.file = blob;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
this.context.log(
|
|
92
|
+
'error',
|
|
93
|
+
"Failed to download file to cache it locally. The file will still be available using its URL. Check the file server's CORS configuration.",
|
|
94
|
+
err,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// split files into data and files
|
|
101
|
+
const fileData: Array<Omit<PersistedFileData, 'file'>> = [];
|
|
102
|
+
const files: Array<File> = [];
|
|
103
|
+
|
|
104
|
+
for (const fileExport of storedFiles) {
|
|
105
|
+
const file = fileExport.file;
|
|
106
|
+
delete fileExport.file;
|
|
107
|
+
fileData.push(fileExport);
|
|
108
|
+
if (file) {
|
|
109
|
+
// rename with ID
|
|
110
|
+
const asFile = new File(
|
|
111
|
+
[file],
|
|
112
|
+
this.getFileExportName(fileExport.name, fileExport.id),
|
|
113
|
+
{
|
|
114
|
+
type: fileExport.type,
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
files.push(asFile);
|
|
118
|
+
} else {
|
|
119
|
+
this.context.log(
|
|
120
|
+
'warn',
|
|
121
|
+
`File ${fileExport.id} was could not be loaded locally or from the server. It will be missing in the export.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
fileData,
|
|
127
|
+
files,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
import = async ({
|
|
132
|
+
fileData,
|
|
133
|
+
files,
|
|
134
|
+
}: {
|
|
135
|
+
fileData: Array<Omit<PersistedFileData, 'file'>>;
|
|
136
|
+
files: File[];
|
|
137
|
+
}) => {
|
|
138
|
+
// re-attach files to their file data and import
|
|
139
|
+
const fileToIdMap = new Map(
|
|
140
|
+
files.map((file) => {
|
|
141
|
+
const { id } = this.parseFileExportname(file.name);
|
|
142
|
+
return [id, file];
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
const importedFiles: PersistedFileData[] = fileData.map((fileData) => {
|
|
146
|
+
const file = fileToIdMap.get(fileData.id);
|
|
147
|
+
|
|
148
|
+
if (!file) {
|
|
149
|
+
this.context.log('warn', `File ${fileData.id} was not found in import`);
|
|
150
|
+
return fileData;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
...fileData,
|
|
155
|
+
file,
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
await Promise.all(importedFiles.map((file) => this.add(file)));
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
private parseFileExportname = (name: string) => {
|
|
162
|
+
const [id, originalFileName] = name.split('___');
|
|
163
|
+
return { id, originalFileName };
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
downloadRemoteFile = async (url: string, retries = 0, maxRetries = 0) => {
|
|
167
|
+
const resp = await fetch(url, {
|
|
168
|
+
method: 'GET',
|
|
169
|
+
credentials: 'include',
|
|
170
|
+
});
|
|
171
|
+
if (!resp.ok) {
|
|
172
|
+
if (retries < maxRetries) {
|
|
173
|
+
return new Promise<Blob>((resolve, reject) => {
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
this.downloadRemoteFile(url, retries + 1, maxRetries).then(
|
|
176
|
+
resolve,
|
|
177
|
+
reject,
|
|
178
|
+
);
|
|
179
|
+
}, 1000);
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Failed to download file after ${maxRetries} retries (status: ${resp.status})`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return await resp.blob();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
cleanupDeletedFiles = async () => {
|
|
191
|
+
let count = 0;
|
|
192
|
+
let skipCount = 0;
|
|
193
|
+
await this.iterateOverPendingDelete((fileData, store) => {
|
|
194
|
+
if (this.config.canCleanupDeletedFile(fileData)) {
|
|
195
|
+
count++;
|
|
196
|
+
store.delete(fileData.id);
|
|
197
|
+
} else {
|
|
198
|
+
skipCount++;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
this.context.log(
|
|
203
|
+
'info',
|
|
204
|
+
`Cleaned up ${count} files, skipped ${skipCount} files`,
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
private onFileRefsDeleted = async (fileRefs: FileRef[]) => {
|
|
209
|
+
const tx = this.db.transaction({
|
|
210
|
+
mode: 'readwrite',
|
|
211
|
+
storeNames: ['files'],
|
|
212
|
+
});
|
|
213
|
+
await Promise.all(
|
|
214
|
+
fileRefs.map(async (fileRef) => {
|
|
215
|
+
try {
|
|
216
|
+
await this.db.markPendingDelete(fileRef.id, { transaction: tx });
|
|
217
|
+
} catch (err) {
|
|
218
|
+
this.context.log('error', 'Failed to mark file for deletion', err);
|
|
219
|
+
}
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
this.context.log(
|
|
223
|
+
'info',
|
|
224
|
+
`Marked ${fileRefs.length} files as pending delete`,
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyPatch,
|
|
3
|
+
assert,
|
|
4
|
+
assignOid,
|
|
5
|
+
ClientMessage,
|
|
6
|
+
DocumentBaseline,
|
|
7
|
+
EventSubscriber,
|
|
8
|
+
getOidRoot,
|
|
9
|
+
ObjectIdentifier,
|
|
10
|
+
Operation,
|
|
11
|
+
substituteRefsWithObjects,
|
|
12
|
+
} from '@verdant-web/common';
|
|
13
|
+
import {
|
|
14
|
+
AbstractTransaction,
|
|
15
|
+
ClientOperation,
|
|
16
|
+
CommonQueryOptions,
|
|
17
|
+
MetadataExport,
|
|
18
|
+
PersistenceMetadataDb,
|
|
19
|
+
} from './interfaces.js';
|
|
20
|
+
import { InitialContext } from '../context/context.js';
|
|
21
|
+
import { PersistenceRebaser } from './PersistenceRebaser.js';
|
|
22
|
+
import { MessageCreator } from './MessageCreator.js';
|
|
23
|
+
import { Disposable } from '../utils/Disposable.js';
|
|
24
|
+
|
|
25
|
+
export class PersistenceMetadata extends Disposable {
|
|
26
|
+
private rebaser: PersistenceRebaser;
|
|
27
|
+
/** Available to others, like sync... */
|
|
28
|
+
readonly messageCreator: MessageCreator;
|
|
29
|
+
readonly events = new EventSubscriber<{
|
|
30
|
+
syncMessage: (message: ClientMessage) => void;
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
private db: PersistenceMetadataDb,
|
|
35
|
+
private ctx: InitialContext,
|
|
36
|
+
) {
|
|
37
|
+
super();
|
|
38
|
+
this.rebaser = new PersistenceRebaser(db, ctx);
|
|
39
|
+
this.messageCreator = new MessageCreator(db, ctx);
|
|
40
|
+
this.compose(this.db);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private insertOperations = async (
|
|
44
|
+
operations: ClientOperation[],
|
|
45
|
+
options?: { transaction?: AbstractTransaction },
|
|
46
|
+
) => {
|
|
47
|
+
this.ctx.log(
|
|
48
|
+
'debug',
|
|
49
|
+
`Inserting ${operations.length} operations`,
|
|
50
|
+
operations,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const affectedDocumentOids = await this.db.addOperations(
|
|
54
|
+
operations,
|
|
55
|
+
options,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
for (const op of operations) {
|
|
59
|
+
this.ctx.globalEvents.emit('operation', op);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// we can now enqueue and check for rebase opportunities
|
|
63
|
+
if (!this.ctx.config.persistence?.disableRebasing) {
|
|
64
|
+
this.rebaser.tryAutonomousRebase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return affectedDocumentOids;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
private insertLocalOperations = async (
|
|
71
|
+
operations: Operation[],
|
|
72
|
+
options?: { transaction?: AbstractTransaction },
|
|
73
|
+
) => {
|
|
74
|
+
if (operations.length === 0) return;
|
|
75
|
+
|
|
76
|
+
// add local flag, in place.
|
|
77
|
+
for (const operation of operations) {
|
|
78
|
+
(operation as ClientOperation).isLocal = true;
|
|
79
|
+
}
|
|
80
|
+
await this.insertOperations(operations as ClientOperation[], options);
|
|
81
|
+
|
|
82
|
+
const message = await this.messageCreator.createOperation({ operations });
|
|
83
|
+
this.events.emit('syncMessage', message);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
private insertRemoteOperations = async (
|
|
87
|
+
operations: Operation[],
|
|
88
|
+
options?: { transaction?: AbstractTransaction },
|
|
89
|
+
) => {
|
|
90
|
+
if (operations.length === 0) return [];
|
|
91
|
+
|
|
92
|
+
// add local flag, in place
|
|
93
|
+
for (const operation of operations) {
|
|
94
|
+
(operation as ClientOperation).isLocal = false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await this.insertOperations(operations as ClientOperation[], options);
|
|
98
|
+
|
|
99
|
+
this.ack(operations[operations.length - 1].timestamp);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
private insertRemoteBaselines = async (
|
|
103
|
+
baselines: DocumentBaseline[],
|
|
104
|
+
options?: { transaction?: AbstractTransaction },
|
|
105
|
+
) => {
|
|
106
|
+
if (baselines.length === 0) return [];
|
|
107
|
+
this.ctx.log('debug', `Inserting ${baselines.length} remote baselines`);
|
|
108
|
+
|
|
109
|
+
await this.db.setBaselines(baselines, options);
|
|
110
|
+
|
|
111
|
+
// this.ack(baselines[baselines.length - 1].timestamp);
|
|
112
|
+
|
|
113
|
+
const affectedOidSet = new Set<ObjectIdentifier>();
|
|
114
|
+
baselines.forEach((baseline) => {
|
|
115
|
+
affectedOidSet.add(getOidRoot(baseline.oid));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return Array.from(affectedOidSet);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
deleteDocument = async (rootOid: string) => {
|
|
122
|
+
const oids = new Set<ObjectIdentifier>();
|
|
123
|
+
const documentOid = getOidRoot(rootOid);
|
|
124
|
+
assert(documentOid === rootOid, 'Must be root document OID');
|
|
125
|
+
oids.add(documentOid);
|
|
126
|
+
// readwrite mode to block on other write transactions
|
|
127
|
+
const transaction = this.db.transaction({
|
|
128
|
+
storeNames: ['baselines', 'operations'],
|
|
129
|
+
});
|
|
130
|
+
await Promise.all([
|
|
131
|
+
this.db.iterateDocumentBaselines(
|
|
132
|
+
documentOid,
|
|
133
|
+
(baseline) => {
|
|
134
|
+
oids.add(baseline.oid);
|
|
135
|
+
},
|
|
136
|
+
{ transaction },
|
|
137
|
+
),
|
|
138
|
+
this.db.iterateDocumentOperations(
|
|
139
|
+
documentOid,
|
|
140
|
+
(patch) => {
|
|
141
|
+
oids.add(patch.oid);
|
|
142
|
+
},
|
|
143
|
+
{ transaction },
|
|
144
|
+
),
|
|
145
|
+
]);
|
|
146
|
+
const authz = await this.getDocumentAuthz(documentOid);
|
|
147
|
+
const ops = new Array<Operation>();
|
|
148
|
+
for (const oid of oids) {
|
|
149
|
+
ops.push({
|
|
150
|
+
oid,
|
|
151
|
+
timestamp: this.ctx.time.now,
|
|
152
|
+
data: { op: 'delete' },
|
|
153
|
+
authz,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return this.insertLocalOperations(ops);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
deleteCollection = async (collection: string) => {
|
|
160
|
+
const oids = new Set<ObjectIdentifier>();
|
|
161
|
+
const transaction = this.db.transaction({
|
|
162
|
+
storeNames: ['baselines', 'operations'],
|
|
163
|
+
mode: 'readwrite',
|
|
164
|
+
});
|
|
165
|
+
await Promise.all([
|
|
166
|
+
this.db.iterateCollectionBaselines(
|
|
167
|
+
collection,
|
|
168
|
+
(baseline) => {
|
|
169
|
+
oids.add(baseline.oid);
|
|
170
|
+
},
|
|
171
|
+
{ transaction },
|
|
172
|
+
),
|
|
173
|
+
this.db.iterateCollectionOperations(
|
|
174
|
+
collection,
|
|
175
|
+
(patch) => {
|
|
176
|
+
oids.add(patch.oid);
|
|
177
|
+
},
|
|
178
|
+
{ transaction },
|
|
179
|
+
),
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
const ops = new Array<Operation>();
|
|
183
|
+
for (const oid of oids) {
|
|
184
|
+
ops.push({
|
|
185
|
+
oid,
|
|
186
|
+
timestamp: this.ctx.time.now,
|
|
187
|
+
data: { op: 'delete' },
|
|
188
|
+
authz: undefined,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return this.insertLocalOperations(ops);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
getDocumentSnapshot = async (
|
|
196
|
+
oid: ObjectIdentifier,
|
|
197
|
+
options: { to?: string } = {},
|
|
198
|
+
) => {
|
|
199
|
+
const documentOid = getOidRoot(oid);
|
|
200
|
+
assert(documentOid === oid, 'Must be root document OID');
|
|
201
|
+
const transaction = this.db.transaction({
|
|
202
|
+
storeNames: ['baselines', 'operations'],
|
|
203
|
+
mode: 'readwrite',
|
|
204
|
+
});
|
|
205
|
+
const baselines: DocumentBaseline[] = [];
|
|
206
|
+
await this.db.iterateDocumentBaselines(
|
|
207
|
+
documentOid,
|
|
208
|
+
(b) => {
|
|
209
|
+
baselines.push(b);
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
transaction,
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
const objectMap = new Map<ObjectIdentifier, any>();
|
|
216
|
+
for (const baseline of baselines) {
|
|
217
|
+
if (baseline.snapshot) {
|
|
218
|
+
assignOid(baseline.snapshot, baseline.oid);
|
|
219
|
+
}
|
|
220
|
+
objectMap.set(baseline.oid, baseline.snapshot);
|
|
221
|
+
}
|
|
222
|
+
await this.db.iterateDocumentOperations(
|
|
223
|
+
documentOid,
|
|
224
|
+
(op) => {
|
|
225
|
+
const obj = objectMap.get(op.oid) || undefined;
|
|
226
|
+
const newObj = applyPatch(obj, op.data);
|
|
227
|
+
if (newObj) {
|
|
228
|
+
assignOid(newObj, op.oid);
|
|
229
|
+
}
|
|
230
|
+
objectMap.set(op.oid, newObj);
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
transaction,
|
|
234
|
+
// only apply operations up to the current time
|
|
235
|
+
to: options.to || this.ctx.time.now,
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
const root = objectMap.get(documentOid);
|
|
239
|
+
if (root) {
|
|
240
|
+
substituteRefsWithObjects(root, objectMap);
|
|
241
|
+
}
|
|
242
|
+
return root;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
getDocumentData = async (
|
|
246
|
+
oid: ObjectIdentifier,
|
|
247
|
+
options?: { abort?: AbortSignal },
|
|
248
|
+
) => {
|
|
249
|
+
const transaction = this.db.transaction({
|
|
250
|
+
storeNames: ['baselines', 'operations'],
|
|
251
|
+
abort: options?.abort,
|
|
252
|
+
});
|
|
253
|
+
const baselines: DocumentBaseline[] = [];
|
|
254
|
+
const operations: Record<ObjectIdentifier, Operation[]> = {};
|
|
255
|
+
await Promise.all([
|
|
256
|
+
this.db.iterateDocumentBaselines(
|
|
257
|
+
oid,
|
|
258
|
+
(baseline) => {
|
|
259
|
+
baselines.push(baseline);
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
transaction,
|
|
263
|
+
},
|
|
264
|
+
),
|
|
265
|
+
this.db.iterateDocumentOperations(
|
|
266
|
+
oid,
|
|
267
|
+
(op) => {
|
|
268
|
+
operations[op.oid] ??= [];
|
|
269
|
+
operations[op.oid].push(op);
|
|
270
|
+
},
|
|
271
|
+
{ transaction },
|
|
272
|
+
),
|
|
273
|
+
]);
|
|
274
|
+
return {
|
|
275
|
+
baselines,
|
|
276
|
+
operations,
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
getDocumentAuthz = async (oid: ObjectIdentifier) => {
|
|
281
|
+
let authz;
|
|
282
|
+
await this.db.iterateEntityOperations(oid, (op) => {
|
|
283
|
+
if (op.data.op === 'initialize') {
|
|
284
|
+
authz = op.authz;
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return authz;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
insertData = async (
|
|
292
|
+
data: {
|
|
293
|
+
baselines?: DocumentBaseline[];
|
|
294
|
+
operations?: Operation[];
|
|
295
|
+
isLocal?: boolean;
|
|
296
|
+
},
|
|
297
|
+
options?: { abort?: AbortSignal },
|
|
298
|
+
) => {
|
|
299
|
+
const transaction = this.db.transaction({
|
|
300
|
+
storeNames: ['baselines', 'operations'],
|
|
301
|
+
abort: options?.abort,
|
|
302
|
+
mode: 'readwrite',
|
|
303
|
+
});
|
|
304
|
+
if (data.baselines) {
|
|
305
|
+
await this.insertRemoteBaselines(data.baselines, { transaction });
|
|
306
|
+
}
|
|
307
|
+
if (options?.abort?.aborted) return;
|
|
308
|
+
if (data.operations) {
|
|
309
|
+
if (data.isLocal) {
|
|
310
|
+
await this.insertLocalOperations(data.operations, { transaction });
|
|
311
|
+
} else {
|
|
312
|
+
await this.insertRemoteOperations(data.operations, { transaction });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
updateLastSynced = async (timestamp: string) => {
|
|
318
|
+
if (this.ctx.closing) return;
|
|
319
|
+
|
|
320
|
+
return this.db.updateLocalReplica({
|
|
321
|
+
lastSyncedLogicalTime: timestamp,
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
setGlobalAck = async (ack: string) => {
|
|
325
|
+
if (this.ctx.closing) return;
|
|
326
|
+
await this.db.setGlobalAck(ack);
|
|
327
|
+
if (!this.ctx.config.persistence?.disableRebasing) {
|
|
328
|
+
await this.rebaser.scheduleRebase(ack);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
getLocalReplica = async (options?: CommonQueryOptions) => {
|
|
333
|
+
return this.db.getLocalReplica(options);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// used to construct sync messages
|
|
337
|
+
iterateLocalOperations = this.db.iterateLocalOperations;
|
|
338
|
+
iterateAllOperations = this.db.iterateAllOperations;
|
|
339
|
+
iterateAllBaselines = this.db.iterateAllBaselines;
|
|
340
|
+
|
|
341
|
+
reset = this.db.reset;
|
|
342
|
+
stats = this.db.stats;
|
|
343
|
+
|
|
344
|
+
export = async (): Promise<MetadataExport> => {
|
|
345
|
+
const db = this.db;
|
|
346
|
+
const baselines = new Array<DocumentBaseline>();
|
|
347
|
+
const operations = new Array<ClientOperation>();
|
|
348
|
+
const transaction = db.transaction({
|
|
349
|
+
storeNames: ['baselines', 'operations'],
|
|
350
|
+
mode: 'readwrite',
|
|
351
|
+
});
|
|
352
|
+
await this.iterateAllOperations(
|
|
353
|
+
(op) => {
|
|
354
|
+
operations.push(op);
|
|
355
|
+
},
|
|
356
|
+
{ transaction },
|
|
357
|
+
);
|
|
358
|
+
await this.iterateAllBaselines(
|
|
359
|
+
(baseline) => {
|
|
360
|
+
baselines.push(baseline);
|
|
361
|
+
},
|
|
362
|
+
{ transaction },
|
|
363
|
+
);
|
|
364
|
+
const localReplica = await this.db.getLocalReplica();
|
|
365
|
+
return {
|
|
366
|
+
operations,
|
|
367
|
+
baselines,
|
|
368
|
+
localReplica,
|
|
369
|
+
schemaVersion: this.ctx.schema.version,
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
resetFrom = async (data: MetadataExport) => {
|
|
374
|
+
const db = this.db;
|
|
375
|
+
const transaction = db.transaction({
|
|
376
|
+
storeNames: ['baselines', 'operations', 'info'],
|
|
377
|
+
mode: 'readwrite',
|
|
378
|
+
});
|
|
379
|
+
await this.db.reset({ clearReplica: true, transaction });
|
|
380
|
+
if (data.localReplica) {
|
|
381
|
+
await this.db.updateLocalReplica(
|
|
382
|
+
{
|
|
383
|
+
ackedLogicalTime: data.localReplica.ackedLogicalTime,
|
|
384
|
+
lastSyncedLogicalTime: data.localReplica.lastSyncedLogicalTime,
|
|
385
|
+
},
|
|
386
|
+
{ transaction },
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
this.ctx.log('debug', 'Resetting metadata from export', data);
|
|
390
|
+
await this.insertData({
|
|
391
|
+
operations: data.operations,
|
|
392
|
+
baselines: data.baselines,
|
|
393
|
+
isLocal: true,
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
manualRebase = async () => {
|
|
398
|
+
if (this.ctx.closing || this.ctx.config.persistence?.disableRebasing)
|
|
399
|
+
return;
|
|
400
|
+
const ackInfo = await this.db.getAckInfo();
|
|
401
|
+
if (ackInfo.globalAckTimestamp) {
|
|
402
|
+
await this.rebaser.scheduleRebase(ackInfo.globalAckTimestamp);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
private ack = async (timestamp: string) => {
|
|
407
|
+
const localReplicaInfo = await this.db.getLocalReplica();
|
|
408
|
+
// can't ack timestamps from the future.
|
|
409
|
+
if (timestamp > this.ctx.time.now) return;
|
|
410
|
+
|
|
411
|
+
this.events.emit('syncMessage', {
|
|
412
|
+
type: 'ack',
|
|
413
|
+
replicaId: localReplicaInfo.id,
|
|
414
|
+
timestamp,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (
|
|
418
|
+
!this.ctx.closing &&
|
|
419
|
+
(!localReplicaInfo.ackedLogicalTime ||
|
|
420
|
+
timestamp > localReplicaInfo.ackedLogicalTime)
|
|
421
|
+
) {
|
|
422
|
+
this.db.updateLocalReplica({ ackedLogicalTime: timestamp });
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PersistenceQueryDb } from './interfaces.js';
|
|
2
|
+
import { Context } from '../context/context.js';
|
|
3
|
+
import { Disposable } from '../utils/Disposable.js';
|
|
4
|
+
|
|
5
|
+
export class PersistenceQueries extends Disposable {
|
|
6
|
+
constructor(
|
|
7
|
+
private db: PersistenceQueryDb,
|
|
8
|
+
private ctx: Omit<Context, 'queries'>,
|
|
9
|
+
) {
|
|
10
|
+
super();
|
|
11
|
+
this.compose(this.db);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
reset = this.db.reset.bind(this.db);
|
|
15
|
+
|
|
16
|
+
saveEntities = this.db.saveEntities;
|
|
17
|
+
|
|
18
|
+
findOneOid = this.db.findOneOid.bind(this.db);
|
|
19
|
+
findAllOids = this.db.findAllOids.bind(this.db);
|
|
20
|
+
|
|
21
|
+
stats = this.db.stats.bind(this.db);
|
|
22
|
+
}
|