cojson 0.18.5 → 0.18.7

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 (103) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/coValueContentMessage.d.ts +2 -0
  4. package/dist/coValueContentMessage.d.ts.map +1 -1
  5. package/dist/coValueContentMessage.js +7 -0
  6. package/dist/coValueContentMessage.js.map +1 -1
  7. package/dist/coValueCore/SessionMap.d.ts +2 -2
  8. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  9. package/dist/coValueCore/SessionMap.js +2 -4
  10. package/dist/coValueCore/SessionMap.js.map +1 -1
  11. package/dist/coValueCore/branching.d.ts +35 -13
  12. package/dist/coValueCore/branching.d.ts.map +1 -1
  13. package/dist/coValueCore/branching.js +55 -37
  14. package/dist/coValueCore/branching.js.map +1 -1
  15. package/dist/coValueCore/coValueCore.d.ts +15 -7
  16. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  17. package/dist/coValueCore/coValueCore.js +126 -35
  18. package/dist/coValueCore/coValueCore.js.map +1 -1
  19. package/dist/coValueCore/verifiedState.d.ts +4 -2
  20. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  21. package/dist/coValueCore/verifiedState.js +6 -4
  22. package/dist/coValueCore/verifiedState.js.map +1 -1
  23. package/dist/coValues/coList.d.ts +8 -2
  24. package/dist/coValues/coList.d.ts.map +1 -1
  25. package/dist/coValues/coList.js +95 -58
  26. package/dist/coValues/coList.js.map +1 -1
  27. package/dist/coValues/coMap.d.ts +2 -2
  28. package/dist/coValues/coMap.d.ts.map +1 -1
  29. package/dist/coValues/coMap.js +8 -8
  30. package/dist/coValues/coMap.js.map +1 -1
  31. package/dist/coValues/group.d.ts.map +1 -1
  32. package/dist/coValues/group.js +14 -1
  33. package/dist/coValues/group.js.map +1 -1
  34. package/dist/config.d.ts +6 -0
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +8 -0
  37. package/dist/config.js.map +1 -1
  38. package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
  39. package/dist/crypto/PureJSCrypto.js +14 -6
  40. package/dist/crypto/PureJSCrypto.js.map +1 -1
  41. package/dist/exports.d.ts +5 -4
  42. package/dist/exports.d.ts.map +1 -1
  43. package/dist/exports.js +3 -3
  44. package/dist/exports.js.map +1 -1
  45. package/dist/ids.d.ts +1 -0
  46. package/dist/ids.d.ts.map +1 -1
  47. package/dist/ids.js.map +1 -1
  48. package/dist/localNode.d.ts.map +1 -1
  49. package/dist/localNode.js +7 -2
  50. package/dist/localNode.js.map +1 -1
  51. package/dist/storage/storageAsync.d.ts.map +1 -1
  52. package/dist/storage/storageAsync.js +7 -4
  53. package/dist/storage/storageAsync.js.map +1 -1
  54. package/dist/storage/storageSync.d.ts.map +1 -1
  55. package/dist/storage/storageSync.js +7 -4
  56. package/dist/storage/storageSync.js.map +1 -1
  57. package/dist/sync.d.ts +1 -3
  58. package/dist/sync.d.ts.map +1 -1
  59. package/dist/sync.js +8 -18
  60. package/dist/sync.js.map +1 -1
  61. package/dist/tests/branching.test.js +166 -4
  62. package/dist/tests/branching.test.js.map +1 -1
  63. package/dist/tests/coList.test.js +367 -1
  64. package/dist/tests/coList.test.js.map +1 -1
  65. package/dist/tests/coValueCore.test.js +45 -1
  66. package/dist/tests/coValueCore.test.js.map +1 -1
  67. package/dist/tests/messagesTestUtils.d.ts +1 -1
  68. package/dist/tests/sync.content.test.d.ts +2 -0
  69. package/dist/tests/sync.content.test.d.ts.map +1 -0
  70. package/dist/tests/sync.content.test.js +120 -0
  71. package/dist/tests/sync.content.test.js.map +1 -0
  72. package/dist/tests/sync.load.test.js +4 -4
  73. package/dist/tests/sync.load.test.js.map +1 -1
  74. package/dist/tests/sync.storage.test.js +1 -1
  75. package/dist/tests/sync.storageAsync.test.js +6 -10
  76. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  77. package/dist/tests/sync.upload.test.js +2 -2
  78. package/dist/tests/testUtils.d.ts +2 -2
  79. package/package.json +2 -2
  80. package/src/coValueContentMessage.ts +13 -0
  81. package/src/coValueCore/SessionMap.ts +2 -2
  82. package/src/coValueCore/branching.ts +105 -60
  83. package/src/coValueCore/coValueCore.ts +163 -41
  84. package/src/coValueCore/verifiedState.ts +8 -0
  85. package/src/coValues/coList.ts +129 -78
  86. package/src/coValues/coMap.ts +10 -12
  87. package/src/coValues/group.ts +14 -1
  88. package/src/config.ts +9 -0
  89. package/src/crypto/PureJSCrypto.ts +25 -13
  90. package/src/exports.ts +13 -2
  91. package/src/ids.ts +1 -0
  92. package/src/localNode.ts +8 -2
  93. package/src/storage/storageAsync.ts +8 -5
  94. package/src/storage/storageSync.ts +8 -4
  95. package/src/sync.ts +8 -32
  96. package/src/tests/branching.test.ts +255 -4
  97. package/src/tests/coList.test.ts +529 -1
  98. package/src/tests/coValueCore.test.ts +62 -2
  99. package/src/tests/sync.content.test.ts +153 -0
  100. package/src/tests/sync.load.test.ts +4 -4
  101. package/src/tests/sync.storage.test.ts +1 -1
  102. package/src/tests/sync.storageAsync.test.ts +9 -12
  103. package/src/tests/sync.upload.test.ts +2 -2
