@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
|
@@ -4,17 +4,20 @@ import {
|
|
|
4
4
|
Migration,
|
|
5
5
|
MigrationEngine,
|
|
6
6
|
} from '@verdant-web/common';
|
|
7
|
+
import { ContextWithoutPersistence } from '../../context/context.js';
|
|
7
8
|
import { ClientOperation, PersistenceDocumentDb } from '../interfaces.js';
|
|
8
|
-
import {
|
|
9
|
+
import { PersistenceMetadata } from '../PersistenceMetadata.js';
|
|
9
10
|
|
|
10
11
|
export async function finalizeMigration({
|
|
11
12
|
ctx,
|
|
12
13
|
documents,
|
|
13
14
|
migration,
|
|
15
|
+
meta,
|
|
14
16
|
engine,
|
|
15
17
|
}: {
|
|
16
|
-
ctx:
|
|
18
|
+
ctx: ContextWithoutPersistence;
|
|
17
19
|
documents: PersistenceDocumentDb;
|
|
20
|
+
meta: PersistenceMetadata;
|
|
18
21
|
migration: Migration<any>;
|
|
19
22
|
engine: MigrationEngine;
|
|
20
23
|
}) {
|
|
@@ -37,6 +40,7 @@ export async function finalizeMigration({
|
|
|
37
40
|
currentVersion: migration.oldSchema.version,
|
|
38
41
|
newVersion: migration.newSchema.version,
|
|
39
42
|
ctx,
|
|
43
|
+
meta,
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
// once the schema is ready, we can write back the migrated documents
|
|
@@ -58,7 +62,7 @@ export async function finalizeMigration({
|
|
|
58
62
|
const snapshots = await Promise.all(
|
|
59
63
|
oids.map(async (oid) => {
|
|
60
64
|
try {
|
|
61
|
-
const snap = await
|
|
65
|
+
const snap = await meta.getDocumentSnapshot(oid);
|
|
62
66
|
return [oid, snap];
|
|
63
67
|
} catch (e) {
|
|
64
68
|
// this seems to happen with baselines/ops which are not fully
|
|
@@ -103,16 +107,18 @@ async function getDocsWithUnappliedMigrations({
|
|
|
103
107
|
currentVersion,
|
|
104
108
|
newVersion: _,
|
|
105
109
|
ctx,
|
|
110
|
+
meta,
|
|
106
111
|
}: {
|
|
107
112
|
currentVersion: number;
|
|
108
113
|
newVersion: number;
|
|
109
|
-
ctx:
|
|
114
|
+
ctx: ContextWithoutPersistence;
|
|
115
|
+
meta: PersistenceMetadata;
|
|
110
116
|
}) {
|
|
111
117
|
// scan for all operations in metadata after the current version.
|
|
112
118
|
// this could be more efficient if also filtering below or equal newVersion but
|
|
113
119
|
// that seems so unlikely in practice...
|
|
114
120
|
const unappliedOperations: ClientOperation[] = [];
|
|
115
|
-
await
|
|
121
|
+
await meta.iterateAllOperations(
|
|
116
122
|
(op) => {
|
|
117
123
|
unappliedOperations.push(op);
|
|
118
124
|
},
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { Migration } from '@verdant-web/common';
|
|
2
|
+
import { ContextWithoutPersistence } from '../../context/context.js';
|
|
2
3
|
import { ShutdownHandler } from '../../context/ShutdownHandler.js';
|
|
3
4
|
import { PersistenceNamespace } from '../interfaces.js';
|
|
5
|
+
import { PersistenceMetadata } from '../PersistenceMetadata.js';
|
|
4
6
|
import { getMigrationEngine } from './engine.js';
|
|
5
7
|
import { finalizeMigration } from './finalize.js';
|
|
6
8
|
import { getMigrationPath } from './paths.js';
|
|
7
|
-
import { OpenDocumentDbContext } from './types.js';
|
|
8
9
|
|
|
9
10
|
export async function migrate({
|
|
10
11
|
context,
|
|
11
12
|
version,
|
|
13
|
+
meta,
|
|
12
14
|
}: {
|
|
13
|
-
context:
|
|
15
|
+
context: ContextWithoutPersistence;
|
|
16
|
+
meta: PersistenceMetadata;
|
|
14
17
|
version: number;
|
|
15
18
|
}) {
|
|
16
19
|
const ns = await context.persistence.openNamespace(
|
|
@@ -45,7 +48,7 @@ export async function migrate({
|
|
|
45
48
|
'Migrations to run:',
|
|
46
49
|
toRun.map((m) => m.version),
|
|
47
50
|
);
|
|
48
|
-
await runMigrations({ context, ns, toRun });
|
|
51
|
+
await runMigrations({ context, ns, toRun, meta });
|
|
49
52
|
}
|
|
50
53
|
});
|
|
51
54
|
}
|
|
@@ -63,10 +66,12 @@ export async function runMigrations({
|
|
|
63
66
|
context,
|
|
64
67
|
toRun,
|
|
65
68
|
ns,
|
|
69
|
+
meta,
|
|
66
70
|
}: {
|
|
67
|
-
context:
|
|
71
|
+
context: ContextWithoutPersistence;
|
|
68
72
|
toRun: Migration<any>[];
|
|
69
73
|
ns: PersistenceNamespace;
|
|
74
|
+
meta: PersistenceMetadata;
|
|
70
75
|
}) {
|
|
71
76
|
// disable rebasing for the duration of migrations
|
|
72
77
|
context.pauseRebasing = true;
|
|
@@ -76,16 +81,16 @@ export async function runMigrations({
|
|
|
76
81
|
'info',
|
|
77
82
|
`🚀 Running migration v${migration.oldSchema.version} -> v${migration.newSchema.version}`,
|
|
78
83
|
);
|
|
79
|
-
const migrationContext = {
|
|
80
|
-
...context,
|
|
84
|
+
const migrationContext = context.cloneWithOptions({
|
|
81
85
|
schema: migration.oldSchema,
|
|
82
|
-
|
|
83
|
-
};
|
|
86
|
+
persistenceShutdownHandler: new ShutdownHandler(context.log),
|
|
87
|
+
});
|
|
84
88
|
// this will only write to our metadata store via operations!
|
|
85
89
|
const engine = await getMigrationEngine({
|
|
86
90
|
migration,
|
|
87
91
|
context: migrationContext,
|
|
88
92
|
ns,
|
|
93
|
+
meta,
|
|
89
94
|
});
|
|
90
95
|
try {
|
|
91
96
|
context.log(
|
|
@@ -98,6 +103,7 @@ export async function runMigrations({
|
|
|
98
103
|
migration.newSchema.version,
|
|
99
104
|
);
|
|
100
105
|
await migration.migrate(engine);
|
|
106
|
+
context.log('debug', 'Awaiting remaining migration tasks');
|
|
101
107
|
// wait on any out-of-band async operations to complete
|
|
102
108
|
await Promise.all(engine.awaitables);
|
|
103
109
|
} catch (err) {
|
|
@@ -136,6 +142,7 @@ export async function runMigrations({
|
|
|
136
142
|
migration,
|
|
137
143
|
engine,
|
|
138
144
|
documents: upgradedDocuments,
|
|
145
|
+
meta,
|
|
139
146
|
});
|
|
140
147
|
await upgradedDocuments.close();
|
|
141
148
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Context
|
|
1
|
+
import { getOidRoot, VerdantError } from '@verdant-web/common';
|
|
2
|
+
import { Context } from '../context/context.js';
|
|
3
3
|
import { ShutdownHandler } from '../context/ShutdownHandler.js';
|
|
4
4
|
import { getWipNamespace } from '../utils/wip.js';
|
|
5
5
|
import { ExportedData } from './interfaces.js';
|
|
@@ -8,28 +8,26 @@ import { PersistenceFiles } from './PersistenceFiles.js';
|
|
|
8
8
|
import { PersistenceMetadata } from './PersistenceMetadata.js';
|
|
9
9
|
import { PersistenceDocuments } from './PersistenceQueries.js';
|
|
10
10
|
|
|
11
|
-
export async function initializePersistence(
|
|
12
|
-
ctx
|
|
13
|
-
): Promise<Context> {
|
|
14
|
-
let context = ctx as any as Context;
|
|
11
|
+
export async function initializePersistence(ctx: Context) {
|
|
12
|
+
const initialSchema = ctx.schema;
|
|
15
13
|
if (ctx.schema.wip) {
|
|
16
14
|
// this is a WIP database, so we need to create a new namespace for the WIP data.
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
ctx.namespace = getWipNamespace(ctx.originalNamespace, ctx.schema);
|
|
16
|
+
ctx.log('info', '🔨', 'Switched to WIP namespace', ctx.namespace);
|
|
19
17
|
// check if this WIP database is already in use
|
|
20
|
-
const namespaces = await
|
|
18
|
+
const namespaces = await ctx.persistence.getNamespaces();
|
|
21
19
|
|
|
22
|
-
if (!namespaces.includes(
|
|
20
|
+
if (!namespaces.includes(ctx.namespace)) {
|
|
23
21
|
// copy all data to WIP namespace -- from the current version of local
|
|
24
22
|
// data, not the WIP schema version. this may not be n-1, we might
|
|
25
23
|
// be loading a WIP schema over older data.
|
|
26
|
-
const currentVersion = await
|
|
27
|
-
|
|
24
|
+
const currentVersion = await ctx.persistence.getNamespaceVersion(
|
|
25
|
+
ctx.originalNamespace,
|
|
28
26
|
);
|
|
29
27
|
|
|
30
28
|
if (currentVersion === 0) {
|
|
31
29
|
// there is no existing data. nothing to copy.
|
|
32
|
-
|
|
30
|
+
ctx.log('debug', 'No existing data to copy to WIP namespace');
|
|
33
31
|
} else {
|
|
34
32
|
const currentSchema = ctx.oldSchemas?.find(
|
|
35
33
|
(s) => s.version === currentVersion,
|
|
@@ -41,52 +39,62 @@ export async function initializePersistence(
|
|
|
41
39
|
`Trying to open WIP database for version ${ctx.schema.version}, but the current local data is version ${currentVersion} and a historical schema for that version is not available.`,
|
|
42
40
|
);
|
|
43
41
|
}
|
|
44
|
-
|
|
42
|
+
ctx.log(
|
|
45
43
|
'info',
|
|
46
|
-
`Copying data from ${
|
|
44
|
+
`Copying data from ${ctx.originalNamespace} to ${ctx.namespace}`,
|
|
47
45
|
);
|
|
48
|
-
await
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
await ctx.persistence.copyNamespace(
|
|
47
|
+
ctx.originalNamespace,
|
|
48
|
+
ctx.namespace,
|
|
51
49
|
// needs to be the original schema; the copy should be of the original
|
|
52
50
|
// data and schema structure; the WIP schema migration application happens
|
|
53
51
|
// below.
|
|
54
|
-
{
|
|
55
|
-
...context,
|
|
52
|
+
ctx.cloneWithOptions({
|
|
56
53
|
schema: currentSchema,
|
|
57
|
-
},
|
|
54
|
+
}),
|
|
58
55
|
);
|
|
59
56
|
}
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
const namespace = await ctx.persistence.openNamespace(
|
|
64
|
-
context.namespace,
|
|
65
|
-
context,
|
|
66
|
-
);
|
|
60
|
+
const namespace = await ctx.persistence.openNamespace(ctx.namespace, ctx);
|
|
67
61
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await namespace.openMetadata(ctx),
|
|
71
|
-
ctx,
|
|
72
|
-
);
|
|
62
|
+
ctx.log('info', 'Opening persistence metadata', ctx.namespace);
|
|
63
|
+
const meta = new PersistenceMetadata(await namespace.openMetadata(ctx), ctx);
|
|
73
64
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
await namespace.openFiles(context),
|
|
77
|
-
context,
|
|
78
|
-
);
|
|
65
|
+
ctx.log('info', 'Opening persistence files', ctx.namespace);
|
|
66
|
+
const files = new PersistenceFiles(await namespace.openFiles(ctx), ctx);
|
|
79
67
|
|
|
80
|
-
|
|
68
|
+
ctx.log('info', 'Migrating document database');
|
|
81
69
|
await migrate({
|
|
82
|
-
context,
|
|
70
|
+
context: ctx,
|
|
83
71
|
version: ctx.schema.version,
|
|
72
|
+
meta,
|
|
84
73
|
});
|
|
85
74
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
ctx.log('info', 'Opening persistence documents');
|
|
76
|
+
if (ctx.schema.version <= 0) {
|
|
77
|
+
// debugging....
|
|
78
|
+
if (ctx.schema !== initialSchema) {
|
|
79
|
+
ctx.log(
|
|
80
|
+
'critical',
|
|
81
|
+
'Schema at initialization does not match original schema. This is likely a bug in Verdant!',
|
|
82
|
+
);
|
|
83
|
+
throw new VerdantError(
|
|
84
|
+
VerdantError.Code.ConfigurationError,
|
|
85
|
+
undefined,
|
|
86
|
+
`Schema at initialization does not match original schema. This is likely a bug in Verdant!`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
throw new VerdantError(
|
|
90
|
+
VerdantError.Code.ConfigurationError,
|
|
91
|
+
undefined,
|
|
92
|
+
`Schema version must be greater than 0. Found version ${ctx.schema.version} with collections [${Object.keys(ctx.schema.collections).join(', ')}]\n${JSON.stringify(ctx.schema)}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const documents = new PersistenceDocuments(
|
|
96
|
+
await namespace.openDocuments(ctx),
|
|
97
|
+
ctx,
|
|
90
98
|
);
|
|
91
99
|
|
|
92
100
|
if (!ctx.schema.wip) {
|
|
@@ -100,7 +108,7 @@ export async function initializePersistence(
|
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
return
|
|
111
|
+
return { meta, files, documents };
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
export async function importPersistence(
|
|
@@ -123,26 +131,17 @@ export async function importPersistence(
|
|
|
123
131
|
// using a new namespace to put all of this into a temporary zone
|
|
124
132
|
const importedNamespace = `@@import_${Date.now()}`;
|
|
125
133
|
|
|
126
|
-
const importedContext =
|
|
127
|
-
...ctx,
|
|
134
|
+
const importedContext = ctx.cloneWithOptions({
|
|
128
135
|
schema: exportedSchema,
|
|
129
136
|
namespace: importedNamespace,
|
|
130
|
-
|
|
131
|
-
// no-op entity events -- don't need to inform queries of changes.
|
|
132
|
-
entityEvents: new EventSubscriber(),
|
|
133
|
-
internalEvents: new EventSubscriber(),
|
|
134
|
-
globalEvents: new EventSubscriber(),
|
|
135
|
-
config: {
|
|
136
|
-
...ctx.config,
|
|
137
|
-
persistence: {
|
|
138
|
-
...ctx.config.persistence,
|
|
139
|
-
disableRebasing: true,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
137
|
+
disableRebasing: true,
|
|
142
138
|
persistenceShutdownHandler: new ShutdownHandler(ctx.log),
|
|
143
139
|
});
|
|
140
|
+
await importedContext.reinitialize();
|
|
141
|
+
const importedMeta = await importedContext.meta;
|
|
142
|
+
|
|
144
143
|
// load imported data into persistence
|
|
145
|
-
await
|
|
144
|
+
await importedMeta.resetFrom(exportedData.data);
|
|
146
145
|
// need to write indexes here!
|
|
147
146
|
const affectedOids = new Set<string>();
|
|
148
147
|
for (const baseline of exportedData.data.baselines) {
|
|
@@ -153,15 +152,15 @@ export async function importPersistence(
|
|
|
153
152
|
}
|
|
154
153
|
const toSave = await Promise.all(
|
|
155
154
|
Array.from(affectedOids).map(async (oid) => {
|
|
156
|
-
const snapshot = await
|
|
155
|
+
const snapshot = await importedMeta.getDocumentSnapshot(oid);
|
|
157
156
|
return {
|
|
158
157
|
oid,
|
|
159
158
|
getSnapshot: () => snapshot,
|
|
160
159
|
};
|
|
161
160
|
}),
|
|
162
161
|
);
|
|
163
|
-
await importedContext.documents.saveEntities(toSave);
|
|
164
|
-
await importedContext.files.import(exportedData);
|
|
162
|
+
await (await importedContext.documents).saveEntities(toSave);
|
|
163
|
+
await (await importedContext.files).import(exportedData);
|
|
165
164
|
|
|
166
165
|
ctx.log('debug', 'Imported data into temporary namespace', importedNamespace);
|
|
167
166
|
|
|
@@ -172,13 +171,14 @@ export async function importPersistence(
|
|
|
172
171
|
// an upgrade of the imported data is needed ; it's an older version
|
|
173
172
|
// of the schema.
|
|
174
173
|
|
|
175
|
-
// upgrade the imported data to the latest schema
|
|
176
|
-
|
|
177
|
-
const upgradedContext =
|
|
178
|
-
...importedContext,
|
|
174
|
+
// upgrade the imported data to the latest schema by re-initializing
|
|
175
|
+
// a context at the latest version, pointing at the imported namespace
|
|
176
|
+
const upgradedContext = importedContext.cloneWithOptions({
|
|
179
177
|
persistenceShutdownHandler: new ShutdownHandler(ctx.log),
|
|
180
|
-
schema:
|
|
178
|
+
schema: ctx.schema,
|
|
179
|
+
oldSchemas: ctx.oldSchemas,
|
|
181
180
|
});
|
|
181
|
+
await upgradedContext.reinitialize();
|
|
182
182
|
|
|
183
183
|
ctx.log('debug', 'Upgraded imported data to current schema');
|
|
184
184
|
|
|
@@ -192,15 +192,17 @@ export async function importPersistence(
|
|
|
192
192
|
|
|
193
193
|
// copy the imported data into the current namespace
|
|
194
194
|
await ctx.persistence.copyNamespace(importedNamespace, ctx.namespace, ctx);
|
|
195
|
+
ctx.log('debug', 'Copied imported data to primary namespace');
|
|
195
196
|
|
|
196
197
|
// restart the persistence layer
|
|
197
|
-
await
|
|
198
|
+
await ctx.reinitialize();
|
|
199
|
+
ctx.log('debug', 'Reinitialized primary persistence layer');
|
|
198
200
|
|
|
199
201
|
// verify integrity -- this can only be done if imported data was same
|
|
200
202
|
// version as current schema, because migrations could add or remove
|
|
201
203
|
// operations. still, it's a good sanity check.
|
|
202
204
|
if (exportedData.data.schemaVersion === ctx.schema.version) {
|
|
203
|
-
const stats = await ctx.meta.stats();
|
|
205
|
+
const stats = await (await ctx.meta).stats();
|
|
204
206
|
if (stats.operationsSize.count !== exportedData.data.operations.length) {
|
|
205
207
|
ctx.log(
|
|
206
208
|
'critical',
|
|
@@ -231,6 +233,15 @@ export async function importPersistence(
|
|
|
231
233
|
'Imported documents count mismatch',
|
|
232
234
|
);
|
|
233
235
|
}
|
|
236
|
+
} else {
|
|
237
|
+
ctx.log(
|
|
238
|
+
'debug',
|
|
239
|
+
'Skipping integrity check due to schema version mismatch (not an error)',
|
|
240
|
+
{
|
|
241
|
+
exportedVersion: exportedData.data.schemaVersion,
|
|
242
|
+
currentVersion: ctx.schema.version,
|
|
243
|
+
},
|
|
244
|
+
);
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
ctx.log('debug', 'Data copied to primary namespace');
|
|
@@ -23,7 +23,9 @@ export class FindAllQuery<T> extends BaseQuery<T[]> {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
protected run = async () => {
|
|
26
|
-
const { result: oids } = await
|
|
26
|
+
const { result: oids } = await (
|
|
27
|
+
await this.context.documents
|
|
28
|
+
).findAllOids({
|
|
27
29
|
collection: this.collection,
|
|
28
30
|
index: this.index,
|
|
29
31
|
});
|
|
@@ -37,7 +37,9 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
protected run = async () => {
|
|
40
|
-
const { result, hasNextPage } = await
|
|
40
|
+
const { result, hasNextPage } = await (
|
|
41
|
+
await this.context.documents
|
|
42
|
+
).findAllOids({
|
|
41
43
|
collection: this.collection,
|
|
42
44
|
limit: this._pageSize * this._upToPage,
|
|
43
45
|
offset: 0,
|
|
@@ -48,7 +50,9 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
|
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
public loadMore = async () => {
|
|
51
|
-
const { result, hasNextPage } = await
|
|
53
|
+
const { result, hasNextPage } = await (
|
|
54
|
+
await this.context.documents
|
|
55
|
+
).findAllOids({
|
|
52
56
|
collection: this.collection,
|
|
53
57
|
limit: this._pageSize,
|
|
54
58
|
offset: this._pageSize * this._upToPage,
|
|
@@ -23,7 +23,9 @@ export class FindOneQuery<T> extends BaseQuery<T | null> {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
protected run = async () => {
|
|
26
|
-
const oid = await
|
|
26
|
+
const oid = await (
|
|
27
|
+
await this.context.documents
|
|
28
|
+
).findOneOid({
|
|
27
29
|
collection: this.collection,
|
|
28
30
|
index: this.index,
|
|
29
31
|
});
|
|
@@ -48,7 +48,9 @@ export class FindPageQuery<T> extends BaseQuery<T[]> {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
protected run = async () => {
|
|
51
|
-
const { result, hasNextPage } = await
|
|
51
|
+
const { result, hasNextPage } = await (
|
|
52
|
+
await this.context.documents
|
|
53
|
+
).findAllOids({
|
|
52
54
|
collection: this.collection,
|
|
53
55
|
index: this.index,
|
|
54
56
|
limit: this._pageSize,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ServerMessage,
|
|
3
|
-
EventSubscriber,
|
|
4
|
-
Batcher,
|
|
5
2
|
Batch,
|
|
3
|
+
Batcher,
|
|
4
|
+
EventSubscriber,
|
|
5
|
+
ServerMessage,
|
|
6
6
|
VerdantInternalPresence,
|
|
7
7
|
initialInternalPresence,
|
|
8
8
|
} from '@verdant-web/common';
|
|
9
|
-
import type { UserInfo } from '../index.js';
|
|
10
9
|
import { Context } from '../context/context.js';
|
|
10
|
+
import type { UserInfo } from '../index.js';
|
|
11
11
|
import { LocalReplicaInfo } from '../persistence/interfaces.js';
|
|
12
12
|
|
|
13
13
|
export const HANDLE_MESSAGE = Symbol('handleMessage');
|
|
@@ -90,9 +90,12 @@ export class PresenceManager<
|
|
|
90
90
|
this.self.replicaId = '';
|
|
91
91
|
|
|
92
92
|
// set the local replica ID as soon as it's loaded
|
|
93
|
-
ctx.
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
ctx.waitForInitialization
|
|
94
|
+
.then(() => ctx.meta)
|
|
95
|
+
.then((meta) => meta.getLocalReplica())
|
|
96
|
+
.then((info) => {
|
|
97
|
+
this.self.replicaId = info.id;
|
|
98
|
+
});
|
|
96
99
|
|
|
97
100
|
this._updateBatcher = new Batcher(this.flushPresenceUpdates);
|
|
98
101
|
this._updateBatch = this._updateBatcher.add({
|
package/src/sync/PushPullSync.ts
CHANGED
|
@@ -140,7 +140,9 @@ export class PushPullSync
|
|
|
140
140
|
if (message.ackThisNonce) {
|
|
141
141
|
this.ctx.log('debug', 'Sending sync ack', message.ackThisNonce);
|
|
142
142
|
await this.sendRequest([
|
|
143
|
-
await
|
|
143
|
+
await (
|
|
144
|
+
await this.ctx.meta
|
|
145
|
+
).messageCreator.createAck(message.ackThisNonce),
|
|
144
146
|
]);
|
|
145
147
|
}
|
|
146
148
|
}
|
|
@@ -210,10 +212,10 @@ export class PushPullSync
|
|
|
210
212
|
// will include the client's own presence info and fill in missing profile
|
|
211
213
|
// data on the first request. otherwise it would have to wait for the second.
|
|
212
214
|
this.sendRequest([
|
|
213
|
-
await
|
|
214
|
-
this.
|
|
215
|
-
),
|
|
216
|
-
await this.ctx.meta.messageCreator.createSyncStep1(),
|
|
215
|
+
await (
|
|
216
|
+
await this.ctx.meta
|
|
217
|
+
).messageCreator.createPresenceUpdate(this.presence.self),
|
|
218
|
+
await (await this.ctx.meta).messageCreator.createSyncStep1(),
|
|
217
219
|
]);
|
|
218
220
|
};
|
|
219
221
|
|
|
@@ -227,7 +229,7 @@ export class PushPullSync
|
|
|
227
229
|
|
|
228
230
|
syncOnce = async () => {
|
|
229
231
|
await this.sendRequest([
|
|
230
|
-
await this.ctx.meta.messageCreator.createSyncStep1(),
|
|
232
|
+
await (await this.ctx.meta).messageCreator.createSyncStep1(),
|
|
231
233
|
]);
|
|
232
234
|
};
|
|
233
235
|
|
package/src/sync/Sync.ts
CHANGED
|
@@ -10,16 +10,16 @@ import {
|
|
|
10
10
|
VerdantError,
|
|
11
11
|
VerdantInternalPresence,
|
|
12
12
|
} from '@verdant-web/common';
|
|
13
|
-
import {
|
|
13
|
+
import { Context } from '../context/context.js';
|
|
14
|
+
import { attemptToRegisterBackgroundSync } from './background.js';
|
|
14
15
|
import { FilePullResult, FileSync, FileUploadResult } from './FileSync.js';
|
|
16
|
+
import { HANDLE_MESSAGE, PresenceManager } from './PresenceManager.js';
|
|
15
17
|
import { PushPullSync } from './PushPullSync.js';
|
|
16
18
|
import {
|
|
17
19
|
ServerSyncEndpointProvider,
|
|
18
20
|
ServerSyncEndpointProviderConfig,
|
|
19
21
|
} from './ServerSyncEndpointProvider.js';
|
|
20
22
|
import { WebSocketSync } from './WebSocketSync.js';
|
|
21
|
-
import { Context } from '../context/context.js';
|
|
22
|
-
import { attemptToRegisterBackgroundSync } from './background.js';
|
|
23
23
|
|
|
24
24
|
type SyncEvents = {
|
|
25
25
|
onlineChange: (isOnline: boolean) => void;
|
|
@@ -282,6 +282,11 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
282
282
|
this.handleBroadcastChannelMessage,
|
|
283
283
|
);
|
|
284
284
|
}
|
|
285
|
+
ctx.log(
|
|
286
|
+
'info',
|
|
287
|
+
'Sync initialized with transport:',
|
|
288
|
+
initialTransport ?? 'pull',
|
|
289
|
+
);
|
|
285
290
|
if (initialTransport === 'realtime') {
|
|
286
291
|
this.activeSync = this.webSocketSync;
|
|
287
292
|
} else {
|
|
@@ -290,8 +295,7 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
290
295
|
|
|
291
296
|
this.presence.subscribe('update', this.handlePresenceUpdate);
|
|
292
297
|
|
|
293
|
-
ctx.
|
|
294
|
-
|
|
298
|
+
ctx.internalEvents.subscribe('outgoingSyncMessage', this.send);
|
|
295
299
|
this.webSocketSync.subscribe('message', this.handleMessage);
|
|
296
300
|
this.webSocketSync.subscribe('onlineChange', this.handleOnlineChange);
|
|
297
301
|
|
|
@@ -384,11 +388,11 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
384
388
|
baselines: message.baselines,
|
|
385
389
|
});
|
|
386
390
|
if (message.globalAckTimestamp) {
|
|
387
|
-
await this.ctx.meta.setGlobalAck(message.globalAckTimestamp);
|
|
391
|
+
await (await this.ctx.meta).setGlobalAck(message.globalAckTimestamp);
|
|
388
392
|
}
|
|
389
393
|
break;
|
|
390
394
|
case 'global-ack':
|
|
391
|
-
await this.ctx.meta.setGlobalAck(message.timestamp);
|
|
395
|
+
await (await this.ctx.meta).setGlobalAck(message.timestamp);
|
|
392
396
|
break;
|
|
393
397
|
case 'sync-resp':
|
|
394
398
|
this._activelySyncing = true;
|
|
@@ -400,10 +404,10 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
400
404
|
});
|
|
401
405
|
|
|
402
406
|
if (message.globalAckTimestamp) {
|
|
403
|
-
await this.ctx.meta.setGlobalAck(message.globalAckTimestamp);
|
|
407
|
+
await (await this.ctx.meta).setGlobalAck(message.globalAckTimestamp);
|
|
404
408
|
}
|
|
405
409
|
|
|
406
|
-
await this.ctx.meta.updateLastSynced(message.ackedTimestamp);
|
|
410
|
+
await (await this.ctx.meta).updateLastSynced(message.ackedTimestamp);
|
|
407
411
|
this._activelySyncing = false;
|
|
408
412
|
this.emit('syncingChange', false);
|
|
409
413
|
this._hasSynced = true;
|
|
@@ -411,13 +415,15 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
411
415
|
break;
|
|
412
416
|
case 'need-since':
|
|
413
417
|
this.emit('serverReset', message.since);
|
|
414
|
-
this.ctx.files.onServerReset(message.since);
|
|
418
|
+
(await this.ctx.files).onServerReset(message.since);
|
|
415
419
|
this.activeSync.send(
|
|
416
|
-
await
|
|
420
|
+
await (
|
|
421
|
+
await this.ctx.meta
|
|
422
|
+
).messageCreator.createSyncStep1(message.since),
|
|
417
423
|
);
|
|
418
424
|
break;
|
|
419
425
|
case 'server-ack':
|
|
420
|
-
await this.ctx.meta.updateLastSynced(message.timestamp);
|
|
426
|
+
await (await this.ctx.meta).updateLastSynced(message.timestamp);
|
|
421
427
|
}
|
|
422
428
|
|
|
423
429
|
// avoid rebroadcasting messages
|
|
@@ -430,7 +436,7 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
430
436
|
|
|
431
437
|
// update presence if necessary
|
|
432
438
|
this.presence[HANDLE_MESSAGE](
|
|
433
|
-
await this.ctx.meta.getLocalReplica(),
|
|
439
|
+
await (await this.ctx.meta).getLocalReplica(),
|
|
434
440
|
message,
|
|
435
441
|
);
|
|
436
442
|
};
|
|
@@ -439,7 +445,7 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
439
445
|
|
|
440
446
|
// if online, attempt to upload any unsynced files.
|
|
441
447
|
if (online) {
|
|
442
|
-
const unsyncedFiles = await this.ctx.files.listUnsynced();
|
|
448
|
+
const unsyncedFiles = await (await this.ctx.files).listUnsynced();
|
|
443
449
|
const results = await Promise.allSettled(
|
|
444
450
|
unsyncedFiles.map((file) => this.fileSync.uploadFile(file)),
|
|
445
451
|
);
|
|
@@ -458,7 +464,9 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
458
464
|
presence?: Presence;
|
|
459
465
|
internal?: VerdantInternalPresence;
|
|
460
466
|
}) => {
|
|
461
|
-
this.send(
|
|
467
|
+
this.send(
|
|
468
|
+
await (await this.ctx.meta).messageCreator.createPresenceUpdate(data),
|
|
469
|
+
);
|
|
462
470
|
};
|
|
463
471
|
|
|
464
472
|
setMode = (transport: SyncTransportMode) => {
|
|
@@ -512,7 +520,7 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
512
520
|
if (message.type === 'sync' || message.type === 'op') {
|
|
513
521
|
rewriteAuthzOriginator(message, userId);
|
|
514
522
|
}
|
|
515
|
-
|
|
523
|
+
this.activeSync.send(message);
|
|
516
524
|
this.onOutgoingMessage?.(message);
|
|
517
525
|
}
|
|
518
526
|
};
|
|
@@ -570,10 +578,12 @@ export class ServerSync<Presence = any, Profile = any>
|
|
|
570
578
|
};
|
|
571
579
|
|
|
572
580
|
public start = () => {
|
|
581
|
+
this.ctx.log('info', 'Starting sync');
|
|
573
582
|
return this.activeSync.start();
|
|
574
583
|
};
|
|
575
584
|
|
|
576
585
|
public stop = () => {
|
|
586
|
+
this.ctx.log('info', 'Stopping sync');
|
|
577
587
|
return this.activeSync.stop();
|
|
578
588
|
};
|
|
579
589
|
|