cojson 0.13.16 → 0.13.18

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 (168) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/dist/PeerState.d.ts +3 -0
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +9 -0
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/SyncStateManager.d.ts.map +1 -1
  8. package/dist/SyncStateManager.js +2 -3
  9. package/dist/SyncStateManager.js.map +1 -1
  10. package/dist/coValue.d.ts +6 -4
  11. package/dist/coValue.d.ts.map +1 -1
  12. package/dist/coValue.js +5 -4
  13. package/dist/coValue.js.map +1 -1
  14. package/dist/coValueCore/coValueCore.d.ts +143 -0
  15. package/dist/coValueCore/coValueCore.d.ts.map +1 -0
  16. package/dist/{coValueCore.js → coValueCore/coValueCore.js} +314 -246
  17. package/dist/coValueCore/coValueCore.js.map +1 -0
  18. package/dist/coValueCore/verifiedState.d.ts +65 -0
  19. package/dist/coValueCore/verifiedState.d.ts.map +1 -0
  20. package/dist/coValueCore/verifiedState.js +210 -0
  21. package/dist/coValueCore/verifiedState.js.map +1 -0
  22. package/dist/coValues/account.d.ts +8 -10
  23. package/dist/coValues/account.d.ts.map +1 -1
  24. package/dist/coValues/account.js +12 -13
  25. package/dist/coValues/account.js.map +1 -1
  26. package/dist/coValues/coList.d.ts +10 -6
  27. package/dist/coValues/coList.d.ts.map +1 -1
  28. package/dist/coValues/coList.js +41 -15
  29. package/dist/coValues/coList.js.map +1 -1
  30. package/dist/coValues/coMap.d.ts +4 -3
  31. package/dist/coValues/coMap.d.ts.map +1 -1
  32. package/dist/coValues/coMap.js +5 -3
  33. package/dist/coValues/coMap.js.map +1 -1
  34. package/dist/coValues/coPlainText.d.ts +2 -2
  35. package/dist/coValues/coPlainText.d.ts.map +1 -1
  36. package/dist/coValues/coPlainText.js +5 -5
  37. package/dist/coValues/coPlainText.js.map +1 -1
  38. package/dist/coValues/coStream.d.ts +5 -4
  39. package/dist/coValues/coStream.d.ts.map +1 -1
  40. package/dist/coValues/coStream.js +5 -3
  41. package/dist/coValues/coStream.js.map +1 -1
  42. package/dist/coValues/group.d.ts +7 -2
  43. package/dist/coValues/group.d.ts.map +1 -1
  44. package/dist/coValues/group.js +29 -26
  45. package/dist/coValues/group.js.map +1 -1
  46. package/dist/coreToCoValue.d.ts +4 -3
  47. package/dist/coreToCoValue.d.ts.map +1 -1
  48. package/dist/coreToCoValue.js +10 -14
  49. package/dist/coreToCoValue.js.map +1 -1
  50. package/dist/exports.d.ts +6 -5
  51. package/dist/exports.d.ts.map +1 -1
  52. package/dist/exports.js +3 -4
  53. package/dist/exports.js.map +1 -1
  54. package/dist/localNode.d.ts +30 -24
  55. package/dist/localNode.d.ts.map +1 -1
  56. package/dist/localNode.js +153 -177
  57. package/dist/localNode.js.map +1 -1
  58. package/dist/permissions.d.ts +2 -1
  59. package/dist/permissions.d.ts.map +1 -1
  60. package/dist/permissions.js +15 -11
  61. package/dist/permissions.js.map +1 -1
  62. package/dist/priority.d.ts +1 -1
  63. package/dist/priority.d.ts.map +1 -1
  64. package/dist/sync.d.ts +2 -2
  65. package/dist/sync.d.ts.map +1 -1
  66. package/dist/sync.js +86 -55
  67. package/dist/sync.js.map +1 -1
  68. package/dist/tests/coList.test.js +133 -13
  69. package/dist/tests/coList.test.js.map +1 -1
  70. package/dist/tests/coMap.test.js +43 -14
  71. package/dist/tests/coMap.test.js.map +1 -1
  72. package/dist/tests/coPlainText.test.js +9 -10
  73. package/dist/tests/coPlainText.test.js.map +1 -1
  74. package/dist/tests/coStream.test.js +49 -18
  75. package/dist/tests/coStream.test.js.map +1 -1
  76. package/dist/tests/coValueCore.test.js +22 -28
  77. package/dist/tests/coValueCore.test.js.map +1 -1
  78. package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
  79. package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
  80. package/dist/tests/coValueCoreLoadingState.test.js +227 -0
  81. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
  82. package/dist/tests/group.test.js +42 -43
  83. package/dist/tests/group.test.js.map +1 -1
  84. package/dist/tests/messagesTestUtils.d.ts +2 -2
  85. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  86. package/dist/tests/messagesTestUtils.js +1 -1
  87. package/dist/tests/messagesTestUtils.js.map +1 -1
  88. package/dist/tests/permissions.test.js +224 -292
  89. package/dist/tests/permissions.test.js.map +1 -1
  90. package/dist/tests/priority.test.js +13 -14
  91. package/dist/tests/priority.test.js.map +1 -1
  92. package/dist/tests/sync.auth.test.d.ts +2 -0
  93. package/dist/tests/sync.auth.test.d.ts.map +1 -0
  94. package/dist/tests/sync.auth.test.js +141 -0
  95. package/dist/tests/sync.auth.test.js.map +1 -0
  96. package/dist/tests/sync.load.test.js +60 -2
  97. package/dist/tests/sync.load.test.js.map +1 -1
  98. package/dist/tests/sync.mesh.test.js +70 -10
  99. package/dist/tests/sync.mesh.test.js.map +1 -1
  100. package/dist/tests/sync.peerReconciliation.test.js +19 -19
  101. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  102. package/dist/tests/sync.storage.test.js +20 -13
  103. package/dist/tests/sync.storage.test.js.map +1 -1
  104. package/dist/tests/sync.test.js +32 -39
  105. package/dist/tests/sync.test.js.map +1 -1
  106. package/dist/tests/sync.upload.test.js +126 -37
  107. package/dist/tests/sync.upload.test.js.map +1 -1
  108. package/dist/tests/testUtils.d.ts +24 -15
  109. package/dist/tests/testUtils.d.ts.map +1 -1
  110. package/dist/tests/testUtils.js +88 -61
  111. package/dist/tests/testUtils.js.map +1 -1
  112. package/dist/typeUtils/expectGroup.js +1 -1
  113. package/dist/typeUtils/expectGroup.js.map +1 -1
  114. package/package.json +1 -1
  115. package/src/PeerState.ts +11 -0
  116. package/src/SyncStateManager.ts +2 -3
  117. package/src/coValue.ts +14 -8
  118. package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +470 -413
  119. package/src/coValueCore/verifiedState.ts +376 -0
  120. package/src/coValues/account.ts +20 -25
  121. package/src/coValues/coList.ts +63 -29
  122. package/src/coValues/coMap.ts +13 -6
  123. package/src/coValues/coPlainText.ts +10 -8
  124. package/src/coValues/coStream.ts +12 -7
  125. package/src/coValues/group.ts +50 -28
  126. package/src/coreToCoValue.ts +14 -15
  127. package/src/exports.ts +9 -7
  128. package/src/localNode.ts +248 -283
  129. package/src/permissions.ts +18 -12
  130. package/src/priority.ts +1 -1
  131. package/src/sync.ts +96 -63
  132. package/src/tests/coList.test.ts +200 -12
  133. package/src/tests/coMap.test.ts +65 -14
  134. package/src/tests/coPlainText.test.ts +12 -9
  135. package/src/tests/coStream.test.ts +80 -17
  136. package/src/tests/coValueCore.test.ts +30 -27
  137. package/src/tests/coValueCoreLoadingState.test.ts +337 -0
  138. package/src/tests/group.test.ts +44 -68
  139. package/src/tests/messagesTestUtils.ts +3 -8
  140. package/src/tests/permissions.test.ts +283 -449
  141. package/src/tests/priority.test.ts +17 -13
  142. package/src/tests/sync.auth.test.ts +188 -0
  143. package/src/tests/sync.load.test.ts +79 -2
  144. package/src/tests/sync.mesh.test.ts +89 -9
  145. package/src/tests/sync.peerReconciliation.test.ts +25 -25
  146. package/src/tests/sync.storage.test.ts +20 -13
  147. package/src/tests/sync.test.ts +43 -43
  148. package/src/tests/sync.upload.test.ts +157 -37
  149. package/src/tests/testUtils.ts +120 -74
  150. package/src/typeUtils/expectGroup.ts +1 -1
  151. package/dist/CoValuesStore.d.ts +0 -14
  152. package/dist/CoValuesStore.d.ts.map +0 -1
  153. package/dist/CoValuesStore.js +0 -32
  154. package/dist/CoValuesStore.js.map +0 -1
  155. package/dist/coValueCore.d.ts +0 -141
  156. package/dist/coValueCore.d.ts.map +0 -1
  157. package/dist/coValueCore.js.map +0 -1
  158. package/dist/coValueState.d.ts +0 -34
  159. package/dist/coValueState.d.ts.map +0 -1
  160. package/dist/coValueState.js +0 -228
  161. package/dist/coValueState.js.map +0 -1
  162. package/dist/tests/coValueState.test.d.ts +0 -2
  163. package/dist/tests/coValueState.test.d.ts.map +0 -1
  164. package/dist/tests/coValueState.test.js +0 -344
  165. package/dist/tests/coValueState.test.js.map +0 -1
  166. package/src/CoValuesStore.ts +0 -41
  167. package/src/coValueState.ts +0 -300
  168. package/src/tests/coValueState.test.ts +0 -525
