@verdant-web/store 2.5.8 → 2.7.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.
Files changed (145) hide show
  1. package/dist/bundle/index.js +15 -10
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/cjs/{entities/FakeWeakRef.d.ts → FakeWeakRef.d.ts} +2 -2
  4. package/dist/cjs/{entities/FakeWeakRef.js → FakeWeakRef.js} +4 -4
  5. package/dist/cjs/FakeWeakRef.js.map +1 -0
  6. package/dist/cjs/IDBService.d.ts +1 -1
  7. package/dist/cjs/IDBService.js +18 -1
  8. package/dist/cjs/IDBService.js.map +1 -1
  9. package/dist/cjs/__tests__/documents.test.js +17 -0
  10. package/dist/cjs/__tests__/documents.test.js.map +1 -1
  11. package/dist/cjs/__tests__/fixtures/testStorage.d.ts +1 -1
  12. package/dist/cjs/__tests__/fixtures/testStorage.js +3 -2
  13. package/dist/cjs/__tests__/fixtures/testStorage.js.map +1 -1
  14. package/dist/cjs/__tests__/mutations.test.d.ts +1 -0
  15. package/dist/cjs/__tests__/mutations.test.js +42 -0
  16. package/dist/cjs/__tests__/mutations.test.js.map +1 -0
  17. package/dist/cjs/__tests__/queries.test.js +2 -0
  18. package/dist/cjs/__tests__/queries.test.js.map +1 -1
  19. package/dist/cjs/client/Client.d.ts +6 -4
  20. package/dist/cjs/client/Client.js +24 -16
  21. package/dist/cjs/client/Client.js.map +1 -1
  22. package/dist/cjs/client/ClientDescriptor.d.ts +15 -4
  23. package/dist/cjs/client/ClientDescriptor.js +117 -36
  24. package/dist/cjs/client/ClientDescriptor.js.map +1 -1
  25. package/dist/cjs/context.d.ts +1 -0
  26. package/dist/cjs/entities/DocumentFamiliyCache.d.ts +22 -2
  27. package/dist/cjs/entities/DocumentFamiliyCache.js +39 -21
  28. package/dist/cjs/entities/DocumentFamiliyCache.js.map +1 -1
  29. package/dist/cjs/entities/Entity.d.ts +7 -2
  30. package/dist/cjs/entities/Entity.js +33 -3
  31. package/dist/cjs/entities/Entity.js.map +1 -1
  32. package/dist/cjs/entities/EntityStore.d.ts +2 -1
  33. package/dist/cjs/entities/EntityStore.js +50 -20
  34. package/dist/cjs/entities/EntityStore.js.map +1 -1
  35. package/dist/cjs/idb.d.ts +2 -0
  36. package/dist/cjs/idb.js +9 -1
  37. package/dist/cjs/idb.js.map +1 -1
  38. package/dist/cjs/index.d.ts +1 -1
  39. package/dist/cjs/index.js +2 -1
  40. package/dist/cjs/index.js.map +1 -1
  41. package/dist/cjs/metadata/BaselinesStore.js +15 -5
  42. package/dist/cjs/metadata/BaselinesStore.js.map +1 -1
  43. package/dist/cjs/metadata/openMetadataDatabase.d.ts +11 -2
  44. package/dist/cjs/metadata/openMetadataDatabase.js +56 -3
  45. package/dist/cjs/metadata/openMetadataDatabase.js.map +1 -1
  46. package/dist/cjs/migration/db.d.ts +1 -1
  47. package/dist/cjs/migration/db.js +5 -2
  48. package/dist/cjs/migration/db.js.map +1 -1
  49. package/dist/cjs/migration/openDatabase.d.ts +8 -0
  50. package/dist/cjs/migration/openDatabase.js +217 -165
  51. package/dist/cjs/migration/openDatabase.js.map +1 -1
  52. package/dist/cjs/queries/BaseQuery.js +12 -1
  53. package/dist/cjs/queries/BaseQuery.js.map +1 -1
  54. package/dist/cjs/sync/Sync.d.ts +6 -5
  55. package/dist/cjs/sync/Sync.js.map +1 -1
  56. package/dist/cjs/sync/WebSocketSync.js +4 -3
  57. package/dist/cjs/sync/WebSocketSync.js.map +1 -1
  58. package/dist/esm/{entities/FakeWeakRef.d.ts → FakeWeakRef.d.ts} +2 -2
  59. package/dist/esm/{entities/FakeWeakRef.js → FakeWeakRef.js} +2 -2
  60. package/dist/esm/FakeWeakRef.js.map +1 -0
  61. package/dist/esm/IDBService.d.ts +1 -1
  62. package/dist/esm/IDBService.js +18 -1
  63. package/dist/esm/IDBService.js.map +1 -1
  64. package/dist/esm/__tests__/documents.test.js +17 -0
  65. package/dist/esm/__tests__/documents.test.js.map +1 -1
  66. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
  67. package/dist/esm/__tests__/fixtures/testStorage.js +4 -3
  68. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  69. package/dist/esm/__tests__/mutations.test.d.ts +1 -0
  70. package/dist/esm/__tests__/mutations.test.js +40 -0
  71. package/dist/esm/__tests__/mutations.test.js.map +1 -0
  72. package/dist/esm/__tests__/queries.test.js +2 -0
  73. package/dist/esm/__tests__/queries.test.js.map +1 -1
  74. package/dist/esm/client/Client.d.ts +6 -4
  75. package/dist/esm/client/Client.js +25 -17
  76. package/dist/esm/client/Client.js.map +1 -1
  77. package/dist/esm/client/ClientDescriptor.d.ts +15 -4
  78. package/dist/esm/client/ClientDescriptor.js +121 -40
  79. package/dist/esm/client/ClientDescriptor.js.map +1 -1
  80. package/dist/esm/context.d.ts +1 -0
  81. package/dist/esm/entities/DocumentFamiliyCache.d.ts +22 -2
  82. package/dist/esm/entities/DocumentFamiliyCache.js +39 -21
  83. package/dist/esm/entities/DocumentFamiliyCache.js.map +1 -1
  84. package/dist/esm/entities/Entity.d.ts +7 -2
  85. package/dist/esm/entities/Entity.js +33 -3
  86. package/dist/esm/entities/Entity.js.map +1 -1
  87. package/dist/esm/entities/EntityStore.d.ts +2 -1
  88. package/dist/esm/entities/EntityStore.js +51 -21
  89. package/dist/esm/entities/EntityStore.js.map +1 -1
  90. package/dist/esm/idb.d.ts +2 -0
  91. package/dist/esm/idb.js +6 -0
  92. package/dist/esm/idb.js.map +1 -1
  93. package/dist/esm/index.d.ts +1 -1
  94. package/dist/esm/index.js +1 -1
  95. package/dist/esm/index.js.map +1 -1
  96. package/dist/esm/metadata/BaselinesStore.js +16 -6
  97. package/dist/esm/metadata/BaselinesStore.js.map +1 -1
  98. package/dist/esm/metadata/openMetadataDatabase.d.ts +11 -2
  99. package/dist/esm/metadata/openMetadataDatabase.js +54 -2
  100. package/dist/esm/metadata/openMetadataDatabase.js.map +1 -1
  101. package/dist/esm/migration/db.d.ts +1 -1
  102. package/dist/esm/migration/db.js +5 -2
  103. package/dist/esm/migration/db.js.map +1 -1
  104. package/dist/esm/migration/openDatabase.d.ts +8 -0
  105. package/dist/esm/migration/openDatabase.js +215 -164
  106. package/dist/esm/migration/openDatabase.js.map +1 -1
  107. package/dist/esm/queries/BaseQuery.js +12 -1
  108. package/dist/esm/queries/BaseQuery.js.map +1 -1
  109. package/dist/esm/sync/Sync.d.ts +6 -5
  110. package/dist/esm/sync/Sync.js.map +1 -1
  111. package/dist/esm/sync/WebSocketSync.js +4 -3
  112. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  113. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  114. package/dist/tsconfig.tsbuildinfo +1 -1
  115. package/package.json +9 -4
  116. package/src/{entities/FakeWeakRef.ts → FakeWeakRef.ts} +2 -2
  117. package/src/IDBService.ts +20 -2
  118. package/src/__tests__/documents.test.ts +19 -0
  119. package/src/__tests__/fixtures/testStorage.ts +4 -7
  120. package/src/__tests__/mutations.test.ts +51 -0
  121. package/src/__tests__/queries.test.ts +3 -0
  122. package/src/client/Client.ts +29 -21
  123. package/src/client/ClientDescriptor.ts +176 -53
  124. package/src/context.ts +1 -0
  125. package/src/entities/DocumentFamiliyCache.ts +66 -21
  126. package/src/entities/Entity.ts +41 -6
  127. package/src/entities/EntityStore.ts +68 -21
  128. package/src/idb.ts +10 -0
  129. package/src/index.ts +1 -0
  130. package/src/metadata/BaselinesStore.ts +17 -6
  131. package/src/metadata/openMetadataDatabase.ts +96 -13
  132. package/src/migration/db.ts +14 -1
  133. package/src/migration/openDatabase.ts +350 -219
  134. package/src/queries/BaseQuery.ts +14 -1
  135. package/src/sync/Sync.ts +13 -9
  136. package/src/sync/WebSocketSync.ts +1 -0
  137. package/dist/cjs/entities/FakeWeakRef.js.map +0 -1
  138. package/dist/cjs/indexes.d.ts +0 -3
  139. package/dist/cjs/indexes.js +0 -20
  140. package/dist/cjs/indexes.js.map +0 -1
  141. package/dist/esm/entities/FakeWeakRef.js.map +0 -1
  142. package/dist/esm/indexes.d.ts +0 -3
  143. package/dist/esm/indexes.js +0 -15
  144. package/dist/esm/indexes.js.map +0 -1
  145. package/src/indexes.ts +0 -31
