@verdant-web/store 4.0.0 → 4.1.0-alpha.1
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/LICENSE +21 -650
- package/dist/bundle/index.js +11 -11
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -2
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -5
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/client/Client.d.ts +6 -2
- package/dist/esm/client/Client.js +18 -6
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +7 -5
- package/dist/esm/client/ClientDescriptor.js +18 -4
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/context/ShutdownHandler.d.ts +8 -0
- package/dist/esm/context/ShutdownHandler.js +24 -0
- package/dist/esm/context/ShutdownHandler.js.map +1 -0
- package/dist/esm/context/context.d.ts +15 -4
- package/dist/esm/entities/EntityStore.js +6 -3
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/files/EntityFile.d.ts +1 -0
- package/dist/esm/files/EntityFile.js +16 -11
- package/dist/esm/files/EntityFile.js.map +1 -1
- package/dist/esm/files/FileManager.d.ts +1 -3
- package/dist/esm/files/FileManager.js +12 -10
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/index.d.ts +4 -5
- package/dist/esm/index.js +2 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.d.ts +6 -0
- package/dist/esm/internal.js +5 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/persistence/MessageCreator.d.ts +3 -1
- package/dist/esm/persistence/MessageCreator.js +58 -55
- package/dist/esm/persistence/MessageCreator.js.map +1 -1
- package/dist/esm/persistence/PersistenceFiles.d.ts +8 -21
- package/dist/esm/persistence/PersistenceFiles.js +44 -30
- package/dist/esm/persistence/PersistenceFiles.js.map +1 -1
- package/dist/esm/persistence/PersistenceMetadata.d.ts +12 -11
- package/dist/esm/persistence/PersistenceMetadata.js +201 -137
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
- package/dist/esm/persistence/PersistenceQueries.d.ts +10 -11
- package/dist/esm/persistence/PersistenceQueries.js +33 -5
- package/dist/esm/persistence/PersistenceQueries.js.map +1 -1
- package/dist/esm/persistence/PersistenceRebaser.d.ts +5 -9
- package/dist/esm/persistence/PersistenceRebaser.js +63 -47
- package/dist/esm/persistence/PersistenceRebaser.js.map +1 -1
- package/dist/esm/persistence/idb/IdbService.d.ts +0 -1
- package/dist/esm/persistence/idb/IdbService.js +28 -16
- package/dist/esm/persistence/idb/IdbService.js.map +1 -1
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +11 -31
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js +31 -36
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -1
- package/dist/esm/persistence/idb/idbPersistence.d.ts +17 -9
- package/dist/esm/persistence/idb/idbPersistence.js +80 -39
- package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +7 -10
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +45 -71
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.d.ts +1 -12
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js +3 -56
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/esm/persistence/idb/queries/{IdbQueryDb.d.ts → IdbDocumentDb.d.ts} +7 -13
- package/dist/esm/persistence/idb/queries/{IdbQueryDb.js → IdbDocumentDb.js} +15 -32
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -0
- package/dist/esm/persistence/idb/queries/migration/db.d.ts +3 -5
- package/dist/esm/persistence/idb/queries/migration/db.js +13 -28
- package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
- package/dist/esm/persistence/idb/util.d.ts +8 -4
- package/dist/esm/persistence/idb/util.js +64 -21
- package/dist/esm/persistence/idb/util.js.map +1 -1
- package/dist/esm/persistence/interfaces.d.ts +68 -75
- package/dist/esm/persistence/{idb/queries/migration → migration}/engine.d.ts +4 -7
- package/dist/esm/persistence/{idb/queries/migration → migration}/engine.js +18 -10
- package/dist/esm/persistence/migration/engine.js.map +1 -0
- package/dist/esm/persistence/migration/finalize.d.ts +9 -0
- package/dist/esm/persistence/migration/finalize.js +75 -0
- package/dist/esm/persistence/migration/finalize.js.map +1 -0
- package/dist/esm/persistence/migration/migrate.d.ts +12 -0
- package/dist/esm/persistence/migration/migrate.js +89 -0
- package/dist/esm/persistence/migration/migrate.js.map +1 -0
- package/dist/esm/persistence/migration/paths.js.map +1 -0
- package/dist/esm/persistence/migration/paths.test.js.map +1 -0
- package/dist/esm/persistence/migration/types.d.ts +3 -0
- package/dist/esm/persistence/migration/types.js.map +1 -0
- package/dist/esm/persistence/persistence.js +25 -15
- package/dist/esm/persistence/persistence.js.map +1 -1
- package/dist/esm/queries/FindAllQuery.js +1 -1
- package/dist/esm/queries/FindAllQuery.js.map +1 -1
- package/dist/esm/queries/FindInfiniteQuery.js +2 -2
- package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
- package/dist/esm/queries/FindOneQuery.js +1 -1
- package/dist/esm/queries/FindOneQuery.js.map +1 -1
- package/dist/esm/queries/FindPageQuery.js +1 -1
- package/dist/esm/queries/FindPageQuery.js.map +1 -1
- package/dist/esm/sync/FileSync.js +3 -3
- package/dist/esm/sync/FileSync.js.map +1 -1
- package/dist/esm/sync/PushPullSync.d.ts +2 -3
- package/dist/esm/sync/PushPullSync.js +4 -2
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/ServerSyncEndpointProvider.d.ts +3 -7
- package/dist/esm/sync/ServerSyncEndpointProvider.js +3 -2
- package/dist/esm/sync/ServerSyncEndpointProvider.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +6 -1
- package/dist/esm/sync/Sync.js +12 -4
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +10 -4
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/utils/wip.js +1 -1
- package/package.json +6 -2
- package/src/__tests__/fixtures/testStorage.ts +6 -6
- package/src/client/Client.ts +26 -8
- package/src/client/ClientDescriptor.ts +27 -9
- package/src/context/ShutdownHandler.ts +26 -0
- package/src/context/context.ts +16 -4
- package/src/entities/EntityStore.ts +9 -3
- package/src/files/EntityFile.ts +11 -6
- package/src/files/FileManager.ts +13 -10
- package/src/index.ts +8 -9
- package/src/internal.ts +27 -0
- package/src/persistence/MessageCreator.ts +79 -73
- package/src/persistence/PersistenceFiles.ts +57 -31
- package/src/persistence/PersistenceMetadata.ts +287 -195
- package/src/persistence/PersistenceQueries.ts +45 -9
- package/src/persistence/PersistenceRebaser.ts +105 -70
- package/src/persistence/idb/IdbService.ts +40 -22
- package/src/persistence/idb/files/IdbPersistenceFileDb.ts +30 -62
- package/src/persistence/idb/idbPersistence.ts +123 -47
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +75 -97
- package/src/persistence/idb/metadata/openMetadataDatabase.ts +2 -96
- package/src/persistence/idb/queries/{IdbQueryDb.ts → IdbDocumentDb.ts} +17 -57
- package/src/persistence/idb/queries/migration/db.ts +20 -39
- package/src/persistence/idb/util.ts +84 -21
- package/src/persistence/interfaces.ts +89 -90
- package/src/persistence/{idb/queries/migration → migration}/engine.ts +30 -15
- package/src/persistence/migration/finalize.ts +126 -0
- package/src/persistence/migration/migrate.ts +169 -0
- package/src/persistence/migration/types.ts +4 -0
- package/src/persistence/persistence.ts +37 -14
- package/src/queries/FindAllQuery.ts +1 -1
- package/src/queries/FindInfiniteQuery.ts +2 -2
- package/src/queries/FindOneQuery.ts +1 -1
- package/src/queries/FindPageQuery.ts +1 -1
- package/src/sync/FileSync.ts +21 -15
- package/src/sync/PushPullSync.ts +3 -4
- package/src/sync/ServerSyncEndpointProvider.ts +6 -8
- package/src/sync/Sync.ts +20 -7
- package/src/sync/WebSocketSync.ts +10 -4
- package/src/utils/wip.ts +1 -1
- package/dist/esm/client/constants.d.ts +0 -1
- package/dist/esm/client/constants.js +0 -2
- package/dist/esm/client/constants.js.map +0 -1
- package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/engine.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/migrations.d.ts +0 -15
- package/dist/esm/persistence/idb/queries/migration/migrations.js +0 -243
- package/dist/esm/persistence/idb/queries/migration/migrations.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.d.ts +0 -8
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js +0 -24
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/paths.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/types.d.ts +0 -6
- package/dist/esm/persistence/idb/queries/migration/types.js.map +0 -1
- package/src/client/constants.ts +0 -1
- package/src/persistence/idb/queries/migration/migrations.ts +0 -345
- package/src/persistence/idb/queries/migration/openQueryDatabase.ts +0 -54
- package/src/persistence/idb/queries/migration/types.ts +0 -8
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.d.ts +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.js +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.d.ts +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.js +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/types.js +0 -0
- /package/src/persistence/{idb/queries/migration → migration}/paths.test.ts +0 -0
- /package/src/persistence/{idb/queries/migration → migration}/paths.ts +0 -0
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
|
|
15
15
|
import { Context } from '../context/context.js';
|
|
16
16
|
import { PersistenceMetadataDb } from './interfaces.js';
|
|
17
|
+
import type { PersistenceMetadata } from './PersistenceMetadata.js';
|
|
17
18
|
|
|
18
19
|
export class MessageCreator {
|
|
19
20
|
constructor(
|
|
20
21
|
private db: PersistenceMetadataDb,
|
|
22
|
+
private meta: PersistenceMetadata,
|
|
21
23
|
private ctx: Pick<Context, 'time' | 'schema' | 'log'>,
|
|
22
24
|
) {}
|
|
23
25
|
|
|
@@ -26,7 +28,8 @@ export class MessageCreator {
|
|
|
26
28
|
timestamp?: string;
|
|
27
29
|
},
|
|
28
30
|
): Promise<OperationMessage> => {
|
|
29
|
-
const localInfo = await this.
|
|
31
|
+
const localInfo = await this.meta.getLocalReplica();
|
|
32
|
+
this.ctx.log('debug', 'Creating operation message', init.operations.length);
|
|
30
33
|
return {
|
|
31
34
|
type: 'op',
|
|
32
35
|
timestamp: this.ctx.time.now,
|
|
@@ -39,7 +42,7 @@ export class MessageCreator {
|
|
|
39
42
|
* @param since - override local understanding of last sync time
|
|
40
43
|
*/
|
|
41
44
|
createSyncStep1 = async (since?: string | null): Promise<SyncMessage> => {
|
|
42
|
-
const localReplicaInfo = await this.
|
|
45
|
+
const localReplicaInfo = await this.meta.getLocalReplica();
|
|
43
46
|
|
|
44
47
|
const provideChangesSince =
|
|
45
48
|
since === null ? null : localReplicaInfo.lastSyncedLogicalTime;
|
|
@@ -49,83 +52,86 @@ export class MessageCreator {
|
|
|
49
52
|
const operations: Operation[] = [];
|
|
50
53
|
const affectedDocs = new Set<ObjectIdentifier>();
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
return this.db.transaction(
|
|
56
|
+
{
|
|
57
|
+
mode: 'readwrite',
|
|
58
|
+
storeNames: ['operations', 'baselines'],
|
|
59
|
+
},
|
|
60
|
+
async (tx) => {
|
|
61
|
+
// FIXME: this branch gives bad vibes. should we always
|
|
62
|
+
// send all operations from other replicas too? is there
|
|
63
|
+
// ever a case where we have a "since" timestamp and there
|
|
64
|
+
// are foreign ops that match it?
|
|
65
|
+
if (provideChangesSince) {
|
|
66
|
+
this.ctx.log(
|
|
67
|
+
'debug',
|
|
68
|
+
'Syncing local operations since',
|
|
69
|
+
provideChangesSince,
|
|
70
|
+
);
|
|
71
|
+
await this.db.iterateLocalOperations(
|
|
72
|
+
(patch) => {
|
|
73
|
+
operations.push(pickValidOperationKeys(patch));
|
|
74
|
+
affectedDocs.add(getOidRoot(patch.oid));
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
after: provideChangesSince,
|
|
78
|
+
// block on writes to prevent race conditions
|
|
79
|
+
transaction: tx,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
this.ctx.log('debug', 'Syncing all operations');
|
|
84
|
+
// if providing the whole history, don't limit to only local
|
|
85
|
+
// operations
|
|
86
|
+
await this.db.iterateAllOperations(
|
|
87
|
+
(patch) => {
|
|
88
|
+
operations.push(pickValidOperationKeys(patch));
|
|
89
|
+
affectedDocs.add(getOidRoot(patch.oid));
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
transaction: tx,
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
// we only need to send baselines if we've never synced before
|
|
97
|
+
let baselines: DocumentBaseline[] = [];
|
|
98
|
+
if (!provideChangesSince) {
|
|
99
|
+
await this.db.iterateAllBaselines(
|
|
100
|
+
(b) => {
|
|
101
|
+
baselines.push(b);
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
transaction: tx,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
}
|
|
56
108
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
'debug',
|
|
64
|
-
'Syncing local operations since',
|
|
65
|
-
provideChangesSince,
|
|
66
|
-
);
|
|
67
|
-
await this.db.iterateLocalOperations(
|
|
68
|
-
(patch) => {
|
|
69
|
-
operations.push(pickValidOperationKeys(patch));
|
|
70
|
-
affectedDocs.add(getOidRoot(patch.oid));
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
after: provideChangesSince,
|
|
74
|
-
// block on writes to prevent race conditions
|
|
75
|
-
transaction: tx,
|
|
76
|
-
},
|
|
77
|
-
);
|
|
78
|
-
} else {
|
|
79
|
-
this.ctx.log('debug', 'Syncing all operations');
|
|
80
|
-
// if providing the whole history, don't limit to only local
|
|
81
|
-
// operations
|
|
82
|
-
await this.db.iterateAllOperations(
|
|
83
|
-
(patch) => {
|
|
84
|
-
operations.push(pickValidOperationKeys(patch));
|
|
85
|
-
affectedDocs.add(getOidRoot(patch.oid));
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
transaction: tx,
|
|
89
|
-
},
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
// we only need to send baselines if we've never synced before
|
|
93
|
-
let baselines: DocumentBaseline[] = [];
|
|
94
|
-
if (!provideChangesSince) {
|
|
95
|
-
await this.db.iterateAllBaselines(
|
|
96
|
-
(b) => {
|
|
97
|
-
baselines.push(b);
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
transaction: tx,
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
}
|
|
109
|
+
if (operations.length > 0) {
|
|
110
|
+
this.ctx.log(
|
|
111
|
+
'debug',
|
|
112
|
+
`Syncing ${operations.length} operations since ${provideChangesSince}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
104
115
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
resyncAll: !localReplicaInfo.lastSyncedLogicalTime,
|
|
118
|
-
operations,
|
|
119
|
-
baselines,
|
|
120
|
-
since: provideChangesSince,
|
|
121
|
-
};
|
|
116
|
+
return {
|
|
117
|
+
type: 'sync',
|
|
118
|
+
schemaVersion: this.ctx.schema.version,
|
|
119
|
+
timestamp: this.ctx.time.now,
|
|
120
|
+
replicaId: localReplicaInfo.id,
|
|
121
|
+
resyncAll: !localReplicaInfo.lastSyncedLogicalTime,
|
|
122
|
+
operations,
|
|
123
|
+
baselines,
|
|
124
|
+
since: provideChangesSince,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
);
|
|
122
128
|
};
|
|
123
129
|
|
|
124
130
|
createPresenceUpdate = async (data: {
|
|
125
131
|
presence?: any;
|
|
126
132
|
internal?: VerdantInternalPresence;
|
|
127
133
|
}): Promise<PresenceUpdateMessage> => {
|
|
128
|
-
const localReplicaInfo = await this.
|
|
134
|
+
const localReplicaInfo = await this.meta.getLocalReplica();
|
|
129
135
|
return {
|
|
130
136
|
type: 'presence-update',
|
|
131
137
|
presence: data.presence,
|
|
@@ -135,7 +141,7 @@ export class MessageCreator {
|
|
|
135
141
|
};
|
|
136
142
|
|
|
137
143
|
createHeartbeat = async (): Promise<HeartbeatMessage> => {
|
|
138
|
-
const localReplicaInfo = await this.
|
|
144
|
+
const localReplicaInfo = await this.meta.getLocalReplica();
|
|
139
145
|
return {
|
|
140
146
|
type: 'heartbeat',
|
|
141
147
|
timestamp: this.ctx.time.now,
|
|
@@ -144,7 +150,7 @@ export class MessageCreator {
|
|
|
144
150
|
};
|
|
145
151
|
|
|
146
152
|
createAck = async (nonce: string): Promise<AckMessage> => {
|
|
147
|
-
const localReplicaInfo = await this.
|
|
153
|
+
const localReplicaInfo = await this.meta.getLocalReplica();
|
|
148
154
|
return {
|
|
149
155
|
type: 'ack',
|
|
150
156
|
timestamp: this.ctx.time.now,
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { FileData, FileRef } from '@verdant-web/common';
|
|
2
2
|
import { Context, FileConfig } from '../context/context.js';
|
|
3
3
|
import { PersistedFileData, PersistenceFileDb } from './interfaces.js';
|
|
4
|
-
import { Disposable } from '../utils/Disposable.js';
|
|
5
4
|
|
|
6
|
-
export class PersistenceFiles
|
|
5
|
+
export class PersistenceFiles {
|
|
7
6
|
constructor(
|
|
8
7
|
private db: PersistenceFileDb,
|
|
9
8
|
private context: Omit<Context, 'queries'>,
|
|
10
9
|
) {
|
|
11
|
-
super();
|
|
12
10
|
context.internalEvents.subscribe('filesDeleted', this.onFileRefsDeleted);
|
|
13
|
-
this.compose(this.db);
|
|
14
11
|
// on startup, try deleting old files.
|
|
15
12
|
this.cleanupDeletedFiles();
|
|
16
13
|
}
|
|
@@ -29,7 +26,7 @@ export class PersistenceFiles extends Disposable {
|
|
|
29
26
|
|
|
30
27
|
onServerReset = (since: string | null) =>
|
|
31
28
|
this.db.resetSyncedStatusSince(since);
|
|
32
|
-
add = async (file: FileData
|
|
29
|
+
add = async (file: FileData) => {
|
|
33
30
|
// this method accepts a FileData which refers to a remote
|
|
34
31
|
// file, as well as local files. in the case of a remote file,
|
|
35
32
|
// we actually re-download and upload the file again. this powers
|
|
@@ -37,16 +34,26 @@ export class PersistenceFiles extends Disposable {
|
|
|
37
34
|
// and re-upload to a new file ID. otherwise, when the cloned
|
|
38
35
|
// filedata was marked deleted, the original file would be deleted
|
|
39
36
|
// and the clone would refer to a missing file.
|
|
40
|
-
if (file.url && !file.file) {
|
|
37
|
+
if (file.url && !(file.localPath || file.file)) {
|
|
41
38
|
this.context.log(
|
|
42
39
|
'debug',
|
|
43
40
|
'Remote file added to an entity. This usually means an entity was cloned. Downloading remote file...',
|
|
44
41
|
file.id,
|
|
45
42
|
);
|
|
46
|
-
const blob = await this.
|
|
43
|
+
const blob = await this.loadFileContents(file, 0, 3);
|
|
47
44
|
// convert blob to file with name and type
|
|
48
45
|
file.file = new File([blob], file.name, { type: file.type });
|
|
49
|
-
|
|
46
|
+
// remove the URL - it points to the original file's uploaded server version,
|
|
47
|
+
// but this file is a clone
|
|
48
|
+
delete file.url;
|
|
49
|
+
this.context.log(
|
|
50
|
+
'debug',
|
|
51
|
+
'Downloaded remote file',
|
|
52
|
+
file.id,
|
|
53
|
+
file.name,
|
|
54
|
+
'. Cleared its remote URL.',
|
|
55
|
+
);
|
|
56
|
+
} else if (!file.url && !file.file && !file.localPath) {
|
|
50
57
|
this.context.log(
|
|
51
58
|
'warn',
|
|
52
59
|
'File added without a file or URL. This file will not be available for use.',
|
|
@@ -54,19 +61,30 @@ export class PersistenceFiles extends Disposable {
|
|
|
54
61
|
);
|
|
55
62
|
}
|
|
56
63
|
|
|
64
|
+
// always reset remote status to false, this is a new file just created
|
|
65
|
+
// and must be uploaded, even if it is cloned from an uploaded file.
|
|
57
66
|
file.remote = false;
|
|
67
|
+
|
|
58
68
|
// fire event for processing immediately
|
|
59
69
|
this.context.internalEvents.emit('fileAdded', file);
|
|
60
70
|
// store in persistence db
|
|
61
|
-
await this.db.add(file
|
|
71
|
+
await this.db.add(file);
|
|
72
|
+
this.context.globalEvents.emit('fileSaved', file);
|
|
62
73
|
this.context.log(
|
|
63
74
|
'debug',
|
|
64
75
|
'File added',
|
|
65
76
|
file.id,
|
|
66
77
|
file.name,
|
|
67
78
|
file.type,
|
|
68
|
-
file.file
|
|
79
|
+
file.file
|
|
80
|
+
? 'with binary file'
|
|
81
|
+
: file.url
|
|
82
|
+
? 'with url'
|
|
83
|
+
: file.localPath
|
|
84
|
+
? 'with local path'
|
|
85
|
+
: 'with no data',
|
|
69
86
|
);
|
|
87
|
+
return file;
|
|
70
88
|
};
|
|
71
89
|
onUploaded = this.db.markUploaded.bind(this.db);
|
|
72
90
|
get = this.db.get.bind(this.db);
|
|
@@ -78,22 +96,29 @@ export class PersistenceFiles extends Disposable {
|
|
|
78
96
|
private getFileExportName = (originalFileName: string, id: string) => {
|
|
79
97
|
return `${id}___${originalFileName}`;
|
|
80
98
|
};
|
|
81
|
-
export = async (downloadRemote =
|
|
99
|
+
export = async (downloadRemote = true) => {
|
|
82
100
|
const storedFiles = await this.getAll();
|
|
83
101
|
if (downloadRemote) {
|
|
84
102
|
for (const storedFile of storedFiles) {
|
|
85
103
|
// if it doesn't have a buffer, we need to read one from the server
|
|
86
|
-
if (!storedFile.file && storedFile.url) {
|
|
104
|
+
if (!storedFile.file && (storedFile.url || storedFile.localPath)) {
|
|
87
105
|
try {
|
|
88
|
-
const blob = await this.
|
|
106
|
+
const blob = await this.loadFileContents(storedFile);
|
|
89
107
|
storedFile.file = blob;
|
|
90
108
|
} catch (err) {
|
|
91
109
|
this.context.log(
|
|
92
110
|
'error',
|
|
93
111
|
"Failed to download file to cache it locally. The file will still be available using its URL. Check the file server's CORS configuration.",
|
|
112
|
+
storedFile,
|
|
94
113
|
err,
|
|
95
114
|
);
|
|
96
115
|
}
|
|
116
|
+
} else if (!storedFile.file) {
|
|
117
|
+
this.context.log(
|
|
118
|
+
'warn',
|
|
119
|
+
`File ${storedFile.id} has no file or URL. It will be missing in the export.`,
|
|
120
|
+
storedFile,
|
|
121
|
+
);
|
|
97
122
|
}
|
|
98
123
|
}
|
|
99
124
|
}
|
|
@@ -163,41 +188,46 @@ export class PersistenceFiles extends Disposable {
|
|
|
163
188
|
return { id, originalFileName };
|
|
164
189
|
};
|
|
165
190
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
191
|
+
private loadFileContents = async (
|
|
192
|
+
file: FileData,
|
|
193
|
+
retries = 0,
|
|
194
|
+
maxRetries = 0,
|
|
195
|
+
) => {
|
|
196
|
+
try {
|
|
197
|
+
return await this.db.loadFileContents(file, this.context);
|
|
198
|
+
} catch (err) {
|
|
172
199
|
if (retries < maxRetries) {
|
|
173
200
|
return new Promise<Blob>((resolve, reject) => {
|
|
174
201
|
setTimeout(() => {
|
|
175
|
-
this.
|
|
202
|
+
this.loadFileContents(file, retries + 1, maxRetries).then(
|
|
176
203
|
resolve,
|
|
177
204
|
reject,
|
|
178
205
|
);
|
|
179
206
|
}, 1000);
|
|
180
207
|
});
|
|
181
208
|
} else {
|
|
182
|
-
throw new Error(
|
|
183
|
-
|
|
184
|
-
);
|
|
209
|
+
throw new Error(`Failed to download file after ${maxRetries} retries`, {
|
|
210
|
+
cause: err,
|
|
211
|
+
});
|
|
185
212
|
}
|
|
186
213
|
}
|
|
187
|
-
return await resp.blob();
|
|
188
214
|
};
|
|
189
215
|
|
|
190
216
|
cleanupDeletedFiles = async () => {
|
|
191
217
|
let count = 0;
|
|
192
218
|
let skipCount = 0;
|
|
193
|
-
|
|
219
|
+
const deletable: string[] = [];
|
|
220
|
+
await this.iterateOverPendingDelete((fileData) => {
|
|
194
221
|
if (this.config.canCleanupDeletedFile(fileData)) {
|
|
195
222
|
count++;
|
|
196
|
-
|
|
223
|
+
deletable.push(fileData.id);
|
|
197
224
|
} else {
|
|
198
225
|
skipCount++;
|
|
199
226
|
}
|
|
200
227
|
});
|
|
228
|
+
for (const id of deletable) {
|
|
229
|
+
await this.db.delete(id);
|
|
230
|
+
}
|
|
201
231
|
|
|
202
232
|
this.context.log(
|
|
203
233
|
'info',
|
|
@@ -206,14 +236,10 @@ export class PersistenceFiles extends Disposable {
|
|
|
206
236
|
};
|
|
207
237
|
|
|
208
238
|
private onFileRefsDeleted = async (fileRefs: FileRef[]) => {
|
|
209
|
-
const tx = this.db.transaction({
|
|
210
|
-
mode: 'readwrite',
|
|
211
|
-
storeNames: ['files'],
|
|
212
|
-
});
|
|
213
239
|
await Promise.all(
|
|
214
240
|
fileRefs.map(async (fileRef) => {
|
|
215
241
|
try {
|
|
216
|
-
await this.db.markPendingDelete(fileRef.id
|
|
242
|
+
await this.db.markPendingDelete(fileRef.id);
|
|
217
243
|
} catch (err) {
|
|
218
244
|
this.context.log('error', 'Failed to mark file for deletion', err);
|
|
219
245
|
}
|