@@ -0,0 +1,376 @@
1
+ import { Result, err, ok } from "neverthrow";
2
+ import { AnyRawCoValue } from "../coValue.js";
3
+ import {
4
+ CryptoProvider,
5
+ Encrypted,
6
+ Hash,
7
+ KeyID,
8
+ Signature,
9
+ SignerID,
10
+ StreamingHash,
11
+ } from "../crypto/crypto.js";
12
+ import { RawCoID, SessionID, TransactionID } from "../ids.js";
13
+ import { Stringified } from "../jsonStringify.js";
14
+ import { JsonObject, JsonValue } from "../jsonValue.js";
15
+ import { PermissionsDef as RulesetDef } from "../permissions.js";
16
+ import { getPriorityFromHeader } from "../priority.js";
17
+ import { CoValueKnownState, NewContentMessage } from "../sync.js";
18
+ import {
19
+ InvalidHashError,
20
+ InvalidSignatureError,
21
+ MAX_RECOMMENDED_TX_SIZE,
22
+ } from "./coValueCore.js";
23
+ import { TryAddTransactionsError } from "./coValueCore.js";
24
+
25
+ export type CoValueHeader = {
26
+ type: AnyRawCoValue["type"];
27
+ ruleset: RulesetDef;
28
+ meta: JsonObject | null;
29
+ } & CoValueUniqueness;
30
+
31
+ export type CoValueUniqueness = {
32
+ uniqueness: JsonValue;
33
+ createdAt?: `2${string}` | null;
34
+ };
35
+
36
+ export type PrivateTransaction = {
37
+ privacy: "private";
38
+ madeAt: number;
39
+ keyUsed: KeyID;
40
+ encryptedChanges: Encrypted<JsonValue[], { in: RawCoID; tx: TransactionID }>;
41
+ };
42
+
43
+ export type TrustingTransaction = {
44
+ privacy: "trusting";
45
+ madeAt: number;
46
+ changes: Stringified<JsonValue[]>;
47
+ };
48
+
49
+ export type Transaction = PrivateTransaction | TrustingTransaction;
50
+
51
+ type SessionLog = {
52
+ readonly transactions: Transaction[];
53
+ lastHash?: Hash;
54
+ streamingHash: StreamingHash;
55
+ readonly signatureAfter: { [txIdx: number]: Signature | undefined };
56
+ lastSignature: Signature;
57
+ };
58
+
59
+ export type ValidatedSessions = Map<SessionID, SessionLog>;
60
+
61
+ export class VerifiedState {
62
+ readonly id: RawCoID;
63
+ readonly crypto: CryptoProvider;
64
+ readonly header: CoValueHeader;
65
+ readonly sessions: ValidatedSessions;
66
+ private _cachedKnownState?: CoValueKnownState;
67
+ private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
68
+
69
+ constructor(
70
+ id: RawCoID,
71
+ crypto: CryptoProvider,
72
+ header: CoValueHeader,
73
+ sessions: ValidatedSessions,
74
+ ) {
75
+ this.id = id;
76
+ this.crypto = crypto;
77
+ this.header = header;
78
+ this.sessions = sessions;
79
+ }
80
+
81
+ clone(): VerifiedState {
82
+ // do a deep clone, including the sessions
83
+ const clonedSessions = new Map();
84
+ for (let [sessionID, sessionLog] of this.sessions) {
85
+ clonedSessions.set(sessionID, {
86
+ lastSignature: sessionLog.lastSignature,
87
+ lastHash: sessionLog.lastHash,
88
+ streamingHash: sessionLog.streamingHash.clone(),
89
+ signatureAfter: { ...sessionLog.signatureAfter },
90
+ transactions: sessionLog.transactions.slice(),
91
+ } satisfies SessionLog);
92
+ }
93
+ return new VerifiedState(this.id, this.crypto, this.header, clonedSessions);
94
+ }
95
+
96
+ tryAddTransactions(
97
+ sessionID: SessionID,
98
+ signerID: SignerID,
99
+ newTransactions: Transaction[],
100
+ givenExpectedNewHash: Hash | undefined,
101
+ newSignature: Signature,
102
+ skipVerify: boolean = false,
103
+ givenNewStreamingHash?: StreamingHash,
104
+ ): Result<true, TryAddTransactionsError> {
105
+ if (skipVerify === true && givenNewStreamingHash && givenExpectedNewHash) {
106
+ this.doAddTransactions(
107
+ sessionID,
108
+ newTransactions,
109
+ newSignature,
110
+ givenExpectedNewHash,
111
+ givenNewStreamingHash,
112
+ );
113
+ } else {
114
+ const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
115
+ sessionID,
116
+ newTransactions,
117
+ );
118
+
119
+ if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
120
+ return err({
121
+ type: "InvalidHash",
122
+ id: this.id,
123
+ expectedNewHash,
124
+ givenExpectedNewHash,
125
+ } satisfies InvalidHashError);
126
+ }
127
+
128
+ if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
129
+ return err({
130
+ type: "InvalidSignature",
131
+ id: this.id,
132
+ newSignature,
133
+ sessionID,
134
+ signerID,
135
+ } satisfies InvalidSignatureError);
136
+ }
137
+
138
+ this.doAddTransactions(
139
+ sessionID,
140
+ newTransactions,
141
+ newSignature,
142
+ expectedNewHash,
143
+ newStreamingHash,
144
+ );
145
+ }
146
+
147
+ return ok(true as const);
148
+ }
149
+
150
+ private doAddTransactions(
151
+ sessionID: SessionID,
152
+ newTransactions: Transaction[],
153
+ newSignature: Signature,
154
+ expectedNewHash: Hash,
155
+ newStreamingHash: StreamingHash,
156
+ ) {
157
+ const transactions = this.sessions.get(sessionID)?.transactions ?? [];
158
+
159
+ for (const tx of newTransactions) {
160
+ transactions.push(tx);
161
+ }
162
+
163
+ const signatureAfter = this.sessions.get(sessionID)?.signatureAfter ?? {};
164
+
165
+ const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
166
+ (max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
167
+ -1,
168
+ );
169
+
170
+ const sizeOfTxsSinceLastInbetweenSignature = transactions
171
+ .slice(lastInbetweenSignatureIdx + 1)
172
+ .reduce(
173
+ (sum, tx) =>
174
+ sum +
175
+ (tx.privacy === "private"
176
+ ? tx.encryptedChanges.length
177
+ : tx.changes.length),
178
+ 0,
179
+ );
180
+
181
+ if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
182
+ signatureAfter[transactions.length - 1] = newSignature;
183
+ }
184
+
185
+ this.sessions.set(sessionID, {
186
+ transactions,
187
+ lastHash: expectedNewHash,
188
+ streamingHash: newStreamingHash,
189
+ lastSignature: newSignature,
190
+ signatureAfter: signatureAfter,
191
+ });
192
+
193
+ this._cachedNewContentSinceEmpty = undefined;
194
+ this._cachedKnownState = undefined;
195
+ }
196
+
197
+ expectedNewHashAfter(
198
+ sessionID: SessionID,
199
+ newTransactions: Transaction[],
200
+ ): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
201
+ const streamingHash =
202
+ this.sessions.get(sessionID)?.streamingHash.clone() ??
203
+ new StreamingHash(this.crypto);
204
+
205
+ for (const transaction of newTransactions) {
206
+ streamingHash.update(transaction);
207
+ }
208
+
209
+ return {
210
+ expectedNewHash: streamingHash.digest(),
211
+ newStreamingHash: streamingHash,
212
+ };
213
+ }
214
+
215
+ newContentSince(
216
+ knownState: CoValueKnownState | undefined,
217
+ ): NewContentMessage[] | undefined {
218
+ const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
219
+
220
+ if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
221
+ return this._cachedNewContentSinceEmpty;
222
+ }
223
+
224
+ let currentPiece: NewContentMessage = {
225
+ action: "content",
226
+ id: this.id,
227
+ header: knownState?.header ? undefined : this.header,
228
+ priority: getPriorityFromHeader(this.header),
229
+ new: {},
230
+ };
231
+
232
+ const pieces = [currentPiece];
233
+
234
+ const sentState: CoValueKnownState["sessions"] = {};
235
+
236
+ let pieceSize = 0;
237
+
238
+ let sessionsTodoAgain: Set<SessionID> | undefined | "first" = "first";
239
+
240
+ while (sessionsTodoAgain === "first" || sessionsTodoAgain?.size || 0 > 0) {
241
+ if (sessionsTodoAgain === "first") {
242
+ sessionsTodoAgain = undefined;
243
+ }
244
+ const sessionsTodo = sessionsTodoAgain ?? this.sessions.keys();
245
+
246
+ for (const sessionIDKey of sessionsTodo) {
247
+ const sessionID = sessionIDKey as SessionID;
248
+ const log = this.sessions.get(sessionID)!;
249
+ const knownStateForSessionID = knownState?.sessions[sessionID];
250
+ const sentStateForSessionID = sentState[sessionID];
251
+ const nextKnownSignatureIdx = getNextKnownSignatureIdx(
252
+ log,
253
+ knownStateForSessionID,
254
+ sentStateForSessionID,
255
+ );
256
+
257
+ const firstNewTxIdx =
258
+ sentStateForSessionID ?? knownStateForSessionID ?? 0;
259
+ const afterLastNewTxIdx =
260
+ nextKnownSignatureIdx === undefined
261
+ ? log.transactions.length
262
+ : nextKnownSignatureIdx + 1;
263
+
264
+ const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
265
+
266
+ if (nNewTx === 0) {
267
+ sessionsTodoAgain?.delete(sessionID);
268
+ continue;
269
+ }
270
+
271
+ if (afterLastNewTxIdx < log.transactions.length) {
272
+ if (!sessionsTodoAgain) {
273
+ sessionsTodoAgain = new Set();
274
+ }
275
+ sessionsTodoAgain.add(sessionID);
276
+ }
277
+
278
+ const oldPieceSize = pieceSize;
279
+ for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
280
+ const tx = log.transactions[txIdx]!;
281
+ pieceSize +=
282
+ tx.privacy === "private"
283
+ ? tx.encryptedChanges.length
284
+ : tx.changes.length;
285
+ }
286
+
287
+ if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
288
+ currentPiece = {
289
+ action: "content",
290
+ id: this.id,
291
+ header: undefined,
292
+ new: {},
293
+ priority: getPriorityFromHeader(this.header),
294
+ };
295
+ pieces.push(currentPiece);
296
+ pieceSize = pieceSize - oldPieceSize;
297
+ }
298
+
299
+ let sessionEntry = currentPiece.new[sessionID];
300
+ if (!sessionEntry) {
301
+ sessionEntry = {
302
+ after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
303
+ newTransactions: [],
304
+ lastSignature: "WILL_BE_REPLACED" as Signature,
305
+ };
306
+ currentPiece.new[sessionID] = sessionEntry;
307
+ }
308
+
309
+ for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
310
+ const tx = log.transactions[txIdx]!;
311
+ sessionEntry.newTransactions.push(tx);
312
+ }
313
+
314
+ sessionEntry.lastSignature =
315
+ nextKnownSignatureIdx === undefined
316
+ ? log.lastSignature!
317
+ : log.signatureAfter[nextKnownSignatureIdx]!;
318
+
319
+ sentState[sessionID] =
320
+ (sentStateForSessionID ?? knownStateForSessionID ?? 0) + nNewTx;
321
+ }
322
+ }
323
+
324
+ const piecesWithContent = pieces.filter(
325
+ (piece) => Object.keys(piece.new).length > 0 || piece.header,
326
+ );
327
+
328
+ if (piecesWithContent.length === 0) {
329
+ return undefined;
330
+ }
331
+
332
+ if (isKnownStateEmpty) {
333
+ this._cachedNewContentSinceEmpty = piecesWithContent;
334
+ }
335
+
336
+ return piecesWithContent;
337
+ }
338
+
339
+ knownState(): CoValueKnownState {
340
+ if (this._cachedKnownState) {
341
+ return this._cachedKnownState;
342
+ } else {
343
+ const knownState = this.knownStateUncached();
344
+ this._cachedKnownState = knownState;
345
+ return knownState;
346
+ }
347
+ }
348
+
349
+ /** @internal */
350
+ knownStateUncached(): CoValueKnownState {
351
+ const sessions: CoValueKnownState["sessions"] = {};
352
+
353
+ for (const [sessionID, sessionLog] of this.sessions.entries()) {
354
+ sessions[sessionID] = sessionLog.transactions.length;
355
+ }
356
+
357
+ return {
358
+ id: this.id,
359
+ header: true,
360
+ sessions,
361
+ };
362
+ }
363
+ }
364
+
365
+ function getNextKnownSignatureIdx(
366
+ log: SessionLog,
367
+ knownStateForSessionID?: number,
368
+ sentStateForSessionID?: number,
369
+ ) {
370
+ return Object.keys(log.signatureAfter)
371
+ .map(Number)
372
+ .sort((a, b) => a - b)
373
+ .find(
374
+ (idx) => idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1),
375
+ );
376
+ }
@@ -1,9 +1,12 @@
1
1
  import { CoID, RawCoValue } from "../coValue.js";
