@verdant-web/store 4.6.1 → 5.0.1
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/EntityFile.js +1 -1
- package/dist/esm/files/EntityFile.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/EntityFile.ts +1 -1
- 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
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Migration } from '@verdant-web/common';
|
|
2
|
+
import { ContextWithoutPersistence } from '../../context/context.js';
|
|
2
3
|
import {
|
|
3
|
-
PersistenceImplementation,
|
|
4
4
|
PersistenceFileDb,
|
|
5
|
+
PersistenceImplementation,
|
|
5
6
|
PersistenceNamespace,
|
|
6
7
|
} from '../interfaces.js';
|
|
7
8
|
import { IdbPersistenceFileDb } from './files/IdbPersistenceFileDb.js';
|
|
8
9
|
import { IdbMetadataDb } from './metadata/IdbMetadataDb.js';
|
|
9
10
|
import { openMetadataDatabase } from './metadata/openMetadataDatabase.js';
|
|
10
11
|
import { IdbDocumentDb } from './queries/IdbDocumentDb.js';
|
|
12
|
+
import { openDatabase, upgradeDatabase } from './queries/migration/db.js';
|
|
11
13
|
import {
|
|
12
14
|
closeDatabase,
|
|
13
15
|
deleteDatabase,
|
|
@@ -16,9 +18,6 @@ import {
|
|
|
16
18
|
getNamespaceFromDatabaseInfo,
|
|
17
19
|
overwriteDatabase,
|
|
18
20
|
} from './util.js';
|
|
19
|
-
import { openDatabase, upgradeDatabase } from './queries/migration/db.js';
|
|
20
|
-
import { Migration } from '@verdant-web/common';
|
|
21
|
-
import { OpenDocumentDbContext } from '../migration/types.js';
|
|
22
21
|
|
|
23
22
|
export class IdbPersistence implements PersistenceImplementation {
|
|
24
23
|
name = 'IdbPersistence';
|
|
@@ -45,10 +44,7 @@ export class IdbPersistence implements PersistenceImplementation {
|
|
|
45
44
|
return 0;
|
|
46
45
|
};
|
|
47
46
|
|
|
48
|
-
deleteNamespace = async (
|
|
49
|
-
namespace: string,
|
|
50
|
-
ctx: InitialContext,
|
|
51
|
-
): Promise<void> => {
|
|
47
|
+
deleteNamespace = async (namespace: string): Promise<void> => {
|
|
52
48
|
await Promise.all([
|
|
53
49
|
deleteDatabase(getMetadataDbName(namespace), this.indexedDB),
|
|
54
50
|
deleteDatabase([namespace, 'collections'].join('_'), this.indexedDB),
|
|
@@ -64,10 +60,14 @@ export class IdbPersistence implements PersistenceImplementation {
|
|
|
64
60
|
copyNamespace = async (
|
|
65
61
|
from: string,
|
|
66
62
|
to: string,
|
|
67
|
-
ctx:
|
|
63
|
+
ctx: ContextWithoutPersistence,
|
|
68
64
|
): Promise<void> => {
|
|
69
|
-
const fromCtx = {
|
|
70
|
-
|
|
65
|
+
const fromCtx = ctx.cloneWithOptions({
|
|
66
|
+
namespace: from,
|
|
67
|
+
}) as ContextWithoutPersistence;
|
|
68
|
+
const toCtx = ctx.cloneWithOptions({
|
|
69
|
+
namespace: to,
|
|
70
|
+
}) as ContextWithoutPersistence;
|
|
71
71
|
const { db: fromMetaDb } = await openMetadataDatabase({
|
|
72
72
|
indexedDB: this.indexedDB,
|
|
73
73
|
log: fromCtx.log,
|
|
@@ -108,12 +108,13 @@ export class IdbPersistence implements PersistenceImplementation {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
111
|
-
constructor(
|
|
111
|
+
constructor(
|
|
112
|
+
private indexedDB: IDBFactory,
|
|
113
|
+
private namespace: string,
|
|
114
|
+
) {}
|
|
112
115
|
private metadataDb: IDBDatabase | undefined;
|
|
113
116
|
|
|
114
|
-
openFiles(
|
|
115
|
-
ctx: Omit<Context, 'files' | 'documents'>,
|
|
116
|
-
): Promise<PersistenceFileDb> {
|
|
117
|
+
openFiles(ctx: ContextWithoutPersistence): Promise<PersistenceFileDb> {
|
|
117
118
|
if (!this.metadataDb) {
|
|
118
119
|
throw new Error(
|
|
119
120
|
'Metadata database must be opened first. This is a bug in Verdant.',
|
|
@@ -122,7 +123,7 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
122
123
|
return Promise.resolve(new IdbPersistenceFileDb(this.metadataDb, ctx));
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
openMetadata = async (ctx:
|
|
126
|
+
openMetadata = async (ctx: ContextWithoutPersistence) => {
|
|
126
127
|
const { db } = await openMetadataDatabase({
|
|
127
128
|
indexedDB: this.indexedDB,
|
|
128
129
|
log: ctx.log,
|
|
@@ -133,7 +134,7 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
133
134
|
return new IdbMetadataDb(db, ctx);
|
|
134
135
|
};
|
|
135
136
|
|
|
136
|
-
openDocuments = async (ctx:
|
|
137
|
+
openDocuments = async (ctx: ContextWithoutPersistence) => {
|
|
137
138
|
const db = await openDatabase({
|
|
138
139
|
version: ctx.schema.version,
|
|
139
140
|
indexedDB: this.indexedDB,
|
|
@@ -145,7 +146,7 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
applyMigration = async (
|
|
148
|
-
ctx:
|
|
149
|
+
ctx: ContextWithoutPersistence,
|
|
149
150
|
migration: Migration<any>,
|
|
150
151
|
): Promise<void> => {
|
|
151
152
|
ctx.log(
|
|
@@ -167,6 +168,11 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
for (const collection of migration.allCollections) {
|
|
171
|
+
if (!db.objectStoreNames.contains(collection)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Expected object store for collection ${collection} to exist during migration, but it did not`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
170
176
|
const store = transaction.objectStore(collection);
|
|
171
177
|
// apply new indexes
|
|
172
178
|
for (const newIndex of migration.addedIndexes[collection] || []) {
|
|
@@ -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,
|