@verdant-web/store 2.1.0 → 2.3.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/cjs/DocumentManager.js +4 -4
- package/dist/cjs/DocumentManager.js.map +1 -1
- package/dist/cjs/client/Client.js +4 -16
- package/dist/cjs/client/Client.js.map +1 -1
- package/dist/cjs/client/ClientDescriptor.d.ts +1 -0
- package/dist/cjs/client/ClientDescriptor.js +7 -3
- package/dist/cjs/client/ClientDescriptor.js.map +1 -1
- package/dist/cjs/files/utils.js +1 -1
- package/dist/cjs/files/utils.js.map +1 -1
- package/dist/cjs/idb.d.ts +1 -0
- package/dist/cjs/idb.js +17 -1
- package/dist/cjs/idb.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/migration/db.d.ts +8 -0
- package/dist/cjs/migration/db.js +109 -0
- package/dist/cjs/migration/db.js.map +1 -0
- package/dist/cjs/migration/errors.d.ts +5 -0
- package/dist/cjs/migration/errors.js +12 -0
- package/dist/cjs/migration/errors.js.map +1 -0
- package/dist/cjs/{openDocumentDatabase.d.ts → migration/openDatabase.d.ts} +2 -2
- package/dist/cjs/{openDocumentDatabase.js → migration/openDatabase.js} +52 -155
- package/dist/cjs/migration/openDatabase.js.map +1 -0
- package/dist/cjs/migration/paths.d.ts +6 -0
- package/dist/cjs/migration/paths.js +53 -0
- package/dist/cjs/migration/paths.js.map +1 -0
- package/dist/cjs/migration/paths.test.d.ts +1 -0
- package/dist/cjs/migration/paths.test.js +91 -0
- package/dist/cjs/migration/paths.test.js.map +1 -0
- package/dist/cjs/queries2/GetQuery.js +1 -1
- package/dist/cjs/queries2/GetQuery.js.map +1 -1
- package/dist/cjs/reactives/DocumentFamiliyCache.d.ts +5 -1
- package/dist/cjs/reactives/DocumentFamiliyCache.js +11 -7
- package/dist/cjs/reactives/DocumentFamiliyCache.js.map +1 -1
- package/dist/cjs/reactives/Entity.d.ts +0 -1
- package/dist/cjs/reactives/Entity.js +4 -6
- package/dist/cjs/reactives/Entity.js.map +1 -1
- package/dist/cjs/sync/Heartbeat.js +1 -1
- package/dist/cjs/sync/Heartbeat.js.map +1 -1
- package/dist/cjs/sync/WebSocketSync.js +6 -0
- package/dist/cjs/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/DocumentManager.js +4 -4
- package/dist/esm/DocumentManager.js.map +1 -1
- package/dist/esm/client/Client.js +3 -15
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +1 -0
- package/dist/esm/client/ClientDescriptor.js +6 -2
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/files/utils.js +1 -1
- package/dist/esm/files/utils.js.map +1 -1
- package/dist/esm/idb.d.ts +1 -0
- package/dist/esm/idb.js +15 -0
- package/dist/esm/idb.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/migration/db.d.ts +8 -0
- package/dist/esm/migration/db.js +101 -0
- package/dist/esm/migration/db.js.map +1 -0
- package/dist/esm/migration/errors.d.ts +5 -0
- package/dist/esm/migration/errors.js +8 -0
- package/dist/esm/migration/errors.js.map +1 -0
- package/dist/esm/{openDocumentDatabase.d.ts → migration/openDatabase.d.ts} +2 -2
- package/dist/esm/{openDocumentDatabase.js → migration/openDatabase.js} +44 -147
- package/dist/esm/migration/openDatabase.js.map +1 -0
- package/dist/esm/migration/paths.d.ts +6 -0
- package/dist/esm/migration/paths.js +49 -0
- package/dist/esm/migration/paths.js.map +1 -0
- package/dist/esm/migration/paths.test.d.ts +1 -0
- package/dist/esm/migration/paths.test.js +89 -0
- package/dist/esm/migration/paths.test.js.map +1 -0
- package/dist/esm/queries2/GetQuery.js +1 -1
- package/dist/esm/queries2/GetQuery.js.map +1 -1
- package/dist/esm/reactives/DocumentFamiliyCache.d.ts +5 -1
- package/dist/esm/reactives/DocumentFamiliyCache.js +11 -7
- package/dist/esm/reactives/DocumentFamiliyCache.js.map +1 -1
- package/dist/esm/reactives/Entity.d.ts +0 -1
- package/dist/esm/reactives/Entity.js +4 -6
- package/dist/esm/reactives/Entity.js.map +1 -1
- package/dist/esm/sync/Heartbeat.js +1 -1
- package/dist/esm/sync/Heartbeat.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +6 -0
- 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/DocumentManager.ts +4 -6
- package/src/client/Client.ts +7 -17
- package/src/client/ClientDescriptor.ts +7 -3
- package/src/files/utils.ts +1 -1
- package/src/idb.ts +19 -0
- package/src/index.ts +1 -0
- package/src/migration/db.ts +147 -0
- package/src/migration/errors.ts +7 -0
- package/src/{openDocumentDatabase.ts → migration/openDatabase.ts} +73 -206
- package/src/migration/paths.test.ts +93 -0
- package/src/migration/paths.ts +73 -0
- package/src/queries2/GetQuery.ts +1 -1
- package/src/reactives/DocumentFamiliyCache.ts +31 -9
- package/src/reactives/Entity.ts +2 -9
- package/src/sync/Heartbeat.ts +1 -1
- package/src/sync/WebSocketSync.ts +9 -0
- package/dist/cjs/openDocumentDatabase.js.map +0 -1
- package/dist/esm/openDocumentDatabase.js.map +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export async function getDatabaseVersion(
|
|
2
|
+
indexedDB: IDBFactory,
|
|
3
|
+
namespace: string,
|
|
4
|
+
version: number,
|
|
5
|
+
log?: (...args: any[]) => void,
|
|
6
|
+
): Promise<number> {
|
|
7
|
+
function openAndGetVersion(
|
|
8
|
+
resolve: (res: [number, IDBDatabase]) => void,
|
|
9
|
+
reject: (err: Error) => void,
|
|
10
|
+
) {
|
|
11
|
+
let currentVersion: number;
|
|
12
|
+
let database: IDBDatabase;
|
|
13
|
+
const request = indexedDB.open(
|
|
14
|
+
[namespace, 'collections'].join('_'),
|
|
15
|
+
version,
|
|
16
|
+
);
|
|
17
|
+
request.onupgradeneeded = async (event) => {
|
|
18
|
+
currentVersion = event.oldVersion;
|
|
19
|
+
const transaction = request.transaction!;
|
|
20
|
+
database = request.result;
|
|
21
|
+
transaction.abort();
|
|
22
|
+
};
|
|
23
|
+
request.onsuccess = (event) => {
|
|
24
|
+
resolve([request.result.version, request.result]);
|
|
25
|
+
};
|
|
26
|
+
request.onblocked = (event) => {
|
|
27
|
+
// retry if blocked
|
|
28
|
+
log?.('Database blocked, waiting...');
|
|
29
|
+
// setTimeout(() => {
|
|
30
|
+
// openAndGetVersion(resolve, reject);
|
|
31
|
+
// }, 200);
|
|
32
|
+
};
|
|
33
|
+
request.onerror = (event) => {
|
|
34
|
+
// FIXME: this fails if the code is older than the local database
|
|
35
|
+
resolve([currentVersion!, database!]);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const [currentVersion, db] = await new Promise<[number, IDBDatabase]>(
|
|
39
|
+
openAndGetVersion,
|
|
40
|
+
);
|
|
41
|
+
await closeDatabase(db);
|
|
42
|
+
return currentVersion;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function closeDatabase(db: IDBDatabase) {
|
|
46
|
+
db.close();
|
|
47
|
+
// FIXME: this isn't right!!!!
|
|
48
|
+
await new Promise<void>((resolve) => resolve());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Upgrades the database to the given version, using the given upgrader function.
|
|
53
|
+
*/
|
|
54
|
+
export async function upgradeDatabase(
|
|
55
|
+
indexedDb: IDBFactory,
|
|
56
|
+
namespace: string,
|
|
57
|
+
version: number,
|
|
58
|
+
upgrader: (
|
|
59
|
+
transaction: IDBTransaction,
|
|
60
|
+
db: IDBDatabase,
|
|
61
|
+
event: IDBVersionChangeEvent,
|
|
62
|
+
) => void,
|
|
63
|
+
log?: (...args: any[]) => void,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
function openAndUpgrade(resolve: () => void, reject: (err: Error) => void) {
|
|
66
|
+
const request = indexedDb.open(
|
|
67
|
+
[namespace, 'collections'].join('_'),
|
|
68
|
+
version,
|
|
69
|
+
);
|
|
70
|
+
let wasUpgraded = false;
|
|
71
|
+
request.onupgradeneeded = (event) => {
|
|
72
|
+
const transaction = request.transaction!;
|
|
73
|
+
upgrader(transaction, request.result, event);
|
|
74
|
+
wasUpgraded = true;
|
|
75
|
+
};
|
|
76
|
+
request.onsuccess = (event) => {
|
|
77
|
+
request.result.close();
|
|
78
|
+
if (wasUpgraded) {
|
|
79
|
+
resolve();
|
|
80
|
+
} else {
|
|
81
|
+
reject(
|
|
82
|
+
new Error(
|
|
83
|
+
'Database was not upgraded when a version change was expected',
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
request.onerror = (event) => {
|
|
89
|
+
reject(request.error || new Error('Unknown error'));
|
|
90
|
+
};
|
|
91
|
+
request.onblocked = (event) => {
|
|
92
|
+
log?.('Database upgrade blocked, waiting...');
|
|
93
|
+
// setTimeout(() => {
|
|
94
|
+
// openAndUpgrade(resolve, reject);
|
|
95
|
+
// }, 200);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return new Promise(openAndUpgrade);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function acquireLock(
|
|
102
|
+
namespace: string,
|
|
103
|
+
procedure: () => Promise<void>,
|
|
104
|
+
) {
|
|
105
|
+
if (typeof navigator !== 'undefined' && navigator.locks) {
|
|
106
|
+
await navigator.locks.request(`verdant_migration_${namespace}`, procedure);
|
|
107
|
+
} else {
|
|
108
|
+
// TODO: is there a fallback?
|
|
109
|
+
await procedure();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function openDatabase(
|
|
114
|
+
indexedDb: IDBFactory,
|
|
115
|
+
namespace: string,
|
|
116
|
+
version: number,
|
|
117
|
+
): Promise<IDBDatabase> {
|
|
118
|
+
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
|
119
|
+
const request = indexedDb.open(
|
|
120
|
+
[namespace, 'collections'].join('_'),
|
|
121
|
+
version,
|
|
122
|
+
);
|
|
123
|
+
request.onupgradeneeded = async (event) => {
|
|
124
|
+
const transaction = request.transaction!;
|
|
125
|
+
transaction.abort();
|
|
126
|
+
|
|
127
|
+
reject(
|
|
128
|
+
new Error('Migration error: database version changed while migrating'),
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
request.onsuccess = (event) => {
|
|
132
|
+
resolve(request.result);
|
|
133
|
+
};
|
|
134
|
+
request.onblocked = (event) => {
|
|
135
|
+
reject(new Error('Migration error: database blocked'));
|
|
136
|
+
};
|
|
137
|
+
request.onerror = (event) => {
|
|
138
|
+
reject(new Error('Migration error: database error'));
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
db.addEventListener('versionchange', (event) => {
|
|
143
|
+
db.close();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return db;
|
|
147
|
+
}
|
|
@@ -5,21 +5,33 @@ import {
|
|
|
5
5
|
ObjectIdentifier,
|
|
6
6
|
StorageSchema,
|
|
7
7
|
addFieldDefaults,
|
|
8
|
+
assert,
|
|
8
9
|
assignIndexValues,
|
|
10
|
+
assignOid,
|
|
9
11
|
assignOidPropertiesToAllSubObjects,
|
|
10
12
|
assignOidsToAllSubObjects,
|
|
11
13
|
cloneDeep,
|
|
12
14
|
createOid,
|
|
13
15
|
decomposeOid,
|
|
14
16
|
diffToPatches,
|
|
17
|
+
getOid,
|
|
18
|
+
hasOid,
|
|
15
19
|
initialToPatches,
|
|
16
20
|
migrationRange,
|
|
17
21
|
removeOidPropertiesFromAllSubObjects,
|
|
18
22
|
removeOidsFromAllSubObjects,
|
|
19
23
|
} from '@verdant-web/common';
|
|
20
|
-
import { Context } from '
|
|
21
|
-
import { Metadata } from '
|
|
22
|
-
import { findAllOids, findOneOid } from '
|
|
24
|
+
import { Context } from '../context.js';
|
|
25
|
+
import { Metadata } from '../metadata/Metadata.js';
|
|
26
|
+
import { findAllOids, findOneOid } from '../queries2/dbQueries.js';
|
|
27
|
+
import {
|
|
28
|
+
acquireLock,
|
|
29
|
+
closeDatabase,
|
|
30
|
+
getDatabaseVersion,
|
|
31
|
+
openDatabase,
|
|
32
|
+
upgradeDatabase,
|
|
33
|
+
} from './db.js';
|
|
34
|
+
import { getMigrationPath } from './paths.js';
|
|
23
35
|
|
|
24
36
|
const globalIDB =
|
|
25
37
|
typeof window !== 'undefined' ? window.indexedDB : (undefined as any);
|
|
@@ -53,13 +65,11 @@ export async function openDocumentDatabase({
|
|
|
53
65
|
version,
|
|
54
66
|
);
|
|
55
67
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
throw new Error(`No migration found for version(s) ${toRunVersions}`);
|
|
62
|
-
}
|
|
68
|
+
const toRun = getMigrationPath({
|
|
69
|
+
currentVersion,
|
|
70
|
+
targetVersion: version,
|
|
71
|
+
migrations,
|
|
72
|
+
});
|
|
63
73
|
|
|
64
74
|
if (toRun.length > 0) {
|
|
65
75
|
await acquireLock(context.namespace, async () => {
|
|
@@ -153,7 +163,7 @@ export async function openDocumentDatabase({
|
|
|
153
163
|
const readStore = documentReadTransaction.objectStore(collection);
|
|
154
164
|
const keys = await getAllKeys(readStore);
|
|
155
165
|
// map the keys to OIDs
|
|
156
|
-
const oids = keys.map((key) => createOid(collection, `${key}
|
|
166
|
+
const oids = keys.map((key) => createOid(collection, `${key}`));
|
|
157
167
|
oids.push(
|
|
158
168
|
...engine.newOids.filter((oid) => {
|
|
159
169
|
return decomposeOid(oid).collection === collection;
|
|
@@ -240,86 +250,6 @@ export async function openDocumentDatabase({
|
|
|
240
250
|
}
|
|
241
251
|
}
|
|
242
252
|
|
|
243
|
-
async function getDatabaseVersion(
|
|
244
|
-
indexedDB: IDBFactory,
|
|
245
|
-
namespace: string,
|
|
246
|
-
version: number,
|
|
247
|
-
log?: (...args: any[]) => void,
|
|
248
|
-
): Promise<number> {
|
|
249
|
-
function openAndGetVersion(
|
|
250
|
-
resolve: (res: [number, IDBDatabase]) => void,
|
|
251
|
-
reject: (err: Error) => void,
|
|
252
|
-
) {
|
|
253
|
-
let currentVersion: number;
|
|
254
|
-
let database: IDBDatabase;
|
|
255
|
-
const request = indexedDB.open(
|
|
256
|
-
[namespace, 'collections'].join('_'),
|
|
257
|
-
version,
|
|
258
|
-
);
|
|
259
|
-
request.onupgradeneeded = async (event) => {
|
|
260
|
-
currentVersion = event.oldVersion;
|
|
261
|
-
const transaction = request.transaction!;
|
|
262
|
-
database = request.result;
|
|
263
|
-
transaction.abort();
|
|
264
|
-
};
|
|
265
|
-
request.onsuccess = (event) => {
|
|
266
|
-
resolve([request.result.version, request.result]);
|
|
267
|
-
};
|
|
268
|
-
request.onblocked = (event) => {
|
|
269
|
-
// retry if blocked
|
|
270
|
-
log?.('Database blocked, waiting...');
|
|
271
|
-
// setTimeout(() => {
|
|
272
|
-
// openAndGetVersion(resolve, reject);
|
|
273
|
-
// }, 200);
|
|
274
|
-
};
|
|
275
|
-
request.onerror = (event) => {
|
|
276
|
-
// FIXME: this fails if the code is older than the local database
|
|
277
|
-
resolve([currentVersion!, database!]);
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
const [currentVersion, db] = await new Promise<[number, IDBDatabase]>(
|
|
281
|
-
openAndGetVersion,
|
|
282
|
-
);
|
|
283
|
-
await closeDatabase(db);
|
|
284
|
-
return currentVersion;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async function openDatabase(
|
|
288
|
-
indexedDb: IDBFactory,
|
|
289
|
-
namespace: string,
|
|
290
|
-
version: number,
|
|
291
|
-
): Promise<IDBDatabase> {
|
|
292
|
-
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
|
293
|
-
const request = indexedDb.open(
|
|
294
|
-
[namespace, 'collections'].join('_'),
|
|
295
|
-
version,
|
|
296
|
-
);
|
|
297
|
-
request.onupgradeneeded = async (event) => {
|
|
298
|
-
const transaction = request.transaction!;
|
|
299
|
-
transaction.abort();
|
|
300
|
-
|
|
301
|
-
reject(
|
|
302
|
-
new Error('Migration error: database version changed while migrating'),
|
|
303
|
-
);
|
|
304
|
-
};
|
|
305
|
-
request.onsuccess = (event) => {
|
|
306
|
-
resolve(request.result);
|
|
307
|
-
};
|
|
308
|
-
request.onblocked = (event) => {
|
|
309
|
-
reject(new Error('Migration error: database blocked'));
|
|
310
|
-
};
|
|
311
|
-
request.onerror = (event) => {
|
|
312
|
-
reject(new Error('Migration error: database error'));
|
|
313
|
-
};
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
db.addEventListener('versionchange', (event) => {
|
|
317
|
-
db.close();
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
return db;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
253
|
function getMigrationMutations({
|
|
324
254
|
migration,
|
|
325
255
|
meta,
|
|
@@ -338,7 +268,7 @@ function getMigrationMutations({
|
|
|
338
268
|
addFieldDefaults(migration.newSchema.collections[collectionName], doc);
|
|
339
269
|
const primaryKey =
|
|
340
270
|
doc[migration.newSchema.collections[collectionName].primaryKey];
|
|
341
|
-
const oid = createOid(collectionName, primaryKey
|
|
271
|
+
const oid = createOid(collectionName, primaryKey);
|
|
342
272
|
newOids.push(oid);
|
|
343
273
|
await meta.insertLocalOperation(
|
|
344
274
|
initialToPatches(doc, oid, getMigrationNow),
|
|
@@ -346,7 +276,7 @@ function getMigrationMutations({
|
|
|
346
276
|
return doc;
|
|
347
277
|
},
|
|
348
278
|
delete: (id: string) => {
|
|
349
|
-
const oid = createOid(collectionName, id
|
|
279
|
+
const oid = createOid(collectionName, id);
|
|
350
280
|
return meta.insertLocalOperation([
|
|
351
281
|
{
|
|
352
282
|
oid,
|
|
@@ -360,28 +290,21 @@ function getMigrationMutations({
|
|
|
360
290
|
}, {} as any);
|
|
361
291
|
}
|
|
362
292
|
|
|
363
|
-
function
|
|
364
|
-
meta,
|
|
293
|
+
function getMigrationQueries({
|
|
365
294
|
migration,
|
|
366
295
|
context,
|
|
296
|
+
meta,
|
|
367
297
|
}: {
|
|
368
|
-
|
|
369
|
-
migration: Migration;
|
|
370
|
-
meta: Metadata;
|
|
298
|
+
migration: Migration<any>;
|
|
371
299
|
context: Context;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const newOids = new Array<ObjectIdentifier>();
|
|
378
|
-
|
|
379
|
-
const queries = migration.oldCollections.reduce((acc, collectionName) => {
|
|
300
|
+
meta: Metadata;
|
|
301
|
+
}) {
|
|
302
|
+
return migration.oldCollections.reduce((acc, collectionName) => {
|
|
380
303
|
acc[collectionName] = {
|
|
381
304
|
get: async (id: string) => {
|
|
382
|
-
const oid = createOid(collectionName, id
|
|
305
|
+
const oid = createOid(collectionName, id);
|
|
383
306
|
const doc = await meta.getDocumentSnapshot(oid);
|
|
384
|
-
removeOidsFromAllSubObjects(doc);
|
|
307
|
+
// removeOidsFromAllSubObjects(doc);
|
|
385
308
|
return doc;
|
|
386
309
|
},
|
|
387
310
|
findOne: async (filter: CollectionFilter) => {
|
|
@@ -392,7 +315,7 @@ function getMigrationEngine({
|
|
|
392
315
|
});
|
|
393
316
|
if (!oid) return null;
|
|
394
317
|
const doc = await meta.getDocumentSnapshot(oid);
|
|
395
|
-
removeOidsFromAllSubObjects(doc);
|
|
318
|
+
// removeOidsFromAllSubObjects(doc);
|
|
396
319
|
return doc;
|
|
397
320
|
},
|
|
398
321
|
findAll: async (filter: CollectionFilter) => {
|
|
@@ -404,12 +327,35 @@ function getMigrationEngine({
|
|
|
404
327
|
const docs = await Promise.all(
|
|
405
328
|
oids.map((oid) => meta.getDocumentSnapshot(oid)),
|
|
406
329
|
);
|
|
407
|
-
docs.forEach((doc) => removeOidsFromAllSubObjects(doc));
|
|
330
|
+
// docs.forEach((doc) => removeOidsFromAllSubObjects(doc));
|
|
408
331
|
return docs;
|
|
409
332
|
},
|
|
410
333
|
};
|
|
411
334
|
return acc;
|
|
412
335
|
}, {} as any);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function getMigrationEngine({
|
|
339
|
+
meta,
|
|
340
|
+
migration,
|
|
341
|
+
context,
|
|
342
|
+
}: {
|
|
343
|
+
log?: (...args: any[]) => void;
|
|
344
|
+
migration: Migration;
|
|
345
|
+
meta: Metadata;
|
|
346
|
+
context: Context;
|
|
347
|
+
}): MigrationEngine<any, any> {
|
|
348
|
+
function getMigrationNow() {
|
|
349
|
+
return meta.time.zero(migration.version);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const newOids = new Array<ObjectIdentifier>();
|
|
353
|
+
|
|
354
|
+
const queries = getMigrationQueries({
|
|
355
|
+
migration,
|
|
356
|
+
context,
|
|
357
|
+
meta,
|
|
358
|
+
});
|
|
413
359
|
const mutations = getMigrationMutations({
|
|
414
360
|
migration,
|
|
415
361
|
getMigrationNow,
|
|
@@ -418,45 +364,27 @@ function getMigrationEngine({
|
|
|
418
364
|
});
|
|
419
365
|
const awaitables = new Array<Promise<any>>();
|
|
420
366
|
const engine: MigrationEngine<StorageSchema, StorageSchema> = {
|
|
367
|
+
log: context.log,
|
|
421
368
|
newOids,
|
|
422
369
|
migrate: async (collection, strategy) => {
|
|
423
|
-
const docs = await
|
|
424
|
-
const transaction = context.documentDb.transaction(
|
|
425
|
-
collection,
|
|
426
|
-
'readonly',
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
const store = transaction.objectStore(collection);
|
|
430
|
-
const cursorReq = store.openCursor();
|
|
431
|
-
|
|
432
|
-
const documentsToMigrate: any[] = [];
|
|
433
|
-
|
|
434
|
-
cursorReq.onsuccess = async (event) => {
|
|
435
|
-
const cursor = cursorReq.result;
|
|
436
|
-
if (cursor) {
|
|
437
|
-
documentsToMigrate.push(cursor.value);
|
|
438
|
-
cursor.continue();
|
|
439
|
-
} else {
|
|
440
|
-
resolve(documentsToMigrate);
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
cursorReq.onerror = (event) => {
|
|
444
|
-
reject(cursorReq.error);
|
|
445
|
-
};
|
|
446
|
-
});
|
|
370
|
+
const docs = await queries[collection].findAll();
|
|
447
371
|
|
|
448
372
|
await Promise.all(
|
|
449
|
-
docs.map(async (doc) => {
|
|
373
|
+
docs.map(async (doc: any) => {
|
|
374
|
+
assert(
|
|
375
|
+
hasOid(doc),
|
|
376
|
+
`Document is missing an OID: ${JSON.stringify(doc)}`,
|
|
377
|
+
);
|
|
450
378
|
const original = cloneDeep(doc);
|
|
451
379
|
// remove any indexes before computing the diff
|
|
452
|
-
const collectionSpec = migration.oldSchema.collections[collection];
|
|
453
|
-
const indexKeys = [
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
];
|
|
457
|
-
indexKeys.forEach((key) => {
|
|
458
|
-
|
|
459
|
-
});
|
|
380
|
+
// const collectionSpec = migration.oldSchema.collections[collection];
|
|
381
|
+
// const indexKeys = [
|
|
382
|
+
// ...Object.keys(collectionSpec.synthetics || {}),
|
|
383
|
+
// ...Object.keys(collectionSpec.compounds || {}),
|
|
384
|
+
// ];
|
|
385
|
+
// indexKeys.forEach((key) => {
|
|
386
|
+
// delete doc[key];
|
|
387
|
+
// });
|
|
460
388
|
// @ts-ignore - excessive type resolution
|
|
461
389
|
const newValue = await strategy(doc);
|
|
462
390
|
if (newValue) {
|
|
@@ -470,7 +398,6 @@ function getMigrationEngine({
|
|
|
470
398
|
original,
|
|
471
399
|
newValue,
|
|
472
400
|
getMigrationNow,
|
|
473
|
-
[],
|
|
474
401
|
undefined,
|
|
475
402
|
[],
|
|
476
403
|
{
|
|
@@ -494,6 +421,7 @@ function getMigrationEngine({
|
|
|
494
421
|
function getVersion1MigrationEngine({
|
|
495
422
|
meta,
|
|
496
423
|
migration,
|
|
424
|
+
context,
|
|
497
425
|
}: {
|
|
498
426
|
context: OpenDocumentDbContext;
|
|
499
427
|
migration: Migration;
|
|
@@ -520,6 +448,7 @@ function getVersion1MigrationEngine({
|
|
|
520
448
|
meta,
|
|
521
449
|
});
|
|
522
450
|
const engine: MigrationEngine<StorageSchema, StorageSchema> = {
|
|
451
|
+
log: context.log,
|
|
523
452
|
newOids,
|
|
524
453
|
migrate: (collection, strategy) => {
|
|
525
454
|
throw new Error(
|
|
@@ -533,59 +462,6 @@ function getVersion1MigrationEngine({
|
|
|
533
462
|
return engine;
|
|
534
463
|
}
|
|
535
464
|
|
|
536
|
-
async function closeDatabase(db: IDBDatabase) {
|
|
537
|
-
db.close();
|
|
538
|
-
// FIXME: this isn't right!!!!
|
|
539
|
-
await new Promise<void>((resolve) => resolve());
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
async function upgradeDatabase(
|
|
543
|
-
indexedDb: IDBFactory,
|
|
544
|
-
namespace: string,
|
|
545
|
-
version: number,
|
|
546
|
-
upgrader: (
|
|
547
|
-
transaction: IDBTransaction,
|
|
548
|
-
db: IDBDatabase,
|
|
549
|
-
event: IDBVersionChangeEvent,
|
|
550
|
-
) => void,
|
|
551
|
-
log?: (...args: any[]) => void,
|
|
552
|
-
): Promise<void> {
|
|
553
|
-
function openAndUpgrade(resolve: () => void, reject: (err: Error) => void) {
|
|
554
|
-
const request = indexedDb.open(
|
|
555
|
-
[namespace, 'collections'].join('_'),
|
|
556
|
-
version,
|
|
557
|
-
);
|
|
558
|
-
let wasUpgraded = false;
|
|
559
|
-
request.onupgradeneeded = (event) => {
|
|
560
|
-
const transaction = request.transaction!;
|
|
561
|
-
upgrader(transaction, request.result, event);
|
|
562
|
-
wasUpgraded = true;
|
|
563
|
-
};
|
|
564
|
-
request.onsuccess = (event) => {
|
|
565
|
-
request.result.close();
|
|
566
|
-
if (wasUpgraded) {
|
|
567
|
-
resolve();
|
|
568
|
-
} else {
|
|
569
|
-
reject(
|
|
570
|
-
new Error(
|
|
571
|
-
'Database was not upgraded when a version change was expected',
|
|
572
|
-
),
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
request.onerror = (event) => {
|
|
577
|
-
reject(request.error || new Error('Unknown error'));
|
|
578
|
-
};
|
|
579
|
-
request.onblocked = (event) => {
|
|
580
|
-
log?.('Database upgrade blocked, waiting...');
|
|
581
|
-
// setTimeout(() => {
|
|
582
|
-
// openAndUpgrade(resolve, reject);
|
|
583
|
-
// }, 200);
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
return new Promise(openAndUpgrade);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
465
|
async function getAllKeys(store: IDBObjectStore) {
|
|
590
466
|
return new Promise<IDBValidKey[]>((resolve, reject) => {
|
|
591
467
|
const request = store.getAllKeys();
|
|
@@ -621,12 +497,3 @@ async function putView(store: IDBObjectStore, view: any) {
|
|
|
621
497
|
};
|
|
622
498
|
});
|
|
623
499
|
}
|
|
624
|
-
|
|
625
|
-
async function acquireLock(namespace: string, procedure: () => Promise<void>) {
|
|
626
|
-
if (typeof navigator !== 'undefined' && navigator.locks) {
|
|
627
|
-
await navigator.locks.request(`lo-fi_migration_${namespace}`, procedure);
|
|
628
|
-
} else {
|
|
629
|
-
// TODO: is there a fallback?
|
|
630
|
-
await procedure();
|
|
631
|
-
}
|
|
632
|
-
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Migration } from '@verdant-web/common';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { getMigrationPath } from './paths.js';
|
|
4
|
+
|
|
5
|
+
function mockMigration(from: number, to: number) {
|
|
6
|
+
return {
|
|
7
|
+
oldSchema: {
|
|
8
|
+
version: from,
|
|
9
|
+
},
|
|
10
|
+
newSchema: {
|
|
11
|
+
version: to,
|
|
12
|
+
},
|
|
13
|
+
} as any as Migration;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('migration pathfinding', () => {
|
|
17
|
+
it('should follow a linear chain', () => {
|
|
18
|
+
const migrations = [
|
|
19
|
+
mockMigration(0, 1),
|
|
20
|
+
mockMigration(1, 2),
|
|
21
|
+
mockMigration(2, 3),
|
|
22
|
+
mockMigration(3, 4),
|
|
23
|
+
mockMigration(4, 5),
|
|
24
|
+
];
|
|
25
|
+
const path = getMigrationPath({
|
|
26
|
+
currentVersion: 0,
|
|
27
|
+
targetVersion: 5,
|
|
28
|
+
migrations,
|
|
29
|
+
});
|
|
30
|
+
expect(path).toEqual(migrations);
|
|
31
|
+
});
|
|
32
|
+
it('should choose the shortest path', () => {
|
|
33
|
+
const migrations = [
|
|
34
|
+
mockMigration(0, 1),
|
|
35
|
+
mockMigration(1, 2),
|
|
36
|
+
mockMigration(2, 3),
|
|
37
|
+
mockMigration(3, 4),
|
|
38
|
+
mockMigration(4, 5),
|
|
39
|
+
mockMigration(0, 5),
|
|
40
|
+
];
|
|
41
|
+
const path = getMigrationPath({
|
|
42
|
+
currentVersion: 0,
|
|
43
|
+
targetVersion: 5,
|
|
44
|
+
migrations,
|
|
45
|
+
});
|
|
46
|
+
expect(path).toEqual([mockMigration(0, 5)]);
|
|
47
|
+
});
|
|
48
|
+
it('should be resilient to dead ends', () => {
|
|
49
|
+
const migrations = [
|
|
50
|
+
mockMigration(0, 1),
|
|
51
|
+
mockMigration(1, 2),
|
|
52
|
+
mockMigration(4, 5),
|
|
53
|
+
mockMigration(5, 6),
|
|
54
|
+
mockMigration(1, 4),
|
|
55
|
+
];
|
|
56
|
+
const path = getMigrationPath({
|
|
57
|
+
currentVersion: 0,
|
|
58
|
+
targetVersion: 6,
|
|
59
|
+
migrations,
|
|
60
|
+
});
|
|
61
|
+
expect(path).toEqual([
|
|
62
|
+
mockMigration(0, 1),
|
|
63
|
+
mockMigration(1, 4),
|
|
64
|
+
mockMigration(4, 5),
|
|
65
|
+
mockMigration(5, 6),
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
it('should error when no path exists to target', () => {
|
|
69
|
+
const migrations = [
|
|
70
|
+
mockMigration(0, 1),
|
|
71
|
+
mockMigration(2, 3),
|
|
72
|
+
mockMigration(3, 4),
|
|
73
|
+
mockMigration(4, 5),
|
|
74
|
+
mockMigration(5, 6),
|
|
75
|
+
];
|
|
76
|
+
expect(() => {
|
|
77
|
+
getMigrationPath({
|
|
78
|
+
currentVersion: 0,
|
|
79
|
+
targetVersion: 6,
|
|
80
|
+
migrations,
|
|
81
|
+
});
|
|
82
|
+
}).toThrow();
|
|
83
|
+
});
|
|
84
|
+
it('should return empty when versions are the same', () => {
|
|
85
|
+
expect(
|
|
86
|
+
getMigrationPath({
|
|
87
|
+
currentVersion: 1,
|
|
88
|
+
targetVersion: 1,
|
|
89
|
+
migrations: [],
|
|
90
|
+
}),
|
|
91
|
+
).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
});
|