@@ -1,11 +1,7 @@
1
1
  import { CoID, RawCoValue } from "../coValue.js";
2
- import {
3
- AvailableCoValueCore,
4
- CoValueCore,
5
- } from "../coValueCore/coValueCore.js";
2
+ import { AvailableCoValueCore } from "../coValueCore/coValueCore.js";
6
3
  import { AgentID, SessionID, TransactionID } from "../ids.js";
7
4
  import { JsonObject, JsonValue } from "../jsonValue.js";
8
- import { CoValueKnownState } from "../sync.js";
9
5
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
10
6
  import { isCoValue } from "../typeUtils/isCoValue.js";
11
7
  import { RawAccountID } from "./account.js";
@@ -39,12 +35,14 @@ type InsertionEntry<T extends JsonValue> = {
39
35
  madeAt: number;
40
36
  predecessors: OpID[];
41
37
  successors: OpID[];
42
- } & InsertionOpPayload<T>;
38
+ change: InsertionOpPayload<T>;
39
+ };
43
40
 
44
41
  type DeletionEntry = {
45
42
  madeAt: number;
46
43
  deletionID: OpID;
47
- } & DeletionOpPayload;
44
+ change: DeletionOpPayload;
45
+ };
48
46
 
49
47
  export class RawCoList<
50
48
  Item extends JsonValue = JsonValue,
@@ -109,6 +107,89 @@ export class RawCoList<
109
107
  this.processNewTransactions();
110
108
  }
111
109
 
