@verdant-web/store 3.12.1 → 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/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/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
package/src/files/FileManager.ts
CHANGED
|
@@ -1,184 +1,48 @@
|
|
|
1
|
-
import { FileData
|
|
2
|
-
import { Context } from '../context.js';
|
|
3
|
-
import { Metadata } from '../metadata/Metadata.js';
|
|
1
|
+
import { FileData } from '@verdant-web/common';
|
|
2
|
+
import { Context } from '../context/context.js';
|
|
4
3
|
import { Sync } from '../sync/Sync.js';
|
|
5
|
-
import {
|
|
6
|
-
EntityFile,
|
|
7
|
-
MARK_FAILED,
|
|
8
|
-
MARK_UPLOADED,
|
|
9
|
-
UPDATE,
|
|
10
|
-
} from './EntityFile.js';
|
|
11
|
-
import {
|
|
12
|
-
FileStorage,
|
|
13
|
-
ReturnedFileData,
|
|
14
|
-
StoredFileData,
|
|
15
|
-
} from './FileStorage.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Default: if file was deleted > 3 days ago
|
|
19
|
-
*/
|
|
20
|
-
function defaultCanCleanup(fileData: ReturnedFileData) {
|
|
21
|
-
return (
|
|
22
|
-
fileData.deletedAt !== null &&
|
|
23
|
-
fileData.deletedAt < Date.now() - 1000 * 60 * 24 * 3
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface FileManagerConfig {
|
|
28
|
-
/**
|
|
29
|
-
* Override the heuristic for deciding when a deleted file can be cleaned up.
|
|
30
|
-
* By default this waits 3 days since deletion, then deletes the file data.
|
|
31
|
-
* If the file has been synchronized to a server, it could still be restored
|
|
32
|
-
* if the server has not yet deleted it.
|
|
33
|
-
*/
|
|
34
|
-
canCleanupDeletedFile?: (file: ReturnedFileData) => boolean;
|
|
35
|
-
}
|
|
4
|
+
import { EntityFile, MARK_FAILED, UPDATE } from './EntityFile.js';
|
|
36
5
|
|
|
37
6
|
export class FileManager {
|
|
38
|
-
private storage;
|
|
39
7
|
private sync;
|
|
40
8
|
private context;
|
|
41
9
|
|
|
42
|
-
private
|
|
43
|
-
private config: Required<FileManagerConfig>;
|
|
44
|
-
private meta: Metadata;
|
|
10
|
+
private cache = new Map<string, EntityFile>();
|
|
45
11
|
|
|
46
|
-
|
|
47
|
-
private maxDownloadRetries = 3;
|
|
48
|
-
|
|
49
|
-
constructor({
|
|
50
|
-
db,
|
|
51
|
-
sync,
|
|
52
|
-
context,
|
|
53
|
-
meta,
|
|
54
|
-
config = {},
|
|
55
|
-
}: {
|
|
56
|
-
db: IDBDatabase;
|
|
57
|
-
sync: Sync;
|
|
58
|
-
context: Context;
|
|
59
|
-
config?: FileManagerConfig;
|
|
60
|
-
meta: Metadata;
|
|
61
|
-
}) {
|
|
62
|
-
this.storage = new FileStorage(db);
|
|
12
|
+
constructor({ sync, context }: { sync: Sync; context: Context }) {
|
|
63
13
|
this.sync = sync;
|
|
64
14
|
this.context = context;
|
|
65
|
-
this.meta = meta;
|
|
66
|
-
this.config = {
|
|
67
|
-
canCleanupDeletedFile: defaultCanCleanup,
|
|
68
|
-
...config,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
this.sync.subscribe('onlineChange', this.onOnlineChange);
|
|
72
|
-
this.meta.subscribe('filesDeleted', this.handleFileRefsDeleted);
|
|
73
|
-
this.sync.subscribe('serverReset', this.storage.resetSyncedStatusSince);
|
|
74
|
-
// check on startup to see if files can be cleaned up
|
|
75
|
-
this.tryCleanupDeletedFiles();
|
|
76
15
|
}
|
|
77
16
|
|
|
78
|
-
add = async (file: FileData) => {
|
|
79
|
-
// this method accepts a FileData which refers to a remote
|
|
80
|
-
// file, as well as local files. in the case of a remote file,
|
|
81
|
-
// we actually re-download and upload the file again. this powers
|
|
82
|
-
// the cloning of documents with files; we clone their filedata
|
|
83
|
-
// and re-upload to a new file ID. otherwise, when the cloned
|
|
84
|
-
// filedata was marked deleted, the original file would be deleted
|
|
85
|
-
// and the clone would refer to a missing file.
|
|
86
|
-
if (file.url && !file.file) {
|
|
87
|
-
const blob = await this.downloadRemoteFile(file.url);
|
|
88
|
-
// convert blob to file with name and type
|
|
89
|
-
file.file = new File([blob], file.name, { type: file.type });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
file.remote = false;
|
|
17
|
+
add = async (file: FileData, options?: { downloadRemote: boolean }) => {
|
|
93
18
|
// immediately cache the file
|
|
94
|
-
if (!this.
|
|
95
|
-
const entityFile = new EntityFile(file.id);
|
|
19
|
+
if (!this.cache.has(file.id)) {
|
|
20
|
+
const entityFile = new EntityFile(file.id, { ctx: this.context });
|
|
96
21
|
entityFile[UPDATE](file);
|
|
97
|
-
this.
|
|
22
|
+
this.cache.set(file.id, entityFile);
|
|
98
23
|
} else {
|
|
99
|
-
this.
|
|
100
|
-
}
|
|
101
|
-
// write to local storage and send to sync immediately
|
|
102
|
-
await this.storage.addFile(file);
|
|
103
|
-
// send to sync
|
|
104
|
-
if (file.file && this.sync.status === 'active') {
|
|
105
|
-
await this.uploadFile(file);
|
|
24
|
+
this.cache.get(file.id);
|
|
106
25
|
}
|
|
107
|
-
};
|
|
108
26
|
|
|
109
|
-
|
|
110
|
-
const result = await this.sync.uploadFile(file);
|
|
111
|
-
if (result.success) {
|
|
112
|
-
await this.storage.markUploaded(file.id);
|
|
113
|
-
const cached = this.files.get(file.id);
|
|
114
|
-
if (cached) {
|
|
115
|
-
cached[MARK_UPLOADED]();
|
|
116
|
-
}
|
|
117
|
-
this.context.log('info', 'File uploaded', file.id);
|
|
118
|
-
} else {
|
|
119
|
-
if (result.retry && retries < this.maxUploadRetries) {
|
|
120
|
-
this.context.log(
|
|
121
|
-
'error',
|
|
122
|
-
`Error uploading file ${file.id}, retrying...`,
|
|
123
|
-
result.error,
|
|
124
|
-
);
|
|
125
|
-
// schedule a retry
|
|
126
|
-
setTimeout(this.uploadFile, 1000, file, retries + 1);
|
|
127
|
-
} else {
|
|
128
|
-
this.context.log(
|
|
129
|
-
'error',
|
|
130
|
-
`Failed to upload file ${file.id}. Not retrying until next sync.`,
|
|
131
|
-
result.error,
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
private downloadRemoteFile = async (
|
|
138
|
-
url: string,
|
|
139
|
-
retries = 0,
|
|
140
|
-
): Promise<Blob> => {
|
|
141
|
-
const resp = await fetch(url, {
|
|
142
|
-
method: 'GET',
|
|
143
|
-
credentials: 'include',
|
|
144
|
-
});
|
|
145
|
-
if (!resp.ok) {
|
|
146
|
-
if (retries < this.maxDownloadRetries) {
|
|
147
|
-
return new Promise((resolve, reject) => {
|
|
148
|
-
setTimeout(() => {
|
|
149
|
-
this.downloadRemoteFile(url, retries + 1).then(resolve, reject);
|
|
150
|
-
}, 1000);
|
|
151
|
-
});
|
|
152
|
-
} else {
|
|
153
|
-
throw new Error(`Failed to download file: ${resp.status}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const blob = await resp.blob();
|
|
157
|
-
return blob;
|
|
27
|
+
await this.context.files.add(file, options);
|
|
158
28
|
};
|
|
159
29
|
|
|
160
30
|
/**
|
|
161
31
|
* Immediately returns an EntityFile to use, then either loads
|
|
162
32
|
* the file from cache, local database, or the server.
|
|
163
33
|
*/
|
|
164
|
-
get = (id: string, options
|
|
165
|
-
if (this.
|
|
166
|
-
return this.
|
|
34
|
+
get = (id: string, options: { downloadRemote?: boolean; ctx: Context }) => {
|
|
35
|
+
if (this.cache.has(id)) {
|
|
36
|
+
return this.cache.get(id)!;
|
|
167
37
|
}
|
|
168
38
|
const file = new EntityFile(id, options);
|
|
169
|
-
this.
|
|
39
|
+
this.cache.set(id, file);
|
|
170
40
|
this.load(file);
|
|
171
41
|
return file;
|
|
172
42
|
};
|
|
173
43
|
|
|
174
|
-
private load = async (file: EntityFile
|
|
175
|
-
|
|
176
|
-
this.context.log('error', 'Failed to load file after 5 retries');
|
|
177
|
-
file[MARK_FAILED]();
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const fileData = await this.storage.getFile(file.id);
|
|
44
|
+
private load = async (file: EntityFile) => {
|
|
45
|
+
const fileData = await this.context.files.get(file.id);
|
|
182
46
|
if (fileData) {
|
|
183
47
|
file[UPDATE](fileData);
|
|
184
48
|
} else {
|
|
@@ -187,108 +51,17 @@ export class FileManager {
|
|
|
187
51
|
const result = await this.sync.getFile(file.id);
|
|
188
52
|
if (result.success) {
|
|
189
53
|
file[UPDATE](result.data);
|
|
190
|
-
await this.
|
|
54
|
+
await this.context.files.add(result.data, {
|
|
191
55
|
downloadRemote: file.downloadRemote,
|
|
192
56
|
});
|
|
193
57
|
} else {
|
|
194
58
|
this.context.log('error', 'Failed to load file', result);
|
|
195
59
|
file[MARK_FAILED]();
|
|
196
|
-
if (result.retry) {
|
|
197
|
-
// schedule a retry
|
|
198
|
-
setTimeout(this.load, 1000, file, retries + 1);
|
|
199
|
-
}
|
|
200
60
|
}
|
|
201
61
|
} catch (err) {
|
|
202
62
|
this.context.log('error', 'Failed to load file', err);
|
|
203
63
|
file[MARK_FAILED]();
|
|
204
|
-
// schedule a retry
|
|
205
|
-
setTimeout(this.load, 1000, file, retries + 1);
|
|
206
64
|
}
|
|
207
65
|
}
|
|
208
66
|
};
|
|
209
|
-
|
|
210
|
-
listUnsynced = async () => {
|
|
211
|
-
return this.storage.listUnsynced();
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
exportAll = async (downloadRemote = false) => {
|
|
215
|
-
const storedFiles = await this.storage.getAll();
|
|
216
|
-
if (downloadRemote) {
|
|
217
|
-
for (const storedFile of storedFiles) {
|
|
218
|
-
// if it doesn't have a buffer, we need to read
|
|
219
|
-
// one from the server
|
|
220
|
-
if (!storedFile.file && storedFile.url) {
|
|
221
|
-
try {
|
|
222
|
-
const blob = await fetch(storedFile.url, {
|
|
223
|
-
method: 'GET',
|
|
224
|
-
credentials: 'include',
|
|
225
|
-
}).then((r) => r.blob());
|
|
226
|
-
storedFile.file = blob;
|
|
227
|
-
} catch (err) {
|
|
228
|
-
this.context.log(
|
|
229
|
-
'error',
|
|
230
|
-
"Failed to download file to cache it locally. The file will still be available using its URL. Check the file server's CORS configuration.",
|
|
231
|
-
err,
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return storedFiles;
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
importAll = async (files: ReturnedFileData[]) => {
|
|
241
|
-
await Promise.all(files.map((file) => this.add(file)));
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
private onOnlineChange = async (online: boolean) => {
|
|
245
|
-
// if online, try to upload any unsynced files
|
|
246
|
-
if (online) {
|
|
247
|
-
const unsynced = await this.listUnsynced();
|
|
248
|
-
await Promise.all(unsynced.map(this.uploadFile));
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
tryCleanupDeletedFiles = async () => {
|
|
253
|
-
let count = 0;
|
|
254
|
-
let skipCount = 0;
|
|
255
|
-
await this.storage.iterateOverPendingDelete((fileData, store) => {
|
|
256
|
-
if (this.config.canCleanupDeletedFile(fileData)) {
|
|
257
|
-
count++;
|
|
258
|
-
store.delete(fileData.id);
|
|
259
|
-
} else {
|
|
260
|
-
skipCount++;
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
this.context.log(
|
|
265
|
-
'info',
|
|
266
|
-
`Cleaned up ${count} files, skipped ${skipCount} files`,
|
|
267
|
-
);
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
private handleFileRefsDeleted = async (fileRefs: FileRef[]) => {
|
|
271
|
-
const tx = this.storage.createTransaction(['files'], { mode: 'readwrite' });
|
|
272
|
-
await Promise.all(
|
|
273
|
-
fileRefs.map(async (fileRef) => {
|
|
274
|
-
try {
|
|
275
|
-
await this.storage.markPendingDelete(fileRef.id, { transaction: tx });
|
|
276
|
-
} catch (err) {
|
|
277
|
-
this.context.log('error', 'Failed to mark file for deletion', err);
|
|
278
|
-
}
|
|
279
|
-
}),
|
|
280
|
-
);
|
|
281
|
-
this.context.log(
|
|
282
|
-
'info',
|
|
283
|
-
`Marked ${fileRefs.length} files as pending delete`,
|
|
284
|
-
);
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
close = () => {
|
|
288
|
-
this.storage.dispose();
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
stats = () => {
|
|
292
|
-
return this.storage.stats();
|
|
293
|
-
};
|
|
294
67
|
}
|
package/src/files/utils.ts
CHANGED
|
@@ -55,18 +55,3 @@ export function processValueFiles(
|
|
|
55
55
|
|
|
56
56
|
return value;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
export function fileToArrayBuffer(file: File | Blob) {
|
|
60
|
-
// special case for testing...
|
|
61
|
-
if ('__testReadBuffer' in file) {
|
|
62
|
-
return file.__testReadBuffer;
|
|
63
|
-
}
|
|
64
|
-
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
65
|
-
const reader = new FileReader();
|
|
66
|
-
reader.onload = () => {
|
|
67
|
-
resolve(reader.result as ArrayBuffer);
|
|
68
|
-
};
|
|
69
|
-
reader.onerror = reject;
|
|
70
|
-
reader.readAsArrayBuffer(file);
|
|
71
|
-
});
|
|
72
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -48,12 +48,13 @@ export type {
|
|
|
48
48
|
StorageFieldsSchema,
|
|
49
49
|
IndexValueTag,
|
|
50
50
|
Migration,
|
|
51
|
+
VerdantError,
|
|
52
|
+
VerdantErrorCode,
|
|
51
53
|
} from '@verdant-web/common';
|
|
52
54
|
export type { UserInfo } from '@verdant-web/common';
|
|
53
55
|
export type { Query } from './queries/types.js';
|
|
54
56
|
export type { QueryStatus } from './queries/BaseQuery.js';
|
|
55
57
|
export type { CollectionQueries } from './queries/CollectionQueries.js';
|
|
56
|
-
export { MigrationPathError } from './migration/errors.js';
|
|
57
58
|
export * from './utils/id.js';
|
|
58
59
|
export { UndoHistory } from './UndoHistory.js';
|
|
59
60
|
export * from './authorization.js';
|
|
@@ -8,53 +8,38 @@ import {
|
|
|
8
8
|
OperationMessage,
|
|
9
9
|
pickValidOperationKeys,
|
|
10
10
|
PresenceUpdateMessage,
|
|
11
|
-
SyncAckMessage,
|
|
12
11
|
SyncMessage,
|
|
13
12
|
VerdantInternalPresence,
|
|
14
13
|
} from '@verdant-web/common';
|
|
15
14
|
|
|
16
|
-
import {
|
|
15
|
+
import { Context } from '../context/context.js';
|
|
16
|
+
import { PersistenceMetadataDb } from './interfaces.js';
|
|
17
17
|
|
|
18
18
|
export class MessageCreator {
|
|
19
|
-
constructor(
|
|
19
|
+
constructor(
|
|
20
|
+
private db: PersistenceMetadataDb,
|
|
21
|
+
private ctx: Pick<Context, 'time' | 'schema' | 'log'>,
|
|
22
|
+
) {}
|
|
20
23
|
|
|
21
24
|
createOperation = async (
|
|
22
25
|
init: Pick<OperationMessage, 'operations'> & {
|
|
23
26
|
timestamp?: string;
|
|
24
27
|
},
|
|
25
28
|
): Promise<OperationMessage> => {
|
|
26
|
-
const localInfo = await this.
|
|
29
|
+
const localInfo = await this.db.getLocalReplica();
|
|
27
30
|
return {
|
|
28
31
|
type: 'op',
|
|
29
|
-
timestamp: this.
|
|
32
|
+
timestamp: this.ctx.time.now,
|
|
30
33
|
replicaId: localInfo.id,
|
|
31
34
|
operations: init.operations.map(pickValidOperationKeys),
|
|
32
35
|
};
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
createMigrationOperation = async ({
|
|
36
|
-
targetVersion,
|
|
37
|
-
...init
|
|
38
|
-
}: Pick<OperationMessage, 'operations'> & {
|
|
39
|
-
targetVersion: number;
|
|
40
|
-
}): Promise<OperationMessage> => {
|
|
41
|
-
const localInfo = await this.meta.localReplica.get();
|
|
42
|
-
return {
|
|
43
|
-
type: 'op',
|
|
44
|
-
operations: init.operations.map((op) => ({
|
|
45
|
-
...op,
|
|
46
|
-
timestamp: this.meta.time.zero(targetVersion),
|
|
47
|
-
})),
|
|
48
|
-
timestamp: this.meta.time.zero(targetVersion),
|
|
49
|
-
replicaId: localInfo.id,
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
|
|
53
38
|
/**
|
|
54
39
|
* @param since - override local understanding of last sync time
|
|
55
40
|
*/
|
|
56
41
|
createSyncStep1 = async (since?: string | null): Promise<SyncMessage> => {
|
|
57
|
-
const localReplicaInfo = await this.
|
|
42
|
+
const localReplicaInfo = await this.db.getLocalReplica();
|
|
58
43
|
|
|
59
44
|
const provideChangesSince =
|
|
60
45
|
since === null ? null : localReplicaInfo.lastSyncedLogicalTime;
|
|
@@ -64,12 +49,22 @@ export class MessageCreator {
|
|
|
64
49
|
const operations: Operation[] = [];
|
|
65
50
|
const affectedDocs = new Set<ObjectIdentifier>();
|
|
66
51
|
|
|
52
|
+
const tx = await this.db.transaction({
|
|
53
|
+
mode: 'readwrite',
|
|
54
|
+
storeNames: ['operations', 'baselines'],
|
|
55
|
+
});
|
|
56
|
+
|
|
67
57
|
// FIXME: this branch gives bad vibes. should we always
|
|
68
58
|
// send all operations from other replicas too? is there
|
|
69
59
|
// ever a case where we have a "since" timestamp and there
|
|
70
60
|
// are foreign ops that match it?
|
|
71
61
|
if (provideChangesSince) {
|
|
72
|
-
|
|
62
|
+
this.ctx.log(
|
|
63
|
+
'debug',
|
|
64
|
+
'Syncing local operations since',
|
|
65
|
+
provideChangesSince,
|
|
66
|
+
);
|
|
67
|
+
await this.db.iterateLocalOperations(
|
|
73
68
|
(patch) => {
|
|
74
69
|
operations.push(pickValidOperationKeys(patch));
|
|
75
70
|
affectedDocs.add(getOidRoot(patch.oid));
|
|
@@ -77,32 +72,47 @@ export class MessageCreator {
|
|
|
77
72
|
{
|
|
78
73
|
after: provideChangesSince,
|
|
79
74
|
// block on writes to prevent race conditions
|
|
80
|
-
|
|
75
|
+
transaction: tx,
|
|
81
76
|
},
|
|
82
77
|
);
|
|
83
78
|
} else {
|
|
79
|
+
this.ctx.log('debug', 'Syncing all operations');
|
|
84
80
|
// if providing the whole history, don't limit to only local
|
|
85
81
|
// operations
|
|
86
|
-
await this.
|
|
82
|
+
await this.db.iterateAllOperations(
|
|
87
83
|
(patch) => {
|
|
88
84
|
operations.push(pickValidOperationKeys(patch));
|
|
89
85
|
affectedDocs.add(getOidRoot(patch.oid));
|
|
90
86
|
},
|
|
91
87
|
{
|
|
92
|
-
|
|
88
|
+
transaction: tx,
|
|
93
89
|
},
|
|
94
90
|
);
|
|
95
91
|
}
|
|
96
92
|
// we only need to send baselines if we've never synced before
|
|
97
93
|
let baselines: DocumentBaseline[] = [];
|
|
98
94
|
if (!provideChangesSince) {
|
|
99
|
-
|
|
95
|
+
await this.db.iterateAllBaselines(
|
|
96
|
+
(b) => {
|
|
97
|
+
baselines.push(b);
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
transaction: tx,
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (operations.length > 0) {
|
|
106
|
+
this.ctx.log(
|
|
107
|
+
'debug',
|
|
108
|
+
`Syncing ${operations.length} operations since ${provideChangesSince}`,
|
|
109
|
+
);
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
return {
|
|
103
113
|
type: 'sync',
|
|
104
|
-
schemaVersion: this.
|
|
105
|
-
timestamp: this.
|
|
114
|
+
schemaVersion: this.ctx.schema.version,
|
|
115
|
+
timestamp: this.ctx.time.now,
|
|
106
116
|
replicaId: localReplicaInfo.id,
|
|
107
117
|
resyncAll: !localReplicaInfo.lastSyncedLogicalTime,
|
|
108
118
|
operations,
|
|
@@ -115,7 +125,7 @@ export class MessageCreator {
|
|
|
115
125
|
presence?: any;
|
|
116
126
|
internal?: VerdantInternalPresence;
|
|
117
127
|
}): Promise<PresenceUpdateMessage> => {
|
|
118
|
-
const localReplicaInfo = await this.
|
|
128
|
+
const localReplicaInfo = await this.db.getLocalReplica();
|
|
119
129
|
return {
|
|
120
130
|
type: 'presence-update',
|
|
121
131
|
presence: data.presence,
|
|
@@ -125,19 +135,19 @@ export class MessageCreator {
|
|
|
125
135
|
};
|
|
126
136
|
|
|
127
137
|
createHeartbeat = async (): Promise<HeartbeatMessage> => {
|
|
128
|
-
const localReplicaInfo = await this.
|
|
138
|
+
const localReplicaInfo = await this.db.getLocalReplica();
|
|
129
139
|
return {
|
|
130
140
|
type: 'heartbeat',
|
|
131
|
-
timestamp: this.
|
|
141
|
+
timestamp: this.ctx.time.now,
|
|
132
142
|
replicaId: localReplicaInfo.id,
|
|
133
143
|
};
|
|
134
144
|
};
|
|
135
145
|
|
|
136
146
|
createAck = async (nonce: string): Promise<AckMessage> => {
|
|
137
|
-
const localReplicaInfo = await this.
|
|
147
|
+
const localReplicaInfo = await this.db.getLocalReplica();
|
|
138
148
|
return {
|
|
139
149
|
type: 'ack',
|
|
140
|
-
timestamp: this.
|
|
150
|
+
timestamp: this.ctx.time.now,
|
|
141
151
|
replicaId: localReplicaInfo.id,
|
|
142
152
|
nonce,
|
|
143
153
|
};
|