@verdant-web/store 4.6.1 → 5.0.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 +14 -12
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/__tests__/queries.test.js +3 -3
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/__tests__/schema.test.js +3 -3
- package/dist/esm/__tests__/schema.test.js.map +1 -1
- package/dist/esm/client/Client.d.ts +12 -10
- package/dist/esm/client/Client.js +40 -30
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/context/Time.d.ts +1 -1
- package/dist/esm/context/Time.js +1 -1
- package/dist/esm/context/Time.js.map +1 -1
- package/dist/esm/context/context.d.ts +84 -15
- package/dist/esm/context/context.js +98 -1
- package/dist/esm/context/context.js.map +1 -1
- package/dist/esm/entities/Entity.test.js +0 -1
- package/dist/esm/entities/Entity.test.js.map +1 -1
- package/dist/esm/entities/EntityMetadata.js +11 -5
- package/dist/esm/entities/EntityMetadata.js.map +1 -1
- package/dist/esm/entities/EntityStore.js +9 -7
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/files/FileManager.js +5 -5
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/index.d.ts +6 -4
- package/dist/esm/index.js +2 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.d.ts +3 -4
- package/dist/esm/internal.js +1 -2
- package/dist/esm/internal.js.map +1 -1
- package/dist/esm/persistence/PersistenceMetadata.d.ts +3 -6
- package/dist/esm/persistence/PersistenceMetadata.js +5 -6
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
- package/dist/esm/persistence/idb/IdbService.d.ts +3 -3
- package/dist/esm/persistence/idb/IdbService.js +0 -1
- package/dist/esm/persistence/idb/IdbService.js.map +1 -1
- package/dist/esm/persistence/idb/idbPersistence.d.ts +9 -10
- package/dist/esm/persistence/idb/idbPersistence.js +11 -4
- package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +2 -2
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.d.ts +3 -2
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js +16 -15
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -1
- package/dist/esm/persistence/idb/queries/migration/db.js +7 -0
- package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
- package/dist/esm/persistence/idb/util.js +27 -17
- package/dist/esm/persistence/idb/util.js.map +1 -1
- package/dist/esm/persistence/interfaces.d.ts +8 -8
- package/dist/esm/persistence/migration/engine.d.ts +5 -3
- package/dist/esm/persistence/migration/engine.js +23 -14
- package/dist/esm/persistence/migration/engine.js.map +1 -1
- package/dist/esm/persistence/migration/finalize.d.ts +5 -3
- package/dist/esm/persistence/migration/finalize.js +5 -4
- package/dist/esm/persistence/migration/finalize.js.map +1 -1
- package/dist/esm/persistence/migration/migrate.d.ts +8 -5
- package/dist/esm/persistence/migration/migrate.js +10 -4
- package/dist/esm/persistence/migration/migrate.js.map +1 -1
- package/dist/esm/persistence/persistence.d.ts +9 -2
- package/dist/esm/persistence/persistence.js +65 -32
- 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/PresenceManager.d.ts +2 -2
- package/dist/esm/sync/PresenceManager.js +5 -2
- package/dist/esm/sync/PresenceManager.js.map +1 -1
- package/dist/esm/sync/PushPullSync.js +4 -4
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +2 -2
- package/dist/esm/sync/Sync.js +17 -14
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +12 -7
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/sync/serviceWorker.d.ts +2 -2
- package/dist/esm/sync/serviceWorker.js +3 -4
- package/dist/esm/sync/serviceWorker.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/fixtures/testStorage.ts +4 -8
- package/src/__tests__/queries.test.ts +3 -5
- package/src/__tests__/schema.test.ts +3 -3
- package/src/client/Client.ts +50 -34
- package/src/context/Time.ts +2 -2
- package/src/context/context.ts +189 -17
- package/src/entities/Entity.test.ts +0 -1
- package/src/entities/EntityMetadata.ts +16 -5
- package/src/entities/EntityStore.ts +14 -10
- package/src/files/FileManager.ts +5 -5
- package/src/index.ts +6 -10
- package/src/internal.ts +10 -11
- package/src/persistence/PersistenceMetadata.ts +9 -11
- package/src/persistence/idb/IdbService.ts +2 -3
- package/src/persistence/idb/idbPersistence.ts +25 -19
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +3 -3
- package/src/persistence/idb/queries/IdbDocumentDb.ts +31 -17
- package/src/persistence/idb/queries/migration/db.ts +10 -0
- package/src/persistence/idb/util.ts +46 -24
- package/src/persistence/interfaces.ts +21 -12
- package/src/persistence/migration/engine.ts +33 -18
- package/src/persistence/migration/finalize.ts +11 -5
- package/src/persistence/migration/migrate.ts +15 -8
- package/src/persistence/persistence.ts +78 -67
- package/src/queries/FindAllQuery.ts +3 -1
- package/src/queries/FindInfiniteQuery.ts +6 -2
- package/src/queries/FindOneQuery.ts +3 -1
- package/src/queries/FindPageQuery.ts +3 -1
- package/src/sync/PresenceManager.ts +10 -7
- package/src/sync/PushPullSync.ts +8 -6
- package/src/sync/Sync.ts +26 -16
- package/src/sync/WebSocketSync.ts +25 -9
- package/src/sync/serviceWorker.ts +4 -6
- package/dist/esm/client/ClientDescriptor.d.ts +0 -87
- package/dist/esm/client/ClientDescriptor.js +0 -133
- package/dist/esm/client/ClientDescriptor.js.map +0 -1
- package/dist/esm/persistence/migration/types.d.ts +0 -3
- package/dist/esm/persistence/migration/types.js +0 -2
- package/dist/esm/persistence/migration/types.js.map +0 -1
- package/src/client/ClientDescriptor.ts +0 -246
- package/src/persistence/migration/types.ts +0 -4
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getOidSubIdRange,
|
|
9
9
|
ObjectIdentifier,
|
|
10
10
|
} from '@verdant-web/common';
|
|
11
|
-
import {
|
|
11
|
+
import { ContextWithoutPersistence } from '../../../context/context.js';
|
|
12
12
|
import {
|
|
13
13
|
AbstractTransaction,
|
|
14
14
|
AckInfo,
|
|
@@ -37,10 +37,10 @@ export class IdbMetadataDb
|
|
|
37
37
|
extends IdbService
|
|
38
38
|
implements PersistenceMetadataDb<IDBTransaction>
|
|
39
39
|
{
|
|
40
|
-
constructor(db: IDBDatabase, ctx:
|
|
40
|
+
constructor(db: IDBDatabase, ctx: ContextWithoutPersistence) {
|
|
41
41
|
super(db, ctx);
|
|
42
42
|
this.addDispose(() => {
|
|
43
|
-
this.ctx.log('info', `Closing metadata DB for`,
|
|
43
|
+
this.ctx.log('info', `Closing metadata DB for`, ctx.namespace);
|
|
44
44
|
return closeDatabase(db);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
getIndexValues,
|
|
6
6
|
ObjectIdentifier,
|
|
7
7
|
} from '@verdant-web/common';
|
|
8
|
-
import {
|
|
8
|
+
import { ContextWithoutPersistence } from '../../../context/context.js';
|
|
9
9
|
import { PersistenceDocumentDb } from '../../interfaces.js';
|
|
10
10
|
import { IdbService } from '../IdbService.js';
|
|
11
11
|
import {
|
|
@@ -17,10 +17,17 @@ import {
|
|
|
17
17
|
import { getRange } from './ranges.js';
|
|
18
18
|
|
|
19
19
|
export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
20
|
-
constructor(
|
|
20
|
+
constructor(
|
|
21
|
+
db: IDBDatabase,
|
|
22
|
+
private context: ContextWithoutPersistence,
|
|
23
|
+
) {
|
|
21
24
|
super(db, context);
|
|
22
25
|
this.addDispose(() => {
|
|
23
|
-
this.
|
|
26
|
+
this.context.log(
|
|
27
|
+
'info',
|
|
28
|
+
'Closing document database for',
|
|
29
|
+
context.namespace,
|
|
30
|
+
);
|
|
24
31
|
return closeDatabase(this.db);
|
|
25
32
|
});
|
|
26
33
|
}
|
|
@@ -32,7 +39,7 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
32
39
|
stats = async (): Promise<
|
|
33
40
|
Record<string, { count: number; size: number }>
|
|
34
41
|
> => {
|
|
35
|
-
const collectionNames = Object.keys(this.
|
|
42
|
+
const collectionNames = Object.keys(this.context.schema.collections);
|
|
36
43
|
const collections: Record<string, { count: number; size: number }> = {};
|
|
37
44
|
await Promise.all(
|
|
38
45
|
collectionNames.map(async (name) => {
|
|
@@ -54,7 +61,11 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
54
61
|
? store.index(opts.index.where)
|
|
55
62
|
: store;
|
|
56
63
|
const direction = opts.index?.order === 'desc' ? 'prev' : 'next';
|
|
57
|
-
const range = getRange(
|
|
64
|
+
const range = getRange(
|
|
65
|
+
this.context.schema,
|
|
66
|
+
opts.collection,
|
|
67
|
+
opts.index,
|
|
68
|
+
);
|
|
58
69
|
return source.openCursor(range, direction);
|
|
59
70
|
},
|
|
60
71
|
{ mode: 'readonly' },
|
|
@@ -82,7 +93,7 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
82
93
|
const store = tx.objectStore(collection);
|
|
83
94
|
const source = index?.where ? store.index(index.where) : store;
|
|
84
95
|
const direction = index?.order === 'desc' ? 'prev' : 'next';
|
|
85
|
-
const range = getRange(this.
|
|
96
|
+
const range = getRange(this.context.schema, collection, index);
|
|
86
97
|
const request = source.openCursor(range, direction);
|
|
87
98
|
|
|
88
99
|
let hasNextPage = false;
|
|
@@ -124,7 +135,7 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
124
135
|
|
|
125
136
|
request.onerror = () => {
|
|
126
137
|
if (request.error?.name === 'InvalidStateError') {
|
|
127
|
-
this.
|
|
138
|
+
this.context.log(
|
|
128
139
|
'error',
|
|
129
140
|
`find query failed with InvalidStateError`,
|
|
130
141
|
request.error,
|
|
@@ -161,7 +172,7 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
161
172
|
try {
|
|
162
173
|
await this.saveDocument(e.oid, snapshot, options);
|
|
163
174
|
} catch (err) {
|
|
164
|
-
this.
|
|
175
|
+
this.context.log(
|
|
165
176
|
'error',
|
|
166
177
|
`Error saving document ${e.oid} (${JSON.stringify(snapshot)})`,
|
|
167
178
|
err,
|
|
@@ -186,7 +197,7 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
186
197
|
'Failed to save any documents. Something must be quite wrong.',
|
|
187
198
|
);
|
|
188
199
|
}
|
|
189
|
-
this.
|
|
200
|
+
this.context.log(
|
|
190
201
|
'error',
|
|
191
202
|
'Failed to save documents:',
|
|
192
203
|
failures,
|
|
@@ -198,15 +209,15 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
198
209
|
};
|
|
199
210
|
|
|
200
211
|
reset = async (): Promise<void> => {
|
|
201
|
-
const names = Object.keys(this.
|
|
212
|
+
const names = Object.keys(this.context.schema.collections);
|
|
202
213
|
const tx = this.createTransaction(names, { mode: 'readwrite' });
|
|
203
214
|
await Promise.all(
|
|
204
215
|
names.map((name) =>
|
|
205
216
|
this.run(name, (store) => store.clear(), { transaction: tx }),
|
|
206
217
|
),
|
|
207
218
|
);
|
|
208
|
-
this.
|
|
209
|
-
this.
|
|
219
|
+
this.context.entityEvents.emit('collectionsChanged', names);
|
|
220
|
+
this.context.log('info', '💨 Reset queryable storage');
|
|
210
221
|
};
|
|
211
222
|
|
|
212
223
|
private saveDocument = async (
|
|
@@ -214,7 +225,7 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
214
225
|
doc: any,
|
|
215
226
|
{ transaction }: { transaction?: IDBTransaction },
|
|
216
227
|
) => {
|
|
217
|
-
this.
|
|
228
|
+
this.context.log('debug', `Saving document indexes for querying ${oid}`);
|
|
218
229
|
const { collection, id } = decomposeOid(oid);
|
|
219
230
|
try {
|
|
220
231
|
if (!doc) {
|
|
@@ -222,9 +233,12 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
222
233
|
mode: 'readwrite',
|
|
223
234
|
transaction,
|
|
224
235
|
});
|
|
225
|
-
this.
|
|
236
|
+
this.context.log(
|
|
237
|
+
'debug',
|
|
238
|
+
`Deleted document indexes for querying ${oid}`,
|
|
239
|
+
);
|
|
226
240
|
} else {
|
|
227
|
-
const schema = this.
|
|
241
|
+
const schema = this.context.schema.collections[collection];
|
|
228
242
|
// no need to validate before storing; the entity's snapshot is already validated.
|
|
229
243
|
const indexes = getIndexValues(schema, doc);
|
|
230
244
|
indexes['@@@snapshot'] = JSON.stringify(doc);
|
|
@@ -232,10 +246,10 @@ export class IdbDocumentDb extends IdbService implements PersistenceDocumentDb {
|
|
|
232
246
|
mode: 'readwrite',
|
|
233
247
|
transaction,
|
|
234
248
|
});
|
|
235
|
-
this.
|
|
249
|
+
this.context.log('debug', `Save complete for ${oid}`, indexes);
|
|
236
250
|
}
|
|
237
251
|
} catch (err) {
|
|
238
|
-
this.
|
|
252
|
+
this.context.log('error', `Error saving document ${oid}`, err);
|
|
239
253
|
throw err;
|
|
240
254
|
}
|
|
241
255
|
};
|
|
@@ -67,12 +67,22 @@ export async function openDatabase({
|
|
|
67
67
|
version: number;
|
|
68
68
|
log?: Context['log'];
|
|
69
69
|
}): Promise<IDBDatabase> {
|
|
70
|
+
if (version <= 0) {
|
|
71
|
+
throw new Error('Cannot open database at version less than 1');
|
|
72
|
+
}
|
|
70
73
|
log?.('debug', 'Opening database', namespace, 'at version', version);
|
|
71
74
|
const db = await baseOpenDatabase(
|
|
72
75
|
getDocumentDbName(namespace),
|
|
73
76
|
version,
|
|
74
77
|
indexedDB,
|
|
75
78
|
);
|
|
79
|
+
log?.('debug', 'Database opened', namespace, 'at version', db.version);
|
|
80
|
+
if (db.version !== version) {
|
|
81
|
+
log?.(
|
|
82
|
+
'warn',
|
|
83
|
+
`Opened database version ${db.version} but expected version ${version} for namespace ${namespace}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
76
86
|
|
|
77
87
|
db.addEventListener('versionchange', (event) => {
|
|
78
88
|
db.close();
|
|
@@ -267,7 +267,15 @@ export async function overwriteDatabase(
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
const to = await new Promise<IDBDatabase>((resolve, reject) => {
|
|
270
|
-
ctx.log(
|
|
270
|
+
ctx.log(
|
|
271
|
+
'debug',
|
|
272
|
+
'Copying to database',
|
|
273
|
+
toName,
|
|
274
|
+
'at',
|
|
275
|
+
from.version,
|
|
276
|
+
'from',
|
|
277
|
+
from.name,
|
|
278
|
+
);
|
|
271
279
|
const openRequest = indexedDB.open(toName, from.version);
|
|
272
280
|
openRequest.onupgradeneeded = () => {
|
|
273
281
|
ctx.log(
|
|
@@ -284,6 +292,7 @@ export async function overwriteDatabase(
|
|
|
284
292
|
throw new Error('No transaction');
|
|
285
293
|
}
|
|
286
294
|
for (const storeName of Array.from(original.objectStoreNames)) {
|
|
295
|
+
ctx.log('debug', 'Copying object store', storeName);
|
|
287
296
|
const originalObjectStore = original
|
|
288
297
|
.transaction(storeName)
|
|
289
298
|
.objectStore(storeName);
|
|
@@ -314,31 +323,35 @@ export async function overwriteDatabase(
|
|
|
314
323
|
reject(openRequest.error ?? new Error('Unknown database upgrade error'));
|
|
315
324
|
});
|
|
316
325
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
Array.from(to.objectStoreNames),
|
|
324
|
-
'readwrite',
|
|
326
|
+
// only copy data to new database if there are object stores to
|
|
327
|
+
// copy to
|
|
328
|
+
if (to.objectStoreNames.length > 0) {
|
|
329
|
+
const records = await getAllFromObjectStores(
|
|
330
|
+
from,
|
|
331
|
+
Array.from(from.objectStoreNames),
|
|
325
332
|
);
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
333
|
+
await new Promise<void>((resolve, reject) => {
|
|
334
|
+
const writeTx = to.transaction(
|
|
335
|
+
Array.from(to.objectStoreNames),
|
|
336
|
+
'readwrite',
|
|
337
|
+
);
|
|
338
|
+
for (let i = 0; i < records.length; i++) {
|
|
339
|
+
const store = writeTx.objectStore(from.objectStoreNames[i]);
|
|
340
|
+
for (const record of records[i]) {
|
|
341
|
+
store.add(record);
|
|
342
|
+
}
|
|
330
343
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
};
|
|
341
|
-
}
|
|
344
|
+
writeTx.oncomplete = () => resolve();
|
|
345
|
+
writeTx.onerror = (ev) => {
|
|
346
|
+
const err =
|
|
347
|
+
writeTx.error ??
|
|
348
|
+
(ev.target as any).transaction?.error ??
|
|
349
|
+
new Error('Unknown error');
|
|
350
|
+
ctx.log('critical', 'Error copying data', err);
|
|
351
|
+
reject(err);
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
}
|
|
342
355
|
|
|
343
356
|
await closeDatabase(to);
|
|
344
357
|
}
|
|
@@ -351,6 +364,15 @@ export function openDatabase(
|
|
|
351
364
|
return new Promise<IDBDatabase>((resolve, reject) => {
|
|
352
365
|
const req = indexedDB.open(name, expectedVersion);
|
|
353
366
|
req.onsuccess = () => {
|
|
367
|
+
if (req.result.version !== expectedVersion) {
|
|
368
|
+
req.result.close();
|
|
369
|
+
reject(
|
|
370
|
+
new Error(
|
|
371
|
+
`Migration error: opened database version ${req.result.version} but expected version ${expectedVersion} for database ${name}`,
|
|
372
|
+
),
|
|
373
|
+
);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
354
376
|
resolve(req.result);
|
|
355
377
|
};
|
|
356
378
|
req.onerror = () => {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ObjectIdentifier,
|
|
7
7
|
Operation,
|
|
8
8
|
} from '@verdant-web/common';
|
|
9
|
-
import { Context,
|
|
9
|
+
import { Context, ContextWithoutPersistence } from '../context/context.js';
|
|
10
10
|
|
|
11
11
|
export interface AckInfo {
|
|
12
12
|
globalAckTimestamp: string | null;
|
|
@@ -187,21 +187,22 @@ export interface PersistenceFileDb {
|
|
|
187
187
|
iterateOverPendingDelete(
|
|
188
188
|
iterator: (file: PersistedFileData) => void,
|
|
189
189
|
): Promise<void>;
|
|
190
|
-
loadFileContents(
|
|
190
|
+
loadFileContents(
|
|
191
|
+
file: FileData,
|
|
192
|
+
ctx: Omit<Context, 'queries'>,
|
|
193
|
+
): Promise<Blob>;
|
|
191
194
|
getAll(): Promise<PersistedFileData[]>;
|
|
192
195
|
stats(): Promise<{ size: { count: number; size: number } }>;
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
export interface PersistenceNamespace {
|
|
196
|
-
openMetadata(ctx:
|
|
199
|
+
openMetadata(ctx: ContextWithoutPersistence): Promise<PersistenceMetadataDb>;
|
|
197
200
|
/**
|
|
198
201
|
* Open the Documents database according to the schema in the given
|
|
199
202
|
* context. By the time this is called with a version, relevant migrations
|
|
200
203
|
* will have been applied.
|
|
201
204
|
*/
|
|
202
|
-
openDocuments(
|
|
203
|
-
ctx: Omit<Context, 'documents' | 'files'>,
|
|
204
|
-
): Promise<PersistenceDocumentDb>;
|
|
205
|
+
openDocuments(ctx: ContextWithoutPersistence): Promise<PersistenceDocumentDb>;
|
|
205
206
|
/**
|
|
206
207
|
* Apply a migration to the namespace provided in ctx.
|
|
207
208
|
* This should make any transformations necessary to the
|
|
@@ -212,10 +213,11 @@ export interface PersistenceNamespace {
|
|
|
212
213
|
* This method should also store the new version to persisted
|
|
213
214
|
* metadata, however your implementation chooses to do that.
|
|
214
215
|
*/
|
|
215
|
-
applyMigration(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
): Promise<
|
|
216
|
+
applyMigration(
|
|
217
|
+
ctx: ContextWithoutPersistence,
|
|
218
|
+
migration: Migration<any>,
|
|
219
|
+
): Promise<void>;
|
|
220
|
+
openFiles(ctx: ContextWithoutPersistence): Promise<PersistenceFileDb>;
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
export interface PersistenceImplementation {
|
|
@@ -227,7 +229,10 @@ export interface PersistenceImplementation {
|
|
|
227
229
|
/** Returns a list of all persisted namespaces visible to this app. */
|
|
228
230
|
getNamespaces(): Promise<string[]>;
|
|
229
231
|
/** Deletes all data from a particular namespace. */
|
|
230
|
-
deleteNamespace(
|
|
232
|
+
deleteNamespace(
|
|
233
|
+
namespace: string,
|
|
234
|
+
ctx: ContextWithoutPersistence,
|
|
235
|
+
): Promise<void>;
|
|
231
236
|
/** Gets the schema version of the given namespace */
|
|
232
237
|
getNamespaceVersion(namespace: string): Promise<number>;
|
|
233
238
|
/**
|
|
@@ -235,5 +240,9 @@ export interface PersistenceImplementation {
|
|
|
235
240
|
* overwrite the target namespace such that data and database
|
|
236
241
|
* schema are identical.
|
|
237
242
|
*/
|
|
238
|
-
copyNamespace(
|
|
243
|
+
copyNamespace(
|
|
244
|
+
from: string,
|
|
245
|
+
to: string,
|
|
246
|
+
ctx: ContextWithoutPersistence,
|
|
247
|
+
): Promise<void>;
|
|
239
248
|
}
|
|
@@ -13,17 +13,20 @@ import {
|
|
|
13
13
|
getOid,
|
|
14
14
|
removeOidPropertiesFromAllSubObjects,
|
|
15
15
|
} from '@verdant-web/common';
|
|
16
|
+
import { ContextWithoutPersistence } from '../../context/context.js';
|
|
16
17
|
import { PersistenceDocumentDb, PersistenceNamespace } from '../interfaces.js';
|
|
17
|
-
import {
|
|
18
|
+
import { PersistenceMetadata } from '../PersistenceMetadata.js';
|
|
18
19
|
|
|
19
20
|
function getMigrationMutations({
|
|
20
21
|
migration,
|
|
21
22
|
newOids,
|
|
22
23
|
ctx,
|
|
24
|
+
meta,
|
|
23
25
|
}: {
|
|
24
26
|
migration: Migration<any>;
|
|
25
27
|
newOids: string[];
|
|
26
|
-
ctx:
|
|
28
|
+
ctx: ContextWithoutPersistence;
|
|
29
|
+
meta: PersistenceMetadata;
|
|
27
30
|
}) {
|
|
28
31
|
return migration.allCollections.reduce((acc, collectionName) => {
|
|
29
32
|
acc[collectionName] = {
|
|
@@ -36,7 +39,7 @@ function getMigrationMutations({
|
|
|
36
39
|
newOids.push(oid);
|
|
37
40
|
|
|
38
41
|
await ctx.time.withMigrationTime(migration.version, () =>
|
|
39
|
-
|
|
42
|
+
meta.insertData({
|
|
40
43
|
operations: ctx.patchCreator.createInitialize(
|
|
41
44
|
doc,
|
|
42
45
|
oid,
|
|
@@ -50,7 +53,7 @@ function getMigrationMutations({
|
|
|
50
53
|
delete: async (id: string) => {
|
|
51
54
|
const rootOid = createOid(collectionName, id);
|
|
52
55
|
await ctx.time.withMigrationTime(migration.version, () =>
|
|
53
|
-
|
|
56
|
+
meta.deleteDocument(rootOid),
|
|
54
57
|
);
|
|
55
58
|
},
|
|
56
59
|
};
|
|
@@ -62,16 +65,18 @@ function getMigrationQueries({
|
|
|
62
65
|
migration,
|
|
63
66
|
context,
|
|
64
67
|
documents,
|
|
68
|
+
meta,
|
|
65
69
|
}: {
|
|
66
70
|
migration: Migration<any>;
|
|
67
|
-
context:
|
|
71
|
+
context: ContextWithoutPersistence;
|
|
68
72
|
documents: PersistenceDocumentDb;
|
|
73
|
+
meta: PersistenceMetadata;
|
|
69
74
|
}) {
|
|
70
75
|
return migration.oldCollections.reduce((acc, collectionName) => {
|
|
71
76
|
acc[collectionName] = {
|
|
72
77
|
get: async (id: string) => {
|
|
73
78
|
const oid = createOid(collectionName, id);
|
|
74
|
-
const doc = await
|
|
79
|
+
const doc = await meta.getDocumentSnapshot(oid, {
|
|
75
80
|
// only get the snapshot up to the previous version (newer operations may have synced)
|
|
76
81
|
to: context.time.nowWithVersion(migration.oldSchema.version),
|
|
77
82
|
});
|
|
@@ -83,7 +88,7 @@ function getMigrationQueries({
|
|
|
83
88
|
index: filter,
|
|
84
89
|
});
|
|
85
90
|
if (!oid) return null;
|
|
86
|
-
const doc = await
|
|
91
|
+
const doc = await meta.getDocumentSnapshot(oid, {
|
|
87
92
|
// only get the snapshot up to the previous version (newer operations may have synced)
|
|
88
93
|
to: context.time.nowWithVersion(migration.oldSchema.version),
|
|
89
94
|
});
|
|
@@ -95,8 +100,8 @@ function getMigrationQueries({
|
|
|
95
100
|
index: filter,
|
|
96
101
|
});
|
|
97
102
|
const docs = await Promise.all(
|
|
98
|
-
oids.map((oid) =>
|
|
99
|
-
|
|
103
|
+
oids.map(async (oid) =>
|
|
104
|
+
meta.getDocumentSnapshot(oid, {
|
|
100
105
|
// only get the snapshot up to the previous version (newer operations may have synced)
|
|
101
106
|
to: context.time.nowWithVersion(migration.oldSchema.version),
|
|
102
107
|
}),
|
|
@@ -113,18 +118,23 @@ export async function getMigrationEngine({
|
|
|
113
118
|
migration,
|
|
114
119
|
context,
|
|
115
120
|
ns,
|
|
121
|
+
meta,
|
|
116
122
|
}: {
|
|
117
123
|
log?: (...args: any[]) => void;
|
|
118
124
|
migration: Migration;
|
|
119
|
-
context:
|
|
125
|
+
context: ContextWithoutPersistence;
|
|
120
126
|
ns: PersistenceNamespace;
|
|
127
|
+
meta: PersistenceMetadata;
|
|
121
128
|
}): Promise<MigrationEngine> {
|
|
122
|
-
const migrationContext = {
|
|
123
|
-
...context,
|
|
129
|
+
const migrationContext = context.cloneWithOptions({
|
|
124
130
|
schema: migration.oldSchema,
|
|
125
|
-
};
|
|
131
|
+
});
|
|
126
132
|
if (migration.oldSchema.version === 0) {
|
|
127
|
-
return getInitialMigrationEngine({
|
|
133
|
+
return getInitialMigrationEngine({
|
|
134
|
+
migration,
|
|
135
|
+
context: migrationContext,
|
|
136
|
+
meta,
|
|
137
|
+
});
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
const newOids = new Array<ObjectIdentifier>();
|
|
@@ -134,14 +144,16 @@ export async function getMigrationEngine({
|
|
|
134
144
|
migration,
|
|
135
145
|
context: migrationContext,
|
|
136
146
|
documents,
|
|
147
|
+
meta,
|
|
137
148
|
});
|
|
138
149
|
const mutations = getMigrationMutations({
|
|
139
150
|
migration,
|
|
140
151
|
newOids,
|
|
141
152
|
ctx: migrationContext,
|
|
153
|
+
meta,
|
|
142
154
|
});
|
|
143
155
|
const deleteCollection = async (collection: string) => {
|
|
144
|
-
await
|
|
156
|
+
await meta.deleteCollection(collection);
|
|
145
157
|
};
|
|
146
158
|
const awaitables = new Array<Promise<any>>();
|
|
147
159
|
const engine: MigrationEngine = {
|
|
@@ -166,7 +178,7 @@ export async function getMigrationEngine({
|
|
|
166
178
|
// when the snapshots themselves are derived from the same data...)
|
|
167
179
|
// maybe don't use the findAll query, and instead go a level
|
|
168
180
|
// lower to retain access to lower level data here?
|
|
169
|
-
const authz = await
|
|
181
|
+
const authz = await meta.getDocumentAuthz(rootOid);
|
|
170
182
|
const original = cloneDeep(doc);
|
|
171
183
|
// @ts-ignore - excessive type resolution
|
|
172
184
|
const newValue = await strategy(doc);
|
|
@@ -196,7 +208,7 @@ export async function getMigrationEngine({
|
|
|
196
208
|
},
|
|
197
209
|
);
|
|
198
210
|
if (patches.length > 0) {
|
|
199
|
-
await
|
|
211
|
+
await meta.insertData({
|
|
200
212
|
operations: patches,
|
|
201
213
|
isLocal: true,
|
|
202
214
|
});
|
|
@@ -218,9 +230,11 @@ export async function getMigrationEngine({
|
|
|
218
230
|
function getInitialMigrationEngine({
|
|
219
231
|
migration,
|
|
220
232
|
context,
|
|
233
|
+
meta,
|
|
221
234
|
}: {
|
|
222
|
-
context:
|
|
235
|
+
context: ContextWithoutPersistence;
|
|
223
236
|
migration: Migration;
|
|
237
|
+
meta: PersistenceMetadata;
|
|
224
238
|
}): MigrationEngine {
|
|
225
239
|
const newOids = new Array<ObjectIdentifier>();
|
|
226
240
|
|
|
@@ -236,6 +250,7 @@ function getInitialMigrationEngine({
|
|
|
236
250
|
migration,
|
|
237
251
|
newOids,
|
|
238
252
|
ctx: context,
|
|
253
|
+
meta,
|
|
239
254
|
});
|
|
240
255
|
const engine: MigrationEngine = {
|
|
241
256
|
log: context.log,
|
|
@@ -4,17 +4,20 @@ import {
|
|
|
4
4
|
Migration,
|
|
5
5
|
MigrationEngine,
|
|
6
6
|
} from '@verdant-web/common';
|
|
7
|
+
import { ContextWithoutPersistence } from '../../context/context.js';
|
|
7
8
|
import { ClientOperation, PersistenceDocumentDb } from '../interfaces.js';
|
|
8
|
-
import {
|
|
9
|
+
import { PersistenceMetadata } from '../PersistenceMetadata.js';
|
|
9
10
|
|
|
10
11
|
export async function finalizeMigration({
|
|
11
12
|
ctx,
|
|
12
13
|
documents,
|
|
13
14
|
migration,
|
|
15
|
+
meta,
|
|
14
16
|
engine,
|
|
15
17
|
}: {
|
|
16
|
-
ctx:
|
|
18
|
+
ctx: ContextWithoutPersistence;
|
|
17
19
|
documents: PersistenceDocumentDb;
|
|
20
|
+
meta: PersistenceMetadata;
|
|
18
21
|
migration: Migration<any>;
|
|
19
22
|
engine: MigrationEngine;
|
|
20
23
|
}) {
|
|
@@ -37,6 +40,7 @@ export async function finalizeMigration({
|
|
|
37
40
|
currentVersion: migration.oldSchema.version,
|
|
38
41
|
newVersion: migration.newSchema.version,
|
|
39
42
|
ctx,
|
|
43
|
+
meta,
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
// once the schema is ready, we can write back the migrated documents
|
|
@@ -58,7 +62,7 @@ export async function finalizeMigration({
|
|
|
58
62
|
const snapshots = await Promise.all(
|
|
59
63
|
oids.map(async (oid) => {
|
|
60
64
|
try {
|
|
61
|
-
const snap = await
|
|
65
|
+
const snap = await meta.getDocumentSnapshot(oid);
|
|
62
66
|
return [oid, snap];
|
|
63
67
|
} catch (e) {
|
|
64
68
|
// this seems to happen with baselines/ops which are not fully
|
|
@@ -103,16 +107,18 @@ async function getDocsWithUnappliedMigrations({
|
|
|
103
107
|
currentVersion,
|
|
104
108
|
newVersion: _,
|
|
105
109
|
ctx,
|
|
110
|
+
meta,
|
|
106
111
|
}: {
|
|
107
112
|
currentVersion: number;
|
|
108
113
|
newVersion: number;
|
|
109
|
-
ctx:
|
|
114
|
+
ctx: ContextWithoutPersistence;
|
|
115
|
+
meta: PersistenceMetadata;
|
|
110
116
|
}) {
|
|
111
117
|
// scan for all operations in metadata after the current version.
|
|
112
118
|
// this could be more efficient if also filtering below or equal newVersion but
|
|
113
119
|
// that seems so unlikely in practice...
|
|
114
120
|
const unappliedOperations: ClientOperation[] = [];
|
|
115
|
-
await
|
|
121
|
+
await meta.iterateAllOperations(
|
|
116
122
|
(op) => {
|
|
117
123
|
unappliedOperations.push(op);
|
|
118
124
|
},
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { Migration } from '@verdant-web/common';
|
|
2
|
+
import { ContextWithoutPersistence } from '../../context/context.js';
|
|
2
3
|
import { ShutdownHandler } from '../../context/ShutdownHandler.js';
|
|
3
4
|
import { PersistenceNamespace } from '../interfaces.js';
|
|
5
|
+
import { PersistenceMetadata } from '../PersistenceMetadata.js';
|
|
4
6
|
import { getMigrationEngine } from './engine.js';
|
|
5
7
|
import { finalizeMigration } from './finalize.js';
|
|
6
8
|
import { getMigrationPath } from './paths.js';
|
|
7
|
-
import { OpenDocumentDbContext } from './types.js';
|
|
8
9
|
|
|
9
10
|
export async function migrate({
|
|
10
11
|
context,
|
|
11
12
|
version,
|
|
13
|
+
meta,
|
|
12
14
|
}: {
|
|
13
|
-
context:
|
|
15
|
+
context: ContextWithoutPersistence;
|
|
16
|
+
meta: PersistenceMetadata;
|
|
14
17
|
version: number;
|
|
15
18
|
}) {
|
|
16
19
|
const ns = await context.persistence.openNamespace(
|
|
@@ -45,7 +48,7 @@ export async function migrate({
|
|
|
45
48
|
'Migrations to run:',
|
|
46
49
|
toRun.map((m) => m.version),
|
|
47
50
|
);
|
|
48
|
-
await runMigrations({ context, ns, toRun });
|
|
51
|
+
await runMigrations({ context, ns, toRun, meta });
|
|
49
52
|
}
|
|
50
53
|
});
|
|
51
54
|
}
|
|
@@ -63,10 +66,12 @@ export async function runMigrations({
|
|
|
63
66
|
context,
|
|
64
67
|
toRun,
|
|
65
68
|
ns,
|
|
69
|
+
meta,
|
|
66
70
|
}: {
|
|
67
|
-
context:
|
|
71
|
+
context: ContextWithoutPersistence;
|
|
68
72
|
toRun: Migration<any>[];
|
|
69
73
|
ns: PersistenceNamespace;
|
|
74
|
+
meta: PersistenceMetadata;
|
|
70
75
|
}) {
|
|
71
76
|
// disable rebasing for the duration of migrations
|
|
72
77
|
context.pauseRebasing = true;
|
|
@@ -76,16 +81,16 @@ export async function runMigrations({
|
|
|
76
81
|
'info',
|
|
77
82
|
`🚀 Running migration v${migration.oldSchema.version} -> v${migration.newSchema.version}`,
|
|
78
83
|
);
|
|
79
|
-
const migrationContext = {
|
|
80
|
-
...context,
|
|
84
|
+
const migrationContext = context.cloneWithOptions({
|
|
81
85
|
schema: migration.oldSchema,
|
|
82
|
-
|
|
83
|
-
};
|
|
86
|
+
persistenceShutdownHandler: new ShutdownHandler(context.log),
|
|
87
|
+
});
|
|
84
88
|
// this will only write to our metadata store via operations!
|
|
85
89
|
const engine = await getMigrationEngine({
|
|
86
90
|
migration,
|
|
87
91
|
context: migrationContext,
|
|
88
92
|
ns,
|
|
93
|
+
meta,
|
|
89
94
|
});
|
|
90
95
|
try {
|
|
91
96
|
context.log(
|
|
@@ -98,6 +103,7 @@ export async function runMigrations({
|
|
|
98
103
|
migration.newSchema.version,
|
|
99
104
|
);
|
|
100
105
|
await migration.migrate(engine);
|
|
106
|
+
context.log('debug', 'Awaiting remaining migration tasks');
|
|
101
107
|
// wait on any out-of-band async operations to complete
|
|
102
108
|
await Promise.all(engine.awaitables);
|
|
103
109
|
} catch (err) {
|
|
@@ -136,6 +142,7 @@ export async function runMigrations({
|
|
|
136
142
|
migration,
|
|
137
143
|
engine,
|
|
138
144
|
documents: upgradedDocuments,
|
|
145
|
+
meta,
|
|
139
146
|
});
|
|
140
147
|
await upgradedDocuments.close();
|
|
141
148
|
|