cojson 0.15.7 → 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.
Files changed (218) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/IncomingMessagesQueue.d.ts +27 -0
  4. package/dist/IncomingMessagesQueue.d.ts.map +1 -0
  5. package/dist/IncomingMessagesQueue.js +114 -0
  6. package/dist/IncomingMessagesQueue.js.map +1 -0
  7. package/dist/PeerState.d.ts +2 -10
  8. package/dist/PeerState.d.ts.map +1 -1
  9. package/dist/PeerState.js +9 -90
  10. package/dist/PeerState.js.map +1 -1
  11. package/dist/PriorityBasedMessageQueue.d.ts +2 -1
  12. package/dist/PriorityBasedMessageQueue.d.ts.map +1 -1
  13. package/dist/PriorityBasedMessageQueue.js +9 -6
  14. package/dist/PriorityBasedMessageQueue.js.map +1 -1
  15. package/dist/SyncStateManager.d.ts +1 -0
  16. package/dist/SyncStateManager.d.ts.map +1 -1
  17. package/dist/SyncStateManager.js +1 -1
  18. package/dist/SyncStateManager.js.map +1 -1
  19. package/dist/coValue.d.ts +1 -1
  20. package/dist/coValueCore/coValueCore.d.ts +9 -17
  21. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  22. package/dist/coValueCore/coValueCore.js +75 -50
  23. package/dist/coValueCore/coValueCore.js.map +1 -1
  24. package/dist/coValueCore/verifiedState.d.ts +10 -3
  25. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  26. package/dist/coValueCore/verifiedState.js +73 -14
  27. package/dist/coValueCore/verifiedState.js.map +1 -1
  28. package/dist/coValues/coMap.d.ts +3 -3
  29. package/dist/coValues/coStream.d.ts +2 -2
  30. package/dist/coValues/group.d.ts +1 -1
  31. package/dist/coValues/group.d.ts.map +1 -1
  32. package/dist/coValues/group.js +2 -4
  33. package/dist/coValues/group.js.map +1 -1
  34. package/dist/config.d.ts +19 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +23 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  39. package/dist/crypto/WasmCrypto.js +2 -1
  40. package/dist/crypto/WasmCrypto.js.map +1 -1
  41. package/dist/exports.d.ts +18 -7
  42. package/dist/exports.d.ts.map +1 -1
  43. package/dist/exports.js +11 -8
  44. package/dist/exports.js.map +1 -1
  45. package/dist/localNode.d.ts +8 -2
  46. package/dist/localNode.d.ts.map +1 -1
  47. package/dist/localNode.js +19 -12
  48. package/dist/localNode.js.map +1 -1
  49. package/dist/storage/StoreQueue.d.ts +15 -0
  50. package/dist/storage/StoreQueue.d.ts.map +1 -0
  51. package/dist/storage/StoreQueue.js +35 -0
  52. package/dist/storage/StoreQueue.js.map +1 -0
  53. package/dist/storage/index.d.ts +6 -0
  54. package/dist/storage/index.d.ts.map +1 -0
  55. package/dist/storage/index.js +6 -0
  56. package/dist/storage/index.js.map +1 -0
  57. package/dist/storage/knownState.d.ts +18 -0
  58. package/dist/storage/knownState.d.ts.map +1 -0
  59. package/dist/storage/knownState.js +63 -0
  60. package/dist/storage/knownState.js.map +1 -0
  61. package/dist/storage/sqlite/client.d.ts +37 -0
  62. package/dist/storage/sqlite/client.d.ts.map +1 -0
  63. package/dist/storage/sqlite/client.js +89 -0
  64. package/dist/storage/sqlite/client.js.map +1 -0
  65. package/dist/storage/sqlite/index.d.ts +5 -0
  66. package/dist/storage/sqlite/index.d.ts.map +1 -0
  67. package/dist/storage/sqlite/index.js +13 -0
  68. package/dist/storage/sqlite/index.js.map +1 -0
  69. package/dist/storage/sqlite/sqliteMigrations.d.ts +3 -0
  70. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -0
  71. package/dist/storage/sqlite/sqliteMigrations.js +44 -0
  72. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -0
  73. package/dist/storage/sqlite/types.d.ts +8 -0
  74. package/dist/storage/sqlite/types.d.ts.map +1 -0
  75. package/dist/storage/sqlite/types.js +2 -0
  76. package/dist/storage/sqlite/types.js.map +1 -0
  77. package/dist/storage/sqliteAsync/client.d.ts +37 -0
  78. package/dist/storage/sqliteAsync/client.d.ts.map +1 -0
  79. package/dist/storage/sqliteAsync/client.js +88 -0
  80. package/dist/storage/sqliteAsync/client.js.map +1 -0
  81. package/dist/storage/sqliteAsync/index.d.ts +6 -0
  82. package/dist/storage/sqliteAsync/index.d.ts.map +1 -0
  83. package/dist/storage/sqliteAsync/index.js +15 -0
  84. package/dist/storage/sqliteAsync/index.js.map +1 -0
  85. package/dist/storage/sqliteAsync/types.d.ts +9 -0
  86. package/dist/storage/sqliteAsync/types.d.ts.map +1 -0
  87. package/dist/storage/sqliteAsync/types.js +2 -0
  88. package/dist/storage/sqliteAsync/types.js.map +1 -0
  89. package/dist/storage/storageAsync.d.ts +22 -0
  90. package/dist/storage/storageAsync.d.ts.map +1 -0
  91. package/dist/storage/storageAsync.js +214 -0
  92. package/dist/storage/storageAsync.js.map +1 -0
  93. package/dist/storage/storageSync.d.ts +21 -0
  94. package/dist/storage/storageSync.d.ts.map +1 -0
  95. package/dist/storage/storageSync.js +206 -0
  96. package/dist/storage/storageSync.js.map +1 -0
  97. package/dist/storage/syncUtils.d.ts +13 -0
  98. package/dist/storage/syncUtils.d.ts.map +1 -0
  99. package/dist/storage/syncUtils.js +25 -0
  100. package/dist/storage/syncUtils.js.map +1 -0
  101. package/dist/storage/types.d.ts +82 -0
  102. package/dist/storage/types.d.ts.map +1 -0
  103. package/dist/storage/types.js +2 -0
  104. package/dist/storage/types.js.map +1 -0
  105. package/dist/streamUtils.d.ts +13 -9
  106. package/dist/streamUtils.d.ts.map +1 -1
  107. package/dist/streamUtils.js +46 -13
  108. package/dist/streamUtils.js.map +1 -1
  109. package/dist/sync.d.ts +22 -14
  110. package/dist/sync.d.ts.map +1 -1
  111. package/dist/sync.js +143 -125
  112. package/dist/sync.js.map +1 -1
  113. package/dist/tests/IncomingMessagesQueue.test.d.ts +2 -0
  114. package/dist/tests/IncomingMessagesQueue.test.d.ts.map +1 -0
  115. package/dist/tests/IncomingMessagesQueue.test.js +437 -0
  116. package/dist/tests/IncomingMessagesQueue.test.js.map +1 -0
  117. package/dist/tests/PeerState.test.js +6 -94
  118. package/dist/tests/PeerState.test.js.map +1 -1
  119. package/dist/tests/PriorityBasedMessageQueue.test.js +14 -14
  120. package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -1
  121. package/dist/tests/StoreQueue.test.d.ts +2 -0
  122. package/dist/tests/StoreQueue.test.d.ts.map +1 -0
  123. package/dist/tests/StoreQueue.test.js +208 -0
  124. package/dist/tests/StoreQueue.test.js.map +1 -0
  125. package/dist/tests/SyncStateManager.test.js +3 -1
  126. package/dist/tests/SyncStateManager.test.js.map +1 -1
  127. package/dist/tests/account.test.js +9 -9
  128. package/dist/tests/account.test.js.map +1 -1
  129. package/dist/tests/coStream.test.js +1 -1
  130. package/dist/tests/coStream.test.js.map +1 -1
  131. package/dist/tests/coValueCore.test.js +208 -1
  132. package/dist/tests/coValueCore.test.js.map +1 -1
  133. package/dist/tests/coValueCoreLoadingState.test.js +2 -2
  134. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -1
  135. package/dist/tests/group.addMember.test.js.map +1 -1
  136. package/dist/tests/group.removeMember.test.js +1 -1
  137. package/dist/tests/group.removeMember.test.js.map +1 -1
  138. package/dist/tests/messagesTestUtils.js +1 -1
  139. package/dist/tests/messagesTestUtils.js.map +1 -1
  140. package/dist/tests/sync.auth.test.js +23 -15
  141. package/dist/tests/sync.auth.test.js.map +1 -1
  142. package/dist/tests/sync.invite.test.js +10 -16
  143. package/dist/tests/sync.invite.test.js.map +1 -1
  144. package/dist/tests/sync.load.test.js +52 -50
  145. package/dist/tests/sync.load.test.js.map +1 -1
  146. package/dist/tests/sync.mesh.test.js +173 -56
  147. package/dist/tests/sync.mesh.test.js.map +1 -1
  148. package/dist/tests/sync.peerReconciliation.test.js +42 -32
  149. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  150. package/dist/tests/sync.storage.test.js +162 -62
  151. package/dist/tests/sync.storage.test.js.map +1 -1
  152. package/dist/tests/sync.storageAsync.test.d.ts +2 -0
  153. package/dist/tests/sync.storageAsync.test.d.ts.map +1 -0
  154. package/dist/tests/sync.storageAsync.test.js +361 -0
  155. package/dist/tests/sync.storageAsync.test.js.map +1 -0
  156. package/dist/tests/sync.test.js +16 -21
  157. package/dist/tests/sync.test.js.map +1 -1
  158. package/dist/tests/sync.upload.test.js +28 -25
  159. package/dist/tests/sync.upload.test.js.map +1 -1
  160. package/dist/tests/testStorage.d.ts +12 -0
  161. package/dist/tests/testStorage.d.ts.map +1 -0
  162. package/dist/tests/testStorage.js +151 -0
  163. package/dist/tests/testStorage.js.map +1 -0
  164. package/dist/tests/testUtils.d.ts +20 -15
  165. package/dist/tests/testUtils.d.ts.map +1 -1
  166. package/dist/tests/testUtils.js +79 -45
  167. package/dist/tests/testUtils.js.map +1 -1
  168. package/package.json +2 -2
  169. package/src/IncomingMessagesQueue.ts +142 -0
  170. package/src/PeerState.ts +11 -110
  171. package/src/PriorityBasedMessageQueue.ts +13 -5
  172. package/src/SyncStateManager.ts +1 -1
  173. package/src/coValueCore/coValueCore.ts +100 -66
  174. package/src/coValueCore/verifiedState.ts +91 -21
  175. package/src/coValues/group.ts +2 -4
  176. package/src/config.ts +26 -0
  177. package/src/crypto/WasmCrypto.ts +3 -1
  178. package/src/exports.ts +20 -27
  179. package/src/localNode.ts +27 -12
  180. package/src/storage/StoreQueue.ts +56 -0
  181. package/src/storage/index.ts +5 -0
  182. package/src/storage/knownState.ts +88 -0
  183. package/src/storage/sqlite/client.ts +180 -0
  184. package/src/storage/sqlite/index.ts +19 -0
  185. package/src/storage/sqlite/sqliteMigrations.ts +44 -0
  186. package/src/storage/sqlite/types.ts +7 -0
  187. package/src/storage/sqliteAsync/client.ts +179 -0
  188. package/src/storage/sqliteAsync/index.ts +25 -0
  189. package/src/storage/sqliteAsync/types.ts +8 -0
  190. package/src/storage/storageAsync.ts +367 -0
  191. package/src/storage/storageSync.ts +343 -0
  192. package/src/storage/syncUtils.ts +50 -0
  193. package/src/storage/types.ts +162 -0
  194. package/src/streamUtils.ts +61 -19
  195. package/src/sync.ts +191 -160
  196. package/src/tests/IncomingMessagesQueue.test.ts +626 -0
  197. package/src/tests/PeerState.test.ts +6 -118
  198. package/src/tests/PriorityBasedMessageQueue.test.ts +18 -14
  199. package/src/tests/StoreQueue.test.ts +283 -0
  200. package/src/tests/SyncStateManager.test.ts +4 -1
  201. package/src/tests/account.test.ts +11 -12
  202. package/src/tests/coStream.test.ts +1 -3
  203. package/src/tests/coValueCore.test.ts +270 -1
  204. package/src/tests/coValueCoreLoadingState.test.ts +2 -2
  205. package/src/tests/group.addMember.test.ts +1 -0
  206. package/src/tests/group.removeMember.test.ts +2 -8
  207. package/src/tests/messagesTestUtils.ts +2 -2
  208. package/src/tests/sync.auth.test.ts +24 -14
  209. package/src/tests/sync.invite.test.ts +11 -17
  210. package/src/tests/sync.load.test.ts +53 -49
  211. package/src/tests/sync.mesh.test.ts +198 -56
  212. package/src/tests/sync.peerReconciliation.test.ts +44 -34
  213. package/src/tests/sync.storage.test.ts +231 -64
  214. package/src/tests/sync.storageAsync.test.ts +486 -0
  215. package/src/tests/sync.test.ts +17 -23
  216. package/src/tests/sync.upload.test.ts +29 -24
  217. package/src/tests/testStorage.ts +216 -0
  218. 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.syncManager.hasStoragePeers()) {
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.loadFromPeers(peers).catch((e) => {
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,5 @@
1
+ export * from "./types.js";
2
+ export * from "./sqlite/index.js";
3
+ export * from "./sqliteAsync/index.js";
4
+ export * from "./storageSync.js";
5
+ export * from "./storageAsync.js";
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ export interface SQLiteDatabaseDriver {
2
+ run(sql: string, params: unknown[]): void;
3
+ get<T>(sql: string, params: unknown[]): T | undefined;
4
+ query<T>(sql: string, params: unknown[]): T[];
5
+ transaction(callback: () => unknown): void;
6
+ closeDb(): void;
7
+ }