2
2
  import {
3
+ AvailableCoValueCore,
3
4
  CoValueCore,
5
+ } from "../coValueCore/coValueCore.js";
6
+ import {
4
7
  CoValueHeader,
5
8
  CoValueUniqueness,
6
- } from "../coValueCore.js";
9
+ } from "../coValueCore/verifiedState.js";
7
10
  import {
8
11
  AgentSecret,
9
12
  CryptoProvider,
@@ -85,35 +88,20 @@ export interface ControlledAccountOrAgent {
85
88
  }
86
89
 
87
90
  /** @hidden */
88
- export class RawControlledAccount<Meta extends AccountMeta = AccountMeta>
89
- extends RawAccount<Meta>
90
- implements ControlledAccountOrAgent
91
- {
91
+ export class ControlledAccount implements ControlledAccountOrAgent {
92
+ account: RawAccount<AccountMeta>;
92
93
  agentSecret: AgentSecret;
94
+ _cachedCurrentAgentID: AgentID | undefined;
93
95
  crypto: CryptoProvider;
94
96
 
95
- constructor(core: CoValueCore, agentSecret: AgentSecret) {
96
- super(core);
97
-
97
+ constructor(account: RawAccount<AccountMeta>, agentSecret: AgentSecret) {
98
+ this.account = account;
98
99
  this.agentSecret = agentSecret;
99
- this.crypto = core.node.crypto;
100
- }
101
-
102
- /**
103
- * Creates a new group (with the current account as the group's first admin).
104
- * @category 1. High-level
105
- */
106
- createGroup(
107
- uniqueness: CoValueUniqueness = this.core.crypto.createdNowUnique(),
108
- ) {
109
- return this.core.node.createGroup(uniqueness);
100
+ this.crypto = account.core.node.crypto;
110
101
  }
111
102
 
112
- async acceptInvite<T extends RawCoValue>(
113
- groupOrOwnedValueID: CoID<T>,
114
- inviteSecret: InviteSecret,
115
- ): Promise<void> {
116
- return this.core.node.acceptInvite(groupOrOwnedValueID, inviteSecret);
103
+ get id(): RawAccountID {
104
+ return this.account.id;
117
105
  }
118
106
 
119
107
  currentAgentID(): AgentID {
@@ -186,7 +174,14 @@ export class RawProfile<
186
174
  > extends RawCoMap<Shape, Meta> {}
187
175
 
188
176
  export type RawAccountMigration<Meta extends AccountMeta = AccountMeta> = (
189
- account: RawControlledAccount<Meta>,
177
+ account: RawAccount<Meta>,
190
178
  localNode: LocalNode,
191
179
  creationProps?: { name: string },
192
180
  ) => void | Promise<void>;
181
+
182
+ export function expectAccount(content: RawCoValue): RawAccount {
183
+ if (!(content instanceof RawAccount)) {
184
+ throw new Error("Expected an account");
185
+ }
186
+ return content;
187
+ }
@@ -1,7 +1,11 @@
1
1
  import { CoID, RawCoValue } from "../coValue.js";
2
- import { CoValueCore } from "../coValueCore.js";
2
+ import {
3
+ AvailableCoValueCore,
4
+ CoValueCore,
5
+ } from "../coValueCore/coValueCore.js";
3
6
  import { AgentID, SessionID, TransactionID } from "../ids.js";
4
7
  import { JsonObject, JsonValue } from "../jsonValue.js";
8
+ import { CoValueKnownState } from "../sync.js";
5
9
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
6
10
  import { isCoValue } from "../typeUtils/isCoValue.js";
7
11
  import { RawAccountID } from "./account.js";
@@ -41,7 +45,7 @@ type DeletionEntry = {
41
45
  deletionID: OpID;
42
46
  } & DeletionOpPayload;
43
47
 
44
- export class RawCoListView<
48
+ export class RawCoList<
45
49
  Item extends JsonValue = JsonValue,
46
50
  Meta extends JsonObject | null = null,
47
51
  > implements RawCoValue
@@ -51,7 +55,7 @@ export class RawCoListView<
51
55
  /** @category 6. Meta */
52
56
  type: "colist" | "coplaintext" = "colist" as const;
53
57
  /** @category 6. Meta */
54
- core: CoValueCore;
58
+ core: AvailableCoValueCore;
55
59
  /** @internal */
56
60
  afterStart: OpID[];
57
61
  /** @internal */
@@ -81,26 +85,52 @@ export class RawCoListView<
81
85
  madeAt: number;
82
86
  opID: OpID;
83
87
  }[];
88
+ /** @internal */
89
+ totalValidTransactions = 0;
90
+ knownTransactions: CoValueKnownState["sessions"] = {};
91
+ lastValidTransaction: number | undefined;
84
92
 
85
93
  /** @internal */
86
- constructor(core: CoValueCore) {
94
+ constructor(core: AvailableCoValueCore) {
87
95
  this.id = core.id as CoID<this>;
88
96
  this.core = core;
89
- this.afterStart = [];
90
- this.beforeEnd = [];
91
- this.insertions = {};
92
- this.deletionsByInsertion = {};
93
97
 
94
98
  this.insertions = {};
95
99
  this.deletionsByInsertion = {};
96
100
  this.afterStart = [];
97
101
  this.beforeEnd = [];
102
+ this.knownTransactions = {};
103
+
104
+ this.processNewTransactions();
105
+ }
106
+
107
+ processNewTransactions() {
108
+ const transactions = this.core.getValidSortedTransactions({
109
+ ignorePrivateTransactions: false,
110
+ knownTransactions: this.knownTransactions,
111
+ });
112
+
113
+ if (transactions.length === 0) {
114
+ return;
115
+ }
116
+
117
+ this.totalValidTransactions += transactions.length;
118
+ let lastValidTransaction: number | undefined = undefined;
119
+ let oldestValidTransaction: number | undefined = undefined;
120
+ this._cachedEntries = undefined;
121
+
122
+ for (const { txID, changes, madeAt } of transactions) {
123
+ lastValidTransaction = Math.max(lastValidTransaction ?? 0, madeAt);
124
+ oldestValidTransaction = Math.min(
125
+ oldestValidTransaction ?? Infinity,
126
+ madeAt,
127
+ );
128
+
129
+ this.knownTransactions[txID.sessionID] = Math.max(
130
+ this.knownTransactions[txID.sessionID] ?? 0,
131
+ txID.txIndex,
132
+ );
98
133
 
99
- for (const {
100
- txID,
101
- changes,
102
- madeAt,
103
- } of this.core.getValidSortedTransactions()) {
104
134
  for (const [changeIdx, changeUntyped] of changes.entries()) {
105
135
  const change = changeUntyped as ListOpPayload<Item>;
106
136
 
@@ -193,11 +223,21 @@ export class RawCoListView<
193
223
  }
194
224
  }
195
225
  }
226
+
227
+ if (
228
+ this.lastValidTransaction &&
229
+ oldestValidTransaction &&
230
+ oldestValidTransaction < this.lastValidTransaction
231
+ ) {
232
+ this.rebuildFromCore();
233
+ } else {
234
+ this.lastValidTransaction = lastValidTransaction;
235
+ }
196
236
  }
197
237
 
198
238
  /** @category 6. Meta */
199
239
  get headerMeta(): Meta {
200
- return this.core.header.meta as Meta;
240
+ return this.core.verified.header.meta as Meta;
201
241
  }
202
242
 
203
243
  /** @category 6. Meta */
@@ -403,19 +443,11 @@ export class RawCoListView<
403
443
 
404
444
  /** @category 3. Subscription */
405
445
  subscribe(listener: (coList: this) => void): () => void {
406
- return this.core.subscribe((content) => {
407
- listener(content as this);
446
+ return this.core.subscribe((core) => {
447
+ listener(core.getCurrentContent() as this);
408
448
  });
409
449
  }
410
- }
411
450
 
412
- export class RawCoList<
413
- Item extends JsonValue = JsonValue,
414
- Meta extends JsonObject | null = JsonObject | null,
415
- >
416
- extends RawCoListView<Item, Meta>
417
- implements RawCoValue
418
- {
419
451
  /** Appends `item` after the item currently at index `after`.
420
452
  *
421
453
  * If `privacy` is `"private"` **(default)**, `item` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
@@ -480,8 +512,7 @@ export class RawCoList<
480
512
  }
481
513
 
482
514
  this.core.makeTransaction(changes, privacy);
483
-
484
- this.rebuildFromCore();
515
+ this.processNewTransactions();
485
516
  }
486
517
 
487
518
  /**
@@ -528,7 +559,7 @@ export class RawCoList<
528
559
  privacy,
529
560
  );
530
561
 
531
- this.rebuildFromCore();
562
+ this.processNewTransactions();
532
563
  }
533
564
 
534
565
  /** Deletes the item at index `at`.
@@ -555,7 +586,7 @@ export class RawCoList<
555
586
  privacy,
556
587
  );
557
588
 
558
- this.rebuildFromCore();
589
+ this.processNewTransactions();
559
590
  }
560
591
 
561
592
  replace(
@@ -583,7 +614,7 @@ export class RawCoList<
583
614
  ],
584
615
  privacy,
585
616
  );
586
- this.rebuildFromCore();
617
+ this.processNewTransactions();
587
618
  }
588
619
 
589
620
  /** @internal */
@@ -593,6 +624,9 @@ export class RawCoList<
593
624
  this.afterStart = listAfter.afterStart;
594
625
  this.beforeEnd = listAfter.beforeEnd;
595
626
  this.insertions = listAfter.insertions;
627
+ this.totalValidTransactions = listAfter.totalValidTransactions;
628
+ this.lastValidTransaction = listAfter.lastValidTransaction;
629
+ this.knownTransactions = listAfter.knownTransactions;
596
630
  this.deletionsByInsertion = listAfter.deletionsByInsertion;
597
631
  this._cachedEntries = undefined;
598
632
  }
@@ -1,5 +1,8 @@
1
1
  import { CoID, RawCoValue } from "../coValue.js";
2
- import { CoValueCore } from "../coValueCore.js";
2
+ import {
3
+ AvailableCoValueCore,
4
+ CoValueCore,
5
+ } from "../coValueCore/coValueCore.js";
3
6
  import { AgentID, TransactionID } from "../ids.js";
4
7
  import { JsonObject, JsonValue } from "../jsonValue.js";
5
8
  import { CoValueKnownState } from "../sync.js";
@@ -39,7 +42,7 @@ export class RawCoMapView<
39
42
  /** @category 6. Meta */
40
43
  type = "comap" as const;
41
44
  /** @category 6. Meta */
42
- core: CoValueCore;
45
+ core: AvailableCoValueCore;
43
46
  /** @internal */
44
47
  latest: {
45
48
  [Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>;
@@ -60,9 +63,11 @@ export class RawCoMapView<
60
63
  /** @category 6. Meta */
61
64
  readonly _shape!: Shape;
62
65
 
66
+ totalValidTransactions = 0;
67
+
63
68
  /** @internal */
64
69
  constructor(
65
- core: CoValueCore,
70
+ core: AvailableCoValueCore,
66
71
  options?: {
67
72
  ignorePrivateTransactions: boolean;
68
73
  },
@@ -133,6 +138,8 @@ export class RawCoMapView<
133
138
  }
134
139
  }
135
140
 
141
+ this.totalValidTransactions += newValidTransactions.length;
142
+
136
143
  for (const entries of changedEntries.values()) {
137
144
  entries.sort(this.core.compareTransactions);
138
145
  }
@@ -148,7 +155,7 @@ export class RawCoMapView<
148
155
 
149
156
  /** @category 6. Meta */
150
157
  get headerMeta(): Meta {
151
- return this.core.header.meta as Meta;
158
+ return this.core.verified.header.meta as Meta;
152
159
  }
153
160
 
154
161
  /** @category 6. Meta */
@@ -340,8 +347,8 @@ export class RawCoMapView<
340
347
 
341
348
  /** @category 3. Subscription */
342
349
  subscribe(listener: (coMap: this) => void): () => void {
343
- return this.core.subscribe((content) => {
344
- listener(content as this);
350
+ return this.core.subscribe((core) => {
351
+ listener(core.getCurrentContent() as this);
345
352
  });
346
353
  }
347
354
  }