cojson 0.15.8 → 0.15.9
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 +10 -0
- package/dist/IncomingMessagesQueue.d.ts +27 -0
- package/dist/IncomingMessagesQueue.d.ts.map +1 -0
- package/dist/IncomingMessagesQueue.js +114 -0
- package/dist/IncomingMessagesQueue.js.map +1 -0
- package/dist/PeerState.d.ts +2 -10
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +9 -90
- package/dist/PeerState.js.map +1 -1
- package/dist/PriorityBasedMessageQueue.d.ts +2 -1
- package/dist/PriorityBasedMessageQueue.d.ts.map +1 -1
- package/dist/PriorityBasedMessageQueue.js +9 -6
- package/dist/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/SyncStateManager.d.ts +1 -0
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +1 -1
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValue.d.ts +1 -1
- package/dist/coValueCore/coValueCore.d.ts +9 -17
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +75 -50
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +10 -3
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +73 -14
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coStream.d.ts +2 -2
- package/dist/coValues/group.d.ts +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +2 -4
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +2 -1
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/exports.d.ts +18 -7
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +11 -8
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +8 -2
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +19 -12
- package/dist/localNode.js.map +1 -1
- package/dist/storage/StoreQueue.d.ts +15 -0
- package/dist/storage/StoreQueue.d.ts.map +1 -0
- package/dist/storage/StoreQueue.js +35 -0
- package/dist/storage/StoreQueue.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/knownState.d.ts +18 -0
- package/dist/storage/knownState.d.ts.map +1 -0
- package/dist/storage/knownState.js +63 -0
- package/dist/storage/knownState.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +37 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -0
- package/dist/storage/sqlite/client.js +89 -0
- package/dist/storage/sqlite/client.js.map +1 -0
- package/dist/storage/sqlite/index.d.ts +5 -0
- package/dist/storage/sqlite/index.d.ts.map +1 -0
- package/dist/storage/sqlite/index.js +13 -0
- package/dist/storage/sqlite/index.js.map +1 -0
- package/dist/storage/sqlite/sqliteMigrations.d.ts +3 -0
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -0
- package/dist/storage/sqlite/sqliteMigrations.js +44 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -0
- package/dist/storage/sqlite/types.d.ts +8 -0
- package/dist/storage/sqlite/types.d.ts.map +1 -0
- package/dist/storage/sqlite/types.js +2 -0
- package/dist/storage/sqlite/types.js.map +1 -0
- package/dist/storage/sqliteAsync/client.d.ts +37 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -0
- package/dist/storage/sqliteAsync/client.js +88 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -0
- package/dist/storage/sqliteAsync/index.d.ts +6 -0
- package/dist/storage/sqliteAsync/index.d.ts.map +1 -0
- package/dist/storage/sqliteAsync/index.js +15 -0
- package/dist/storage/sqliteAsync/index.js.map +1 -0
- package/dist/storage/sqliteAsync/types.d.ts +9 -0
- package/dist/storage/sqliteAsync/types.d.ts.map +1 -0
- package/dist/storage/sqliteAsync/types.js +2 -0
- package/dist/storage/sqliteAsync/types.js.map +1 -0
- package/dist/storage/storageAsync.d.ts +22 -0
- package/dist/storage/storageAsync.d.ts.map +1 -0
- package/dist/storage/storageAsync.js +214 -0
- package/dist/storage/storageAsync.js.map +1 -0
- package/dist/storage/storageSync.d.ts +21 -0
- package/dist/storage/storageSync.d.ts.map +1 -0
- package/dist/storage/storageSync.js +206 -0
- package/dist/storage/storageSync.js.map +1 -0
- package/dist/storage/syncUtils.d.ts +13 -0
- package/dist/storage/syncUtils.d.ts.map +1 -0
- package/dist/storage/syncUtils.js +25 -0
- package/dist/storage/syncUtils.js.map +1 -0
- package/dist/storage/types.d.ts +82 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/streamUtils.d.ts +13 -9
- package/dist/streamUtils.d.ts.map +1 -1
- package/dist/streamUtils.js +46 -13
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.d.ts +22 -14
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +143 -125
- package/dist/sync.js.map +1 -1
- package/dist/tests/IncomingMessagesQueue.test.d.ts +2 -0
- package/dist/tests/IncomingMessagesQueue.test.d.ts.map +1 -0
- package/dist/tests/IncomingMessagesQueue.test.js +437 -0
- package/dist/tests/IncomingMessagesQueue.test.js.map +1 -0
- package/dist/tests/PeerState.test.js +6 -94
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/PriorityBasedMessageQueue.test.js +14 -14
- package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -1
- package/dist/tests/StoreQueue.test.d.ts +2 -0
- package/dist/tests/StoreQueue.test.d.ts.map +1 -0
- package/dist/tests/StoreQueue.test.js +208 -0
- package/dist/tests/StoreQueue.test.js.map +1 -0
- package/dist/tests/SyncStateManager.test.js +3 -1
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/account.test.js +9 -9
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/coStream.test.js +1 -1
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +208 -1
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueCoreLoadingState.test.js +2 -2
- package/dist/tests/coValueCoreLoadingState.test.js.map +1 -1
- package/dist/tests/group.addMember.test.js.map +1 -1
- package/dist/tests/group.removeMember.test.js +1 -1
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.js +1 -1
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/sync.auth.test.js +23 -15
- package/dist/tests/sync.auth.test.js.map +1 -1
- package/dist/tests/sync.invite.test.js +10 -16
- package/dist/tests/sync.invite.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +52 -50
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +173 -56
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +42 -32
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +162 -62
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.d.ts +2 -0
- package/dist/tests/sync.storageAsync.test.d.ts.map +1 -0
- package/dist/tests/sync.storageAsync.test.js +361 -0
- package/dist/tests/sync.storageAsync.test.js.map +1 -0
- package/dist/tests/sync.test.js +16 -21
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +28 -25
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +12 -0
- package/dist/tests/testStorage.d.ts.map +1 -0
- package/dist/tests/testStorage.js +151 -0
- package/dist/tests/testStorage.js.map +1 -0
- package/dist/tests/testUtils.d.ts +20 -15
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +79 -45
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/IncomingMessagesQueue.ts +142 -0
- package/src/PeerState.ts +11 -110
- package/src/PriorityBasedMessageQueue.ts +13 -5
- package/src/SyncStateManager.ts +1 -1
- package/src/coValueCore/coValueCore.ts +100 -66
- package/src/coValueCore/verifiedState.ts +91 -21
- package/src/coValues/group.ts +2 -4
- package/src/config.ts +26 -0
- package/src/crypto/WasmCrypto.ts +3 -1
- package/src/exports.ts +20 -27
- package/src/localNode.ts +27 -12
- package/src/storage/StoreQueue.ts +56 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/knownState.ts +88 -0
- package/src/storage/sqlite/client.ts +180 -0
- package/src/storage/sqlite/index.ts +19 -0
- package/src/storage/sqlite/sqliteMigrations.ts +44 -0
- package/src/storage/sqlite/types.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +179 -0
- package/src/storage/sqliteAsync/index.ts +25 -0
- package/src/storage/sqliteAsync/types.ts +8 -0
- package/src/storage/storageAsync.ts +367 -0
- package/src/storage/storageSync.ts +343 -0
- package/src/storage/syncUtils.ts +50 -0
- package/src/storage/types.ts +162 -0
- package/src/streamUtils.ts +61 -19
- package/src/sync.ts +191 -160
- package/src/tests/IncomingMessagesQueue.test.ts +626 -0
- package/src/tests/PeerState.test.ts +6 -118
- package/src/tests/PriorityBasedMessageQueue.test.ts +18 -14
- package/src/tests/StoreQueue.test.ts +283 -0
- package/src/tests/SyncStateManager.test.ts +4 -1
- package/src/tests/account.test.ts +11 -12
- package/src/tests/coStream.test.ts +1 -3
- package/src/tests/coValueCore.test.ts +270 -1
- package/src/tests/coValueCoreLoadingState.test.ts +2 -2
- package/src/tests/group.addMember.test.ts +1 -0
- package/src/tests/group.removeMember.test.ts +2 -8
- package/src/tests/messagesTestUtils.ts +2 -2
- package/src/tests/sync.auth.test.ts +24 -14
- package/src/tests/sync.invite.test.ts +11 -17
- package/src/tests/sync.load.test.ts +53 -49
- package/src/tests/sync.mesh.test.ts +198 -56
- package/src/tests/sync.peerReconciliation.test.ts +44 -34
- package/src/tests/sync.storage.test.ts +231 -64
- package/src/tests/sync.storageAsync.test.ts +486 -0
- package/src/tests/sync.test.ts +17 -23
- package/src/tests/sync.upload.test.ts +29 -24
- package/src/tests/testStorage.ts +216 -0
- package/src/tests/testUtils.ts +89 -54
package/src/localNode.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { CoID } from "./coValue.js";
|
|
|
3
3
|
import { RawCoValue } from "./coValue.js";
|
|
4
4
|
import {
|
|
5
5
|
AvailableCoValueCore,
|
|
6
|
-
CO_VALUE_LOADING_CONFIG,
|
|
7
6
|
CoValueCore,
|
|
8
7
|
idforHeader,
|
|
9
8
|
} from "./coValueCore/coValueCore.js";
|
|
@@ -31,9 +30,11 @@ import {
|
|
|
31
30
|
RawGroup,
|
|
32
31
|
secretSeedFromInviteSecret,
|
|
33
32
|
} from "./coValues/group.js";
|
|
33
|
+
import { CO_VALUE_LOADING_CONFIG } from "./config.js";
|
|
34
34
|
import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
|
|
35
35
|
import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
|
|
36
36
|
import { logger } from "./logger.js";
|
|
37
|
+
import { StorageAPI } from "./storage/index.js";
|
|
37
38
|
import { Peer, PeerID, SyncManager } from "./sync.js";
|
|
38
39
|
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
39
40
|
import { expectGroup } from "./typeUtils/expectGroup.js";
|
|
@@ -64,6 +65,8 @@ export class LocalNode {
|
|
|
64
65
|
|
|
65
66
|
crashed: Error | undefined = undefined;
|
|
66
67
|
|
|
68
|
+
storage?: StorageAPI;
|
|
69
|
+
|
|
67
70
|
/** @category 3. Low-level */
|
|
68
71
|
constructor(
|
|
69
72
|
agentSecret: AgentSecret,
|
|
@@ -75,6 +78,10 @@ export class LocalNode {
|
|
|
75
78
|
this.crypto = crypto;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
setStorage(storage: StorageAPI) {
|
|
82
|
+
this.storage = storage;
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
getCoValue(id: RawCoID) {
|
|
79
86
|
let entry = this.coValues.get(id);
|
|
80
87
|
|
|
@@ -142,6 +149,7 @@ export class LocalNode {
|
|
|
142
149
|
crypto: CryptoProvider;
|
|
143
150
|
initialAgentSecret?: AgentSecret;
|
|
144
151
|
peersToLoadFrom?: Peer[];
|
|
152
|
+
storage?: StorageAPI;
|
|
145
153
|
}): RawAccount {
|
|
146
154
|
const {
|
|
147
155
|
crypto,
|
|
@@ -160,6 +168,10 @@ export class LocalNode {
|
|
|
160
168
|
crypto,
|
|
161
169
|
);
|
|
162
170
|
|
|
171
|
+
if (opts.storage) {
|
|
172
|
+
node.setStorage(opts.storage);
|
|
173
|
+
}
|
|
174
|
+
|
|
163
175
|
for (const peer of peersToLoadFrom) {
|
|
164
176
|
node.syncManager.addPeer(peer);
|
|
165
177
|
}
|
|
@@ -198,12 +210,14 @@ export class LocalNode {
|
|
|
198
210
|
migration,
|
|
199
211
|
crypto,
|
|
200
212
|
initialAgentSecret = crypto.newRandomAgentSecret(),
|
|
213
|
+
storage,
|
|
201
214
|
}: {
|
|
202
215
|
creationProps: { name: string };
|
|
203
216
|
peersToLoadFrom?: Peer[];
|
|
204
217
|
migration?: RawAccountMigration<AccountMeta>;
|
|
205
218
|
crypto: CryptoProvider;
|
|
206
219
|
initialAgentSecret?: AgentSecret;
|
|
220
|
+
storage?: StorageAPI;
|
|
207
221
|
}): Promise<{
|
|
208
222
|
node: LocalNode;
|
|
209
223
|
accountID: RawAccountID;
|
|
@@ -214,6 +228,7 @@ export class LocalNode {
|
|
|
214
228
|
crypto,
|
|
215
229
|
initialAgentSecret,
|
|
216
230
|
peersToLoadFrom,
|
|
231
|
+
storage,
|
|
217
232
|
});
|
|
218
233
|
const node = account.core.node;
|
|
219
234
|
|
|
@@ -234,7 +249,7 @@ export class LocalNode {
|
|
|
234
249
|
throw new Error("Must set account profile in initial migration");
|
|
235
250
|
}
|
|
236
251
|
|
|
237
|
-
if (node.
|
|
252
|
+
if (node.storage) {
|
|
238
253
|
await Promise.all([
|
|
239
254
|
node.syncManager.waitForStorageSync(account.id),
|
|
240
255
|
node.syncManager.waitForStorageSync(profileId),
|
|
@@ -257,6 +272,7 @@ export class LocalNode {
|
|
|
257
272
|
peersToLoadFrom,
|
|
258
273
|
crypto,
|
|
259
274
|
migration,
|
|
275
|
+
storage,
|
|
260
276
|
}: {
|
|
261
277
|
accountID: RawAccountID;
|
|
262
278
|
accountSecret: AgentSecret;
|
|
@@ -264,6 +280,7 @@ export class LocalNode {
|
|
|
264
280
|
peersToLoadFrom: Peer[];
|
|
265
281
|
crypto: CryptoProvider;
|
|
266
282
|
migration?: RawAccountMigration<AccountMeta>;
|
|
283
|
+
storage?: StorageAPI;
|
|
267
284
|
}): Promise<LocalNode> {
|
|
268
285
|
try {
|
|
269
286
|
const node = new LocalNode(
|
|
@@ -272,6 +289,10 @@ export class LocalNode {
|
|
|
272
289
|
crypto,
|
|
273
290
|
);
|
|
274
291
|
|
|
292
|
+
if (storage) {
|
|
293
|
+
node.setStorage(storage);
|
|
294
|
+
}
|
|
295
|
+
|
|
275
296
|
for (const peer of peersToLoadFrom) {
|
|
276
297
|
node.syncManager.addPeer(peer);
|
|
277
298
|
}
|
|
@@ -354,23 +375,16 @@ export class LocalNode {
|
|
|
354
375
|
coValue.loadingState === "unknown" ||
|
|
355
376
|
coValue.loadingState === "unavailable"
|
|
356
377
|
) {
|
|
357
|
-
const peers =
|
|
358
|
-
this.syncManager.getServerAndStoragePeers(skipLoadingFromPeer);
|
|
378
|
+
const peers = this.syncManager.getServerPeers(skipLoadingFromPeer);
|
|
359
379
|
|
|
360
|
-
if (peers.length === 0) {
|
|
380
|
+
if (!this.storage && peers.length === 0) {
|
|
361
381
|
return coValue;
|
|
362
382
|
}
|
|
363
383
|
|
|
364
|
-
coValue.
|
|
365
|
-
logger.error("Error loading from peers", {
|
|
366
|
-
id,
|
|
367
|
-
err: e,
|
|
368
|
-
});
|
|
369
|
-
});
|
|
384
|
+
coValue.load(peers);
|
|
370
385
|
}
|
|
371
386
|
|
|
372
387
|
const result = await coValue.waitForAvailableOrUnavailable();
|
|
373
|
-
|
|
374
388
|
if (
|
|
375
389
|
result.isAvailable() ||
|
|
376
390
|
skipRetry ||
|
|
@@ -714,6 +728,7 @@ export class LocalNode {
|
|
|
714
728
|
}
|
|
715
729
|
|
|
716
730
|
gracefulShutdown() {
|
|
731
|
+
this.storage?.close();
|
|
717
732
|
this.syncManager.gracefulShutdown();
|
|
718
733
|
}
|
|
719
734
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LinkedList } from "../PriorityBasedMessageQueue.js";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
import { CoValueKnownState, NewContentMessage } from "../sync.js";
|
|
4
|
+
|
|
5
|
+
type StoreQueueEntry = {
|
|
6
|
+
data: NewContentMessage[];
|
|
7
|
+
correctionCallback: (data: CoValueKnownState) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class StoreQueue {
|
|
11
|
+
private queue = new LinkedList<StoreQueueEntry>();
|
|
12
|
+
|
|
13
|
+
public push(
|
|
14
|
+
data: NewContentMessage[],
|
|
15
|
+
correctionCallback: (data: CoValueKnownState) => void,
|
|
16
|
+
) {
|
|
17
|
+
this.queue.push({ data, correctionCallback });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public pull() {
|
|
21
|
+
return this.queue.shift();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
processing = false;
|
|
25
|
+
|
|
26
|
+
async processQueue(
|
|
27
|
+
callback: (
|
|
28
|
+
data: NewContentMessage[],
|
|
29
|
+
correctionCallback: (data: CoValueKnownState) => void,
|
|
30
|
+
) => Promise<void>,
|
|
31
|
+
) {
|
|
32
|
+
if (this.processing) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.processing = true;
|
|
37
|
+
|
|
38
|
+
let entry: StoreQueueEntry | undefined;
|
|
39
|
+
|
|
40
|
+
while ((entry = this.pull())) {
|
|
41
|
+
const { data, correctionCallback } = entry;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await callback(data, correctionCallback);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
logger.error("Error processing message in store queue", { err });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.processing = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
drain() {
|
|
54
|
+
while (this.pull()) {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getIsUploaded } from "../SyncStateManager.js";
|
|
2
|
+
import { type CoValueCore } from "../exports.js";
|
|
3
|
+
import { RawCoID } from "../ids.js";
|
|
4
|
+
import { CoValueKnownState, emptyKnownState } from "../sync.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Track how much data we have stored inside our storage
|
|
8
|
+
* and provides the API to wait for the data to be fully stored.
|
|
9
|
+
*/
|
|
10
|
+
export class StorageKnownState {
|
|
11
|
+
knwonStates = new Map<string, CoValueKnownState>();
|
|
12
|
+
|
|
13
|
+
getKnownState(id: string): CoValueKnownState {
|
|
14
|
+
const knownState = this.knwonStates.get(id);
|
|
15
|
+
|
|
16
|
+
if (!knownState) {
|
|
17
|
+
const empty = emptyKnownState(id as RawCoID);
|
|
18
|
+
this.knwonStates.set(id, empty);
|
|
19
|
+
return empty;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return knownState;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setKnownState(id: string, knownState: CoValueKnownState) {
|
|
26
|
+
this.knwonStates.set(id, knownState);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
handleUpdate(id: string, knownState: CoValueKnownState) {
|
|
30
|
+
const requests = this.waitForSyncRequests.get(id);
|
|
31
|
+
|
|
32
|
+
if (!requests) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const request of requests) {
|
|
37
|
+
if (isInSync(request.knownState, knownState)) {
|
|
38
|
+
request.resolve();
|
|
39
|
+
requests.delete(request);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
waitForSyncRequests = new Map<
|
|
45
|
+
string,
|
|
46
|
+
Set<{
|
|
47
|
+
knownState: CoValueKnownState;
|
|
48
|
+
resolve: (value: void) => void;
|
|
49
|
+
}>
|
|
50
|
+
>();
|
|
51
|
+
|
|
52
|
+
waitForSync(id: string, coValue: CoValueCore) {
|
|
53
|
+
const initialKnownState = coValue.knownState();
|
|
54
|
+
if (isInSync(initialKnownState, this.getKnownState(id))) {
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const requests = this.waitForSyncRequests.get(id) || new Set();
|
|
59
|
+
this.waitForSyncRequests.set(id, requests);
|
|
60
|
+
|
|
61
|
+
return new Promise<void>((resolve) => {
|
|
62
|
+
const unsubscribe = coValue.subscribe((coValue) => {
|
|
63
|
+
req.knownState = coValue.knownState();
|
|
64
|
+
this.handleUpdate(id, this.getKnownState(id));
|
|
65
|
+
}, false);
|
|
66
|
+
|
|
67
|
+
const handleResolve = () => {
|
|
68
|
+
resolve();
|
|
69
|
+
unsubscribe();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const req = { knownState: initialKnownState, resolve: handleResolve };
|
|
73
|
+
|
|
74
|
+
requests.add(req);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isInSync(
|
|
80
|
+
knownState: CoValueKnownState,
|
|
81
|
+
knownStateFromStorage: CoValueKnownState,
|
|
82
|
+
) {
|
|
83
|
+
if (!knownStateFromStorage.header && knownState.header) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return getIsUploaded(knownState.sessions, knownStateFromStorage.sessions);
|
|
88
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CoValueHeader,
|
|
3
|
+
Transaction,
|
|
4
|
+
} from "../../coValueCore/verifiedState.js";
|
|
5
|
+
import type { Signature } from "../../crypto/crypto.js";
|
|
6
|
+
import type { RawCoID, SessionID } from "../../exports.js";
|
|
7
|
+
import { logger } from "../../logger.js";
|
|
8
|
+
import type { NewContentMessage } from "../../sync.js";
|
|
9
|
+
import type {
|
|
10
|
+
DBClientInterfaceSync,
|
|
11
|
+
SessionRow,
|
|
12
|
+
SignatureAfterRow,
|
|
13
|
+
StoredCoValueRow,
|
|
14
|
+
StoredSessionRow,
|
|
15
|
+
TransactionRow,
|
|
16
|
+
} from "../types.js";
|
|
17
|
+
import type { SQLiteDatabaseDriver } from "./types.js";
|
|
18
|
+
|
|
19
|
+
export type RawCoValueRow = {
|
|
20
|
+
id: RawCoID;
|
|
21
|
+
header: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type RawTransactionRow = {
|
|
25
|
+
ses: number;
|
|
26
|
+
idx: number;
|
|
27
|
+
tx: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function getErrorMessage(error: unknown) {
|
|
31
|
+
return error instanceof Error ? error.message : "Unknown error";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class SQLiteClient implements DBClientInterfaceSync {
|
|
35
|
+
private readonly db: SQLiteDatabaseDriver;
|
|
36
|
+
|
|
37
|
+
constructor(db: SQLiteDatabaseDriver) {
|
|
38
|
+
this.db = db;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getCoValue(coValueId: RawCoID): StoredCoValueRow | undefined {
|
|
42
|
+
const coValueRow = this.db.get<RawCoValueRow & { rowID: number }>(
|
|
43
|
+
"SELECT * FROM coValues WHERE id = ?",
|
|
44
|
+
[coValueId],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (!coValueRow) return;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const parsedHeader = (coValueRow?.header &&
|
|
51
|
+
JSON.parse(coValueRow.header)) as CoValueHeader;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
...coValueRow,
|
|
55
|
+
header: parsedHeader,
|
|
56
|
+
};
|
|
57
|
+
} catch (e) {
|
|
58
|
+
const headerValue = coValueRow?.header ?? "";
|
|
59
|
+
logger.warn(`Invalid JSON in header: ${headerValue}`, {
|
|
60
|
+
id: coValueId,
|
|
61
|
+
err: e,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getCoValueSessions(coValueRowId: number): StoredSessionRow[] {
|
|
68
|
+
return this.db.query<StoredSessionRow>(
|
|
69
|
+
"SELECT * FROM sessions WHERE coValue = ?",
|
|
70
|
+
[coValueRowId],
|
|
71
|
+
) as StoredSessionRow[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getSingleCoValueSession(
|
|
75
|
+
coValueRowId: number,
|
|
76
|
+
sessionID: SessionID,
|
|
77
|
+
): StoredSessionRow | undefined {
|
|
78
|
+
return this.db.get<StoredSessionRow>(
|
|
79
|
+
"SELECT * FROM sessions WHERE coValue = ? AND sessionID = ?",
|
|
80
|
+
[coValueRowId, sessionID],
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getNewTransactionInSession(
|
|
85
|
+
sessionRowId: number,
|
|
86
|
+
fromIdx: number,
|
|
87
|
+
toIdx: number,
|
|
88
|
+
): TransactionRow[] {
|
|
89
|
+
const txs = this.db.query<RawTransactionRow>(
|
|
90
|
+
"SELECT * FROM transactions WHERE ses = ? AND idx >= ? AND idx <= ?",
|
|
91
|
+
[sessionRowId, fromIdx, toIdx],
|
|
92
|
+
) as RawTransactionRow[];
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
return txs.map((transactionRow) => ({
|
|
96
|
+
...transactionRow,
|
|
97
|
+
tx: JSON.parse(transactionRow.tx) as Transaction,
|
|
98
|
+
}));
|
|
99
|
+
} catch (e) {
|
|
100
|
+
logger.warn("Invalid JSON in transaction", { err: e });
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getSignatures(
|
|
106
|
+
sessionRowId: number,
|
|
107
|
+
firstNewTxIdx: number,
|
|
108
|
+
): SignatureAfterRow[] {
|
|
109
|
+
return this.db.query<SignatureAfterRow>(
|
|
110
|
+
"SELECT * FROM signatureAfter WHERE ses = ? AND idx >= ?",
|
|
111
|
+
[sessionRowId, firstNewTxIdx],
|
|
112
|
+
) as SignatureAfterRow[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
addCoValue(msg: NewContentMessage): number {
|
|
116
|
+
const result = this.db.get<{ rowID: number }>(
|
|
117
|
+
"INSERT INTO coValues (id, header) VALUES (?, ?) RETURNING rowID",
|
|
118
|
+
[msg.id, JSON.stringify(msg.header)],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!result) {
|
|
122
|
+
throw new Error("Failed to add coValue");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result.rowID;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
addSessionUpdate({
|
|
129
|
+
sessionUpdate,
|
|
130
|
+
}: {
|
|
131
|
+
sessionUpdate: SessionRow;
|
|
132
|
+
}): number {
|
|
133
|
+
const result = this.db.get<{ rowID: number }>(
|
|
134
|
+
`INSERT INTO sessions (coValue, sessionID, lastIdx, lastSignature, bytesSinceLastSignature) VALUES (?, ?, ?, ?, ?)
|
|
135
|
+
ON CONFLICT(coValue, sessionID) DO UPDATE SET lastIdx=excluded.lastIdx, lastSignature=excluded.lastSignature, bytesSinceLastSignature=excluded.bytesSinceLastSignature
|
|
136
|
+
RETURNING rowID`,
|
|
137
|
+
[
|
|
138
|
+
sessionUpdate.coValue,
|
|
139
|
+
sessionUpdate.sessionID,
|
|
140
|
+
sessionUpdate.lastIdx,
|
|
141
|
+
sessionUpdate.lastSignature,
|
|
142
|
+
sessionUpdate.bytesSinceLastSignature,
|
|
143
|
+
],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (!result) {
|
|
147
|
+
throw new Error("Failed to add session update");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result.rowID;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
addTransaction(
|
|
154
|
+
sessionRowID: number,
|
|
155
|
+
nextIdx: number,
|
|
156
|
+
newTransaction: Transaction,
|
|
157
|
+
) {
|
|
158
|
+
this.db.run("INSERT INTO transactions (ses, idx, tx) VALUES (?, ?, ?)", [
|
|
159
|
+
sessionRowID,
|
|
160
|
+
nextIdx,
|
|
161
|
+
JSON.stringify(newTransaction),
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
addSignatureAfter({
|
|
166
|
+
sessionRowID,
|
|
167
|
+
idx,
|
|
168
|
+
signature,
|
|
169
|
+
}: { sessionRowID: number; idx: number; signature: Signature }) {
|
|
170
|
+
this.db.run(
|
|
171
|
+
"INSERT INTO signatureAfter (ses, idx, signature) VALUES (?, ?, ?)",
|
|
172
|
+
[sessionRowID, idx, signature],
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
transaction(operationsCallback: () => unknown) {
|
|
177
|
+
this.db.transaction(operationsCallback);
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { StorageApiSync } from "../storageSync.js";
|
|
2
|
+
import { SQLiteClient } from "./client.js";
|
|
3
|
+
import { getSQLiteMigrationQueries } from "./sqliteMigrations.js";
|
|
4
|
+
import type { SQLiteDatabaseDriver } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export type { SQLiteDatabaseDriver };
|
|
7
|
+
|
|
8
|
+
export function getSqliteStorage(db: SQLiteDatabaseDriver) {
|
|
9
|
+
const rows = db.query<{ user_version: string }>("PRAGMA user_version", []);
|
|
10
|
+
const userVersion = Number(rows[0]?.user_version) ?? 0;
|
|
11
|
+
|
|
12
|
+
const migrations = getSQLiteMigrationQueries(userVersion);
|
|
13
|
+
|
|
14
|
+
for (const migration of migrations) {
|
|
15
|
+
db.run(migration, []);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new StorageApiSync(new SQLiteClient(db));
|
|
19
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const migrations: Record<number, string[]> = {
|
|
2
|
+
1: [
|
|
3
|
+
`CREATE TABLE IF NOT EXISTS transactions (
|
|
4
|
+
ses INTEGER,
|
|
5
|
+
idx INTEGER,
|
|
6
|
+
tx TEXT NOT NULL,
|
|
7
|
+
PRIMARY KEY (ses, idx)
|
|
8
|
+
) WITHOUT ROWID;`,
|
|
9
|
+
`CREATE TABLE IF NOT EXISTS sessions (
|
|
10
|
+
rowID INTEGER PRIMARY KEY,
|
|
11
|
+
coValue INTEGER NOT NULL,
|
|
12
|
+
sessionID TEXT NOT NULL,
|
|
13
|
+
lastIdx INTEGER,
|
|
14
|
+
lastSignature TEXT,
|
|
15
|
+
UNIQUE (sessionID, coValue)
|
|
16
|
+
);`,
|
|
17
|
+
"CREATE INDEX IF NOT EXISTS sessionsByCoValue ON sessions (coValue);",
|
|
18
|
+
`CREATE TABLE IF NOT EXISTS coValues (
|
|
19
|
+
rowID INTEGER PRIMARY KEY,
|
|
20
|
+
id TEXT NOT NULL UNIQUE,
|
|
21
|
+
header TEXT NOT NULL UNIQUE
|
|
22
|
+
);`,
|
|
23
|
+
"CREATE INDEX IF NOT EXISTS coValuesByID ON coValues (id);",
|
|
24
|
+
"PRAGMA user_version = 1;",
|
|
25
|
+
],
|
|
26
|
+
3: [
|
|
27
|
+
`CREATE TABLE IF NOT EXISTS signatureAfter (
|
|
28
|
+
ses INTEGER,
|
|
29
|
+
idx INTEGER,
|
|
30
|
+
signature TEXT NOT NULL,
|
|
31
|
+
PRIMARY KEY (ses, idx)
|
|
32
|
+
) WITHOUT ROWID;`,
|
|
33
|
+
"ALTER TABLE sessions ADD COLUMN bytesSinceLastSignature INTEGER;",
|
|
34
|
+
"PRAGMA user_version = 3;",
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function getSQLiteMigrationQueries(version: number): string[] {
|
|
39
|
+
return Object.keys(migrations)
|
|
40
|
+
.map((k) => Number.parseInt(k, 10))
|
|
41
|
+
.filter((v) => v > version)
|
|
42
|
+
.sort((a, b) => a - b)
|
|
43
|
+
.flatMap((v) => migrations[v] ?? []);
|
|
44
|
+
}
|