@verdant-web/store 4.6.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/index.js +14 -12
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/__tests__/queries.test.js +3 -3
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/__tests__/schema.test.js +3 -3
- package/dist/esm/__tests__/schema.test.js.map +1 -1
- package/dist/esm/client/Client.d.ts +12 -10
- package/dist/esm/client/Client.js +40 -30
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/context/Time.d.ts +1 -1
- package/dist/esm/context/Time.js +1 -1
- package/dist/esm/context/Time.js.map +1 -1
- package/dist/esm/context/context.d.ts +84 -15
- package/dist/esm/context/context.js +98 -1
- package/dist/esm/context/context.js.map +1 -1
- package/dist/esm/entities/Entity.test.js +0 -1
- package/dist/esm/entities/Entity.test.js.map +1 -1
- package/dist/esm/entities/EntityMetadata.js +11 -5
- package/dist/esm/entities/EntityMetadata.js.map +1 -1
- package/dist/esm/entities/EntityStore.js +9 -7
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/files/FileManager.js +5 -5
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/index.d.ts +6 -4
- package/dist/esm/index.js +2 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.d.ts +3 -4
- package/dist/esm/internal.js +1 -2
- package/dist/esm/internal.js.map +1 -1
- package/dist/esm/persistence/PersistenceMetadata.d.ts +3 -6
- package/dist/esm/persistence/PersistenceMetadata.js +5 -6
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
- package/dist/esm/persistence/idb/IdbService.d.ts +3 -3
- package/dist/esm/persistence/idb/IdbService.js +0 -1
- package/dist/esm/persistence/idb/IdbService.js.map +1 -1
- package/dist/esm/persistence/idb/idbPersistence.d.ts +9 -10
- package/dist/esm/persistence/idb/idbPersistence.js +11 -4
- package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +2 -2
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.d.ts +3 -2
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js +16 -15
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -1
- package/dist/esm/persistence/idb/queries/migration/db.js +7 -0
- package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
- package/dist/esm/persistence/idb/util.js +27 -17
- package/dist/esm/persistence/idb/util.js.map +1 -1
- package/dist/esm/persistence/interfaces.d.ts +8 -8
- package/dist/esm/persistence/migration/engine.d.ts +5 -3
- package/dist/esm/persistence/migration/engine.js +23 -14
- package/dist/esm/persistence/migration/engine.js.map +1 -1
- package/dist/esm/persistence/migration/finalize.d.ts +5 -3
- package/dist/esm/persistence/migration/finalize.js +5 -4
- package/dist/esm/persistence/migration/finalize.js.map +1 -1
- package/dist/esm/persistence/migration/migrate.d.ts +8 -5
- package/dist/esm/persistence/migration/migrate.js +10 -4
- package/dist/esm/persistence/migration/migrate.js.map +1 -1
- package/dist/esm/persistence/persistence.d.ts +9 -2
- package/dist/esm/persistence/persistence.js +65 -32
- package/dist/esm/persistence/persistence.js.map +1 -1
- package/dist/esm/queries/FindAllQuery.js +1 -1
- package/dist/esm/queries/FindAllQuery.js.map +1 -1
- package/dist/esm/queries/FindInfiniteQuery.js +2 -2
- package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
- package/dist/esm/queries/FindOneQuery.js +1 -1
- package/dist/esm/queries/FindOneQuery.js.map +1 -1
- package/dist/esm/queries/FindPageQuery.js +1 -1
- package/dist/esm/queries/FindPageQuery.js.map +1 -1
- package/dist/esm/sync/Heartbeat.js +2 -0
- package/dist/esm/sync/Heartbeat.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 +10 -4
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +2 -2
- package/dist/esm/sync/Sync.js +17 -14
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +12 -7
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/sync/serviceWorker.d.ts +2 -2
- package/dist/esm/sync/serviceWorker.js +3 -4
- package/dist/esm/sync/serviceWorker.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/fixtures/testStorage.ts +4 -8
- package/src/__tests__/queries.test.ts +3 -5
- package/src/__tests__/schema.test.ts +3 -3
- package/src/client/Client.ts +50 -34
- package/src/context/Time.ts +2 -2
- package/src/context/context.ts +189 -17
- package/src/entities/Entity.test.ts +0 -1
- package/src/entities/EntityMetadata.ts +16 -5
- package/src/entities/EntityStore.ts +14 -10
- package/src/files/FileManager.ts +5 -5
- package/src/index.ts +6 -10
- package/src/internal.ts +10 -11
- package/src/persistence/PersistenceMetadata.ts +9 -11
- package/src/persistence/idb/IdbService.ts +2 -3
- package/src/persistence/idb/idbPersistence.ts +25 -19
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +3 -3
- package/src/persistence/idb/queries/IdbDocumentDb.ts +31 -17
- package/src/persistence/idb/queries/migration/db.ts +10 -0
- package/src/persistence/idb/util.ts +46 -24
- package/src/persistence/interfaces.ts +21 -12
- package/src/persistence/migration/engine.ts +33 -18
- package/src/persistence/migration/finalize.ts +11 -5
- package/src/persistence/migration/migrate.ts +15 -8
- package/src/persistence/persistence.ts +78 -67
- package/src/queries/FindAllQuery.ts +3 -1
- package/src/queries/FindInfiniteQuery.ts +6 -2
- package/src/queries/FindOneQuery.ts +3 -1
- package/src/queries/FindPageQuery.ts +3 -1
- package/src/sync/Heartbeat.ts +2 -0
- package/src/sync/PresenceManager.ts +10 -7
- package/src/sync/PushPullSync.ts +18 -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,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,
|
package/src/sync/Heartbeat.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -154,6 +156,14 @@ export class PushPullSync
|
|
|
154
156
|
}, 3000);
|
|
155
157
|
|
|
156
158
|
send = (message: ClientMessage) => {
|
|
159
|
+
if (this.status !== 'active') {
|
|
160
|
+
this.ctx.log(
|
|
161
|
+
'warn',
|
|
162
|
+
'Attempted to send message while sync is not active',
|
|
163
|
+
message,
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
157
167
|
// only certain messages are sent for pull-based sync.
|
|
158
168
|
switch (message.type) {
|
|
159
169
|
case 'presence-update':
|
|
@@ -173,11 +183,13 @@ export class PushPullSync
|
|
|
173
183
|
if (this.status === 'active') {
|
|
174
184
|
return;
|
|
175
185
|
}
|
|
186
|
+
this.ctx.log('debug', 'Starting push-pull sync');
|
|
176
187
|
await this.endpointProvider.getEndpoints();
|
|
177
188
|
this.heartbeat.start(true);
|
|
178
189
|
this._status = 'active';
|
|
179
190
|
};
|
|
180
191
|
stop(): void {
|
|
192
|
+
this.ctx.log('debug', 'Stopping push-pull sync');
|
|
181
193
|
this.heartbeat.stop();
|
|
182
194
|
this._status = 'paused';
|
|
183
195
|
}
|
|
@@ -200,10 +212,10 @@ export class PushPullSync
|
|
|
200
212
|
// will include the client's own presence info and fill in missing profile
|
|
201
213
|
// data on the first request. otherwise it would have to wait for the second.
|
|
202
214
|
this.sendRequest([
|
|
203
|
-
await
|
|
204
|
-
this.
|
|
205
|
-
),
|
|
206
|
-
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(),
|
|
207
219
|
]);
|
|
208
220
|
};
|
|
209
221
|
|
|
@@ -217,7 +229,7 @@ export class PushPullSync
|
|
|
217
229
|
|
|
218
230
|
syncOnce = async () => {
|
|
219
231
|
await this.sendRequest([
|
|
220
|
-
await this.ctx.meta.messageCreator.createSyncStep1(),
|
|
232
|
+
await (await this.ctx.meta).messageCreator.createSyncStep1(),
|
|
221
233
|
]);
|
|
222
234
|
};
|
|
223
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
|
|
|
@@ -93,12 +93,12 @@ export class WebSocketSync
|
|
|
93
93
|
this.ctx.log('debug', 'Starting sync');
|
|
94
94
|
this.hasStartedSync = true;
|
|
95
95
|
this.synced = false;
|
|
96
|
+
const meta = await this.ctx.meta;
|
|
97
|
+
this.ctx.log('debug', 'HERE');
|
|
96
98
|
this.send(
|
|
97
|
-
await
|
|
98
|
-
this.presence.self,
|
|
99
|
-
),
|
|
99
|
+
await meta.messageCreator.createPresenceUpdate(this.presence.self),
|
|
100
100
|
);
|
|
101
|
-
this.send(await
|
|
101
|
+
this.send(await meta.messageCreator.createSyncStep1());
|
|
102
102
|
this.heartbeat.start();
|
|
103
103
|
}
|
|
104
104
|
this.emit('onlineChange', online);
|
|
@@ -122,7 +122,9 @@ export class WebSocketSync
|
|
|
122
122
|
if (message.ackThisNonce) {
|
|
123
123
|
// we need to send the ack to confirm we got the response
|
|
124
124
|
this.send(
|
|
125
|
-
await
|
|
125
|
+
await (
|
|
126
|
+
await this.ctx.meta
|
|
127
|
+
).messageCreator.createAck(message.ackThisNonce),
|
|
126
128
|
);
|
|
127
129
|
}
|
|
128
130
|
this.hasStartedSync = true;
|
|
@@ -179,7 +181,7 @@ export class WebSocketSync
|
|
|
179
181
|
};
|
|
180
182
|
|
|
181
183
|
private onError = (event: Event) => {
|
|
182
|
-
this.ctx.log('error', 'Sync socket error', event);
|
|
184
|
+
this.ctx.log('error', 'Sync socket error', event, event.target);
|
|
183
185
|
if (this.disposed) return;
|
|
184
186
|
this.reconnectScheduler.next();
|
|
185
187
|
|
|
@@ -187,7 +189,7 @@ export class WebSocketSync
|
|
|
187
189
|
};
|
|
188
190
|
|
|
189
191
|
private onClose = (event: CloseEvent) => {
|
|
190
|
-
this.ctx.log('info', 'Sync socket disconnected');
|
|
192
|
+
this.ctx.log('info', 'Sync socket disconnected', event.code);
|
|
191
193
|
this.onOnlineChange(false);
|
|
192
194
|
if (this.disposed) return;
|
|
193
195
|
this.reconnectScheduler.next();
|
|
@@ -209,7 +211,7 @@ export class WebSocketSync
|
|
|
209
211
|
};
|
|
210
212
|
|
|
211
213
|
private sendHeartbeat = async () => {
|
|
212
|
-
this.send(await this.ctx.meta.messageCreator.createHeartbeat());
|
|
214
|
+
this.send(await (await this.ctx.meta).messageCreator.createHeartbeat());
|
|
213
215
|
};
|
|
214
216
|
|
|
215
217
|
reconnect = () => {
|
|
@@ -227,11 +229,25 @@ export class WebSocketSync
|
|
|
227
229
|
};
|
|
228
230
|
|
|
229
231
|
send = (message: ClientMessage) => {
|
|
230
|
-
if (this.status !== 'active')
|
|
232
|
+
if (this.status !== 'active') {
|
|
233
|
+
this.ctx.log(
|
|
234
|
+
'debug',
|
|
235
|
+
'Ignoring outgoing message',
|
|
236
|
+
message.type,
|
|
237
|
+
'sync is not active',
|
|
238
|
+
);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
231
241
|
|
|
232
242
|
// wait until a sync has started before doing anything other than sync.
|
|
233
243
|
// new "op" messages can arrive before sync has started, so we need to wait
|
|
234
244
|
if (!this.hasStartedSync && !this.canSkipSyncWait(message)) {
|
|
245
|
+
this.ctx.log(
|
|
246
|
+
'debug',
|
|
247
|
+
'Ignoring outgoing message',
|
|
248
|
+
message.type,
|
|
249
|
+
'still waiting to begin initial sync',
|
|
250
|
+
);
|
|
235
251
|
return;
|
|
236
252
|
}
|
|
237
253
|
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Client } from '../client/Client.js';
|
|
2
2
|
|
|
3
|
-
export async function registerBackgroundSync(
|
|
3
|
+
export async function registerBackgroundSync(client: Client) {
|
|
4
4
|
self.addEventListener('periodicsync', (event: any) => {
|
|
5
5
|
if (event.tag === 'verdant-sync') {
|
|
6
6
|
// See the "Think before you sync" section for
|
|
7
7
|
// checks you could perform before syncing.
|
|
8
|
-
event.waitUntil(sync(
|
|
8
|
+
event.waitUntil(sync(client));
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
async function sync(
|
|
13
|
+
async function sync(client: Client) {
|
|
14
14
|
try {
|
|
15
|
-
const client = await clientDesc.open();
|
|
16
|
-
|
|
17
15
|
await client.sync.syncOnce();
|
|
18
16
|
} catch (err) {
|
|
19
17
|
console.error('Failed to sync:', err);
|