110
+ private getInsertionsEntry(opID: OpID) {
111
+ const index = getSessionIndex(opID);
112
+
113
+ const sessionEntry = this.insertions[index];
114
+ if (!sessionEntry) {
115
+ return undefined;
116
+ }
117
+
118
+ const txEntry = sessionEntry[opID.txIndex];
119
+ if (!txEntry) {
120
+ return undefined;
121
+ }
122
+
123
+ return txEntry[opID.changeIdx];
124
+ }
125
+
126
+ private createInsertionsEntry(opID: OpID, value: InsertionEntry<Item>) {
127
+ const index = getSessionIndex(opID);
128
+
129
+ let sessionEntry = this.insertions[index];
130
+ if (!sessionEntry) {
131
+ sessionEntry = {};
132
+ this.insertions[index] = sessionEntry;
133
+ }
134
+
135
+ let txEntry = sessionEntry[opID.txIndex];
136
+ if (!txEntry) {
137
+ txEntry = {};
138
+ sessionEntry[opID.txIndex] = txEntry;
139
+ }
140
+
141
+ // Check if the change index already exists, may be the case of double merges
142
+ if (txEntry[opID.changeIdx]) {
143
+ return false;
144
+ }
145
+
146
+ txEntry[opID.changeIdx] = value;
147
+ return true;
148
+ }
149
+
150
+ private isDeleted(opID: OpID) {
151
+ const index = getSessionIndex(opID);
152
+
153
+ const sessionEntry = this.deletionsByInsertion[index];
154
+
155
+ if (!sessionEntry) {
156
+ return false;
157
+ }
158
+
159
+ const txEntry = sessionEntry[opID.txIndex];
160
+
161
+ if (!txEntry) {
162
+ return false;
163
+ }
164
+
165
+ return Boolean(txEntry[opID.changeIdx]?.length);
166
+ }
167
+
168
+ private pushDeletionsByInsertionEntry(opID: OpID, value: DeletionEntry) {
169
+ const index = getSessionIndex(opID);
170
+
171
+ let sessionEntry = this.deletionsByInsertion[index];
172
+ if (!sessionEntry) {
173
+ sessionEntry = {};
174
+ this.deletionsByInsertion[index] = sessionEntry;
175
+ }
176
+
177
+ let txEntry = sessionEntry[opID.txIndex];
178
+ if (!txEntry) {
179
+ txEntry = {};
180
+ sessionEntry[opID.txIndex] = txEntry;
181
+ }
182
+
183
+ let list = txEntry[opID.changeIdx];
184
+
185
+ if (!list) {
186
+ list = [];
187
+ txEntry[opID.changeIdx] = list;
188
+ }
189
+
190
+ list.push(value);
191
+ }
192
+
112
193
  processNewTransactions() {
113
194
  const transactions = this.core.getValidSortedTransactions({
114
195
  ignorePrivateTransactions: false,
@@ -133,87 +214,56 @@ export class RawCoList<
133
214
  for (const [changeIdx, changeUntyped] of changes.entries()) {
134
215
  const change = changeUntyped as ListOpPayload<Item>;
135
216
 
217
+ const opID = {
218
+ sessionID: txID.sessionID,
219
+ txIndex: txID.txIndex,
220
+ branch: txID.branch,
221
+ changeIdx,
222
+ };
223
+
136
224
  if (change.op === "pre" || change.op === "app") {
137
- let sessionEntry = this.insertions[txID.sessionID];
138
- if (!sessionEntry) {
139
- sessionEntry = {};
140
- this.insertions[txID.sessionID] = sessionEntry;
141
- }
142
- let txEntry = sessionEntry[txID.txIndex];
143
- if (!txEntry) {
144
- txEntry = {};
145
- sessionEntry[txID.txIndex] = txEntry;
146
- }
147
- txEntry[changeIdx] = {
225
+ const created = this.createInsertionsEntry(opID, {
148
226
  madeAt,
149
227
  predecessors: [],
150
228
  successors: [],
151
- ...change,
152
- };
229
+ change,
230
+ });
231
+
232
+ // If the change index already exists, we don't need to process it again
233
+ if (!created) {
234
+ continue;
235
+ }
236
+
153
237
  if (change.op === "pre") {
154
238
  if (change.before === "end") {
155
- this.beforeEnd.push({
156
- ...txID,
157
- changeIdx,
158
- });
239
+ this.beforeEnd.push(opID);
159
240
  } else {
160
- const beforeEntry =
161
- this.insertions[change.before.sessionID]?.[
162
- change.before.txIndex
163
- ]?.[change.before.changeIdx];
241
+ const beforeEntry = this.getInsertionsEntry(change.before);
242
+
164
243
  if (!beforeEntry) {
165
244
  continue;
166
245
  }
167
- beforeEntry.predecessors.splice(0, 0, {
168
- ...txID,
169
- changeIdx,
170
- });
246
+
247
+ beforeEntry.predecessors.push(opID);
171
248
  }
172
249
  } else {
173
250
  if (change.after === "start") {
174
- this.afterStart.push({
175
- ...txID,
176
- changeIdx,
177
- });
251
+ this.afterStart.push(opID);
178
252
  } else {
179
- const afterEntry =
180
- this.insertions[change.after.sessionID]?.[
181
- change.after.txIndex
182
- ]?.[change.after.changeIdx];
253
+ const afterEntry = this.getInsertionsEntry(change.after);
254
+
183
255
  if (!afterEntry) {
184
256
  continue;
185
257
  }
186
- afterEntry.successors.push({
187
- ...txID,
188
- changeIdx,
189
- });
258
+
259
+ afterEntry.successors.push(opID);
190
260
  }
191
261
  }
192
262
  } else if (change.op === "del") {
193
- let sessionEntry =
194
- this.deletionsByInsertion[change.insertion.sessionID];
195
- if (!sessionEntry) {
196
- sessionEntry = {};
197
- this.deletionsByInsertion[change.insertion.sessionID] =
198
- sessionEntry;
199
- }
200
- let txEntry = sessionEntry[change.insertion.txIndex];
201
- if (!txEntry) {
202
- txEntry = {};
203
- sessionEntry[change.insertion.txIndex] = txEntry;
204
- }
205
- let changeEntry = txEntry[change.insertion.changeIdx];
206
- if (!changeEntry) {
207
- changeEntry = [];
208
- txEntry[change.insertion.changeIdx] = changeEntry;
209
- }
210
- changeEntry.push({
263
+ this.pushDeletionsByInsertionEntry(change.insertion, {
211
264
  madeAt,
212
- deletionID: {
213
- ...txID,
214
- changeIdx,
215
- },
216
- ...change,
265
+ deletionID: opID,
266
+ change,
217
267
  });
218
268
  } else {
219
269
  throw new Error(
@@ -324,10 +374,7 @@ export class RawCoList<
324
374
  while (todo.length > 0) {
325
375
  const currentOpID = todo[todo.length - 1]!;
326
376
 
327
- const entry =
328
- this.insertions[currentOpID.sessionID]?.[currentOpID.txIndex]?.[
329
- currentOpID.changeIdx
330
- ];
377
+ const entry = this.getInsertionsEntry(currentOpID);
331
378
 
332
379
  if (!entry) {
333
380
  throw new Error("Missing op " + currentOpID);
@@ -338,22 +385,19 @@ export class RawCoList<
338
385
 
339
386
  // We navigate the predecessors before processing the current opID in the list
340
387
  if (shouldTraversePredecessors) {
341
- for (let i = entry.predecessors.length - 1; i >= 0; i--) {
342
- todo.push(entry.predecessors[i]!);
388
+ for (const predecessor of entry.predecessors) {
389
+ todo.push(predecessor);
343
390
  }
344
391
  predecessorsVisited.add(currentOpID);
345
392
  } else {
346
393
  // Remove the current opID from the todo stack to consider it processed.
347
394
  todo.pop();
348
395
 
349
- const deleted =
350
- (this.deletionsByInsertion[currentOpID.sessionID]?.[
351
- currentOpID.txIndex
352
- ]?.[currentOpID.changeIdx]?.length || 0) > 0;
396
+ const deleted = this.isDeleted(currentOpID);
353
397
 
354
398
  if (!deleted) {
355
399
  arr.push({
356
- value: entry.value,
400
+ value: entry.change.value,
357
401
  madeAt: entry.madeAt,
358
402
  opID: currentOpID,
359
403
  });
@@ -629,3 +673,10 @@ export class RawCoList<
629
673
  this._cachedEntries = undefined;
630
674
  }
631
675
  }
676
+
677
+ function getSessionIndex(txID: TransactionID): SessionID {
678
+ if (txID.branch) {
679
+ return `${txID.sessionID}_branch_${txID.branch}`;
680
+ }
681
+ return txID.sessionID;
682
+ }
@@ -49,10 +49,17 @@ export class RawCoMapView<
49
49
  latest: {
50
50
  [Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>;
51
51
  };
52
+
52
53
  /** @internal */
53
- latestTxMadeAt: number;
54
+ get latestTxMadeAt(): number {
55
+ return this.core.latestTxMadeAt;
56
+ }
57
+
54
58
  /** @internal */
55
- earliestTxMadeAt: number | null;
59
+ get earliestTxMadeAt(): number {
60
+ return this.core.earliestTxMadeAt;
61
+ }
62
+
56
63
  /** @internal */
57
64
  ops: {
58
65
  [Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
@@ -80,8 +87,7 @@ export class RawCoMapView<
80
87
  ) {
81
88
  this.id = core.id as CoID<this>;
82
89
  this.core = core;
83
- this.latestTxMadeAt = 0;
84
- this.earliestTxMadeAt = null;
90
+
85
91
  this.ignorePrivateTransactions =
86
92
  options?.ignorePrivateTransactions ?? false;
87
93
  this.ops = {};
@@ -105,10 +111,6 @@ export class RawCoMapView<
105
111
  return;
106
112
  }
107
113
 
108
- if (this.earliestTxMadeAt === null && newValidTransactions[0]) {
109
- this.earliestTxMadeAt = newValidTransactions[0].madeAt;
110
- }
111
-
112
114
  const { ops } = this;
113
115
 
114
116
  const changedEntries = new Map<
@@ -117,10 +119,6 @@ export class RawCoMapView<
117
119
  >();
118
120
 
119
121
  for (const { txID, changes, madeAt, tx } of newValidTransactions) {
120
- if (madeAt > this.latestTxMadeAt) {
121
- this.latestTxMadeAt = madeAt;
122
- }
123
-
124
122
  for (let changeIdx = 0; changeIdx < changes.length; changeIdx++) {
125
123
  const change = changes[changeIdx] as MapOpPayload<
126
124
  keyof Shape & string,
@@ -1070,6 +1070,9 @@ export class RawGroup<
1070
1070
 
1071
1071
  if (init) {
1072
1072
  map.assign(init, initPrivacy);
1073
+ } else if (!uniqueness.createdAt) {
1074
+ // If the createdAt is not set, we need to make a trusting transaction to set the createdAt
1075
+ map.core.makeTransaction([], "trusting");
1073
1076
  }
1074
1077
 
1075
1078
  return map;
@@ -1101,6 +1104,9 @@ export class RawGroup<
1101
1104
 
1102
1105
  if (init?.length) {
1103
1106
  list.appendItems(init, undefined, initPrivacy);
1107
+ } else if (!uniqueness.createdAt) {
1108
+ // If the createdAt is not set, we need to make a trusting transaction to set the createdAt
1109
+ list.core.makeTransaction([], "trusting");
1104
1110
  }
1105
1111
 
1106
1112
  return list;
@@ -1141,7 +1147,7 @@ export class RawGroup<
1141
1147
  meta?: C["headerMeta"],
1142
1148
  uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
1143
1149
  ): C {
1144
- return this.core.node
1150
+ const stream = this.core.node
1145
1151
  .createCoValue({
1146
1152
  type: "costream",
1147
1153
  ruleset: {
@@ -1152,6 +1158,13 @@ export class RawGroup<
1152
1158
  ...uniqueness,
1153
1159
  })
1154
1160
  .getCurrentContent() as C;
1161
+
1162
+ if (!uniqueness.createdAt) {
1163
+ // If the createdAt is not set, we need to make a trusting transaction to set the createdAt
1164
+ stream.core.makeTransaction([], "trusting");
1165
+ }
1166
+
1167
+ return stream;
1155
1168
  }
1156
1169
 
1157
1170
  /** @category 3. Value creation */
package/src/config.ts CHANGED
@@ -7,12 +7,21 @@
7
7
  **/
8
8
  export const TRANSACTION_CONFIG = {
9
9
  MAX_RECOMMENDED_TX_SIZE: 100 * 1024,
10
+ /**
11
+ * Messages larger than this will be rejected when creating a transaction.
12
+ * The current limit is set at 1MB because that's the limit imposed by Cloudflare to Websocket messages.
13
+ */
14
+ MAX_TX_SIZE_BYTES: 1 * 1024 * 1024,
10
15
  };
11
16
 
12
17
  export function setMaxRecommendedTxSize(size: number) {
13
18
  TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE = size;
14
19
  }
15
20
 
21
+ export function setMaxTxSizeBytes(size: number) {
22
+ TRANSACTION_CONFIG.MAX_TX_SIZE_BYTES = size;
23
+ }
24
+
16
25
  export const CO_VALUE_LOADING_CONFIG = {
17
26
  MAX_RETRIES: 1,
18
27
  TIMEOUT: 30_000,
@@ -32,6 +32,29 @@ import { ControlledAccountOrAgent } from "../coValues/account.js";
32
32
 
33
33
  type Blake3State = ReturnType<typeof blake3.create>;
34
34
 
35
+ const x25519SharedSecretCache = new Map<string, Uint8Array>();
36
+
37
+ function getx25519SharedSecret(
38
+ privateKeyA: SealerSecret,
39
+ publicKeyB: SealerID,
40
+ ): Uint8Array {
41
+ const cacheKey = `${privateKeyA}-${publicKeyB}`;
42
+ let sharedSecret = x25519SharedSecretCache.get(cacheKey);
43
+
44
+ if (!sharedSecret) {
45
+ const privateKeyABytes = base58.decode(
46
+ privateKeyA.substring("sealerSecret_z".length),
47
+ );
48
+ const publicKeyBBytes = base58.decode(
49
+ publicKeyB.substring("sealer_z".length),
50
+ );
51
+ sharedSecret = x25519.getSharedSecret(privateKeyABytes, publicKeyBBytes);
52
+ x25519SharedSecretCache.set(cacheKey, sharedSecret);
53
+ }
54
+
55
+ return sharedSecret;
56
+ }
57
+
35
58
  /**
36
59
  * Pure JavaScript implementation of the CryptoProvider interface using noble-curves and noble-ciphers libraries.
37
60
  * This provides a fallback implementation that doesn't require WebAssembly, offering:
@@ -164,16 +187,10 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
164
187
  to: SealerID;
165
188
  nOnceMaterial: { in: RawCoID; tx: TransactionID };
166
189
  }): Sealed<T> {
190
+ const sharedSecret = getx25519SharedSecret(from, to);
167
191
  const nOnce = this.generateJsonNonce(nOnceMaterial);
168
-
169
- const sealerPub = base58.decode(to.substring("sealer_z".length));
170
-
171
- const senderPriv = base58.decode(from.substring("sealerSecret_z".length));
172
-
173
192
  const plaintext = textEncoder.encode(stableStringify(message));
174
193
 
175
- const sharedSecret = x25519.getSharedSecret(senderPriv, sealerPub);
176
-
177
194
  const sealedBytes = xsalsa20poly1305(sharedSecret, nOnce).encrypt(
178
195
  plaintext,
179
196
  );
@@ -189,14 +206,9 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
189
206
  ): T | undefined {
190
207
  const nOnce = this.generateJsonNonce(nOnceMaterial);
191
208
 
192
- const sealerPriv = base58.decode(sealer.substring("sealerSecret_z".length));
193
-
194
- const senderPub = base58.decode(from.substring("sealer_z".length));
195
-
209
+ const sharedSecret = getx25519SharedSecret(sealer, from);
196
210
  const sealedBytes = base64URLtoBytes(sealed.substring("sealed_U".length));
197
211
 
198
- const sharedSecret = x25519.getSharedSecret(sealerPriv, senderPub);
199
-
200
212
  const plaintext = xsalsa20poly1305(sharedSecret, nOnce).decrypt(
201
213
  sealedBytes,
202
214
  );
package/src/exports.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
2
2
  import { type RawCoValue } from "./coValue.js";
3
- import { CoValueCore, idforHeader } from "./coValueCore/coValueCore.js";
3
+ import {
4
+ CoValueCore,
5
+ idforHeader,
6
+ type AvailableCoValueCore,
7
+ } from "./coValueCore/coValueCore.js";
4
8
  import { CoValueUniqueness } from "./coValueCore/verifiedState.js";
5
9
  import {
6
10
  ControlledAccount,
@@ -59,7 +63,12 @@ import type { JsonObject, JsonValue } from "./jsonValue.js";
59
63
  import type * as Media from "./media.js";
60
64
  import { disablePermissionErrors } from "./permissions.js";
61
65
  import type { Peer, SyncMessage } from "./sync.js";
62
- import { DisconnectedError, SyncManager, emptyKnownState } from "./sync.js";
66
+ import {
67
+ DisconnectedError,
68
+ SyncManager,
69
+ emptyKnownState,
70
+ hwrServerPeerSelector,
71
+ } from "./sync.js";
63
72
 
64
73
  import {
65
74
  getContentMessageSize,
@@ -159,6 +168,7 @@ export {
159
168
  LogLevel,
160
169
  base64URLtoBytes,
161
170
  bytesToBase64url,
171
+ hwrServerPeerSelector,
162
172
  };
163
173
 
164
174
  export type {
@@ -171,6 +181,7 @@ export type {
171
181
  BinaryStreamStart,
172
182
  OpID,
173
183
  AccountRole,
184
+ AvailableCoValueCore,
174
185
  };
175
186
 
176
187
  export * from "./storage/index.js";
package/src/ids.ts CHANGED
@@ -23,6 +23,7 @@ export function rawCoIDfromBytes(bytes: Uint8Array): RawCoID {
23
23
  export type TransactionID = {
24
24
  sessionID: SessionID;
25
25
  txIndex: number;
26
+ branch?: RawCoID;
26
27
  };
27
28
 
28
29
  export type AgentID = `sealer_z${string}/signer_z${string}`;
package/src/localNode.ts CHANGED
@@ -479,8 +479,14 @@ export class LocalNode {
479
479
  return branch.getCurrentContent() as T;
480
480
  }
481
481
 
482
- // Passing skipRetry to true because otherwise creating a new branch would always take 1 retry delay
483
- await this.loadCoValueCore(branch.id, undefined, true);
482
+ // Do a synchronous check to see if the branch exists, if not we don't need to try to load the branch
483
+ if (!source.hasBranch(branchName, branchOwnerID)) {
484
+ return source
485
+ .createBranch(branchName, branchOwnerID)
486
+ .getCurrentContent() as T;
487
+ }
488
+
489
+ await this.loadCoValueCore(branch.id);
484
490
 
485
491
  if (!branch.isAvailable()) {
486
492
  return source
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  createContentMessage,
3
3
  exceedsRecommendedSize,
4
- getTransactionSize,
5
4
  } from "../coValueContentMessage.js";
6
5
  import {
7
6
  type CoValueCore,
@@ -110,10 +109,14 @@ export class StorageApiAsync implements StorageAPI {
110
109
 
111
110
  let idx = 0;
112
111
 
113
- signatures.push({
114
- idx: sessionRow.lastIdx,
115
- signature: sessionRow.lastSignature,
116
- });
112
+ const lastSignature = signatures[signatures.length - 1];
113
+
114
+ if (lastSignature?.signature !== sessionRow.lastSignature) {
115
+ signatures.push({
116
+ idx: sessionRow.lastIdx,
117
+ signature: sessionRow.lastSignature,
118
+ });
119
+ }
117
120
 
118
121
  for (const signature of signatures) {
119
122
  const newTxsInSession = await this.dbClient.getNewTransactionInSession(
@@ -113,10 +113,14 @@ export class StorageApiSync implements StorageAPI {
113
113
 
114
114
  let idx = 0;
115
115
 
116
- signatures.push({
117
- idx: sessionRow.lastIdx,
118
- signature: sessionRow.lastSignature,
119
- });
116
+ const lastSignature = signatures[signatures.length - 1];
117
+
118
+ if (lastSignature?.signature !== sessionRow.lastSignature) {
119
+ signatures.push({
120
+ idx: sessionRow.lastIdx,
121
+ signature: sessionRow.lastSignature,
122
+ });
123
+ }
120
124
 
121
125
  for (const signature of signatures) {
122
126
  const newTxsInSession = this.dbClient.getNewTransactionInSession(
package/src/sync.ts CHANGED
@@ -219,28 +219,7 @@ export class SyncManager {
219
219
  }
220
220
  }
221
221
 
222
- sendNewContentIncludingDependencies(
223
- id: RawCoID,
224
- peer: PeerState,
225
- seen: Set<RawCoID> = new Set(),
226
- ) {
227
- this.sendNewContent(id, peer, seen, true);
228
- }
229
-
230
- sendNewContentWithoutDependencies(
231
- id: RawCoID,
232
- peer: PeerState,
233
- seen: Set<RawCoID> = new Set(),
234
- ) {
235
- this.sendNewContent(id, peer, seen, false);
236
- }
237
-
238
- private sendNewContent(
239
- id: RawCoID,
240
- peer: PeerState,
241
- seen: Set<RawCoID> = new Set(),
242
- includeDependencies: boolean,
243
- ) {
222
+ sendNewContent(id: RawCoID, peer: PeerState, seen: Set<RawCoID> = new Set()) {
244
223
  if (seen.has(id)) {
245
224
  return;
246
225
  }
@@ -253,9 +232,10 @@ export class SyncManager {
253
232
  return;
254
233
  }
255
234
 
235
+ const includeDependencies = peer.role !== "server";
256
236
  if (includeDependencies) {
257
237
  for (const dependency of coValue.getDependedOnCoValues()) {
258
- this.sendNewContentIncludingDependencies(dependency, peer, seen);
238
+ this.sendNewContent(dependency, peer, seen);
259
239
  }
260
240
  }
261
241
 
@@ -434,7 +414,7 @@ export class SyncManager {
434
414
  const coValue = this.local.getCoValue(msg.id);
435
415
 
436
416
  if (coValue.isAvailable()) {
437
- this.sendNewContentIncludingDependencies(msg.id, peer);
417
+ this.sendNewContent(msg.id, peer);
438
418
  return;
439
419
  }
440
420
 
@@ -444,7 +424,7 @@ export class SyncManager {
444
424
 
445
425
  const handleLoadResult = () => {
446
426
  if (coValue.isAvailable()) {
447
- this.sendNewContentIncludingDependencies(msg.id, peer);
427
+ this.sendNewContent(msg.id, peer);
448
428
  return;
449
429
  }
450
430
 
@@ -478,11 +458,7 @@ export class SyncManager {
478
458
  }
479
459
 
480
460
  if (coValue.isAvailable()) {
481
- if (peer.role === "server") {
482
- this.sendNewContentWithoutDependencies(msg.id, peer);
483
- } else {
484
- this.sendNewContentIncludingDependencies(msg.id, peer);
485
- }
461
+ this.sendNewContent(msg.id, peer);
486
462
  }
487
463
  }
488
464
 
@@ -777,7 +753,7 @@ export class SyncManager {
777
753
 
778
754
  // We directly forward the new content to peers that have an active subscription
779
755
  if (peer.optimisticKnownStates.has(coValue.id)) {
780
- this.sendNewContentIncludingDependencies(coValue.id, peer);
756
+ this.sendNewContent(coValue.id, peer);
781
757
  syncedPeers.push(peer);
782
758
  } else if (
783
759
  peer.role === "server" &&
@@ -808,7 +784,7 @@ export class SyncManager {
808
784
  handleCorrection(msg: KnownStateMessage, peer: PeerState) {
809
785
  peer.setKnownState(msg.id, knownStateIn(msg));
810
786
 
811
- return this.sendNewContentIncludingDependencies(msg.id, peer);
787
+ return this.sendNewContent(msg.id, peer);
812
788
  }
813
789
 
814
790
  private syncQueue = new LocalTransactionsSyncQueue((content) =>