@verdant-web/store 4.6.0 → 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.
Files changed (130) hide show
  1. package/dist/bundle/index.js +14 -12
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
  4. package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
  5. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  6. package/dist/esm/__tests__/queries.test.js +3 -3
  7. package/dist/esm/__tests__/queries.test.js.map +1 -1
  8. package/dist/esm/__tests__/schema.test.js +3 -3
  9. package/dist/esm/__tests__/schema.test.js.map +1 -1
  10. package/dist/esm/client/Client.d.ts +12 -10
  11. package/dist/esm/client/Client.js +40 -30
  12. package/dist/esm/client/Client.js.map +1 -1
  13. package/dist/esm/context/Time.d.ts +1 -1
  14. package/dist/esm/context/Time.js +1 -1
  15. package/dist/esm/context/Time.js.map +1 -1
  16. package/dist/esm/context/context.d.ts +84 -15
  17. package/dist/esm/context/context.js +98 -1
  18. package/dist/esm/context/context.js.map +1 -1
  19. package/dist/esm/entities/Entity.test.js +0 -1
  20. package/dist/esm/entities/Entity.test.js.map +1 -1
  21. package/dist/esm/entities/EntityMetadata.js +11 -5
  22. package/dist/esm/entities/EntityMetadata.js.map +1 -1
  23. package/dist/esm/entities/EntityStore.js +9 -7
  24. package/dist/esm/entities/EntityStore.js.map +1 -1
  25. package/dist/esm/files/FileManager.js +5 -5
  26. package/dist/esm/files/FileManager.js.map +1 -1
  27. package/dist/esm/index.d.ts +6 -4
  28. package/dist/esm/index.js +2 -3
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/internal.d.ts +3 -4
  31. package/dist/esm/internal.js +1 -2
  32. package/dist/esm/internal.js.map +1 -1
  33. package/dist/esm/persistence/PersistenceMetadata.d.ts +3 -6
  34. package/dist/esm/persistence/PersistenceMetadata.js +5 -6
  35. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
  36. package/dist/esm/persistence/idb/IdbService.d.ts +3 -3
  37. package/dist/esm/persistence/idb/IdbService.js +0 -1
  38. package/dist/esm/persistence/idb/IdbService.js.map +1 -1
  39. package/dist/esm/persistence/idb/idbPersistence.d.ts +9 -10
  40. package/dist/esm/persistence/idb/idbPersistence.js +11 -4
  41. package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
  42. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +2 -2
  43. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +1 -1
  44. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
  45. package/dist/esm/persistence/idb/queries/IdbDocumentDb.d.ts +3 -2
  46. package/dist/esm/persistence/idb/queries/IdbDocumentDb.js +16 -15
  47. package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -1
  48. package/dist/esm/persistence/idb/queries/migration/db.js +7 -0
  49. package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
  50. package/dist/esm/persistence/idb/util.js +27 -17
  51. package/dist/esm/persistence/idb/util.js.map +1 -1
  52. package/dist/esm/persistence/interfaces.d.ts +8 -8
  53. package/dist/esm/persistence/migration/engine.d.ts +5 -3
  54. package/dist/esm/persistence/migration/engine.js +23 -14
  55. package/dist/esm/persistence/migration/engine.js.map +1 -1
  56. package/dist/esm/persistence/migration/finalize.d.ts +5 -3
  57. package/dist/esm/persistence/migration/finalize.js +5 -4
  58. package/dist/esm/persistence/migration/finalize.js.map +1 -1
  59. package/dist/esm/persistence/migration/migrate.d.ts +8 -5
  60. package/dist/esm/persistence/migration/migrate.js +10 -4
  61. package/dist/esm/persistence/migration/migrate.js.map +1 -1
  62. package/dist/esm/persistence/persistence.d.ts +9 -2
  63. package/dist/esm/persistence/persistence.js +65 -32
  64. package/dist/esm/persistence/persistence.js.map +1 -1
  65. package/dist/esm/queries/FindAllQuery.js +1 -1
  66. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  67. package/dist/esm/queries/FindInfiniteQuery.js +2 -2
  68. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  69. package/dist/esm/queries/FindOneQuery.js +1 -1
  70. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  71. package/dist/esm/queries/FindPageQuery.js +1 -1
  72. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  73. package/dist/esm/sync/Heartbeat.js +2 -0
  74. package/dist/esm/sync/Heartbeat.js.map +1 -1
  75. package/dist/esm/sync/PresenceManager.d.ts +2 -2
  76. package/dist/esm/sync/PresenceManager.js +5 -2
  77. package/dist/esm/sync/PresenceManager.js.map +1 -1
  78. package/dist/esm/sync/PushPullSync.js +10 -4
  79. package/dist/esm/sync/PushPullSync.js.map +1 -1
  80. package/dist/esm/sync/Sync.d.ts +2 -2
  81. package/dist/esm/sync/Sync.js +17 -14
  82. package/dist/esm/sync/Sync.js.map +1 -1
  83. package/dist/esm/sync/WebSocketSync.js +12 -7
  84. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  85. package/dist/esm/sync/serviceWorker.d.ts +2 -2
  86. package/dist/esm/sync/serviceWorker.js +3 -4
  87. package/dist/esm/sync/serviceWorker.js.map +1 -1
  88. package/package.json +2 -2
  89. package/src/__tests__/fixtures/testStorage.ts +4 -8
  90. package/src/__tests__/queries.test.ts +3 -5
  91. package/src/__tests__/schema.test.ts +3 -3
  92. package/src/client/Client.ts +50 -34
  93. package/src/context/Time.ts +2 -2
  94. package/src/context/context.ts +189 -17
  95. package/src/entities/Entity.test.ts +0 -1
  96. package/src/entities/EntityMetadata.ts +16 -5
  97. package/src/entities/EntityStore.ts +14 -10
  98. package/src/files/FileManager.ts +5 -5
  99. package/src/index.ts +6 -10
  100. package/src/internal.ts +10 -11
  101. package/src/persistence/PersistenceMetadata.ts +9 -11
  102. package/src/persistence/idb/IdbService.ts +2 -3
  103. package/src/persistence/idb/idbPersistence.ts +25 -19
  104. package/src/persistence/idb/metadata/IdbMetadataDb.ts +3 -3
  105. package/src/persistence/idb/queries/IdbDocumentDb.ts +31 -17
  106. package/src/persistence/idb/queries/migration/db.ts +10 -0
  107. package/src/persistence/idb/util.ts +46 -24
  108. package/src/persistence/interfaces.ts +21 -12
  109. package/src/persistence/migration/engine.ts +33 -18
  110. package/src/persistence/migration/finalize.ts +11 -5
  111. package/src/persistence/migration/migrate.ts +15 -8
  112. package/src/persistence/persistence.ts +78 -67
  113. package/src/queries/FindAllQuery.ts +3 -1
  114. package/src/queries/FindInfiniteQuery.ts +6 -2
  115. package/src/queries/FindOneQuery.ts +3 -1
  116. package/src/queries/FindPageQuery.ts +3 -1
  117. package/src/sync/Heartbeat.ts +2 -0
  118. package/src/sync/PresenceManager.ts +10 -7
  119. package/src/sync/PushPullSync.ts +18 -6
  120. package/src/sync/Sync.ts +26 -16
  121. package/src/sync/WebSocketSync.ts +25 -9
  122. package/src/sync/serviceWorker.ts +4 -6
  123. package/dist/esm/client/ClientDescriptor.d.ts +0 -87
  124. package/dist/esm/client/ClientDescriptor.js +0 -133
  125. package/dist/esm/client/ClientDescriptor.js.map +0 -1
  126. package/dist/esm/persistence/migration/types.d.ts +0 -3
  127. package/dist/esm/persistence/migration/types.js +0 -2
  128. package/dist/esm/persistence/migration/types.js.map +0 -1
  129. package/src/client/ClientDescriptor.ts +0 -246
  130. 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 { InitialContext } from '../../../context/context.js';
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: InitialContext) {
40
+ constructor(db: IDBDatabase, ctx: ContextWithoutPersistence) {
41
41
  super(db, ctx);
42
42
  this.addDispose(() => {
43
- this.ctx.log('info', `Closing metadata DB for`, this.ctx.namespace);
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 { InitialContext } from '../../../context/context.js';
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(db: IDBDatabase, context: InitialContext) {
20
+ constructor(
21
+ db: IDBDatabase,
22
+ private context: ContextWithoutPersistence,
23
+ ) {
21
24
  super(db, context);
22
25
  this.addDispose(() => {
23
- this.ctx.log('info', 'Closing document database for', this.ctx.namespace);
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.ctx.schema.collections);
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(this.ctx.schema, opts.collection, opts.index);
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.ctx.schema, collection, index);
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.ctx.log(
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.ctx.log(
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.ctx.log(
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.ctx.schema.collections);
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.ctx.entityEvents.emit('collectionsChanged', names);
209
- this.ctx.log('info', '💨 Reset queryable storage');
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.ctx.log('debug', `Saving document indexes for querying ${oid}`);
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.ctx.log('debug', `Deleted document indexes for querying ${oid}`);
236
+ this.context.log(
237
+ 'debug',
238
+ `Deleted document indexes for querying ${oid}`,
239
+ );
226
240
  } else {
227
- const schema = this.ctx.schema.collections[collection];
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.ctx.log('debug', `Save complete for ${oid}`, indexes);
249
+ this.context.log('debug', `Save complete for ${oid}`, indexes);
236
250
  }
237
251
  } catch (err) {
238
- this.ctx.log('error', `Error saving document ${oid}`, err);
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('debug', 'Opening reset database', toName, 'at', from.version);
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
- const records = await getAllFromObjectStores(
318
- from,
319
- Array.from(from.objectStoreNames),
320
- );
321
- await new Promise<void>((resolve, reject) => {
322
- const writeTx = to.transaction(
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
- for (let i = 0; i < records.length; i++) {
327
- const store = writeTx.objectStore(from.objectStoreNames[i]);
328
- for (const record of records[i]) {
329
- store.add(record);
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
- writeTx.oncomplete = () => resolve();
333
- writeTx.onerror = (ev) => {
334
- const err =
335
- writeTx.error ??
336
- (ev.target as any).transaction?.error ??
337
- new Error('Unknown error');
338
- ctx.log('critical', 'Error copying data', err);
339
- reject(err);
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, InitialContext } from '../context/context.js';
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(file: FileData, ctx: Context): Promise<Blob>;
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: InitialContext): Promise<PersistenceMetadataDb>;
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(ctx: InitialContext, migration: Migration<any>): Promise<void>;
216
- openFiles(
217
- ctx: Omit<Context, 'files' | 'documents'>,
218
- ): Promise<PersistenceFileDb>;
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(namespace: string, ctx: InitialContext): Promise<void>;
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(from: string, to: string, ctx: InitialContext): Promise<void>;
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 { OpenDocumentDbContext } from './types.js';
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: OpenDocumentDbContext;
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
- ctx.meta.insertData({
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
- ctx.meta.deleteDocument(rootOid),
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: OpenDocumentDbContext;
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 context.meta.getDocumentSnapshot(oid, {
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 context.meta.getDocumentSnapshot(oid, {
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
- context.meta.getDocumentSnapshot(oid, {
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: OpenDocumentDbContext;
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({ migration, context: migrationContext });
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 context.meta.deleteCollection(collection);
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 context.meta.getDocumentAuthz(rootOid);
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 context.meta.insertData({
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: OpenDocumentDbContext;
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 { OpenDocumentDbContext } from './types.js';
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: OpenDocumentDbContext;
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 ctx.meta.getDocumentSnapshot(oid);
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: OpenDocumentDbContext;
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 ctx.meta.iterateAllOperations(
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: OpenDocumentDbContext;
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: OpenDocumentDbContext;
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
- shutdownHandler: new ShutdownHandler(context.log),
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