@@ -9,6 +9,7 @@ import {
9
9
  DocumentBaseline,
10
10
  EventSubscriber,
11
11
  generateId,
12
+ getIndexValues,
12
13
  getOidRoot,
13
14
  getUndoOperations,
14
15
  groupBaselinesByRootOid,
@@ -18,6 +19,7 @@ import {
18
19
  Operation,
19
20
  removeOidsFromAllSubObjects,
20
21
  StorageCollectionSchema,
22
+ StorageObjectFieldSchema,
21
23
  } from '@verdant-web/common';
22
24
  import { Context } from '../context.js';
23
25
  import { FileManager } from '../files/FileManager.js';
@@ -98,24 +100,37 @@ export class EntityStore {
98
100
  this.context = context;
99
101
  };
100
102
 
101
- private getDocumentSchema = (oid: ObjectIdentifier) => {
103
+ private getDocumentSchema = (
104
+ oid: ObjectIdentifier,
105
+ ): { schema: StorageObjectFieldSchema | null; readonlyKeys: string[] } => {
102
106
  const { collection } = decomposeOid(oid);
103
107
  if (!this.schema.collections[collection]) {
104
108
  this.log('warn', `Missing schema for collection: ${collection}`);
105
- return null;
109
+ return { schema: null, readonlyKeys: [] };
106
110
  }
111
+ const schema = this.schema.collections[collection];
107
112
  return {
108
- type: 'object',
109
- properties: this.schema.collections[collection].fields as any,
110
- } as const;
113
+ readonlyKeys: [schema.primaryKey],
114
+ schema: {
115
+ type: 'object',
116
+ properties: schema.fields as any,
117
+ } as const,
118
+ };
111
119
  };
112
120
 
113
121
  private refreshFamilyCache = async (
114
122
  familyCache: DocumentFamilyCache,
115
123
  dropUnconfirmed = false,
124
+ dropAll = false,
116
125
  ) => {
117
126
  // avoid writing to disposed db
118
- if (this._disposed) return;
127
+ if (this._disposed) {
128
+ this.context.log(
129
+ 'debug',
130
+ `EntityStore is disposed, not refreshing ${familyCache.oid} cache`,
131
+ );
132
+ return;
133
+ }
119
134
 
120
135
  // metadata must be loaded from database to initialize family cache
121
136
  const transaction = this.meta.createTransaction([
@@ -146,21 +161,40 @@ export class EntityStore {
146
161
  { transaction, mode: 'readwrite' },
147
162
  ),
148
163
  ]);
149
- familyCache.reset(operations, baselines, dropUnconfirmed);
164
+ familyCache.reset({
165
+ operations,
166
+ baselines,
167
+ dropExistingUnconfirmed: dropUnconfirmed,
168
+ dropAll,
169
+ });
150
170
  };
151
171
 
152
172
  private openFamilyCache = async (oid: ObjectIdentifier) => {
153
173
  const documentOid = getOidRoot(oid);
154
174
  let familyCache = this.documentFamilyCaches.get(documentOid);
155
175
  if (!familyCache) {
176
+ this.context.log('debug', 'opening family cache for', documentOid);
156
177
  // metadata must be loaded from database to initialize family cache
157
178
  familyCache = new DocumentFamilyCache({
158
179
  oid: documentOid,
159
180
  store: this,
160
181
  context: this.context,
161
182
  });
183
+
184
+ // PROBLEM: because the next line is async, it yields to
185
+ // queued promises which may need data from this cache,
186
+ // but the cache is empty. But if we move the set to
187
+ // after the async, we can clobber an existing cache
188
+ // with race conditions...
189
+ // So as an attempt to fix that, I've added a promise
190
+ // on DocumentFamilyCache which I manually resolve
191
+ // with setInitialized, then await initializedPromise
192
+ // further down even if there was a cache hit.
193
+ // Surely there is a better pattern for this.
194
+ // FIXME:
162
195
  this.documentFamilyCaches.set(documentOid, familyCache);
163
196
  await this.refreshFamilyCache(familyCache);
197
+ familyCache.setInitialized();
164
198
 
165
199
  // this.unsubscribes.push(
166
200
  // familyCache.subscribe('change:*', this.onEntityChange),
@@ -168,6 +202,7 @@ export class EntityStore {
168
202
 
169
203
  // TODO: cleanup cache when all documents are disposed
170
204
  }
205
+ await familyCache.initializedPromise;
171
206
 
172
207
  return familyCache;
173
208
  };
@@ -192,10 +227,10 @@ export class EntityStore {
192
227
 
193
228
  const snapshot = entity?.getSnapshot();
194
229
  if (snapshot) {
195
- const stored = cloneDeep(snapshot);
196
- assignIndexValues(this.schema.collections[collection], stored);
197
- // IMPORTANT! this property must be assigned
198
- assignOidPropertiesToAllSubObjects(stored);
230
+ const stored = getIndexValues(
231
+ this.schema.collections[collection],
232
+ snapshot,
233
+ );
199
234
  try {
200
235
  const tx = this.db.transaction(collection, 'readwrite');
201
236
  const store = tx.objectStore(collection);
@@ -221,11 +256,11 @@ export class EntityStore {
221
256
 
222
257
  get = async (oid: ObjectIdentifier) => {
223
258
  const familyCache = await this.openFamilyCache(oid);
224
- const schema = this.getDocumentSchema(oid);
259
+ const { schema, readonlyKeys } = this.getDocumentSchema(oid);
225
260
  if (!schema) {
226
261
  return null;
227
262
  }
228
- return familyCache.getEntity(oid, schema);
263
+ return familyCache.getEntity(oid, schema, undefined, readonlyKeys);
229
264
  };
230
265
 
231
266
  /**
@@ -236,11 +271,11 @@ export class EntityStore {
236
271
  getCached = (oid: ObjectIdentifier) => {
237
272
  const cache = this.documentFamilyCaches.get(oid);
238
273
  if (cache) {
239
- const schema = this.getDocumentSchema(oid);
274
+ const { schema, readonlyKeys } = this.getDocumentSchema(oid);
240
275
  if (!schema) {
241
276
  return null;
242
277
  }
243
- return cache.getEntity(oid, schema);
278
+ return cache.getEntity(oid, schema, undefined, readonlyKeys);
244
279
  }
245
280
  return null;
246
281
  };
@@ -268,7 +303,7 @@ export class EntityStore {
268
303
  // only holding it in memory would introduce lag before it shows up
269
304
  // in other queries.
270
305
  await this.submitOperations(operations, options);
271
- const schema = this.getDocumentSchema(oid);
306
+ const { schema, readonlyKeys } = this.getDocumentSchema(oid);
272
307
  if (!schema) {
273
308
  throw new Error(
274
309
  `Cannot create a document in the ${
@@ -276,7 +311,7 @@ export class EntityStore {
276
311
  } collection; it is not defined in the current schema version.`,
277
312
  );
278
313
  }
279
- return familyCache.getEntity(oid, schema);
314
+ return familyCache.getEntity(oid, schema, undefined, readonlyKeys);
280
315
  };
281
316
 
282
317
  private addOperationsToOpenCaches = async (
@@ -384,6 +419,10 @@ export class EntityStore {
384
419
  baselines: DocumentBaseline[];
385
420
  reset?: boolean;
386
421
  }) => {
422
+ if (this._disposed) {
423
+ this.log('warn', 'EntityStore is disposed, not adding data');
424
+ return;
425
+ }
387
426
  // convert operations to tagged operations with confirmed = false
388
427
  // while we process and store them. this is in-place so as to
389
428
  // not allocate a bunch of objects...
@@ -427,7 +466,7 @@ export class EntityStore {
427
466
  await this.meta.insertRemoteOperations(operations);
428
467
 
429
468
  if (reset) {
430
- await this.refreshAllCaches(true);
469
+ await this.refreshAllCaches(true, true);
431
470
  }
432
471
 
433
472
  // recompute all affected documents for querying
@@ -511,6 +550,10 @@ export class EntityStore {
511
550
  await this.operationBatcher.flush(this.currentBatchKey);
512
551
  };
513
552
 
553
+ flushAllBatches = async () => {
554
+ await Promise.all(this.operationBatcher.flushAll());
555
+ };
556
+
514
557
  private flushOperations = async (
515
558
  operations: Operation[],
516
559
  batchKey: string,
@@ -632,7 +675,7 @@ export class EntityStore {
632
675
  // );
633
676
  };
634
677
 
635
- destroy = () => {
678
+ destroy = async () => {
636
679
  this._disposed = true;
637
680
  for (const unsubscribe of this.unsubscribes) {
638
681
  unsubscribe();
@@ -641,6 +684,7 @@ export class EntityStore {
641
684
  cache.dispose();
642
685
  }
643
686
  this.documentFamilyCaches.clear();
687
+ await this.flushAllBatches();
644
688
  };
645
689
 
646
690
  private handleRebase = (baselines: DocumentBaseline[]) => {
@@ -661,9 +705,12 @@ export class EntityStore {
661
705
  }
662
706
  };
663
707
 
664
- private refreshAllCaches = async (dropUnconfirmed = false) => {
708
+ private refreshAllCaches = async (
709
+ dropUnconfirmed = false,
710
+ dropAll = false,
711
+ ) => {
665
712
  for (const [_, cache] of this.documentFamilyCaches) {
666
- await this.refreshFamilyCache(cache, dropUnconfirmed);
713
+ await this.refreshFamilyCache(cache, dropUnconfirmed, dropAll);
667
714
  }
668
715
  };
669
716
  }
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/index.ts CHANGED
@@ -28,6 +28,7 @@ export {
28
28
  schema,
29
29
  createDefaultMigration,
30
30
  migrate,
31
+ createMigration,
31
32
  } from '@verdant-web/common';
32
33
  export type {
33
34
  StorageDocument,
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  DocumentBaseline,
3
- getOidRange,
3
+ getOidSubIdRange,
4
+ getOidRoot,
4
5
  ObjectIdentifier,
5
6
  } from '@verdant-web/common';
6
7
  import { IDBService } from '../IDBService.js';
@@ -38,8 +39,14 @@ export class BaselinesStore extends IDBService {
38
39
  return this.iterate(
39
40
  'baselines',
40
41
  (store) => {
41
- const [start, end] = getOidRange(oid);
42
- return store.openCursor(IDBKeyRange.bound(start, end, false, false));
42
+ const root = getOidRoot(oid);
43
+ const [start, end] = getOidSubIdRange(oid);
44
+ return [
45
+ // first the root itself
46
+ store.openCursor(IDBKeyRange.only(root)),
47
+ // then the range of its possible subdocuments
48
+ store.openCursor(IDBKeyRange.bound(start, end, false, false)),
49
+ ];
43
50
  },
44
51
  iterator,
45
52
  mode,
@@ -54,9 +61,13 @@ export class BaselinesStore extends IDBService {
54
61
  const result = await this.runAll<DocumentBaseline[]>(
55
62
  'baselines',
56
63
  (store) => {
57
- return docOids.map((oid) => {
58
- const [start, end] = getOidRange(oid);
59
- return store.getAll(IDBKeyRange.bound(start, end, false, false));
64
+ return docOids.flatMap((oid) => {
65
+ const root = getOidRoot(oid);
66
+ const [start, end] = getOidSubIdRange(oid);
67
+ return [
68
+ store.get(root),
69
+ store.getAll(IDBKeyRange.bound(start, end, false, false)),
70
+ ];
60
71
  });
61
72
  },
62
73
  mode,
@@ -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
- namespace: string,
5
- {
6
- indexedDB = window.indexedDB,
7
- databaseName,
8
- log,
9
- }: {
10
- indexedDB?: IDBFactory;
11
- databaseName: string;
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(databaseName, 4);
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',
@@ -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
- new Error('Migration error: database version changed while migrating'),
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) => {