@verdant-web/store 4.0.0 → 4.1.0-alpha.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/LICENSE +21 -650
- package/dist/bundle/index.js +11 -11
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -2
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -5
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/client/Client.d.ts +6 -2
- package/dist/esm/client/Client.js +18 -6
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +7 -5
- package/dist/esm/client/ClientDescriptor.js +18 -4
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/context/ShutdownHandler.d.ts +8 -0
- package/dist/esm/context/ShutdownHandler.js +24 -0
- package/dist/esm/context/ShutdownHandler.js.map +1 -0
- package/dist/esm/context/context.d.ts +15 -4
- package/dist/esm/entities/EntityStore.js +6 -3
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/files/EntityFile.d.ts +1 -0
- package/dist/esm/files/EntityFile.js +16 -11
- package/dist/esm/files/EntityFile.js.map +1 -1
- package/dist/esm/files/FileManager.d.ts +1 -3
- package/dist/esm/files/FileManager.js +12 -10
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/index.d.ts +4 -5
- package/dist/esm/index.js +2 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.d.ts +6 -0
- package/dist/esm/internal.js +5 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/persistence/MessageCreator.d.ts +3 -1
- package/dist/esm/persistence/MessageCreator.js +58 -55
- package/dist/esm/persistence/MessageCreator.js.map +1 -1
- package/dist/esm/persistence/PersistenceFiles.d.ts +8 -21
- package/dist/esm/persistence/PersistenceFiles.js +44 -30
- package/dist/esm/persistence/PersistenceFiles.js.map +1 -1
- package/dist/esm/persistence/PersistenceMetadata.d.ts +12 -11
- package/dist/esm/persistence/PersistenceMetadata.js +201 -137
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
- package/dist/esm/persistence/PersistenceQueries.d.ts +10 -11
- package/dist/esm/persistence/PersistenceQueries.js +33 -5
- package/dist/esm/persistence/PersistenceQueries.js.map +1 -1
- package/dist/esm/persistence/PersistenceRebaser.d.ts +5 -9
- package/dist/esm/persistence/PersistenceRebaser.js +63 -47
- package/dist/esm/persistence/PersistenceRebaser.js.map +1 -1
- package/dist/esm/persistence/idb/IdbService.d.ts +0 -1
- package/dist/esm/persistence/idb/IdbService.js +28 -16
- package/dist/esm/persistence/idb/IdbService.js.map +1 -1
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +11 -31
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js +31 -36
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -1
- package/dist/esm/persistence/idb/idbPersistence.d.ts +17 -9
- package/dist/esm/persistence/idb/idbPersistence.js +80 -39
- package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +7 -10
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +45 -71
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.d.ts +1 -12
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js +3 -56
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/esm/persistence/idb/queries/{IdbQueryDb.d.ts → IdbDocumentDb.d.ts} +7 -13
- package/dist/esm/persistence/idb/queries/{IdbQueryDb.js → IdbDocumentDb.js} +15 -32
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -0
- package/dist/esm/persistence/idb/queries/migration/db.d.ts +3 -5
- package/dist/esm/persistence/idb/queries/migration/db.js +13 -28
- package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
- package/dist/esm/persistence/idb/util.d.ts +8 -4
- package/dist/esm/persistence/idb/util.js +64 -21
- package/dist/esm/persistence/idb/util.js.map +1 -1
- package/dist/esm/persistence/interfaces.d.ts +68 -75
- package/dist/esm/persistence/{idb/queries/migration → migration}/engine.d.ts +4 -7
- package/dist/esm/persistence/{idb/queries/migration → migration}/engine.js +18 -10
- package/dist/esm/persistence/migration/engine.js.map +1 -0
- package/dist/esm/persistence/migration/finalize.d.ts +9 -0
- package/dist/esm/persistence/migration/finalize.js +75 -0
- package/dist/esm/persistence/migration/finalize.js.map +1 -0
- package/dist/esm/persistence/migration/migrate.d.ts +12 -0
- package/dist/esm/persistence/migration/migrate.js +89 -0
- package/dist/esm/persistence/migration/migrate.js.map +1 -0
- package/dist/esm/persistence/migration/paths.js.map +1 -0
- package/dist/esm/persistence/migration/paths.test.js.map +1 -0
- package/dist/esm/persistence/migration/types.d.ts +3 -0
- package/dist/esm/persistence/migration/types.js.map +1 -0
- package/dist/esm/persistence/persistence.js +25 -15
- 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/FileSync.js +3 -3
- package/dist/esm/sync/FileSync.js.map +1 -1
- package/dist/esm/sync/PushPullSync.d.ts +2 -3
- package/dist/esm/sync/PushPullSync.js +4 -2
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/ServerSyncEndpointProvider.d.ts +3 -7
- package/dist/esm/sync/ServerSyncEndpointProvider.js +3 -2
- package/dist/esm/sync/ServerSyncEndpointProvider.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +6 -1
- package/dist/esm/sync/Sync.js +12 -4
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +10 -4
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/package.json +6 -2
- package/src/__tests__/fixtures/testStorage.ts +6 -6
- package/src/client/Client.ts +26 -8
- package/src/client/ClientDescriptor.ts +27 -9
- package/src/context/ShutdownHandler.ts +26 -0
- package/src/context/context.ts +16 -4
- package/src/entities/EntityStore.ts +9 -3
- package/src/files/EntityFile.ts +11 -6
- package/src/files/FileManager.ts +13 -10
- package/src/index.ts +8 -9
- package/src/internal.ts +27 -0
- package/src/persistence/MessageCreator.ts +79 -73
- package/src/persistence/PersistenceFiles.ts +57 -31
- package/src/persistence/PersistenceMetadata.ts +287 -195
- package/src/persistence/PersistenceQueries.ts +45 -9
- package/src/persistence/PersistenceRebaser.ts +105 -70
- package/src/persistence/idb/IdbService.ts +40 -22
- package/src/persistence/idb/files/IdbPersistenceFileDb.ts +30 -62
- package/src/persistence/idb/idbPersistence.ts +123 -47
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +75 -97
- package/src/persistence/idb/metadata/openMetadataDatabase.ts +2 -96
- package/src/persistence/idb/queries/{IdbQueryDb.ts → IdbDocumentDb.ts} +17 -57
- package/src/persistence/idb/queries/migration/db.ts +20 -39
- package/src/persistence/idb/util.ts +84 -21
- package/src/persistence/interfaces.ts +89 -90
- package/src/persistence/{idb/queries/migration → migration}/engine.ts +30 -15
- package/src/persistence/migration/finalize.ts +126 -0
- package/src/persistence/migration/migrate.ts +169 -0
- package/src/persistence/migration/types.ts +4 -0
- package/src/persistence/persistence.ts +37 -14
- package/src/queries/FindAllQuery.ts +1 -1
- package/src/queries/FindInfiniteQuery.ts +2 -2
- package/src/queries/FindOneQuery.ts +1 -1
- package/src/queries/FindPageQuery.ts +1 -1
- package/src/sync/FileSync.ts +21 -15
- package/src/sync/PushPullSync.ts +3 -4
- package/src/sync/ServerSyncEndpointProvider.ts +6 -8
- package/src/sync/Sync.ts +20 -7
- package/src/sync/WebSocketSync.ts +10 -4
- package/dist/esm/client/constants.d.ts +0 -1
- package/dist/esm/client/constants.js +0 -2
- package/dist/esm/client/constants.js.map +0 -1
- package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/engine.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/migrations.d.ts +0 -15
- package/dist/esm/persistence/idb/queries/migration/migrations.js +0 -243
- package/dist/esm/persistence/idb/queries/migration/migrations.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.d.ts +0 -8
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js +0 -24
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/paths.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/types.d.ts +0 -6
- package/dist/esm/persistence/idb/queries/migration/types.js.map +0 -1
- package/src/client/constants.ts +0 -1
- package/src/persistence/idb/queries/migration/migrations.ts +0 -345
- package/src/persistence/idb/queries/migration/openQueryDatabase.ts +0 -54
- package/src/persistence/idb/queries/migration/types.ts +0 -8
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.d.ts +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.js +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.d.ts +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.js +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/types.js +0 -0
- /package/src/persistence/{idb/queries/migration → migration}/paths.test.ts +0 -0
- /package/src/persistence/{idb/queries/migration → migration}/paths.ts +0 -0
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
AuthorizationKey,
|
|
15
15
|
} from '@verdant-web/common';
|
|
16
16
|
import { OpenDocumentDbContext } from './types.js';
|
|
17
|
-
import {
|
|
17
|
+
import { PersistenceDocumentDb, PersistenceNamespace } from '../interfaces.js';
|
|
18
18
|
|
|
19
19
|
function getMigrationMutations({
|
|
20
20
|
migration,
|
|
@@ -61,14 +61,12 @@ function getMigrationMutations({
|
|
|
61
61
|
function getMigrationQueries({
|
|
62
62
|
migration,
|
|
63
63
|
context,
|
|
64
|
-
|
|
64
|
+
documents,
|
|
65
65
|
}: {
|
|
66
66
|
migration: Migration<any>;
|
|
67
67
|
context: OpenDocumentDbContext;
|
|
68
|
-
|
|
68
|
+
documents: PersistenceDocumentDb;
|
|
69
69
|
}) {
|
|
70
|
-
const queries = new IdbQueryDb(queryDb, context);
|
|
71
|
-
|
|
72
70
|
return migration.oldCollections.reduce((acc, collectionName) => {
|
|
73
71
|
acc[collectionName] = {
|
|
74
72
|
get: async (id: string) => {
|
|
@@ -80,7 +78,7 @@ function getMigrationQueries({
|
|
|
80
78
|
return doc;
|
|
81
79
|
},
|
|
82
80
|
findOne: async (filter: CollectionFilter) => {
|
|
83
|
-
const oid = await
|
|
81
|
+
const oid = await documents.findOneOid({
|
|
84
82
|
collection: collectionName,
|
|
85
83
|
index: filter,
|
|
86
84
|
});
|
|
@@ -92,7 +90,7 @@ function getMigrationQueries({
|
|
|
92
90
|
return doc;
|
|
93
91
|
},
|
|
94
92
|
findAll: async (filter: CollectionFilter) => {
|
|
95
|
-
const { result: oids } = await
|
|
93
|
+
const { result: oids } = await documents.findAllOids({
|
|
96
94
|
collection: collectionName,
|
|
97
95
|
index: filter,
|
|
98
96
|
});
|
|
@@ -111,27 +109,36 @@ function getMigrationQueries({
|
|
|
111
109
|
}, {} as any);
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
export function getMigrationEngine({
|
|
112
|
+
export async function getMigrationEngine({
|
|
115
113
|
migration,
|
|
116
114
|
context,
|
|
117
|
-
|
|
115
|
+
ns,
|
|
118
116
|
}: {
|
|
119
117
|
log?: (...args: any[]) => void;
|
|
120
118
|
migration: Migration;
|
|
121
119
|
context: OpenDocumentDbContext;
|
|
122
|
-
|
|
123
|
-
}): MigrationEngine {
|
|
120
|
+
ns: PersistenceNamespace;
|
|
121
|
+
}): Promise<MigrationEngine> {
|
|
122
|
+
const migrationContext = {
|
|
123
|
+
...context,
|
|
124
|
+
schema: migration.oldSchema,
|
|
125
|
+
};
|
|
126
|
+
if (migration.oldSchema.version === 0) {
|
|
127
|
+
return getInitialMigrationEngine({ migration, context: migrationContext });
|
|
128
|
+
}
|
|
129
|
+
|
|
124
130
|
const newOids = new Array<ObjectIdentifier>();
|
|
125
131
|
|
|
132
|
+
const documents = await ns.openDocuments(migrationContext);
|
|
126
133
|
const queries = getMigrationQueries({
|
|
127
134
|
migration,
|
|
128
|
-
context,
|
|
129
|
-
|
|
135
|
+
context: migrationContext,
|
|
136
|
+
documents,
|
|
130
137
|
});
|
|
131
138
|
const mutations = getMigrationMutations({
|
|
132
139
|
migration,
|
|
133
140
|
newOids,
|
|
134
|
-
ctx:
|
|
141
|
+
ctx: migrationContext,
|
|
135
142
|
});
|
|
136
143
|
const deleteCollection = async (collection: string) => {
|
|
137
144
|
await context.meta.deleteCollection(collection);
|
|
@@ -143,6 +150,10 @@ export function getMigrationEngine({
|
|
|
143
150
|
deleteCollection,
|
|
144
151
|
migrate: async (collection, strategy) => {
|
|
145
152
|
const docs = await queries[collection].findAll();
|
|
153
|
+
context.log(
|
|
154
|
+
'debug',
|
|
155
|
+
`Migrating ${docs.length} documents in ${collection}`,
|
|
156
|
+
);
|
|
146
157
|
|
|
147
158
|
await Promise.all(
|
|
148
159
|
docs.filter(Boolean).map(async (doc: any) => {
|
|
@@ -190,11 +201,14 @@ export function getMigrationEngine({
|
|
|
190
201
|
queries,
|
|
191
202
|
mutations,
|
|
192
203
|
awaitables,
|
|
204
|
+
close: async () => {
|
|
205
|
+
await documents.close();
|
|
206
|
+
},
|
|
193
207
|
};
|
|
194
208
|
return engine;
|
|
195
209
|
}
|
|
196
210
|
|
|
197
|
-
|
|
211
|
+
function getInitialMigrationEngine({
|
|
198
212
|
migration,
|
|
199
213
|
context,
|
|
200
214
|
}: {
|
|
@@ -232,6 +246,7 @@ export function getInitialMigrationEngine({
|
|
|
232
246
|
queries,
|
|
233
247
|
mutations,
|
|
234
248
|
awaitables: [],
|
|
249
|
+
close: () => Promise.resolve(),
|
|
235
250
|
};
|
|
236
251
|
return engine;
|
|
237
252
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decomposeOid,
|
|
3
|
+
getOidRoot,
|
|
4
|
+
Migration,
|
|
5
|
+
MigrationEngine,
|
|
6
|
+
} from '@verdant-web/common';
|
|
7
|
+
import { ClientOperation, PersistenceDocumentDb } from '../interfaces.js';
|
|
8
|
+
import { OpenDocumentDbContext } from './types.js';
|
|
9
|
+
|
|
10
|
+
export async function finalizeMigration({
|
|
11
|
+
ctx,
|
|
12
|
+
documents,
|
|
13
|
+
migration,
|
|
14
|
+
engine,
|
|
15
|
+
}: {
|
|
16
|
+
ctx: OpenDocumentDbContext;
|
|
17
|
+
documents: PersistenceDocumentDb;
|
|
18
|
+
migration: Migration<any>;
|
|
19
|
+
engine: MigrationEngine;
|
|
20
|
+
}) {
|
|
21
|
+
/**
|
|
22
|
+
* In cases where operations from the future have been
|
|
23
|
+
* received by this client, we may have created entire
|
|
24
|
+
* documents in metadata which were not written to storage
|
|
25
|
+
* because all of their operations were in the future (
|
|
26
|
+
* i.e. in the next version). We have to find those documents
|
|
27
|
+
* and also write their snapshots to storage, because they
|
|
28
|
+
* won't be present in storage already to 'refresh,' so
|
|
29
|
+
* if we don't analyze metadata for 'future' operations like
|
|
30
|
+
* this, we won't know they exist.
|
|
31
|
+
*
|
|
32
|
+
* This led to behavior where the metadata would be properly
|
|
33
|
+
* synced, but after upgrading the app and migrating, items
|
|
34
|
+
* would be missing from findAll and findOne queries.
|
|
35
|
+
*/
|
|
36
|
+
const docsWithUnappliedMigrations = await getDocsWithUnappliedMigrations({
|
|
37
|
+
currentVersion: migration.oldSchema.version,
|
|
38
|
+
newVersion: migration.newSchema.version,
|
|
39
|
+
ctx,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// once the schema is ready, we can write back the migrated documents
|
|
43
|
+
|
|
44
|
+
for (const collection of migration.allCollections) {
|
|
45
|
+
// map the keys to OIDs
|
|
46
|
+
const { result: oids } = await documents.findAllOids({
|
|
47
|
+
collection,
|
|
48
|
+
});
|
|
49
|
+
oids.push(
|
|
50
|
+
...engine.newOids.filter((oid) => {
|
|
51
|
+
return decomposeOid(oid).collection === collection;
|
|
52
|
+
}),
|
|
53
|
+
...docsWithUnappliedMigrations.filter((oid) => {
|
|
54
|
+
return decomposeOid(oid).collection === collection;
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const snapshots = await Promise.all(
|
|
59
|
+
oids.map(async (oid) => {
|
|
60
|
+
try {
|
|
61
|
+
const snap = await ctx.meta.getDocumentSnapshot(oid);
|
|
62
|
+
return [oid, snap];
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// this seems to happen with baselines/ops which are not fully
|
|
65
|
+
// cleaned up after deletion?
|
|
66
|
+
ctx.log(
|
|
67
|
+
'error',
|
|
68
|
+
'Could not regenerate snapshot during migration for oid',
|
|
69
|
+
oid,
|
|
70
|
+
'this document will not be preserved',
|
|
71
|
+
e,
|
|
72
|
+
);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const views: [string, any][] = snapshots.filter(
|
|
79
|
+
(s: any): s is [string, any] => !!s,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// now we can write the documents back
|
|
83
|
+
await documents.saveEntities(
|
|
84
|
+
views.map(([oid, snapshot]) => ({
|
|
85
|
+
oid,
|
|
86
|
+
getSnapshot() {
|
|
87
|
+
return snapshot;
|
|
88
|
+
},
|
|
89
|
+
})),
|
|
90
|
+
{
|
|
91
|
+
collections: [collection],
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Gets a list of root OIDs for all documents which had operations stored already
|
|
99
|
+
* that were not applied to their queryable snapshots because they were in the
|
|
100
|
+
* future. These documents need to be refreshed in storage.
|
|
101
|
+
*/
|
|
102
|
+
async function getDocsWithUnappliedMigrations({
|
|
103
|
+
currentVersion,
|
|
104
|
+
newVersion: _,
|
|
105
|
+
ctx,
|
|
106
|
+
}: {
|
|
107
|
+
currentVersion: number;
|
|
108
|
+
newVersion: number;
|
|
109
|
+
ctx: OpenDocumentDbContext;
|
|
110
|
+
}) {
|
|
111
|
+
// scan for all operations in metadata after the current version.
|
|
112
|
+
// this could be more efficient if also filtering below or equal newVersion but
|
|
113
|
+
// that seems so unlikely in practice...
|
|
114
|
+
const unappliedOperations: ClientOperation[] = [];
|
|
115
|
+
await ctx.meta.iterateAllOperations(
|
|
116
|
+
(op) => {
|
|
117
|
+
unappliedOperations.push(op);
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
from: ctx.time.zeroWithVersion(currentVersion + 1),
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
return Array.from(
|
|
124
|
+
new Set(unappliedOperations.map((op) => getOidRoot(op.oid))),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Migration } from '@verdant-web/common';
|
|
2
|
+
import { getMigrationPath } from './paths.js';
|
|
3
|
+
import { OpenDocumentDbContext } from './types.js';
|
|
4
|
+
import { getMigrationEngine } from './engine.js';
|
|
5
|
+
import { finalizeMigration } from './finalize.js';
|
|
6
|
+
import { PersistenceNamespace } from '../interfaces.js';
|
|
7
|
+
import { ShutdownHandler } from '../../context/ShutdownHandler.js';
|
|
8
|
+
|
|
9
|
+
export async function migrate({
|
|
10
|
+
context,
|
|
11
|
+
version,
|
|
12
|
+
}: {
|
|
13
|
+
context: OpenDocumentDbContext;
|
|
14
|
+
version: number;
|
|
15
|
+
}) {
|
|
16
|
+
const ns = await context.persistence.openNamespace(
|
|
17
|
+
context.namespace,
|
|
18
|
+
context,
|
|
19
|
+
);
|
|
20
|
+
await acquireLock(context.namespace, async () => {
|
|
21
|
+
const currentVersion = await context.persistence.getNamespaceVersion(
|
|
22
|
+
context.namespace,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
context.log(
|
|
26
|
+
'debug',
|
|
27
|
+
'Opening index database',
|
|
28
|
+
context.namespace,
|
|
29
|
+
'Current database version:',
|
|
30
|
+
currentVersion,
|
|
31
|
+
'target version:',
|
|
32
|
+
version,
|
|
33
|
+
context.schema.wip ? '(wip)' : '',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const toRun = getMigrationPath({
|
|
37
|
+
currentVersion,
|
|
38
|
+
targetVersion: version,
|
|
39
|
+
migrations: context.migrations,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (toRun.length > 0) {
|
|
43
|
+
context.log(
|
|
44
|
+
'debug',
|
|
45
|
+
'Migrations to run:',
|
|
46
|
+
toRun.map((m) => m.version),
|
|
47
|
+
);
|
|
48
|
+
await runMigrations({ context, ns, toRun });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function acquireLock(namespace: string, procedure: () => Promise<void>) {
|
|
54
|
+
if (typeof navigator !== 'undefined' && navigator.locks) {
|
|
55
|
+
await navigator.locks.request(`verdant_migration_${namespace}`, procedure);
|
|
56
|
+
} else {
|
|
57
|
+
// TODO: is there a fallback?
|
|
58
|
+
await procedure();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function runMigrations({
|
|
63
|
+
context,
|
|
64
|
+
toRun,
|
|
65
|
+
ns,
|
|
66
|
+
}: {
|
|
67
|
+
context: OpenDocumentDbContext;
|
|
68
|
+
toRun: Migration<any>[];
|
|
69
|
+
ns: PersistenceNamespace;
|
|
70
|
+
}) {
|
|
71
|
+
// disable rebasing for the duration of migrations
|
|
72
|
+
context.pauseRebasing = true;
|
|
73
|
+
// now the fun part
|
|
74
|
+
for (const migration of toRun) {
|
|
75
|
+
context.log(
|
|
76
|
+
'info',
|
|
77
|
+
`🚀 Running migration v${migration.oldSchema.version} -> v${migration.newSchema.version}`,
|
|
78
|
+
);
|
|
79
|
+
const migrationContext = {
|
|
80
|
+
...context,
|
|
81
|
+
schema: migration.oldSchema,
|
|
82
|
+
shutdownHandler: new ShutdownHandler(),
|
|
83
|
+
};
|
|
84
|
+
// this will only write to our metadata store via operations!
|
|
85
|
+
const engine = await getMigrationEngine({
|
|
86
|
+
migration,
|
|
87
|
+
context: migrationContext,
|
|
88
|
+
ns,
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
context.log(
|
|
92
|
+
'debug',
|
|
93
|
+
'Migrating data',
|
|
94
|
+
migrationContext.namespace,
|
|
95
|
+
'from version',
|
|
96
|
+
migration.oldSchema.version,
|
|
97
|
+
'to version',
|
|
98
|
+
migration.newSchema.version,
|
|
99
|
+
);
|
|
100
|
+
await migration.migrate(engine);
|
|
101
|
+
// wait on any out-of-band async operations to complete
|
|
102
|
+
await Promise.all(engine.awaitables);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
context.log(
|
|
105
|
+
'critical',
|
|
106
|
+
`Migration failed (${migration.oldSchema.version} -> ${migration.newSchema.version})`,
|
|
107
|
+
err,
|
|
108
|
+
);
|
|
109
|
+
if (err instanceof Error) {
|
|
110
|
+
throw err;
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error('Unknown error during migration');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await engine.close();
|
|
117
|
+
|
|
118
|
+
migrationContext.log(
|
|
119
|
+
'debug',
|
|
120
|
+
'Upgrading database',
|
|
121
|
+
migrationContext.namespace,
|
|
122
|
+
'from version',
|
|
123
|
+
migrationContext.schema.version,
|
|
124
|
+
'to version',
|
|
125
|
+
migration.newSchema.version,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await ns.applyMigration(migrationContext, migration);
|
|
129
|
+
|
|
130
|
+
// switch to the new schema
|
|
131
|
+
migrationContext.schema = migration.newSchema;
|
|
132
|
+
const upgradedDocuments = await ns.openDocuments(migrationContext);
|
|
133
|
+
|
|
134
|
+
await finalizeMigration({
|
|
135
|
+
ctx: migrationContext,
|
|
136
|
+
migration,
|
|
137
|
+
engine,
|
|
138
|
+
documents: upgradedDocuments,
|
|
139
|
+
});
|
|
140
|
+
await upgradedDocuments.close();
|
|
141
|
+
|
|
142
|
+
migrationContext.log(
|
|
143
|
+
'debug',
|
|
144
|
+
`Migration of ${migrationContext.namespace} complete.`,
|
|
145
|
+
);
|
|
146
|
+
migrationContext.log(
|
|
147
|
+
'info',
|
|
148
|
+
`
|
|
149
|
+
⬆️ v${migration.newSchema.version} Migration complete. Here's the rundown:
|
|
150
|
+
- Added collections: ${migration.addedCollections.join(', ')}
|
|
151
|
+
- Removed collections: ${migration.removedCollections.join(', ')}
|
|
152
|
+
- Changed collections: ${migration.changedCollections.join(', ')}
|
|
153
|
+
- New indexes: ${Object.keys(migration.addedIndexes)
|
|
154
|
+
.map((col) =>
|
|
155
|
+
migration.addedIndexes[col].map((i) => `${col}.${i.name}`),
|
|
156
|
+
)
|
|
157
|
+
.flatMap((i) => i)
|
|
158
|
+
.join(', ')}
|
|
159
|
+
- Removed indexes: ${Object.keys(migration.removedIndexes)
|
|
160
|
+
.map((col) =>
|
|
161
|
+
migration.removedIndexes[col].map((i) => `${col}.${i.name}`),
|
|
162
|
+
)
|
|
163
|
+
.flatMap((i) => i)
|
|
164
|
+
.join(', ')}
|
|
165
|
+
`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
context.pauseRebasing = false;
|
|
169
|
+
}
|
|
@@ -4,7 +4,9 @@ import { getWipNamespace } from '../utils/wip.js';
|
|
|
4
4
|
import { ExportedData } from './interfaces.js';
|
|
5
5
|
import { PersistenceFiles } from './PersistenceFiles.js';
|
|
6
6
|
import { PersistenceMetadata } from './PersistenceMetadata.js';
|
|
7
|
-
import {
|
|
7
|
+
import { PersistenceDocuments } from './PersistenceQueries.js';
|
|
8
|
+
import { migrate } from './migration/migrate.js';
|
|
9
|
+
import { ShutdownHandler } from '../context/ShutdownHandler.js';
|
|
8
10
|
|
|
9
11
|
export async function initializePersistence(
|
|
10
12
|
ctx: InitialContext,
|
|
@@ -27,6 +29,7 @@ export async function initializePersistence(
|
|
|
27
29
|
|
|
28
30
|
if (currentVersion === 0) {
|
|
29
31
|
// there is no existing data. nothing to copy.
|
|
32
|
+
context.log('debug', 'No existing data to copy to WIP namespace');
|
|
30
33
|
} else {
|
|
31
34
|
const currentSchema = ctx.oldSchemas?.find(
|
|
32
35
|
(s) => s.version === currentVersion,
|
|
@@ -45,6 +48,9 @@ export async function initializePersistence(
|
|
|
45
48
|
await context.persistence.copyNamespace(
|
|
46
49
|
context.originalNamespace,
|
|
47
50
|
context.namespace,
|
|
51
|
+
// needs to be the original schema; the copy should be of the original
|
|
52
|
+
// data and schema structure; the WIP schema migration application happens
|
|
53
|
+
// below.
|
|
48
54
|
{
|
|
49
55
|
...context,
|
|
50
56
|
schema: currentSchema,
|
|
@@ -54,21 +60,32 @@ export async function initializePersistence(
|
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
const namespace = await ctx.persistence.openNamespace(
|
|
64
|
+
context.namespace,
|
|
65
|
+
context,
|
|
66
|
+
);
|
|
67
|
+
|
|
57
68
|
context.log('info', 'Opening persistence metadata');
|
|
58
69
|
context.meta = new PersistenceMetadata(
|
|
59
|
-
await
|
|
70
|
+
await namespace.openMetadata(ctx),
|
|
60
71
|
ctx,
|
|
61
72
|
);
|
|
62
73
|
|
|
63
74
|
context.log('info', 'Opening persistence files');
|
|
64
75
|
context.files = new PersistenceFiles(
|
|
65
|
-
await
|
|
76
|
+
await namespace.openFiles(context),
|
|
66
77
|
context,
|
|
67
78
|
);
|
|
68
79
|
|
|
69
|
-
context.log('info', '
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
context.log('info', 'Migrating document database');
|
|
81
|
+
await migrate({
|
|
82
|
+
context,
|
|
83
|
+
version: ctx.schema.version,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
context.log('info', 'Opening persistence documents');
|
|
87
|
+
context.documents = new PersistenceDocuments(
|
|
88
|
+
await namespace.openDocuments(context),
|
|
72
89
|
context,
|
|
73
90
|
);
|
|
74
91
|
|
|
@@ -120,6 +137,7 @@ export async function importPersistence(
|
|
|
120
137
|
disableRebasing: true,
|
|
121
138
|
},
|
|
122
139
|
},
|
|
140
|
+
persistenceShutdownHandler: new ShutdownHandler(),
|
|
123
141
|
});
|
|
124
142
|
// load imported data into persistence
|
|
125
143
|
await importedContext.meta.resetFrom(exportedData.data);
|
|
@@ -140,40 +158,42 @@ export async function importPersistence(
|
|
|
140
158
|
};
|
|
141
159
|
}),
|
|
142
160
|
);
|
|
143
|
-
await importedContext.
|
|
161
|
+
await importedContext.documents.saveEntities(toSave);
|
|
144
162
|
await importedContext.files.import(exportedData);
|
|
145
163
|
|
|
146
164
|
ctx.log('debug', 'Imported data into temporary namespace', importedNamespace);
|
|
147
165
|
|
|
148
166
|
// shut down the imported databases
|
|
149
|
-
await importedContext.
|
|
150
|
-
await importedContext.meta.dispose();
|
|
151
|
-
await importedContext.files.dispose();
|
|
167
|
+
await importedContext.persistenceShutdownHandler.shutdown();
|
|
152
168
|
|
|
153
169
|
if (exportedSchema.version !== ctx.schema.version) {
|
|
154
170
|
// an upgrade of the imported data is needed ; it's an older version
|
|
155
171
|
// of the schema.
|
|
156
|
-
ctx.log('debug', 'Shut down imported databases');
|
|
157
172
|
|
|
158
173
|
// upgrade the imported data to the latest schema
|
|
159
174
|
const currentSchema = ctx.schema;
|
|
160
175
|
const upgradedContext = await initializePersistence({
|
|
161
176
|
...importedContext,
|
|
177
|
+
persistenceShutdownHandler: new ShutdownHandler(),
|
|
162
178
|
schema: currentSchema,
|
|
163
179
|
});
|
|
164
180
|
|
|
165
181
|
ctx.log('debug', 'Upgraded imported data to current schema');
|
|
166
182
|
|
|
167
|
-
await upgradedContext.
|
|
168
|
-
await upgradedContext.meta.dispose();
|
|
169
|
-
await upgradedContext.files.dispose();
|
|
183
|
+
await upgradedContext.persistenceShutdownHandler.shutdown();
|
|
170
184
|
|
|
171
185
|
ctx.log('debug', 'Shut down upgraded databases');
|
|
172
186
|
}
|
|
173
187
|
|
|
188
|
+
// shut down the persistence layer
|
|
189
|
+
await ctx.persistenceShutdownHandler.shutdown();
|
|
190
|
+
|
|
174
191
|
// copy the imported data into the current namespace
|
|
175
192
|
await ctx.persistence.copyNamespace(importedNamespace, ctx.namespace, ctx);
|
|
176
193
|
|
|
194
|
+
// restart the persistence layer
|
|
195
|
+
await initializePersistence(ctx);
|
|
196
|
+
|
|
177
197
|
// verify integrity -- this can only be done if imported data was same
|
|
178
198
|
// version as current schema, because migrations could add or remove
|
|
179
199
|
// operations. still, it's a good sanity check.
|
|
@@ -220,4 +240,7 @@ export async function importPersistence(
|
|
|
220
240
|
|
|
221
241
|
ctx.internalEvents.emit('persistenceReset');
|
|
222
242
|
ctx.log('info', 'Data imported successfully');
|
|
243
|
+
|
|
244
|
+
// reset to allow future shutdowns.
|
|
245
|
+
ctx.persistenceShutdownHandler.reset();
|
|
223
246
|
}
|
|
@@ -23,7 +23,7 @@ export class FindAllQuery<T> extends BaseQuery<T[]> {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
protected run = async () => {
|
|
26
|
-
const { result: oids } = await this.context.
|
|
26
|
+
const { result: oids } = await this.context.documents.findAllOids({
|
|
27
27
|
collection: this.collection,
|
|
28
28
|
index: this.index,
|
|
29
29
|
});
|
|
@@ -37,7 +37,7 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
protected run = async () => {
|
|
40
|
-
const { result, hasNextPage } = await this.context.
|
|
40
|
+
const { result, hasNextPage } = await this.context.documents.findAllOids({
|
|
41
41
|
collection: this.collection,
|
|
42
42
|
limit: this._pageSize * this._upToPage,
|
|
43
43
|
offset: 0,
|
|
@@ -48,7 +48,7 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
public loadMore = async () => {
|
|
51
|
-
const { result, hasNextPage } = await this.context.
|
|
51
|
+
const { result, hasNextPage } = await this.context.documents.findAllOids({
|
|
52
52
|
collection: this.collection,
|
|
53
53
|
limit: this._pageSize,
|
|
54
54
|
offset: this._pageSize * this._upToPage,
|
|
@@ -23,7 +23,7 @@ export class FindOneQuery<T> extends BaseQuery<T | null> {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
protected run = async () => {
|
|
26
|
-
const oid = await this.context.
|
|
26
|
+
const oid = await this.context.documents.findOneOid({
|
|
27
27
|
collection: this.collection,
|
|
28
28
|
index: this.index,
|
|
29
29
|
});
|
|
@@ -48,7 +48,7 @@ export class FindPageQuery<T> extends BaseQuery<T[]> {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
protected run = async () => {
|
|
51
|
-
const { result, hasNextPage } = await this.context.
|
|
51
|
+
const { result, hasNextPage } = await this.context.documents.findAllOids({
|
|
52
52
|
collection: this.collection,
|
|
53
53
|
index: this.index,
|
|
54
54
|
limit: this._pageSize,
|
package/src/sync/FileSync.ts
CHANGED
|
@@ -42,7 +42,7 @@ export class FileSync extends Disposable {
|
|
|
42
42
|
this.ctx.log('debug', 'Uploading file', data.id, data.name);
|
|
43
43
|
try {
|
|
44
44
|
await this.uploadFile(data);
|
|
45
|
-
this.ctx.internalEvents.emit(`fileUploaded:${data.id}
|
|
45
|
+
this.ctx.internalEvents.emit(`fileUploaded:${data.id}`, data);
|
|
46
46
|
} catch (e) {
|
|
47
47
|
this.ctx.log('error', 'File upload failed', e);
|
|
48
48
|
}
|
|
@@ -70,14 +70,17 @@ export class FileSync extends Disposable {
|
|
|
70
70
|
formData.append('file', file);
|
|
71
71
|
|
|
72
72
|
try {
|
|
73
|
-
const response = await fetch(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
const response = await this.ctx.environment.fetch(
|
|
74
|
+
fileEndpoint + `/${data.id}`,
|
|
75
|
+
{
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: formData,
|
|
78
|
+
credentials: 'include',
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${token}`,
|
|
81
|
+
},
|
|
79
82
|
},
|
|
80
|
-
|
|
83
|
+
);
|
|
81
84
|
|
|
82
85
|
if (response.ok) {
|
|
83
86
|
this.ctx.log('info', 'File upload successful');
|
|
@@ -132,14 +135,17 @@ export class FileSync extends Disposable {
|
|
|
132
135
|
await this.endpointProvider.getEndpoints();
|
|
133
136
|
|
|
134
137
|
try {
|
|
135
|
-
const response = await fetch(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
const response = await this.ctx.environment.fetch(
|
|
139
|
+
fileEndpoint + `/${id}`,
|
|
140
|
+
{
|
|
141
|
+
method: 'GET',
|
|
142
|
+
credentials: 'include',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
Authorization: `Bearer ${token}`,
|
|
146
|
+
},
|
|
141
147
|
},
|
|
142
|
-
|
|
148
|
+
);
|
|
143
149
|
|
|
144
150
|
if (response.ok) {
|
|
145
151
|
const data = await response.json();
|
package/src/sync/PushPullSync.ts
CHANGED
|
@@ -20,7 +20,9 @@ export class PushPullSync
|
|
|
20
20
|
readonly presence: PresenceManager;
|
|
21
21
|
private endpointProvider;
|
|
22
22
|
private heartbeat;
|
|
23
|
-
private fetch
|
|
23
|
+
private get fetch() {
|
|
24
|
+
return this.ctx.environment.fetch;
|
|
25
|
+
}
|
|
24
26
|
|
|
25
27
|
readonly mode = 'pull';
|
|
26
28
|
private ctx;
|
|
@@ -33,20 +35,17 @@ export class PushPullSync
|
|
|
33
35
|
endpointProvider,
|
|
34
36
|
presence,
|
|
35
37
|
interval = 15 * 1000,
|
|
36
|
-
fetch = window.fetch.bind(window),
|
|
37
38
|
ctx,
|
|
38
39
|
}: {
|
|
39
40
|
endpointProvider: ServerSyncEndpointProvider;
|
|
40
41
|
presence: PresenceManager;
|
|
41
42
|
interval?: number;
|
|
42
|
-
fetch?: typeof window.fetch;
|
|
43
43
|
ctx: Context;
|
|
44
44
|
}) {
|
|
45
45
|
super();
|
|
46
46
|
this.ctx = ctx;
|
|
47
47
|
this.presence = presence;
|
|
48
48
|
this.endpointProvider = endpointProvider;
|
|
49
|
-
this.fetch = fetch;
|
|
50
49
|
|
|
51
50
|
this.heartbeat = new Heartbeat({
|
|
52
51
|
interval,
|