@verdant-web/store 2.5.7 → 2.6.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 +15 -10
- package/dist/bundle/index.js.map +4 -4
- package/dist/cjs/__tests__/queries.test.js +2 -0
- package/dist/cjs/__tests__/queries.test.js.map +1 -1
- package/dist/cjs/client/Client.js +23 -11
- package/dist/cjs/client/Client.js.map +1 -1
- package/dist/cjs/client/ClientDescriptor.d.ts +3 -0
- package/dist/cjs/client/ClientDescriptor.js +100 -36
- package/dist/cjs/client/ClientDescriptor.js.map +1 -1
- package/dist/cjs/entities/DocumentFamiliyCache.d.ts +20 -1
- package/dist/cjs/entities/DocumentFamiliyCache.js +33 -18
- package/dist/cjs/entities/DocumentFamiliyCache.js.map +1 -1
- package/dist/cjs/entities/Entity.js +17 -0
- package/dist/cjs/entities/Entity.js.map +1 -1
- package/dist/cjs/entities/EntityStore.d.ts +2 -1
- package/dist/cjs/entities/EntityStore.js +36 -7
- package/dist/cjs/entities/EntityStore.js.map +1 -1
- package/dist/cjs/idb.d.ts +2 -0
- package/dist/cjs/idb.js +9 -1
- package/dist/cjs/idb.js.map +1 -1
- package/dist/cjs/metadata/Metadata.d.ts +3 -1
- package/dist/cjs/metadata/Metadata.js +3 -1
- package/dist/cjs/metadata/Metadata.js.map +1 -1
- package/dist/cjs/metadata/openMetadataDatabase.d.ts +11 -2
- package/dist/cjs/metadata/openMetadataDatabase.js +56 -3
- package/dist/cjs/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/cjs/migration/db.d.ts +1 -1
- package/dist/cjs/migration/db.js +5 -2
- package/dist/cjs/migration/db.js.map +1 -1
- package/dist/cjs/migration/openDatabase.d.ts +8 -0
- package/dist/cjs/migration/openDatabase.js +228 -148
- package/dist/cjs/migration/openDatabase.js.map +1 -1
- package/dist/cjs/queries/BaseQuery.js +12 -1
- package/dist/cjs/queries/BaseQuery.js.map +1 -1
- package/dist/cjs/sync/WebSocketSync.js +4 -3
- package/dist/cjs/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/__tests__/queries.test.js +2 -0
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/client/Client.js +23 -11
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +3 -0
- package/dist/esm/client/ClientDescriptor.js +104 -40
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/entities/DocumentFamiliyCache.d.ts +20 -1
- package/dist/esm/entities/DocumentFamiliyCache.js +33 -18
- package/dist/esm/entities/DocumentFamiliyCache.js.map +1 -1
- package/dist/esm/entities/Entity.js +17 -0
- package/dist/esm/entities/Entity.js.map +1 -1
- package/dist/esm/entities/EntityStore.d.ts +2 -1
- package/dist/esm/entities/EntityStore.js +36 -7
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/idb.d.ts +2 -0
- package/dist/esm/idb.js +6 -0
- package/dist/esm/idb.js.map +1 -1
- package/dist/esm/metadata/Metadata.d.ts +3 -1
- package/dist/esm/metadata/Metadata.js +3 -1
- package/dist/esm/metadata/Metadata.js.map +1 -1
- package/dist/esm/metadata/openMetadataDatabase.d.ts +11 -2
- package/dist/esm/metadata/openMetadataDatabase.js +54 -2
- package/dist/esm/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/esm/migration/db.d.ts +1 -1
- package/dist/esm/migration/db.js +5 -2
- package/dist/esm/migration/db.js.map +1 -1
- package/dist/esm/migration/openDatabase.d.ts +8 -0
- package/dist/esm/migration/openDatabase.js +225 -146
- package/dist/esm/migration/openDatabase.js.map +1 -1
- package/dist/esm/queries/BaseQuery.js +12 -1
- package/dist/esm/queries/BaseQuery.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +4 -3
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/dist/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/__tests__/queries.test.ts +3 -0
- package/src/client/Client.ts +26 -12
- package/src/client/ClientDescriptor.ts +145 -49
- package/src/entities/DocumentFamiliyCache.ts +59 -19
- package/src/entities/Entity.ts +17 -0
- package/src/entities/EntityStore.ts +45 -6
- package/src/idb.ts +10 -0
- package/src/metadata/Metadata.ts +6 -1
- package/src/metadata/openMetadataDatabase.ts +96 -13
- package/src/migration/db.ts +14 -1
- package/src/migration/openDatabase.ts +357 -194
- package/src/queries/BaseQuery.ts +14 -1
- package/src/sync/WebSocketSync.ts +1 -0
|
@@ -14,6 +14,7 @@ import type { EntityStore } from './EntityStore.js';
|
|
|
14
14
|
import { WeakRef } from './FakeWeakRef.js';
|
|
15
15
|
import { Context } from '../context.js';
|
|
16
16
|
import { TaggedOperation } from '../types.js';
|
|
17
|
+
import { Resolvable } from '../utils/Resolvable.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Local operations: operations on this client that haven't
|
|
@@ -44,6 +45,14 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
44
45
|
private context;
|
|
45
46
|
private storeTools: StoreTools;
|
|
46
47
|
|
|
48
|
+
private _initialized = new Resolvable<boolean>();
|
|
49
|
+
get initializedPromise() {
|
|
50
|
+
return this._initialized.promise;
|
|
51
|
+
}
|
|
52
|
+
setInitialized = () => {
|
|
53
|
+
this._initialized.resolve(true);
|
|
54
|
+
};
|
|
55
|
+
|
|
47
56
|
constructor({
|
|
48
57
|
oid,
|
|
49
58
|
store,
|
|
@@ -236,6 +245,14 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
236
245
|
};
|
|
237
246
|
|
|
238
247
|
computeView = (oid: ObjectIdentifier) => {
|
|
248
|
+
if (
|
|
249
|
+
this.baselinesMap.size === 0 &&
|
|
250
|
+
this.operationsMap.size === 0 &&
|
|
251
|
+
this.localOperationsMap.size === 0
|
|
252
|
+
) {
|
|
253
|
+
this.context.log('debug', `Entity ${oid} accessed with no data at all`);
|
|
254
|
+
return { view: null, deleted: true, lastTimestamp: null };
|
|
255
|
+
}
|
|
239
256
|
const confirmed = this.computeConfirmedView(oid);
|
|
240
257
|
const unconfirmedOperations = this.localOperationsMap.get(oid) || [];
|
|
241
258
|
if (confirmed.empty && !unconfirmedOperations.length) {
|
|
@@ -331,33 +348,56 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
331
348
|
this.entities.clear();
|
|
332
349
|
};
|
|
333
350
|
|
|
334
|
-
reset = (
|
|
335
|
-
operations
|
|
336
|
-
baselines
|
|
351
|
+
reset = ({
|
|
352
|
+
operations,
|
|
353
|
+
baselines,
|
|
354
|
+
dropExistingUnconfirmed: dropUnconfirmed = false,
|
|
355
|
+
unconfirmedOperations,
|
|
356
|
+
dropAll,
|
|
357
|
+
}: {
|
|
358
|
+
operations: TaggedOperation[];
|
|
359
|
+
unconfirmedOperations?: Operation[];
|
|
360
|
+
baselines: DocumentBaseline[];
|
|
337
361
|
/**
|
|
338
362
|
* Whether to drop operations which are only in-memory. Unconfirmed operations
|
|
339
363
|
* will not be restored from storage until they are persisted, so it's not advisable
|
|
340
364
|
* to use this unless the intention is to completely clear the entities.
|
|
341
365
|
*/
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
366
|
+
dropExistingUnconfirmed?: boolean;
|
|
367
|
+
/**
|
|
368
|
+
* Drop unconfirmed and confirmed data before resetting to incoming data.
|
|
369
|
+
* This is dangerous due to race conditions. Only use when a full reset is
|
|
370
|
+
* required.
|
|
371
|
+
*/
|
|
372
|
+
dropAll?: boolean;
|
|
373
|
+
}) => {
|
|
374
|
+
this.context.log(
|
|
375
|
+
'debug',
|
|
376
|
+
`Resetting cache for ${this.oid} with ${operations.length} ops and ${baselines.length} baselines, dropUnconfirmed=${dropUnconfirmed}`,
|
|
347
377
|
);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
378
|
+
const info = { isLocal: false, affectedOids: new Set<ObjectIdentifier>() };
|
|
379
|
+
|
|
380
|
+
// NOTE: not clearing these maps... there are even more
|
|
381
|
+
// race conditions where we begin opening a cache, queue up a
|
|
382
|
+
// reset, then receive incoming operations before the reset
|
|
383
|
+
// actually hits this function, so those incoming ops are
|
|
384
|
+
// dropped.
|
|
385
|
+
// FIXME: include this in a future refactor of this
|
|
386
|
+
// whole system.
|
|
387
|
+
|
|
388
|
+
if (dropAll) this.baselinesMap.clear();
|
|
389
|
+
this.insertBaselines(baselines, info);
|
|
390
|
+
|
|
391
|
+
if (dropAll) this.operationsMap.clear();
|
|
392
|
+
this.insertOperations(operations, info);
|
|
393
|
+
|
|
394
|
+
if (unconfirmedOperations || dropUnconfirmed) {
|
|
395
|
+
this.localOperationsMap.clear();
|
|
396
|
+
if (unconfirmedOperations) {
|
|
397
|
+
this.insertLocalOperations(unconfirmedOperations);
|
|
358
398
|
}
|
|
359
399
|
}
|
|
360
|
-
|
|
400
|
+
|
|
361
401
|
for (const oid of this.entities.keys()) {
|
|
362
402
|
const entityRef = this.entities.get(oid);
|
|
363
403
|
const entity = entityRef?.deref();
|
package/src/entities/Entity.ts
CHANGED
|
@@ -347,6 +347,23 @@ export class Entity<
|
|
|
347
347
|
};
|
|
348
348
|
|
|
349
349
|
protected processInputValue = (value: any, key: any) => {
|
|
350
|
+
// disassociate incoming OIDs on values and generally break object
|
|
351
|
+
// references. cloning doesn't work on files so those are
|
|
352
|
+
// filtered out.
|
|
353
|
+
// The goal here is to be safe about a bunch of cases that could
|
|
354
|
+
// result in corrupt data, like...
|
|
355
|
+
// ent1.set('objField', ent2.get('objField'))
|
|
356
|
+
// or
|
|
357
|
+
// var shared = { foo: 'bar' };
|
|
358
|
+
// ent1.set('objField', shared);
|
|
359
|
+
// ent2.set('objField', shared);
|
|
360
|
+
// ... each of these would result in the same object being
|
|
361
|
+
// referenced in multiple entities, which could mean introduction
|
|
362
|
+
// of foreign OIDs, or one object being assigned different OIDs
|
|
363
|
+
// with unexpected results.
|
|
364
|
+
if (!(value instanceof File)) {
|
|
365
|
+
value = cloneDeep(value, false);
|
|
366
|
+
}
|
|
350
367
|
const fieldSchema = this.getChildFieldSchema(key);
|
|
351
368
|
if (fieldSchema) {
|
|
352
369
|
traverseCollectionFieldsAndApplyDefaults(value, fieldSchema);
|
|
@@ -113,9 +113,16 @@ export class EntityStore {
|
|
|
113
113
|
private refreshFamilyCache = async (
|
|
114
114
|
familyCache: DocumentFamilyCache,
|
|
115
115
|
dropUnconfirmed = false,
|
|
116
|
+
dropAll = false,
|
|
116
117
|
) => {
|
|
117
118
|
// avoid writing to disposed db
|
|
118
|
-
if (this._disposed)
|
|
119
|
+
if (this._disposed) {
|
|
120
|
+
this.context.log(
|
|
121
|
+
'debug',
|
|
122
|
+
`EntityStore is disposed, not refreshing ${familyCache.oid} cache`,
|
|
123
|
+
);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
119
126
|
|
|
120
127
|
// metadata must be loaded from database to initialize family cache
|
|
121
128
|
const transaction = this.meta.createTransaction([
|
|
@@ -146,21 +153,40 @@ export class EntityStore {
|
|
|
146
153
|
{ transaction, mode: 'readwrite' },
|
|
147
154
|
),
|
|
148
155
|
]);
|
|
149
|
-
familyCache.reset(
|
|
156
|
+
familyCache.reset({
|
|
157
|
+
operations,
|
|
158
|
+
baselines,
|
|
159
|
+
dropExistingUnconfirmed: dropUnconfirmed,
|
|
160
|
+
dropAll,
|
|
161
|
+
});
|
|
150
162
|
};
|
|
151
163
|
|
|
152
164
|
private openFamilyCache = async (oid: ObjectIdentifier) => {
|
|
153
165
|
const documentOid = getOidRoot(oid);
|
|
154
166
|
let familyCache = this.documentFamilyCaches.get(documentOid);
|
|
155
167
|
if (!familyCache) {
|
|
168
|
+
this.context.log('debug', 'opening family cache for', documentOid);
|
|
156
169
|
// metadata must be loaded from database to initialize family cache
|
|
157
170
|
familyCache = new DocumentFamilyCache({
|
|
158
171
|
oid: documentOid,
|
|
159
172
|
store: this,
|
|
160
173
|
context: this.context,
|
|
161
174
|
});
|
|
175
|
+
|
|
176
|
+
// PROBLEM: because the next line is async, it yields to
|
|
177
|
+
// queued promises which may need data from this cache,
|
|
178
|
+
// but the cache is empty. But if we move the set to
|
|
179
|
+
// after the async, we can clobber an existing cache
|
|
180
|
+
// with race conditions...
|
|
181
|
+
// So as an attempt to fix that, I've added a promise
|
|
182
|
+
// on DocumentFamilyCache which I manually resolve
|
|
183
|
+
// with setInitialized, then await initializedPromise
|
|
184
|
+
// further down even if there was a cache hit.
|
|
185
|
+
// Surely there is a better pattern for this.
|
|
186
|
+
// FIXME:
|
|
162
187
|
this.documentFamilyCaches.set(documentOid, familyCache);
|
|
163
188
|
await this.refreshFamilyCache(familyCache);
|
|
189
|
+
familyCache.setInitialized();
|
|
164
190
|
|
|
165
191
|
// this.unsubscribes.push(
|
|
166
192
|
// familyCache.subscribe('change:*', this.onEntityChange),
|
|
@@ -168,6 +194,7 @@ export class EntityStore {
|
|
|
168
194
|
|
|
169
195
|
// TODO: cleanup cache when all documents are disposed
|
|
170
196
|
}
|
|
197
|
+
await familyCache.initializedPromise;
|
|
171
198
|
|
|
172
199
|
return familyCache;
|
|
173
200
|
};
|
|
@@ -384,6 +411,10 @@ export class EntityStore {
|
|
|
384
411
|
baselines: DocumentBaseline[];
|
|
385
412
|
reset?: boolean;
|
|
386
413
|
}) => {
|
|
414
|
+
if (this._disposed) {
|
|
415
|
+
this.log('warn', 'EntityStore is disposed, not adding data');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
387
418
|
// convert operations to tagged operations with confirmed = false
|
|
388
419
|
// while we process and store them. this is in-place so as to
|
|
389
420
|
// not allocate a bunch of objects...
|
|
@@ -427,7 +458,7 @@ export class EntityStore {
|
|
|
427
458
|
await this.meta.insertRemoteOperations(operations);
|
|
428
459
|
|
|
429
460
|
if (reset) {
|
|
430
|
-
await this.refreshAllCaches(true);
|
|
461
|
+
await this.refreshAllCaches(true, true);
|
|
431
462
|
}
|
|
432
463
|
|
|
433
464
|
// recompute all affected documents for querying
|
|
@@ -511,6 +542,10 @@ export class EntityStore {
|
|
|
511
542
|
await this.operationBatcher.flush(this.currentBatchKey);
|
|
512
543
|
};
|
|
513
544
|
|
|
545
|
+
flushAllBatches = async () => {
|
|
546
|
+
await Promise.all(this.operationBatcher.flushAll());
|
|
547
|
+
};
|
|
548
|
+
|
|
514
549
|
private flushOperations = async (
|
|
515
550
|
operations: Operation[],
|
|
516
551
|
batchKey: string,
|
|
@@ -632,7 +667,7 @@ export class EntityStore {
|
|
|
632
667
|
// );
|
|
633
668
|
};
|
|
634
669
|
|
|
635
|
-
destroy = () => {
|
|
670
|
+
destroy = async () => {
|
|
636
671
|
this._disposed = true;
|
|
637
672
|
for (const unsubscribe of this.unsubscribes) {
|
|
638
673
|
unsubscribe();
|
|
@@ -641,6 +676,7 @@ export class EntityStore {
|
|
|
641
676
|
cache.dispose();
|
|
642
677
|
}
|
|
643
678
|
this.documentFamilyCaches.clear();
|
|
679
|
+
await this.flushAllBatches();
|
|
644
680
|
};
|
|
645
681
|
|
|
646
682
|
private handleRebase = (baselines: DocumentBaseline[]) => {
|
|
@@ -661,9 +697,12 @@ export class EntityStore {
|
|
|
661
697
|
}
|
|
662
698
|
};
|
|
663
699
|
|
|
664
|
-
private refreshAllCaches = async (
|
|
700
|
+
private refreshAllCaches = async (
|
|
701
|
+
dropUnconfirmed = false,
|
|
702
|
+
dropAll = false,
|
|
703
|
+
) => {
|
|
665
704
|
for (const [_, cache] of this.documentFamilyCaches) {
|
|
666
|
-
await this.refreshFamilyCache(cache, dropUnconfirmed);
|
|
705
|
+
await this.refreshFamilyCache(cache, dropUnconfirmed, dropAll);
|
|
667
706
|
}
|
|
668
707
|
};
|
|
669
708
|
}
|
package/src/idb.ts
CHANGED
|
@@ -105,3 +105,13 @@ export async function deleteAllDatabases(
|
|
|
105
105
|
]);
|
|
106
106
|
window.location.reload();
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
export function deleteDatabase(name: string, indexedDB = window.indexedDB) {
|
|
110
|
+
return storeRequestPromise(indexedDB.deleteDatabase(name));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function getAllDatabaseNamesAndVersions(
|
|
114
|
+
indexedDB: IDBFactory = window.indexedDB,
|
|
115
|
+
) {
|
|
116
|
+
return indexedDB.databases();
|
|
117
|
+
}
|
package/src/metadata/Metadata.ts
CHANGED
|
@@ -141,7 +141,10 @@ export class Metadata extends EventSubscriber<{
|
|
|
141
141
|
return Array.from(oids);
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
-
getDocumentSnapshot = async (
|
|
144
|
+
getDocumentSnapshot = async (
|
|
145
|
+
oid: ObjectIdentifier,
|
|
146
|
+
options: { to?: string } = {},
|
|
147
|
+
) => {
|
|
145
148
|
const documentOid = getOidRoot(oid);
|
|
146
149
|
assert(documentOid === oid, 'Must be root document OID');
|
|
147
150
|
const transaction = this.db.transaction(
|
|
@@ -170,6 +173,8 @@ export class Metadata extends EventSubscriber<{
|
|
|
170
173
|
},
|
|
171
174
|
{
|
|
172
175
|
transaction,
|
|
176
|
+
// only apply operations up to the current time
|
|
177
|
+
to: options.to || this.now,
|
|
173
178
|
},
|
|
174
179
|
);
|
|
175
180
|
const root = objectMap.get(documentOid);
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
import { closeDatabase, storeRequestPromise } from '../idb.js';
|
|
2
|
+
|
|
1
3
|
const migrations = [version1, version2, version3, version4];
|
|
2
4
|
|
|
3
|
-
export function openMetadataDatabase(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
log?: (...args: any[]) => void;
|
|
13
|
-
},
|
|
14
|
-
): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
|
|
5
|
+
export function openMetadataDatabase({
|
|
6
|
+
indexedDB = window.indexedDB,
|
|
7
|
+
namespace,
|
|
8
|
+
log,
|
|
9
|
+
}: {
|
|
10
|
+
indexedDB?: IDBFactory;
|
|
11
|
+
namespace: string;
|
|
12
|
+
log?: (...args: any[]) => void;
|
|
13
|
+
}): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
|
|
15
14
|
return new Promise<{ wasInitialized: boolean; db: IDBDatabase }>(
|
|
16
15
|
(resolve, reject) => {
|
|
17
|
-
const request = indexedDB.open(
|
|
16
|
+
const request = indexedDB.open(`${namespace}_meta`, 4);
|
|
18
17
|
let wasInitialized = false;
|
|
19
18
|
request.onupgradeneeded = async (event) => {
|
|
20
19
|
const db = request.result;
|
|
@@ -40,6 +39,90 @@ export function openMetadataDatabase(
|
|
|
40
39
|
);
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
export async function openWIPMetadataDatabase({
|
|
43
|
+
wipNamespace,
|
|
44
|
+
namespace,
|
|
45
|
+
indexedDB,
|
|
46
|
+
log,
|
|
47
|
+
}: {
|
|
48
|
+
indexedDB?: IDBFactory;
|
|
49
|
+
namespace: string;
|
|
50
|
+
wipNamespace: string;
|
|
51
|
+
log?: (...args: any[]) => void;
|
|
52
|
+
}): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
|
|
53
|
+
const result = await openMetadataDatabase({
|
|
54
|
+
namespace: wipNamespace,
|
|
55
|
+
indexedDB,
|
|
56
|
+
log,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// this WIP database was already set up.
|
|
60
|
+
if (!result.wasInitialized) {
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log?.('debug', 'Beginning copy of production metadata database to WIP');
|
|
65
|
+
// copy all data from production metadata database
|
|
66
|
+
const { db: prodDb } = await openMetadataDatabase({
|
|
67
|
+
namespace,
|
|
68
|
+
indexedDB,
|
|
69
|
+
log,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const tx = prodDb.transaction(
|
|
73
|
+
['baselines', 'operations', 'info'],
|
|
74
|
+
'readonly',
|
|
75
|
+
);
|
|
76
|
+
const [baselines, operations, info] = await Promise.all([
|
|
77
|
+
storeRequestPromise(tx.objectStore('baselines').getAll()),
|
|
78
|
+
storeRequestPromise(tx.objectStore('operations').getAll()),
|
|
79
|
+
storeRequestPromise(tx.objectStore('info').getAll()),
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const wipTx = result.db.transaction(
|
|
83
|
+
['baselines', 'operations', 'info'],
|
|
84
|
+
'readwrite',
|
|
85
|
+
);
|
|
86
|
+
const wipBaselines = wipTx.objectStore('baselines');
|
|
87
|
+
const wipOperations = wipTx.objectStore('operations');
|
|
88
|
+
const wipInfo = wipTx.objectStore('info');
|
|
89
|
+
|
|
90
|
+
for (const baseline of baselines) {
|
|
91
|
+
wipBaselines.put(baseline);
|
|
92
|
+
}
|
|
93
|
+
for (const operation of operations) {
|
|
94
|
+
wipOperations.put(operation);
|
|
95
|
+
}
|
|
96
|
+
for (const infoItem of info) {
|
|
97
|
+
wipInfo.put(infoItem);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await new Promise<void>((resolve, reject) => {
|
|
101
|
+
wipTx.oncomplete = () => {
|
|
102
|
+
resolve();
|
|
103
|
+
};
|
|
104
|
+
wipTx.onerror = (event) => {
|
|
105
|
+
reject(event);
|
|
106
|
+
};
|
|
107
|
+
wipTx.onabort = (event) => {
|
|
108
|
+
reject(event);
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await closeDatabase(prodDb);
|
|
113
|
+
|
|
114
|
+
log?.(
|
|
115
|
+
'debug',
|
|
116
|
+
'Finished copy of production metadata database to WIP. Copied:',
|
|
117
|
+
baselines.length,
|
|
118
|
+
'baselines,',
|
|
119
|
+
operations.length,
|
|
120
|
+
'operations',
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
43
126
|
async function version1(db: IDBDatabase, tx: IDBTransaction) {
|
|
44
127
|
const baselinesStore = db.createObjectStore('baselines', {
|
|
45
128
|
keyPath: 'oid',
|
package/src/migration/db.ts
CHANGED
|
@@ -114,7 +114,9 @@ export async function openDatabase(
|
|
|
114
114
|
indexedDb: IDBFactory,
|
|
115
115
|
namespace: string,
|
|
116
116
|
version: number,
|
|
117
|
+
log?: (...args: any[]) => void,
|
|
117
118
|
): Promise<IDBDatabase> {
|
|
119
|
+
log?.('debug', 'Opening database', namespace, 'at version', version);
|
|
118
120
|
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
|
119
121
|
const request = indexedDb.open(
|
|
120
122
|
[namespace, 'collections'].join('_'),
|
|
@@ -124,8 +126,19 @@ export async function openDatabase(
|
|
|
124
126
|
const transaction = request.transaction!;
|
|
125
127
|
transaction.abort();
|
|
126
128
|
|
|
129
|
+
log?.(
|
|
130
|
+
'error',
|
|
131
|
+
'Database upgrade needed, but not expected',
|
|
132
|
+
'Expected',
|
|
133
|
+
version,
|
|
134
|
+
'Got',
|
|
135
|
+
request.result.version,
|
|
136
|
+
);
|
|
127
137
|
reject(
|
|
128
|
-
|
|
138
|
+
request.error ||
|
|
139
|
+
new Error(
|
|
140
|
+
`Migration error: database version changed unexpectedly when reading current data. Expected ${version}, got ${request.result.version}`,
|
|
141
|
+
),
|
|
129
142
|
);
|
|
130
143
|
};
|
|
131
144
|
request.onsuccess = (event) => {
|