@verdant-web/store 4.0.0 → 4.1.0-alpha.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/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/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/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
|
@@ -7,13 +7,22 @@ import {
|
|
|
7
7
|
} from '@verdant-web/common';
|
|
8
8
|
import { Context } from '../context/context.js';
|
|
9
9
|
import { AbstractTransaction, PersistenceMetadataDb } from './interfaces.js';
|
|
10
|
+
import type { PersistenceMetadata } from './PersistenceMetadata.js';
|
|
10
11
|
|
|
11
12
|
export class PersistenceRebaser {
|
|
12
13
|
constructor(
|
|
13
14
|
private db: PersistenceMetadataDb,
|
|
15
|
+
private meta: PersistenceMetadata,
|
|
14
16
|
private ctx: Pick<
|
|
15
17
|
Context,
|
|
16
|
-
|
|
18
|
+
| 'closing'
|
|
19
|
+
| 'log'
|
|
20
|
+
| 'time'
|
|
21
|
+
| 'internalEvents'
|
|
22
|
+
| 'globalEvents'
|
|
23
|
+
| 'config'
|
|
24
|
+
| 'closeLock'
|
|
25
|
+
| 'persistenceShutdownHandler'
|
|
17
26
|
>,
|
|
18
27
|
) {}
|
|
19
28
|
|
|
@@ -22,9 +31,12 @@ export class PersistenceRebaser {
|
|
|
22
31
|
* keep storage clean for non-syncing clients by compressing history.
|
|
23
32
|
*/
|
|
24
33
|
tryAutonomousRebase = async () => {
|
|
25
|
-
const localReplicaInfo = await this.
|
|
34
|
+
const localReplicaInfo = await this.meta.getLocalReplica();
|
|
26
35
|
if (localReplicaInfo.lastSyncedLogicalTime) return; // cannot autonomously rebase if we've synced
|
|
36
|
+
if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
|
|
37
|
+
return;
|
|
27
38
|
// but if we have never synced... we can rebase everything!
|
|
39
|
+
this.ctx.log('debug', 'Running autonomous library rebase');
|
|
28
40
|
await this.runRebase(this.ctx.time.now);
|
|
29
41
|
};
|
|
30
42
|
|
|
@@ -35,47 +47,55 @@ export class PersistenceRebaser {
|
|
|
35
47
|
* their undo stack.
|
|
36
48
|
*/
|
|
37
49
|
private runRebase = async (globalAckTimestamp: string) => {
|
|
38
|
-
if (this.ctx.closing)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const toRebase = new Set<ObjectIdentifier>();
|
|
43
|
-
const transaction = this.db.transaction({
|
|
44
|
-
storeNames: ['baselines', 'operations'],
|
|
45
|
-
mode: 'readwrite',
|
|
46
|
-
});
|
|
47
|
-
let operationCount = 0;
|
|
48
|
-
await this.db.iterateAllOperations(
|
|
49
|
-
(patch) => {
|
|
50
|
-
toRebase.add(patch.oid);
|
|
51
|
-
lastTimestamp = patch.timestamp;
|
|
52
|
-
operationCount++;
|
|
53
|
-
},
|
|
50
|
+
if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
|
|
51
|
+
return;
|
|
52
|
+
|
|
53
|
+
await this.db.transaction(
|
|
54
54
|
{
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
storeNames: ['baselines', 'operations'],
|
|
56
|
+
mode: 'readwrite',
|
|
57
57
|
},
|
|
58
|
-
|
|
58
|
+
async (transaction) => {
|
|
59
|
+
// find all operations before the global ack
|
|
60
|
+
const toRebase = new Set<ObjectIdentifier>();
|
|
61
|
+
let lastTimestamp;
|
|
62
|
+
let operationCount = 0;
|
|
63
|
+
await this.db.iterateAllOperations(
|
|
64
|
+
(patch) => {
|
|
65
|
+
toRebase.add(patch.oid);
|
|
66
|
+
lastTimestamp = patch.timestamp;
|
|
67
|
+
operationCount++;
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
before: globalAckTimestamp,
|
|
71
|
+
transaction,
|
|
72
|
+
},
|
|
73
|
+
);
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
if (!toRebase.size) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
if (
|
|
80
|
+
this.ctx.closing ||
|
|
81
|
+
this.ctx.persistenceShutdownHandler.isShuttingDown
|
|
82
|
+
) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
// rebase each affected document
|
|
87
|
+
let newBaselines = [];
|
|
88
|
+
for (const oid of toRebase) {
|
|
89
|
+
newBaselines.push(
|
|
90
|
+
await this.rebase(
|
|
91
|
+
oid,
|
|
92
|
+
lastTimestamp || globalAckTimestamp,
|
|
93
|
+
transaction,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
);
|
|
79
99
|
this.ctx.globalEvents.emit('rebase');
|
|
80
100
|
};
|
|
81
101
|
|
|
@@ -92,26 +112,28 @@ export class PersistenceRebaser {
|
|
|
92
112
|
this.ctx.config.persistence?.rebaseTimeout ?? 10000,
|
|
93
113
|
timestamp,
|
|
94
114
|
);
|
|
115
|
+
this.ctx.log('debug', 'Scheduled rebase up to global ack', timestamp);
|
|
95
116
|
};
|
|
96
117
|
private rebaseTimeout: NodeJS.Timeout | null = null;
|
|
97
118
|
|
|
98
|
-
rebase = async (
|
|
119
|
+
private rebase = async (
|
|
99
120
|
oid: ObjectIdentifier,
|
|
100
121
|
upTo: string,
|
|
101
|
-
|
|
122
|
+
transaction: AbstractTransaction,
|
|
102
123
|
) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
storeNames: ['operations', 'baselines'],
|
|
107
|
-
mode: 'readwrite',
|
|
108
|
-
});
|
|
124
|
+
if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
|
|
125
|
+
return;
|
|
126
|
+
|
|
109
127
|
const baseline = await this.db.getBaseline(oid, { transaction });
|
|
110
128
|
let current: any = baseline?.snapshot || undefined;
|
|
111
129
|
let operationsApplied = 0;
|
|
112
130
|
let authz = baseline?.authz;
|
|
113
131
|
const deletedRefs: Ref[] = [];
|
|
114
|
-
|
|
132
|
+
|
|
133
|
+
if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
|
|
134
|
+
return;
|
|
135
|
+
|
|
136
|
+
await this.db.iterateEntityOperations(
|
|
115
137
|
oid,
|
|
116
138
|
(patch) => {
|
|
117
139
|
// FIXME: this seems like the wrong place to do this
|
|
@@ -139,32 +161,45 @@ export class PersistenceRebaser {
|
|
|
139
161
|
timestamp: upTo,
|
|
140
162
|
authz,
|
|
141
163
|
};
|
|
142
|
-
if (newBaseline.snapshot) {
|
|
143
|
-
await this.db.setBaselines([newBaseline], { transaction });
|
|
144
|
-
} else {
|
|
145
|
-
await this.db.deleteBaseline(oid, { transaction });
|
|
146
|
-
}
|
|
147
164
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
oid,
|
|
152
|
-
'up to',
|
|
153
|
-
upTo,
|
|
154
|
-
':',
|
|
155
|
-
current,
|
|
156
|
-
'and deleted',
|
|
157
|
-
operationsApplied,
|
|
158
|
-
'operations',
|
|
159
|
-
);
|
|
165
|
+
// still time to cancel now...
|
|
166
|
+
if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
|
|
167
|
+
return;
|
|
160
168
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
// FROM HERE, WE ARE COMMITTED TO THE REBASE -- otherwise data will be corrupted.
|
|
170
|
+
this.ctx.closeLock = (async () => {
|
|
171
|
+
if (newBaseline.snapshot) {
|
|
172
|
+
await this.db.setBaselines([newBaseline], { transaction });
|
|
173
|
+
} else {
|
|
174
|
+
await this.db.deleteBaseline(oid, { transaction });
|
|
166
175
|
}
|
|
167
|
-
|
|
176
|
+
|
|
177
|
+
await this.db.deleteEntityOperations(oid, {
|
|
178
|
+
to: upTo,
|
|
179
|
+
transaction,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
this.ctx.log(
|
|
183
|
+
'debug',
|
|
184
|
+
'rebased',
|
|
185
|
+
oid,
|
|
186
|
+
'up to',
|
|
187
|
+
upTo,
|
|
188
|
+
':',
|
|
189
|
+
current,
|
|
190
|
+
'and deleted',
|
|
191
|
+
operationsApplied,
|
|
192
|
+
'operations',
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// cleanup deleted refs
|
|
196
|
+
if (deletedRefs.length) {
|
|
197
|
+
const fileRefs = deletedRefs.filter(isFileRef);
|
|
198
|
+
if (fileRefs.length) {
|
|
199
|
+
this.ctx.internalEvents.emit('filesDeleted', fileRefs);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
})();
|
|
168
203
|
|
|
169
204
|
return newBaseline;
|
|
170
205
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Context } from '../../context/context.js';
|
|
2
2
|
import {
|
|
3
|
-
copyDatabase,
|
|
4
3
|
createAbortableTransaction,
|
|
5
4
|
isAbortError,
|
|
6
5
|
storeRequestPromise,
|
|
@@ -33,21 +32,40 @@ export class IdbService extends Disposable {
|
|
|
33
32
|
abort?: AbortSignal;
|
|
34
33
|
},
|
|
35
34
|
) => {
|
|
36
|
-
|
|
37
|
-
this.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
try {
|
|
36
|
+
if (this.globalAbortController.signal.aborted) {
|
|
37
|
+
throw new Error('Global abort signal is already aborted');
|
|
38
|
+
}
|
|
39
|
+
const tx = createAbortableTransaction(
|
|
40
|
+
this.db,
|
|
41
|
+
storeNames,
|
|
42
|
+
opts?.mode || 'readonly',
|
|
43
|
+
opts?.abort,
|
|
44
|
+
this.log,
|
|
45
|
+
);
|
|
46
|
+
this.globalAbortController.signal.addEventListener('abort', tx.abort);
|
|
47
|
+
tx.addEventListener('complete', () => {
|
|
48
|
+
this.globalAbortController.signal.removeEventListener(
|
|
49
|
+
'abort',
|
|
50
|
+
tx.abort,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
tx.addEventListener('error', () => {
|
|
54
|
+
this.globalAbortController.signal.removeEventListener(
|
|
55
|
+
'abort',
|
|
56
|
+
tx.abort,
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
return tx;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
this.log?.(
|
|
62
|
+
'error',
|
|
63
|
+
'Failed to create abortable transaction for store names',
|
|
64
|
+
storeNames,
|
|
65
|
+
err,
|
|
66
|
+
);
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
51
69
|
};
|
|
52
70
|
|
|
53
71
|
run = async <T = any>(
|
|
@@ -163,18 +181,18 @@ export class IdbService extends Disposable {
|
|
|
163
181
|
});
|
|
164
182
|
};
|
|
165
183
|
|
|
166
|
-
|
|
167
|
-
await copyDatabase(this.db, otherDb);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
private onVersionChange = () => {
|
|
184
|
+
private onVersionChange = (ev: IDBVersionChangeEvent) => {
|
|
171
185
|
this.log?.(
|
|
172
186
|
'warn',
|
|
173
187
|
`Another tab has requested a version change for ${this.db.name}`,
|
|
174
188
|
);
|
|
175
189
|
this.db.close();
|
|
176
190
|
if (typeof window !== 'undefined') {
|
|
177
|
-
|
|
191
|
+
try {
|
|
192
|
+
window.location.reload();
|
|
193
|
+
} catch (err) {
|
|
194
|
+
this.log?.('error', 'Failed to reload the page', err);
|
|
195
|
+
}
|
|
178
196
|
}
|
|
179
197
|
};
|
|
180
198
|
}
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
AbstractTransaction,
|
|
4
4
|
PersistedFileData,
|
|
5
5
|
PersistenceFileDb,
|
|
6
|
-
QueryMode,
|
|
7
6
|
} from '../../interfaces.js';
|
|
8
7
|
import { IdbService } from '../IdbService.js';
|
|
9
8
|
import { getAllFromObjectStores, getSizeOfObjectStore } from '../util.js';
|
|
9
|
+
import { Context } from '../../../internal.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* When stored in IDB, replace the file blob with an array buffer
|
|
@@ -24,35 +24,11 @@ export class IdbPersistenceFileDb
|
|
|
24
24
|
extends IdbService
|
|
25
25
|
implements PersistenceFileDb
|
|
26
26
|
{
|
|
27
|
-
transaction = (opts: {
|
|
28
|
-
mode?: QueryMode;
|
|
29
|
-
storeNames: string[];
|
|
30
|
-
abort?: AbortSignal;
|
|
31
|
-
}): AbstractTransaction => {
|
|
32
|
-
return this.createTransaction(opts.storeNames, {
|
|
33
|
-
mode: opts.mode,
|
|
34
|
-
abort: opts.abort,
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
|
|
38
27
|
add = async (
|
|
39
28
|
file: FileData,
|
|
40
|
-
options?: { transaction?: AbstractTransaction; downloadRemote?: boolean },
|
|
41
29
|
): Promise<void> => {
|
|
42
30
|
let buffer = file.file ? await fileToArrayBuffer(file.file) : undefined;
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
buffer = await fetch(file.url, {
|
|
46
|
-
method: 'GET',
|
|
47
|
-
credentials: 'include',
|
|
48
|
-
}).then((r) => r.arrayBuffer());
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.error(
|
|
51
|
-
"Failed to download file to cache it locally. The file will still be available using its URL. Check the file server's CORS configuration.",
|
|
52
|
-
err,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
31
|
+
|
|
56
32
|
await this.run(
|
|
57
33
|
'files',
|
|
58
34
|
(store) => {
|
|
@@ -65,19 +41,16 @@ export class IdbPersistenceFileDb
|
|
|
65
41
|
type: file.type,
|
|
66
42
|
url: file.url,
|
|
67
43
|
buffer,
|
|
44
|
+
timestamp: file.timestamp,
|
|
68
45
|
} satisfies StoredFileData);
|
|
69
46
|
},
|
|
70
47
|
{
|
|
71
48
|
mode: 'readwrite',
|
|
72
|
-
transaction: options?.transaction as IDBTransaction,
|
|
73
49
|
},
|
|
74
50
|
);
|
|
75
51
|
};
|
|
76
|
-
markUploaded = async (
|
|
77
|
-
id
|
|
78
|
-
options?: { transaction?: AbstractTransaction },
|
|
79
|
-
): Promise<void> => {
|
|
80
|
-
const current = await this.getFileRaw(id, options);
|
|
52
|
+
markUploaded = async (id: string): Promise<void> => {
|
|
53
|
+
const current = await this.getFileRaw(id);
|
|
81
54
|
|
|
82
55
|
if (!current) {
|
|
83
56
|
throw new Error('File is not in local database');
|
|
@@ -93,24 +66,17 @@ export class IdbPersistenceFileDb
|
|
|
93
66
|
},
|
|
94
67
|
{
|
|
95
68
|
mode: 'readwrite',
|
|
96
|
-
transaction: options?.transaction as IDBTransaction,
|
|
97
69
|
},
|
|
98
70
|
);
|
|
99
71
|
};
|
|
100
|
-
get = async (
|
|
101
|
-
fileId
|
|
102
|
-
options?: { transaction?: AbstractTransaction },
|
|
103
|
-
): Promise<PersistedFileData | null> => {
|
|
104
|
-
const raw = await this.getFileRaw(fileId, options);
|
|
72
|
+
get = async (fileId: string): Promise<PersistedFileData | null> => {
|
|
73
|
+
const raw = await this.getFileRaw(fileId);
|
|
105
74
|
if (!raw) {
|
|
106
75
|
return null;
|
|
107
76
|
}
|
|
108
77
|
return this.hydrateFileData(raw);
|
|
109
78
|
};
|
|
110
|
-
delete = (
|
|
111
|
-
fileId: string,
|
|
112
|
-
options?: { transaction?: AbstractTransaction },
|
|
113
|
-
): Promise<void> => {
|
|
79
|
+
delete = (fileId: string): Promise<void> => {
|
|
114
80
|
return this.run<undefined>(
|
|
115
81
|
'files',
|
|
116
82
|
(store) => {
|
|
@@ -118,15 +84,11 @@ export class IdbPersistenceFileDb
|
|
|
118
84
|
},
|
|
119
85
|
{
|
|
120
86
|
mode: 'readwrite',
|
|
121
|
-
transaction: options?.transaction as IDBTransaction,
|
|
122
87
|
},
|
|
123
88
|
);
|
|
124
89
|
};
|
|
125
|
-
markPendingDelete = async (
|
|
126
|
-
fileId
|
|
127
|
-
options?: { transaction?: AbstractTransaction },
|
|
128
|
-
): Promise<void> => {
|
|
129
|
-
const current = await this.getFileRaw(fileId, options);
|
|
90
|
+
markPendingDelete = async (fileId: string): Promise<void> => {
|
|
91
|
+
const current = await this.getFileRaw(fileId);
|
|
130
92
|
|
|
131
93
|
if (!current) {
|
|
132
94
|
throw new Error('File is not in local database');
|
|
@@ -142,29 +104,23 @@ export class IdbPersistenceFileDb
|
|
|
142
104
|
},
|
|
143
105
|
{
|
|
144
106
|
mode: 'readwrite',
|
|
145
|
-
transaction: options?.transaction as IDBTransaction,
|
|
146
107
|
},
|
|
147
108
|
);
|
|
148
109
|
};
|
|
149
|
-
listUnsynced = async (
|
|
150
|
-
transaction?: AbstractTransaction;
|
|
151
|
-
}): Promise<PersistedFileData[]> => {
|
|
110
|
+
listUnsynced = async (): Promise<PersistedFileData[]> => {
|
|
152
111
|
const raw = await this.run<StoredFileData[]>(
|
|
153
112
|
'files',
|
|
154
113
|
(store) => {
|
|
155
114
|
return store.index('remote').getAll('false');
|
|
156
115
|
},
|
|
157
|
-
{ mode: 'readonly'
|
|
116
|
+
{ mode: 'readonly' },
|
|
158
117
|
);
|
|
159
118
|
return raw.map(this.hydrateFileData);
|
|
160
119
|
};
|
|
161
|
-
resetSyncedStatusSince = async (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const tx: IDBTransaction =
|
|
166
|
-
(options?.transaction as any) ??
|
|
167
|
-
this.createTransaction(['files'], { mode: 'readwrite' });
|
|
120
|
+
resetSyncedStatusSince = async (since: string | null): Promise<void> => {
|
|
121
|
+
const tx: IDBTransaction = this.createTransaction(['files'], {
|
|
122
|
+
mode: 'readwrite',
|
|
123
|
+
});
|
|
168
124
|
const raw = await this.run<StoredFileData[]>(
|
|
169
125
|
'files',
|
|
170
126
|
(store) => {
|
|
@@ -194,7 +150,6 @@ export class IdbPersistenceFileDb
|
|
|
194
150
|
};
|
|
195
151
|
iterateOverPendingDelete = (
|
|
196
152
|
iterator: (file: PersistedFileData, store: IDBObjectStore) => void,
|
|
197
|
-
options?: { transaction?: IDBTransaction },
|
|
198
153
|
): Promise<void> => {
|
|
199
154
|
return this.iterate<StoredFileData>(
|
|
200
155
|
'files',
|
|
@@ -208,7 +163,6 @@ export class IdbPersistenceFileDb
|
|
|
208
163
|
},
|
|
209
164
|
{
|
|
210
165
|
mode: 'readwrite',
|
|
211
|
-
transaction: options?.transaction as IDBTransaction,
|
|
212
166
|
},
|
|
213
167
|
);
|
|
214
168
|
};
|
|
@@ -223,6 +177,20 @@ export class IdbPersistenceFileDb
|
|
|
223
177
|
size: await getSizeOfObjectStore(this.db, 'files'),
|
|
224
178
|
};
|
|
225
179
|
};
|
|
180
|
+
loadFileContents = async (file: FileData, ctx: Context): Promise<Blob> => {
|
|
181
|
+
if (file.file) return file.file;
|
|
182
|
+
if (file.localPath) {
|
|
183
|
+
throw new Error('Local file paths are not supported in browser');
|
|
184
|
+
}
|
|
185
|
+
if (file.url) {
|
|
186
|
+
const response = await ctx.environment.fetch(file.url);
|
|
187
|
+
if (!response.ok) {
|
|
188
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
189
|
+
}
|
|
190
|
+
return response.blob();
|
|
191
|
+
}
|
|
192
|
+
throw new Error('File is missing url, file, and localPath');
|
|
193
|
+
}
|
|
226
194
|
|
|
227
195
|
private hydrateFileData = (raw: StoredFileData): PersistedFileData => {
|
|
228
196
|
(raw as any).remote = raw.remote === 'true';
|