cojson 0.19.22 → 0.20.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +66 -0
- package/dist/PeerState.d.ts +6 -1
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +18 -3
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueContentMessage.d.ts +0 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +0 -8
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +30 -0
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +70 -5
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +302 -31
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +9 -0
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts +4 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +3 -0
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -6
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +2 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -12
- package/dist/config.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +1 -2
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +19 -4
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +19 -4
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +11 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +52 -10
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
- package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
- package/dist/crypto/WasmCryptoEdge.js +4 -1
- package/dist/crypto/WasmCryptoEdge.js.map +1 -1
- package/dist/crypto/crypto.d.ts +3 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +6 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +5 -5
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +4 -3
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +4 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -0
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +2 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/localNode.d.ts +12 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +14 -0
- package/dist/localNode.js.map +1 -1
- package/dist/platformUtils.d.ts +3 -0
- package/dist/platformUtils.d.ts.map +1 -0
- package/dist/platformUtils.js +24 -0
- package/dist/platformUtils.js.map +1 -0
- package/dist/queue/LinkedList.d.ts +9 -3
- package/dist/queue/LinkedList.d.ts.map +1 -1
- package/dist/queue/LinkedList.js +30 -1
- package/dist/queue/LinkedList.js.map +1 -1
- package/dist/queue/OutgoingLoadQueue.d.ts +95 -0
- package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -0
- package/dist/queue/OutgoingLoadQueue.js +240 -0
- package/dist/queue/OutgoingLoadQueue.js.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +3 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +44 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +7 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +42 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +7 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +48 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +6 -0
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +42 -0
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +59 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +66 -43
- package/dist/sync.js.map +1 -1
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
- package/dist/tests/GarbageCollector.test.js +5 -6
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/LinkedList.test.js +90 -0
- package/dist/tests/LinkedList.test.js.map +1 -1
- package/dist/tests/OutgoingLoadQueue.test.d.ts +2 -0
- package/dist/tests/OutgoingLoadQueue.test.d.ts.map +1 -0
- package/dist/tests/OutgoingLoadQueue.test.js +814 -0
- package/dist/tests/OutgoingLoadQueue.test.js.map +1 -0
- package/dist/tests/StorageApiAsync.test.js +484 -152
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +505 -136
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +6 -3
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +34 -13
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +127 -4
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/crypto.test.js +89 -93
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.d.ts +2 -0
- package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
- package/dist/tests/deleteCoValue.test.js +313 -0
- package/dist/tests/deleteCoValue.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +18 -30
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +3 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/sync.concurrentLoad.test.d.ts +2 -0
- package/dist/tests/sync.concurrentLoad.test.d.ts.map +1 -0
- package/dist/tests/sync.concurrentLoad.test.js +481 -0
- package/dist/tests/sync.concurrentLoad.test.js.map +1 -0
- package/dist/tests/sync.deleted.test.d.ts +2 -0
- package/dist/tests/sync.deleted.test.d.ts.map +1 -0
- package/dist/tests/sync.deleted.test.js +214 -0
- package/dist/tests/sync.deleted.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +3 -2
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +4 -3
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +3 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +3 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +17 -1
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +7 -3
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +19 -4
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +6 -16
- package/src/PeerState.ts +26 -3
- package/src/coValueContentMessage.ts +0 -14
- package/src/coValueCore/SessionMap.ts +43 -1
- package/src/coValueCore/coValueCore.ts +415 -27
- package/src/coValueCore/verifiedState.ts +26 -3
- package/src/coValues/coList.ts +9 -3
- package/src/coValues/group.ts +5 -6
- package/src/config.ts +4 -13
- package/src/crypto/NapiCrypto.ts +29 -13
- package/src/crypto/RNCrypto.ts +29 -11
- package/src/crypto/WasmCrypto.ts +67 -20
- package/src/crypto/WasmCryptoEdge.ts +5 -1
- package/src/crypto/crypto.ts +16 -4
- package/src/exports.ts +4 -2
- package/src/ids.ts +11 -1
- package/src/localNode.ts +15 -0
- package/src/platformUtils.ts +26 -0
- package/src/queue/LinkedList.ts +34 -4
- package/src/queue/OutgoingLoadQueue.ts +307 -0
- package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
- package/src/storage/sqlite/client.ts +77 -0
- package/src/storage/sqlite/sqliteMigrations.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +75 -0
- package/src/storage/storageAsync.ts +62 -0
- package/src/storage/storageSync.ts +58 -0
- package/src/storage/types.ts +69 -0
- package/src/sync.ts +78 -46
- package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
- package/src/tests/GarbageCollector.test.ts +6 -10
- package/src/tests/LinkedList.test.ts +111 -0
- package/src/tests/OutgoingLoadQueue.test.ts +1129 -0
- package/src/tests/StorageApiAsync.test.ts +572 -162
- package/src/tests/StorageApiSync.test.ts +580 -143
- package/src/tests/WasmCrypto.test.ts +8 -3
- package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
- package/src/tests/coValueCore.test.ts +49 -14
- package/src/tests/coreWasm.test.ts +319 -10
- package/src/tests/crypto.test.ts +141 -150
- package/src/tests/deleteCoValue.test.ts +528 -0
- package/src/tests/group.removeMember.test.ts +35 -35
- package/src/tests/knownState.lazyLoading.test.ts +6 -0
- package/src/tests/sync.concurrentLoad.test.ts +650 -0
- package/src/tests/sync.deleted.test.ts +294 -0
- package/src/tests/sync.mesh.test.ts +5 -2
- package/src/tests/sync.storage.test.ts +6 -3
- package/src/tests/sync.test.ts +5 -2
- package/src/tests/testStorage.ts +31 -2
- package/src/tests/testUtils.ts +31 -10
- package/dist/crypto/PureJSCrypto.d.ts +0 -77
- package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
- package/dist/crypto/PureJSCrypto.js +0 -236
- package/dist/crypto/PureJSCrypto.js.map +0 -1
- package/dist/tests/PureJSCrypto.test.d.ts +0 -2
- package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
- package/dist/tests/PureJSCrypto.test.js +0 -145
- package/dist/tests/PureJSCrypto.test.js.map +0 -1
- package/src/crypto/PureJSCrypto.ts +0 -429
- package/src/tests/PureJSCrypto.test.ts +0 -217
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
StoredSessionRow,
|
|
16
16
|
TransactionRow,
|
|
17
17
|
} from "../types.js";
|
|
18
|
+
import { DeletedCoValueDeletionStatus } from "../types.js";
|
|
18
19
|
import type { SQLiteDatabaseDriverAsync } from "./types.js";
|
|
19
20
|
import type { PeerID } from "../../sync.js";
|
|
20
21
|
|
|
@@ -29,6 +30,10 @@ export type RawTransactionRow = {
|
|
|
29
30
|
tx: string;
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
type DeletedCoValueQueueRow = {
|
|
34
|
+
id: RawCoID;
|
|
35
|
+
};
|
|
36
|
+
|
|
32
37
|
export function getErrorMessage(error: unknown) {
|
|
33
38
|
return error instanceof Error ? error.message : "Unknown error";
|
|
34
39
|
}
|
|
@@ -146,6 +151,76 @@ export class SQLiteClientAsync
|
|
|
146
151
|
return result.rowID;
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
async markCoValueAsDeleted(id: RawCoID) {
|
|
155
|
+
// Work queue entry. Table only stores the coValueID.
|
|
156
|
+
// Idempotent by design.
|
|
157
|
+
await this.db.run(
|
|
158
|
+
`INSERT INTO deletedCoValues (coValueID) VALUES (?) ON CONFLICT(coValueID) DO NOTHING`,
|
|
159
|
+
[id],
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async eraseCoValueButKeepTombstone(coValueId: RawCoID) {
|
|
164
|
+
const coValueRow = await this.db.get<RawCoValueRow & { rowID: number }>(
|
|
165
|
+
"SELECT * FROM coValues WHERE id = ?",
|
|
166
|
+
[coValueId],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (!coValueRow) {
|
|
170
|
+
logger.warn(`CoValue ${coValueId} not found, skipping deletion`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await this.transaction(async () => {
|
|
175
|
+
await this.db.run(
|
|
176
|
+
`DELETE FROM transactions
|
|
177
|
+
WHERE ses IN (
|
|
178
|
+
SELECT rowID FROM sessions
|
|
179
|
+
WHERE coValue = ?
|
|
180
|
+
AND sessionID NOT LIKE '%$'
|
|
181
|
+
)`,
|
|
182
|
+
[coValueRow.rowID],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
await this.db.run(
|
|
186
|
+
`DELETE FROM signatureAfter
|
|
187
|
+
WHERE ses IN (
|
|
188
|
+
SELECT rowID FROM sessions
|
|
189
|
+
WHERE coValue = ?
|
|
190
|
+
AND sessionID NOT LIKE '%$'
|
|
191
|
+
)`,
|
|
192
|
+
[coValueRow.rowID],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
await this.db.run(
|
|
196
|
+
`DELETE FROM sessions
|
|
197
|
+
WHERE coValue = ?
|
|
198
|
+
AND sessionID NOT LIKE '%$'`,
|
|
199
|
+
[coValueRow.rowID],
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
await this.db.run(
|
|
203
|
+
`INSERT INTO deletedCoValues (coValueID, status) VALUES (?, ?)
|
|
204
|
+
ON CONFLICT(coValueID) DO UPDATE SET status=?`,
|
|
205
|
+
[
|
|
206
|
+
coValueId,
|
|
207
|
+
DeletedCoValueDeletionStatus.Done,
|
|
208
|
+
DeletedCoValueDeletionStatus.Done,
|
|
209
|
+
],
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getAllCoValuesWaitingForDelete(): Promise<RawCoID[]> {
|
|
215
|
+
const rows = await this.db.query<DeletedCoValueQueueRow>(
|
|
216
|
+
`SELECT coValueID as id
|
|
217
|
+
FROM deletedCoValues
|
|
218
|
+
WHERE status = ?`,
|
|
219
|
+
[DeletedCoValueDeletionStatus.Pending],
|
|
220
|
+
);
|
|
221
|
+
return rows.map((r) => r.id);
|
|
222
|
+
}
|
|
223
|
+
|
|
149
224
|
async addSessionUpdate({
|
|
150
225
|
sessionUpdate,
|
|
151
226
|
}: {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
setSessionCounter,
|
|
18
18
|
} from "../knownState.js";
|
|
19
19
|
import { StorageKnownState } from "./knownState.js";
|
|
20
|
+
import { DeletedCoValuesEraserScheduler } from "./DeletedCoValuesEraserScheduler.js";
|
|
20
21
|
import {
|
|
21
22
|
collectNewTxs,
|
|
22
23
|
getDependedOnCoValues,
|
|
@@ -30,10 +31,15 @@ import type {
|
|
|
30
31
|
StoredCoValueRow,
|
|
31
32
|
StoredSessionRow,
|
|
32
33
|
} from "./types.js";
|
|
34
|
+
import { isDeleteSessionID } from "../ids.js";
|
|
33
35
|
|
|
34
36
|
export class StorageApiAsync implements StorageAPI {
|
|
35
37
|
private readonly dbClient: DBClientInterfaceAsync;
|
|
36
38
|
|
|
39
|
+
private deletedCoValuesEraserScheduler:
|
|
40
|
+
| DeletedCoValuesEraserScheduler
|
|
41
|
+
| undefined;
|
|
42
|
+
private eraserController: AbortController | undefined;
|
|
37
43
|
/**
|
|
38
44
|
* Keeps track of CoValues that are in memory, to avoid reloading them from storage
|
|
39
45
|
* when it isn't necessary
|
|
@@ -114,6 +120,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
114
120
|
callback: (data: NewContentMessage) => void,
|
|
115
121
|
done: (found: boolean) => void,
|
|
116
122
|
) {
|
|
123
|
+
this.interruptEraser("load");
|
|
117
124
|
const coValueRow = await this.dbClient.getCoValue(id);
|
|
118
125
|
|
|
119
126
|
if (!coValueRow) {
|
|
@@ -267,10 +274,35 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
267
274
|
this.storeQueue.push(msg, correctionCallback);
|
|
268
275
|
|
|
269
276
|
this.storeQueue.processQueue(async (data, correctionCallback) => {
|
|
277
|
+
this.interruptEraser("store");
|
|
270
278
|
return this.storeSingle(data, correctionCallback);
|
|
271
279
|
});
|
|
272
280
|
}
|
|
273
281
|
|
|
282
|
+
private interruptEraser(reason: string) {
|
|
283
|
+
// Cooperative cancellation: a DB transaction already in progress will complete,
|
|
284
|
+
// but the eraser loop will stop starting further work at its next abort check.
|
|
285
|
+
if (this.eraserController) {
|
|
286
|
+
this.eraserController.abort(reason);
|
|
287
|
+
this.eraserController = undefined;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async eraseAllDeletedCoValues() {
|
|
292
|
+
const ids = await this.dbClient.getAllCoValuesWaitingForDelete();
|
|
293
|
+
|
|
294
|
+
this.eraserController = new AbortController();
|
|
295
|
+
const signal = this.eraserController.signal;
|
|
296
|
+
|
|
297
|
+
for (const id of ids) {
|
|
298
|
+
if (signal.aborted) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
await this.dbClient.eraseCoValueButKeepTombstone(id);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
274
306
|
/**
|
|
275
307
|
* This function is called when the storage lacks the information required to store the incoming content.
|
|
276
308
|
*
|
|
@@ -313,6 +345,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
313
345
|
msg: NewContentMessage,
|
|
314
346
|
correctionCallback: CorrectionCallback,
|
|
315
347
|
): Promise<boolean> {
|
|
348
|
+
this.interruptEraser("store");
|
|
316
349
|
if (this.storeQueue.closed) {
|
|
317
350
|
return false;
|
|
318
351
|
}
|
|
@@ -342,6 +375,10 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
342
375
|
sessionID,
|
|
343
376
|
);
|
|
344
377
|
|
|
378
|
+
if (this.deletedValues.has(id) && isDeleteSessionID(sessionID)) {
|
|
379
|
+
await tx.markCoValueAsDeleted(id);
|
|
380
|
+
}
|
|
381
|
+
|
|
345
382
|
if (sessionRow) {
|
|
346
383
|
setSessionCounter(
|
|
347
384
|
knownState.sessions,
|
|
@@ -445,6 +482,30 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
445
482
|
return newLastIdx;
|
|
446
483
|
}
|
|
447
484
|
|
|
485
|
+
deletedValues = new Set<RawCoID>();
|
|
486
|
+
|
|
487
|
+
markDeleteAsValid(id: RawCoID) {
|
|
488
|
+
this.deletedValues.add(id);
|
|
489
|
+
|
|
490
|
+
if (this.deletedCoValuesEraserScheduler) {
|
|
491
|
+
this.deletedCoValuesEraserScheduler.onEnqueueDeletedCoValue();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
enableDeletedCoValuesErasure() {
|
|
496
|
+
if (this.deletedCoValuesEraserScheduler) return;
|
|
497
|
+
|
|
498
|
+
this.deletedCoValuesEraserScheduler = new DeletedCoValuesEraserScheduler({
|
|
499
|
+
run: async () => {
|
|
500
|
+
// Async storage: no max-time budgeting; drain to completion when scheduled.
|
|
501
|
+
await this.eraseAllDeletedCoValues();
|
|
502
|
+
const remaining = await this.dbClient.getAllCoValuesWaitingForDelete();
|
|
503
|
+
return { hasMore: remaining.length > 0 };
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
this.deletedCoValuesEraserScheduler.scheduleStartupDrain();
|
|
507
|
+
}
|
|
508
|
+
|
|
448
509
|
waitForSync(id: string, coValue: CoValueCore) {
|
|
449
510
|
return this.knownStates.waitForSync(id, coValue);
|
|
450
511
|
}
|
|
@@ -471,6 +532,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
471
532
|
}
|
|
472
533
|
|
|
473
534
|
close() {
|
|
535
|
+
this.deletedCoValuesEraserScheduler?.dispose();
|
|
474
536
|
this.inMemoryCoValues.clear();
|
|
475
537
|
return this.storeQueue.close();
|
|
476
538
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
emptyKnownState,
|
|
17
17
|
setSessionCounter,
|
|
18
18
|
} from "../knownState.js";
|
|
19
|
+
import { isDeleteSessionID } from "../ids.js";
|
|
19
20
|
import {
|
|
20
21
|
collectNewTxs,
|
|
21
22
|
getDependedOnCoValues,
|
|
@@ -29,14 +30,21 @@ import type {
|
|
|
29
30
|
StoredCoValueRow,
|
|
30
31
|
StoredSessionRow,
|
|
31
32
|
} from "./types.js";
|
|
33
|
+
import { DeletedCoValuesEraserScheduler } from "./DeletedCoValuesEraserScheduler.js";
|
|
32
34
|
import {
|
|
33
35
|
ContentCallback,
|
|
34
36
|
StorageStreamingQueue,
|
|
35
37
|
} from "../queue/StorageStreamingQueue.js";
|
|
36
38
|
import { getPriorityFromHeader } from "../priority.js";
|
|
37
39
|
|
|
40
|
+
const MAX_DELETE_SCHEDULE_DURATION_MS = 100;
|
|
41
|
+
|
|
38
42
|
export class StorageApiSync implements StorageAPI {
|
|
39
43
|
private readonly dbClient: DBClientInterfaceSync;
|
|
44
|
+
|
|
45
|
+
private deletedCoValuesEraserScheduler:
|
|
46
|
+
| DeletedCoValuesEraserScheduler
|
|
47
|
+
| undefined;
|
|
40
48
|
/**
|
|
41
49
|
* Keeps track of CoValues that are in memory, to avoid reloading them from storage
|
|
42
50
|
* when it isn't necessary
|
|
@@ -329,6 +337,10 @@ export class StorageApiSync implements StorageAPI {
|
|
|
329
337
|
|
|
330
338
|
for (const sessionID of Object.keys(msg.new) as SessionID[]) {
|
|
331
339
|
this.dbClient.transaction((tx) => {
|
|
340
|
+
if (this.deletedValues.has(id) && isDeleteSessionID(sessionID)) {
|
|
341
|
+
tx.markCoValueAsDeleted(id);
|
|
342
|
+
}
|
|
343
|
+
|
|
332
344
|
const sessionRow = tx.getSingleCoValueSession(
|
|
333
345
|
storedCoValueRowID,
|
|
334
346
|
sessionID,
|
|
@@ -433,6 +445,51 @@ export class StorageApiSync implements StorageAPI {
|
|
|
433
445
|
return newLastIdx;
|
|
434
446
|
}
|
|
435
447
|
|
|
448
|
+
deletedValues = new Set<RawCoID>();
|
|
449
|
+
|
|
450
|
+
markDeleteAsValid(id: RawCoID) {
|
|
451
|
+
this.deletedValues.add(id);
|
|
452
|
+
|
|
453
|
+
if (this.deletedCoValuesEraserScheduler) {
|
|
454
|
+
this.deletedCoValuesEraserScheduler.onEnqueueDeletedCoValue();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async eraseAllDeletedCoValues(): Promise<void> {
|
|
459
|
+
const ids = this.dbClient.getAllCoValuesWaitingForDelete();
|
|
460
|
+
|
|
461
|
+
for (const id of ids) {
|
|
462
|
+
this.dbClient.eraseCoValueButKeepTombstone(id);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
enableDeletedCoValuesErasure() {
|
|
467
|
+
if (this.deletedCoValuesEraserScheduler) return;
|
|
468
|
+
this.deletedCoValuesEraserScheduler = new DeletedCoValuesEraserScheduler({
|
|
469
|
+
run: async () =>
|
|
470
|
+
this.eraseDeletedCoValuesOnceBudgeted(MAX_DELETE_SCHEDULE_DURATION_MS),
|
|
471
|
+
});
|
|
472
|
+
this.deletedCoValuesEraserScheduler.scheduleStartupDrain();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private eraseDeletedCoValuesOnceBudgeted(budgetMs?: number) {
|
|
476
|
+
const startedAt = Date.now();
|
|
477
|
+
const ids = this.dbClient.getAllCoValuesWaitingForDelete();
|
|
478
|
+
|
|
479
|
+
for (const id of ids) {
|
|
480
|
+
// Strict time budget for sync storage to avoid blocking.
|
|
481
|
+
if (budgetMs && Date.now() - startedAt >= budgetMs) {
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this.dbClient.eraseCoValueButKeepTombstone(id);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
hasMore: this.dbClient.getAllCoValuesWaitingForDelete().length > 0,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
436
493
|
waitForSync(id: string, coValue: CoValueCore) {
|
|
437
494
|
return this.knownStates.waitForSync(id, coValue);
|
|
438
495
|
}
|
|
@@ -461,6 +518,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
461
518
|
}
|
|
462
519
|
|
|
463
520
|
close() {
|
|
521
|
+
this.deletedCoValuesEraserScheduler?.dispose();
|
|
464
522
|
this.inMemoryCoValues.clear();
|
|
465
523
|
return undefined;
|
|
466
524
|
}
|
package/src/storage/types.ts
CHANGED
|
@@ -13,12 +13,43 @@ export type CorrectionCallback = (
|
|
|
13
13
|
correction: CoValueKnownState,
|
|
14
14
|
) => NewContentMessage[] | undefined;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Deletion work queue status for `deletedCoValues` (SQLite).
|
|
18
|
+
*
|
|
19
|
+
* Stored as an INTEGER in SQLite:
|
|
20
|
+
* - 0 = pending
|
|
21
|
+
* - 1 = done
|
|
22
|
+
*/
|
|
23
|
+
export enum DeletedCoValueDeletionStatus {
|
|
24
|
+
Pending = 0,
|
|
25
|
+
Done = 1,
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
/**
|
|
17
29
|
* The StorageAPI is the interface that the StorageSync and StorageAsync classes implement.
|
|
18
30
|
*
|
|
19
31
|
* It uses callbacks instead of promises to have no overhead when using the StorageSync and less overhead when using the StorageAsync.
|
|
20
32
|
*/
|
|
21
33
|
export interface StorageAPI {
|
|
34
|
+
/**
|
|
35
|
+
* Flags that the coValue delete is valid.
|
|
36
|
+
*
|
|
37
|
+
* When the delete tx is stored, the storage will mark the coValue as deleted.
|
|
38
|
+
*/
|
|
39
|
+
markDeleteAsValid(id: RawCoID): void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Enable the background erasure scheduler that drains the `deletedCoValues` work queue.
|
|
43
|
+
* This is intentionally opt-in and should be activated by `LocalNode`.
|
|
44
|
+
*/
|
|
45
|
+
enableDeletedCoValuesErasure(): void;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Batch physical deletion for coValues queued in `deletedCoValues` with status `Pending`.
|
|
49
|
+
* Must preserve tombstones (header + delete session(s) + their tx/signatures).
|
|
50
|
+
*/
|
|
51
|
+
eraseAllDeletedCoValues(): Promise<void>;
|
|
52
|
+
|
|
22
53
|
load(
|
|
23
54
|
id: string,
|
|
24
55
|
// This callback is fired when data is found, might be called multiple times if the content requires streaming (e.g when loading files)
|
|
@@ -111,6 +142,13 @@ export interface DBTransactionInterfaceAsync {
|
|
|
111
142
|
sessionID: SessionID,
|
|
112
143
|
): Promise<StoredSessionRow | undefined>;
|
|
113
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Persist a "deleted coValue" marker in storage (work queue entry).
|
|
147
|
+
* This is an enqueue signal: implementations should set status to `Pending`.
|
|
148
|
+
* This is expected to be idempotent (safe to call repeatedly).
|
|
149
|
+
*/
|
|
150
|
+
markCoValueAsDeleted(id: RawCoID): Promise<unknown>;
|
|
151
|
+
|
|
114
152
|
addSessionUpdate({
|
|
115
153
|
sessionUpdate,
|
|
116
154
|
sessionRow,
|
|
@@ -146,6 +184,11 @@ export interface DBClientInterfaceAsync {
|
|
|
146
184
|
header?: CoValueHeader,
|
|
147
185
|
): Promise<number | undefined>;
|
|
148
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Enumerate all coValue IDs currently pending in the "deleted coValues" work queue.
|
|
189
|
+
*/
|
|
190
|
+
getAllCoValuesWaitingForDelete(): Promise<RawCoID[]>;
|
|
191
|
+
|
|
149
192
|
getCoValueSessions(coValueRowId: number): Promise<StoredSessionRow[]>;
|
|
150
193
|
|
|
151
194
|
getNewTransactionInSession(
|
|
@@ -171,6 +214,13 @@ export interface DBClientInterfaceAsync {
|
|
|
171
214
|
|
|
172
215
|
stopTrackingSyncState(id: RawCoID): Promise<void>;
|
|
173
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Physical deletion primitive: erase all persisted history for a deleted coValue,
|
|
219
|
+
* while preserving the tombstone (header + delete session(s)).
|
|
220
|
+
* Must run inside a single storage transaction.
|
|
221
|
+
*/
|
|
222
|
+
eraseCoValueButKeepTombstone(coValueID: RawCoID): Promise<unknown>;
|
|
223
|
+
|
|
174
224
|
/**
|
|
175
225
|
* Get the knownState for a CoValue without loading transactions.
|
|
176
226
|
* Returns undefined if the CoValue doesn't exist.
|
|
@@ -186,6 +236,13 @@ export interface DBTransactionInterfaceSync {
|
|
|
186
236
|
sessionID: SessionID,
|
|
187
237
|
): StoredSessionRow | undefined;
|
|
188
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Persist a "deleted coValue" marker in storage (work queue entry).
|
|
241
|
+
* This is an enqueue signal: implementations should set status to `"pending"`.
|
|
242
|
+
* This is expected to be idempotent (safe to call repeatedly).
|
|
243
|
+
*/
|
|
244
|
+
markCoValueAsDeleted(id: RawCoID): unknown;
|
|
245
|
+
|
|
189
246
|
addSessionUpdate({
|
|
190
247
|
sessionUpdate,
|
|
191
248
|
sessionRow,
|
|
@@ -216,6 +273,11 @@ export interface DBClientInterfaceSync {
|
|
|
216
273
|
|
|
217
274
|
upsertCoValue(id: string, header?: CoValueHeader): number | undefined;
|
|
218
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Enumerate all coValue IDs currently pending in the "deleted coValues" work queue.
|
|
278
|
+
*/
|
|
279
|
+
getAllCoValuesWaitingForDelete(): RawCoID[];
|
|
280
|
+
|
|
219
281
|
getCoValueSessions(coValueRowId: number): StoredSessionRow[];
|
|
220
282
|
|
|
221
283
|
getNewTransactionInSession(
|
|
@@ -239,6 +301,13 @@ export interface DBClientInterfaceSync {
|
|
|
239
301
|
|
|
240
302
|
stopTrackingSyncState(id: RawCoID): void;
|
|
241
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Physical deletion primitive: erase all persisted history for a deleted coValue,
|
|
306
|
+
* while preserving the tombstone (header + delete session(s)).
|
|
307
|
+
* Must run inside a single storage transaction.
|
|
308
|
+
*/
|
|
309
|
+
eraseCoValueButKeepTombstone(coValueID: RawCoID): unknown;
|
|
310
|
+
|
|
242
311
|
/**
|
|
243
312
|
* Get the knownState for a CoValue without loading transactions.
|
|
244
313
|
* Returns undefined if the CoValue doesn't exist.
|
package/src/sync.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { CoValueCore } from "./coValueCore/coValueCore.js";
|
|
15
15
|
import { CoValueHeader, Transaction } from "./coValueCore/verifiedState.js";
|
|
16
16
|
import { Signature } from "./crypto/crypto.js";
|
|
17
|
-
import { RawCoID, SessionID, isRawCoID } from "./ids.js";
|
|
17
|
+
import { isDeleteSessionID, RawCoID, SessionID, isRawCoID } from "./ids.js";
|
|
18
18
|
import { LocalNode } from "./localNode.js";
|
|
19
19
|
import { logger } from "./logger.js";
|
|
20
20
|
import { CoValuePriority } from "./priority.js";
|
|
@@ -213,7 +213,6 @@ export class SyncManager {
|
|
|
213
213
|
return;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
// TODO: validate
|
|
217
216
|
switch (msg.action) {
|
|
218
217
|
case "load":
|
|
219
218
|
return this.handleLoad(msg, peer);
|
|
@@ -265,10 +264,18 @@ export class SyncManager {
|
|
|
265
264
|
|
|
266
265
|
peer.combineOptimisticWith(id, coValue.knownState());
|
|
267
266
|
} else if (!peer.toldKnownState.has(id)) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
if (coValue.isDeleted) {
|
|
268
|
+
// This way we make the peer believe that we've always ingested all the content they sent, even though we skipped it because the coValue is deleted
|
|
269
|
+
this.trySendToPeer(
|
|
270
|
+
peer,
|
|
271
|
+
coValue.stopSyncingKnownStateMessage(peer.getKnownState(id)),
|
|
272
|
+
);
|
|
273
|
+
} else {
|
|
274
|
+
this.trySendToPeer(peer, {
|
|
275
|
+
action: "known",
|
|
276
|
+
...coValue.knownStateWithStreaming(),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
peer.trackToldKnownState(id);
|
|
@@ -398,13 +405,7 @@ export class SyncManager {
|
|
|
398
405
|
// If the coValue is unavailable and we never tried this peer
|
|
399
406
|
// we try to load it from the peer
|
|
400
407
|
if (!peer.loadRequestSent.has(coValue.id)) {
|
|
401
|
-
peer.
|
|
402
|
-
this.trySendToPeer(peer, {
|
|
403
|
-
action: "load",
|
|
404
|
-
header: false,
|
|
405
|
-
id: coValue.id,
|
|
406
|
-
sessions: {},
|
|
407
|
-
});
|
|
408
|
+
peer.sendLoadRequest(coValue, "low-priority");
|
|
408
409
|
}
|
|
409
410
|
} else {
|
|
410
411
|
// Build the list of coValues ordered by dependency
|
|
@@ -424,12 +425,11 @@ export class SyncManager {
|
|
|
424
425
|
* - Subscribe to the coValue updates
|
|
425
426
|
* - Start the sync process in case we or the other peer
|
|
426
427
|
* lacks some transactions
|
|
428
|
+
*
|
|
429
|
+
* Use low priority for reconciliation loads so that user-initiated
|
|
430
|
+
* loads take precedence.
|
|
427
431
|
*/
|
|
428
|
-
peer.
|
|
429
|
-
this.trySendToPeer(peer, {
|
|
430
|
-
action: "load",
|
|
431
|
-
...coValue.knownState(),
|
|
432
|
-
});
|
|
432
|
+
peer.sendLoadRequest(coValue, "low-priority");
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
|
|
@@ -508,7 +508,7 @@ export class SyncManager {
|
|
|
508
508
|
currentTimer - lastTimer >
|
|
509
509
|
SYNC_SCHEDULER_CONFIG.INCOMING_MESSAGES_TIME_BUDGET
|
|
510
510
|
) {
|
|
511
|
-
await
|
|
511
|
+
await waitForNextTick();
|
|
512
512
|
lastTimer = performance.now();
|
|
513
513
|
}
|
|
514
514
|
}
|
|
@@ -726,6 +726,8 @@ export class SyncManager {
|
|
|
726
726
|
if (coValue.isAvailable()) {
|
|
727
727
|
this.sendNewContent(msg.id, peer);
|
|
728
728
|
}
|
|
729
|
+
|
|
730
|
+
peer.trackLoadRequestComplete(coValue);
|
|
729
731
|
}
|
|
730
732
|
|
|
731
733
|
recordTransactionsSize(newTransactions: Transaction[], source: string) {
|
|
@@ -744,6 +746,7 @@ export class SyncManager {
|
|
|
744
746
|
) {
|
|
745
747
|
const coValue = this.local.getCoValue(msg.id);
|
|
746
748
|
const peer = from === "storage" || from === "import" ? undefined : from;
|
|
749
|
+
|
|
747
750
|
const sourceRole =
|
|
748
751
|
from === "storage"
|
|
749
752
|
? "storage"
|
|
@@ -760,6 +763,7 @@ export class SyncManager {
|
|
|
760
763
|
};
|
|
761
764
|
}
|
|
762
765
|
|
|
766
|
+
peer?.trackLoadRequestUpdate(coValue);
|
|
763
767
|
coValue.addDependenciesFromContentMessage(msg);
|
|
764
768
|
|
|
765
769
|
// If some of the dependencies are missing, we wait for them to be available
|
|
@@ -779,7 +783,12 @@ export class SyncManager {
|
|
|
779
783
|
peers.push(peer);
|
|
780
784
|
}
|
|
781
785
|
|
|
782
|
-
|
|
786
|
+
// Use immediate mode to bypass the concurrency limit for dependencies
|
|
787
|
+
// We do this to avoid that the dependency load is blocked
|
|
788
|
+
// by the pending dependendant load
|
|
789
|
+
// Also these should be done with the highest priority, because we need to
|
|
790
|
+
// unblock the coValue wait
|
|
791
|
+
dependencyCoValue.load(peers, "immediate");
|
|
783
792
|
}
|
|
784
793
|
}
|
|
785
794
|
|
|
@@ -875,6 +884,8 @@ export class SyncManager {
|
|
|
875
884
|
new: {},
|
|
876
885
|
};
|
|
877
886
|
|
|
887
|
+
let wasAlreadyDeleted = coValue.isDeleted;
|
|
888
|
+
|
|
878
889
|
/**
|
|
879
890
|
* The coValue is in memory, load the transactions from the content message
|
|
880
891
|
*/
|
|
@@ -882,6 +893,10 @@ export class SyncManager {
|
|
|
882
893
|
sessionID,
|
|
883
894
|
newContentForSession,
|
|
884
895
|
] of getSessionEntriesFromContentMessage(msg)) {
|
|
896
|
+
if (wasAlreadyDeleted && !isDeleteSessionID(sessionID)) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
|
|
885
900
|
const newTransactions = getNewTransactionsFromContentMessage(
|
|
886
901
|
newContentForSession,
|
|
887
902
|
coValue.knownState(),
|
|
@@ -936,12 +951,25 @@ export class SyncManager {
|
|
|
936
951
|
this.recordTransactionsSize(newTransactions, sourceRole);
|
|
937
952
|
}
|
|
938
953
|
|
|
954
|
+
// We reset the new content for the deleted coValue
|
|
955
|
+
// because we want to store only the delete session/transaction
|
|
956
|
+
if (!wasAlreadyDeleted && coValue.isDeleted) {
|
|
957
|
+
wasAlreadyDeleted = true;
|
|
958
|
+
validNewContent.new = {};
|
|
959
|
+
}
|
|
960
|
+
|
|
939
961
|
// The new content for this session has been verified, so we can store it
|
|
940
962
|
validNewContent.new[sessionID] = newContentForSession;
|
|
941
963
|
}
|
|
942
964
|
|
|
943
965
|
if (peer) {
|
|
944
|
-
|
|
966
|
+
if (coValue.isDeleted) {
|
|
967
|
+
// In case of deleted coValues, we combine the known state with the content message
|
|
968
|
+
// to avoid that clients that don't support deleted coValues try to sync their own content indefinitely
|
|
969
|
+
peer.combineWith(msg.id, knownStateFromContent(msg));
|
|
970
|
+
} else {
|
|
971
|
+
peer.combineWith(msg.id, knownStateFromContent(validNewContent));
|
|
972
|
+
}
|
|
945
973
|
}
|
|
946
974
|
|
|
947
975
|
/**
|
|
@@ -973,15 +1001,21 @@ export class SyncManager {
|
|
|
973
1001
|
* This way the sender knows that the content has been received and applied
|
|
974
1002
|
* and can update their peer's knownState accordingly.
|
|
975
1003
|
*/
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1004
|
+
if (coValue.isDeleted) {
|
|
1005
|
+
// This way we make the peer believe that we've ingested all the content, even though we skipped it because the coValue is deleted
|
|
1006
|
+
this.trySendToPeer(
|
|
1007
|
+
peer,
|
|
1008
|
+
coValue.stopSyncingKnownStateMessage(peer.getKnownState(msg.id)),
|
|
1009
|
+
);
|
|
1010
|
+
} else {
|
|
1011
|
+
this.trySendToPeer(peer, {
|
|
1012
|
+
action: "known",
|
|
1013
|
+
...coValue.knownState(),
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
980
1016
|
peer.trackToldKnownState(msg.id);
|
|
981
1017
|
}
|
|
982
1018
|
|
|
983
|
-
const syncedPeers = [];
|
|
984
|
-
|
|
985
1019
|
/**
|
|
986
1020
|
* Store the content and propagate it to the server peers and the subscribed client peers
|
|
987
1021
|
*/
|
|
@@ -995,6 +1029,8 @@ export class SyncManager {
|
|
|
995
1029
|
}
|
|
996
1030
|
}
|
|
997
1031
|
|
|
1032
|
+
peer?.trackLoadRequestComplete(coValue);
|
|
1033
|
+
|
|
998
1034
|
for (const peer of this.getPeers(coValue.id)) {
|
|
999
1035
|
/**
|
|
1000
1036
|
* We sync the content against the source peer if it is a client or server peers
|
|
@@ -1008,25 +1044,8 @@ export class SyncManager {
|
|
|
1008
1044
|
// We directly forward the new content to peers that have an active subscription
|
|
1009
1045
|
if (peer.isCoValueSubscribedToPeer(coValue.id)) {
|
|
1010
1046
|
this.sendNewContent(coValue.id, peer);
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
peer.role === "server" &&
|
|
1014
|
-
!peer.loadRequestSent.has(coValue.id)
|
|
1015
|
-
) {
|
|
1016
|
-
const state = coValue.getLoadingStateForPeer(peer.id);
|
|
1017
|
-
|
|
1018
|
-
// Check if there is a inflight load operation and we
|
|
1019
|
-
// are waiting for other peers to send the load request
|
|
1020
|
-
if (state === "unknown") {
|
|
1021
|
-
// Sending a load message to the peer to get to know how much content is missing
|
|
1022
|
-
// before sending the new content
|
|
1023
|
-
this.trySendToPeer(peer, {
|
|
1024
|
-
action: "load",
|
|
1025
|
-
...coValue.knownStateWithStreaming(),
|
|
1026
|
-
});
|
|
1027
|
-
peer.trackLoadRequestSent(coValue.id);
|
|
1028
|
-
syncedPeers.push(peer);
|
|
1029
|
-
}
|
|
1047
|
+
} else if (peer.role === "server") {
|
|
1048
|
+
peer.sendLoadRequest(coValue);
|
|
1030
1049
|
}
|
|
1031
1050
|
}
|
|
1032
1051
|
}
|
|
@@ -1124,6 +1143,12 @@ export class SyncManager {
|
|
|
1124
1143
|
|
|
1125
1144
|
const value = this.local.getCoValue(content.id);
|
|
1126
1145
|
|
|
1146
|
+
if (value.isDeleted) {
|
|
1147
|
+
// This doesn't persist the delete flag, it only signals the storage
|
|
1148
|
+
// API that the delete transaction is valid
|
|
1149
|
+
storage.markDeleteAsValid(value.id);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1127
1152
|
// Try to store the content as-is for performance
|
|
1128
1153
|
// In case that some transactions are missing, a correction will be requested, but it's an edge case
|
|
1129
1154
|
storage.store(content, (correction) => {
|
|
@@ -1304,3 +1329,10 @@ export function hwrServerPeerSelector(n: number): ServerPeerSelector {
|
|
|
1304
1329
|
.map((wp) => wp.peer);
|
|
1305
1330
|
};
|
|
1306
1331
|
}
|
|
1332
|
+
|
|
1333
|
+
let waitForNextTick = () =>
|
|
1334
|
+
new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
1335
|
+
|
|
1336
|
+
if (typeof setImmediate === "function") {
|
|
1337
|
+
waitForNextTick = () => new Promise<void>((resolve) => setImmediate(resolve));
|
|
1338
|
+
}
|