cojson 0.1.12 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coValue.d.ts +1 -1
- package/dist/coValueCore.d.ts +10 -4
- package/dist/coValueCore.js +91 -46
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/coList.js +2 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +7 -12
- package/dist/coValues/coMap.js +2 -1
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coStream.d.ts +11 -2
- package/dist/coValues/coStream.js +61 -14
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/crypto.d.ts +8 -0
- package/dist/crypto.js +10 -3
- package/dist/crypto.js.map +1 -1
- package/dist/group.d.ts +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/jsonStringify.d.ts +6 -0
- package/dist/{fastJsonStableStringify.js → jsonStringify.js} +10 -6
- package/dist/jsonStringify.js.map +1 -0
- package/dist/jsonValue.d.ts +1 -1
- package/dist/media.d.ts +8 -0
- package/dist/media.js +2 -0
- package/dist/media.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/permissions.js +4 -2
- package/dist/permissions.js.map +1 -1
- package/dist/streamUtils.js +14 -5
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.js +35 -15
- package/dist/sync.js.map +1 -1
- package/package.json +2 -2
- package/src/coValue.test.ts +113 -4
- package/src/coValue.ts +1 -1
- package/src/coValueCore.test.ts +11 -10
- package/src/coValueCore.ts +162 -75
- package/src/coValues/coList.ts +2 -1
- package/src/coValues/coMap.ts +11 -12
- package/src/coValues/coStream.ts +73 -21
- package/src/crypto.ts +22 -4
- package/src/group.ts +1 -1
- package/src/index.ts +7 -2
- package/src/{fastJsonStableStringify.ts → jsonStringify.ts} +23 -11
- package/src/jsonValue.ts +1 -1
- package/src/media.ts +9 -0
- package/src/node.ts +1 -1
- package/src/permissions.ts +5 -2
- package/src/streamUtils.ts +26 -6
- package/src/sync.test.ts +19 -20
- package/src/sync.ts +47 -26
- package/dist/fastJsonStableStringify.d.ts +0 -1
- package/dist/fastJsonStableStringify.js.map +0 -1
package/src/coValueCore.ts
CHANGED
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
sign,
|
|
15
15
|
verify,
|
|
16
16
|
encryptForTransaction,
|
|
17
|
-
decryptForTransaction,
|
|
18
17
|
KeyID,
|
|
19
18
|
decryptKeySecret,
|
|
20
19
|
getAgentSignerID,
|
|
21
20
|
getAgentSealerID,
|
|
21
|
+
decryptRawForTransaction,
|
|
22
22
|
} from "./crypto.js";
|
|
23
23
|
import { JsonObject, JsonValue } from "./jsonValue.js";
|
|
24
24
|
import { base58 } from "@scure/base";
|
|
@@ -32,10 +32,10 @@ import { LocalNode } from "./node.js";
|
|
|
32
32
|
import { CoValueKnownState, NewContentMessage } from "./sync.js";
|
|
33
33
|
import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
|
|
34
34
|
import { CoList } from "./coValues/coList.js";
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
import { AccountID, GeneralizedControlledAccount } from "./account.js";
|
|
36
|
+
import { Stringified, stableStringify } from "./jsonStringify.js";
|
|
37
|
+
|
|
38
|
+
export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
|
|
39
39
|
|
|
40
40
|
export type CoValueHeader = {
|
|
41
41
|
type: CoValueImpl["type"];
|
|
@@ -64,6 +64,7 @@ type SessionLog = {
|
|
|
64
64
|
transactions: Transaction[];
|
|
65
65
|
lastHash?: Hash;
|
|
66
66
|
streamingHash: StreamingHash;
|
|
67
|
+
signatureAfter: { [txIdx: number]: Signature | undefined };
|
|
67
68
|
lastSignature: Signature;
|
|
68
69
|
};
|
|
69
70
|
|
|
@@ -80,14 +81,14 @@ export type PrivateTransaction = {
|
|
|
80
81
|
export type TrustingTransaction = {
|
|
81
82
|
privacy: "trusting";
|
|
82
83
|
madeAt: number;
|
|
83
|
-
changes: JsonValue[]
|
|
84
|
+
changes: Stringified<JsonValue[]>;
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
export type Transaction = PrivateTransaction | TrustingTransaction;
|
|
87
88
|
|
|
88
89
|
export type DecryptedTransaction = {
|
|
89
90
|
txID: TransactionID;
|
|
90
|
-
changes: JsonValue[]
|
|
91
|
+
changes: Stringified<JsonValue[]>;
|
|
91
92
|
madeAt: number;
|
|
92
93
|
};
|
|
93
94
|
|
|
@@ -100,7 +101,11 @@ export class CoValueCore {
|
|
|
100
101
|
_sessions: { [key: SessionID]: SessionLog };
|
|
101
102
|
_cachedContent?: CoValueImpl;
|
|
102
103
|
listeners: Set<(content?: CoValueImpl) => void> = new Set();
|
|
103
|
-
_decryptionCache: {
|
|
104
|
+
_decryptionCache: {
|
|
105
|
+
[key: Encrypted<JsonValue[], JsonValue>]:
|
|
106
|
+
| Stringified<JsonValue[]>
|
|
107
|
+
| undefined;
|
|
108
|
+
} = {};
|
|
104
109
|
|
|
105
110
|
constructor(
|
|
106
111
|
header: CoValueHeader,
|
|
@@ -209,7 +214,8 @@ export class CoValueCore {
|
|
|
209
214
|
// const beforeVerify = performance.now();
|
|
210
215
|
if (!verify(newSignature, expectedNewHash, signerID)) {
|
|
211
216
|
console.warn(
|
|
212
|
-
"Invalid signature",
|
|
217
|
+
"Invalid signature in",
|
|
218
|
+
this.id,
|
|
213
219
|
newSignature,
|
|
214
220
|
expectedNewHash,
|
|
215
221
|
signerID
|
|
@@ -222,25 +228,13 @@ export class CoValueCore {
|
|
|
222
228
|
// afterVerify - beforeVerify
|
|
223
229
|
// );
|
|
224
230
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
streamingHash: newStreamingHash,
|
|
233
|
-
lastSignature: newSignature,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
this._cachedContent = undefined;
|
|
237
|
-
|
|
238
|
-
if (this.listeners.size > 0) {
|
|
239
|
-
const content = this.getCurrentContent();
|
|
240
|
-
for (const listener of this.listeners) {
|
|
241
|
-
listener(content);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
231
|
+
this.doAddTransactions(
|
|
232
|
+
sessionID,
|
|
233
|
+
newTransactions,
|
|
234
|
+
newSignature,
|
|
235
|
+
expectedNewHash,
|
|
236
|
+
newStreamingHash
|
|
237
|
+
);
|
|
244
238
|
|
|
245
239
|
return true;
|
|
246
240
|
}
|
|
@@ -269,10 +263,8 @@ export class CoValueCore {
|
|
|
269
263
|
const nTxBefore = this.sessions[sessionID]?.transactions.length ?? 0;
|
|
270
264
|
|
|
271
265
|
// const beforeHash = performance.now();
|
|
272
|
-
const { expectedNewHash, newStreamingHash } =
|
|
273
|
-
sessionID,
|
|
274
|
-
newTransactions
|
|
275
|
-
);
|
|
266
|
+
const { expectedNewHash, newStreamingHash } =
|
|
267
|
+
await this.expectedNewHashAfterAsync(sessionID, newTransactions);
|
|
276
268
|
// const afterHash = performance.now();
|
|
277
269
|
// console.log(
|
|
278
270
|
// "Hashing took",
|
|
@@ -283,7 +275,7 @@ export class CoValueCore {
|
|
|
283
275
|
|
|
284
276
|
if (nTxAfter !== nTxBefore) {
|
|
285
277
|
const newTransactionLengthBefore = newTransactions.length;
|
|
286
|
-
newTransactions = newTransactions.slice(
|
|
278
|
+
newTransactions = newTransactions.slice(nTxAfter - nTxBefore);
|
|
287
279
|
console.warn("Transactions changed while async hashing", {
|
|
288
280
|
nTxBefore,
|
|
289
281
|
nTxAfter,
|
|
@@ -303,7 +295,8 @@ export class CoValueCore {
|
|
|
303
295
|
// const beforeVerify = performance.now();
|
|
304
296
|
if (!verify(newSignature, expectedNewHash, signerID)) {
|
|
305
297
|
console.warn(
|
|
306
|
-
"Invalid signature",
|
|
298
|
+
"Invalid signature in",
|
|
299
|
+
this.id,
|
|
307
300
|
newSignature,
|
|
308
301
|
expectedNewHash,
|
|
309
302
|
signerID
|
|
@@ -316,15 +309,61 @@ export class CoValueCore {
|
|
|
316
309
|
// afterVerify - beforeVerify
|
|
317
310
|
// );
|
|
318
311
|
|
|
319
|
-
|
|
312
|
+
this.doAddTransactions(
|
|
313
|
+
sessionID,
|
|
314
|
+
newTransactions,
|
|
315
|
+
newSignature,
|
|
316
|
+
expectedNewHash,
|
|
317
|
+
newStreamingHash
|
|
318
|
+
);
|
|
320
319
|
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private doAddTransactions(
|
|
324
|
+
sessionID: SessionID,
|
|
325
|
+
newTransactions: Transaction[],
|
|
326
|
+
newSignature: Signature,
|
|
327
|
+
expectedNewHash: Hash,
|
|
328
|
+
newStreamingHash: StreamingHash
|
|
329
|
+
) {
|
|
330
|
+
const transactions = this.sessions[sessionID]?.transactions ?? [];
|
|
321
331
|
transactions.push(...newTransactions);
|
|
322
332
|
|
|
333
|
+
const signatureAfter = this.sessions[sessionID]?.signatureAfter ?? {};
|
|
334
|
+
|
|
335
|
+
const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
|
|
336
|
+
(max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
|
|
337
|
+
-1
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
|
341
|
+
.slice(lastInbetweenSignatureIdx + 1)
|
|
342
|
+
.reduce(
|
|
343
|
+
(sum, tx) =>
|
|
344
|
+
sum +
|
|
345
|
+
(tx.privacy === "private"
|
|
346
|
+
? tx.encryptedChanges.length
|
|
347
|
+
: tx.changes.length),
|
|
348
|
+
0
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if (sizeOfTxsSinceLastInbetweenSignature > 100 * 1024) {
|
|
352
|
+
// console.log(
|
|
353
|
+
// "Saving inbetween signature for tx ",
|
|
354
|
+
// sessionID,
|
|
355
|
+
// transactions.length - 1,
|
|
356
|
+
// sizeOfTxsSinceLastInbetweenSignature
|
|
357
|
+
// );
|
|
358
|
+
signatureAfter[transactions.length - 1] = newSignature;
|
|
359
|
+
}
|
|
360
|
+
|
|
323
361
|
this._sessions[sessionID] = {
|
|
324
362
|
transactions,
|
|
325
363
|
lastHash: expectedNewHash,
|
|
326
364
|
streamingHash: newStreamingHash,
|
|
327
365
|
lastSignature: newSignature,
|
|
366
|
+
signatureAfter: signatureAfter,
|
|
328
367
|
};
|
|
329
368
|
|
|
330
369
|
this._cachedContent = undefined;
|
|
@@ -335,8 +374,6 @@ export class CoValueCore {
|
|
|
335
374
|
listener(content);
|
|
336
375
|
}
|
|
337
376
|
}
|
|
338
|
-
|
|
339
|
-
return true;
|
|
340
377
|
}
|
|
341
378
|
|
|
342
379
|
subscribe(listener: (content?: CoValueImpl) => void): () => void {
|
|
@@ -376,10 +413,10 @@ export class CoValueCore {
|
|
|
376
413
|
new StreamingHash();
|
|
377
414
|
let before = performance.now();
|
|
378
415
|
for (const transaction of newTransactions) {
|
|
379
|
-
streamingHash.update(transaction)
|
|
416
|
+
streamingHash.update(transaction);
|
|
380
417
|
const after = performance.now();
|
|
381
418
|
if (after - before > 1) {
|
|
382
|
-
console.log("Hashing blocked for", after - before);
|
|
419
|
+
// console.log("Hashing blocked for", after - before);
|
|
383
420
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
384
421
|
before = performance.now();
|
|
385
422
|
}
|
|
@@ -415,7 +452,7 @@ export class CoValueCore {
|
|
|
415
452
|
tx: this.nextTransactionID(),
|
|
416
453
|
});
|
|
417
454
|
|
|
418
|
-
this._decryptionCache[encrypted] = changes;
|
|
455
|
+
this._decryptionCache[encrypted] = stableStringify(changes);
|
|
419
456
|
|
|
420
457
|
transaction = {
|
|
421
458
|
privacy: "private",
|
|
@@ -427,7 +464,7 @@ export class CoValueCore {
|
|
|
427
464
|
transaction = {
|
|
428
465
|
privacy: "trusting",
|
|
429
466
|
madeAt,
|
|
430
|
-
changes,
|
|
467
|
+
changes: stableStringify(changes),
|
|
431
468
|
};
|
|
432
469
|
}
|
|
433
470
|
|
|
@@ -497,10 +534,11 @@ export class CoValueCore {
|
|
|
497
534
|
if (!readKey) {
|
|
498
535
|
return undefined;
|
|
499
536
|
} else {
|
|
500
|
-
let decrytedChanges =
|
|
537
|
+
let decrytedChanges =
|
|
538
|
+
this._decryptionCache[tx.encryptedChanges];
|
|
501
539
|
|
|
502
540
|
if (!decrytedChanges) {
|
|
503
|
-
decrytedChanges =
|
|
541
|
+
decrytedChanges = decryptRawForTransaction(
|
|
504
542
|
tx.encryptedChanges,
|
|
505
543
|
readKey,
|
|
506
544
|
{
|
|
@@ -508,7 +546,8 @@ export class CoValueCore {
|
|
|
508
546
|
tx: txID,
|
|
509
547
|
}
|
|
510
548
|
);
|
|
511
|
-
this._decryptionCache[tx.encryptedChanges] =
|
|
549
|
+
this._decryptionCache[tx.encryptedChanges] =
|
|
550
|
+
decrytedChanges;
|
|
512
551
|
}
|
|
513
552
|
|
|
514
553
|
if (!decrytedChanges) {
|
|
@@ -680,47 +719,95 @@ export class CoValueCore {
|
|
|
680
719
|
|
|
681
720
|
newContentSince(
|
|
682
721
|
knownState: CoValueKnownState | undefined
|
|
683
|
-
): NewContentMessage | undefined {
|
|
684
|
-
|
|
722
|
+
): NewContentMessage[] | undefined {
|
|
723
|
+
let currentPiece: NewContentMessage = {
|
|
685
724
|
action: "content",
|
|
686
725
|
id: this.id,
|
|
687
726
|
header: knownState?.header ? undefined : this.header,
|
|
688
|
-
new:
|
|
689
|
-
|
|
690
|
-
.map(([sessionID, log]) => {
|
|
691
|
-
const newTransactions = log.transactions.slice(
|
|
692
|
-
knownState?.sessions[sessionID as SessionID] || 0
|
|
693
|
-
);
|
|
727
|
+
new: {},
|
|
728
|
+
};
|
|
694
729
|
|
|
695
|
-
|
|
696
|
-
newTransactions.length === 0 ||
|
|
697
|
-
!log.lastHash ||
|
|
698
|
-
!log.lastSignature
|
|
699
|
-
) {
|
|
700
|
-
return undefined;
|
|
701
|
-
}
|
|
730
|
+
const pieces = [currentPiece];
|
|
702
731
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
{
|
|
706
|
-
after:
|
|
707
|
-
knownState?.sessions[
|
|
708
|
-
sessionID as SessionID
|
|
709
|
-
] || 0,
|
|
710
|
-
newTransactions,
|
|
711
|
-
lastSignature: log.lastSignature,
|
|
712
|
-
},
|
|
713
|
-
];
|
|
714
|
-
})
|
|
715
|
-
.filter((x): x is Exclude<typeof x, undefined> => !!x)
|
|
716
|
-
),
|
|
732
|
+
const sentState: CoValueKnownState["sessions"] = {
|
|
733
|
+
...knownState?.sessions,
|
|
717
734
|
};
|
|
718
735
|
|
|
719
|
-
|
|
736
|
+
let newTxsWereAdded = true;
|
|
737
|
+
let pieceSize = 0;
|
|
738
|
+
while (newTxsWereAdded) {
|
|
739
|
+
newTxsWereAdded = false;
|
|
740
|
+
|
|
741
|
+
for (const [sessionID, log] of Object.entries(this.sessions) as [
|
|
742
|
+
SessionID,
|
|
743
|
+
SessionLog
|
|
744
|
+
][]) {
|
|
745
|
+
const nextKnownSignatureIdx = Object.keys(log.signatureAfter)
|
|
746
|
+
.map(Number)
|
|
747
|
+
.sort((a, b) => a - b)
|
|
748
|
+
.find((idx) => idx >= (sentState[sessionID] ?? -1));
|
|
749
|
+
|
|
750
|
+
const txsToAdd = log.transactions.slice(
|
|
751
|
+
sentState[sessionID] ?? 0,
|
|
752
|
+
nextKnownSignatureIdx === undefined
|
|
753
|
+
? undefined
|
|
754
|
+
: nextKnownSignatureIdx + 1
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
if (txsToAdd.length === 0) continue;
|
|
758
|
+
|
|
759
|
+
newTxsWereAdded = true;
|
|
760
|
+
|
|
761
|
+
const oldPieceSize = pieceSize;
|
|
762
|
+
pieceSize += txsToAdd.reduce(
|
|
763
|
+
(sum, tx) =>
|
|
764
|
+
sum +
|
|
765
|
+
(tx.privacy === "private"
|
|
766
|
+
? tx.encryptedChanges.length
|
|
767
|
+
: tx.changes.length),
|
|
768
|
+
0
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
|
|
772
|
+
currentPiece = {
|
|
773
|
+
action: "content",
|
|
774
|
+
id: this.id,
|
|
775
|
+
header: undefined,
|
|
776
|
+
new: {},
|
|
777
|
+
};
|
|
778
|
+
pieces.push(currentPiece);
|
|
779
|
+
pieceSize = pieceSize - oldPieceSize;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
let sessionEntry = currentPiece.new[sessionID];
|
|
783
|
+
if (!sessionEntry) {
|
|
784
|
+
sessionEntry = {
|
|
785
|
+
after: sentState[sessionID] ?? 0,
|
|
786
|
+
newTransactions: [],
|
|
787
|
+
lastSignature: "WILL_BE_REPLACED" as Signature
|
|
788
|
+
};
|
|
789
|
+
currentPiece.new[sessionID] = sessionEntry;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
sessionEntry.newTransactions.push(...txsToAdd);
|
|
793
|
+
sessionEntry.lastSignature = nextKnownSignatureIdx === undefined
|
|
794
|
+
? log.lastSignature!
|
|
795
|
+
: log.signatureAfter[nextKnownSignatureIdx]!
|
|
796
|
+
|
|
797
|
+
sentState[sessionID] =
|
|
798
|
+
(sentState[sessionID] || 0) + txsToAdd.length;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const piecesWithContent = pieces.filter(
|
|
803
|
+
(piece) => Object.keys(piece.new).length > 0 || piece.header
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
if (piecesWithContent.length === 0) {
|
|
720
807
|
return undefined;
|
|
721
808
|
}
|
|
722
809
|
|
|
723
|
-
return
|
|
810
|
+
return piecesWithContent;
|
|
724
811
|
}
|
|
725
812
|
|
|
726
813
|
getDependedOnCoValues(): RawCoID[] {
|
package/src/coValues/coList.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { CoValueCore, accountOrAgentIDfromSessionID } from "../coValueCore.js";
|
|
|
4
4
|
import { SessionID, TransactionID } from "../ids.js";
|
|
5
5
|
import { Group } from "../group.js";
|
|
6
6
|
import { AccountID, isAccountID } from "../account.js";
|
|
7
|
+
import { parseJSON } from "../jsonStringify.js";
|
|
7
8
|
|
|
8
9
|
type OpID = TransactionID & { changeIdx: number };
|
|
9
10
|
|
|
@@ -98,7 +99,7 @@ export class CoList<T extends JsonValue, Meta extends JsonObject | null = null>
|
|
|
98
99
|
changes,
|
|
99
100
|
madeAt,
|
|
100
101
|
} of this.core.getValidSortedTransactions()) {
|
|
101
|
-
for (const [changeIdx, changeUntyped] of changes.entries()) {
|
|
102
|
+
for (const [changeIdx, changeUntyped] of parseJSON(changes).entries()) {
|
|
102
103
|
const change = changeUntyped as ListOpPayload<T>;
|
|
103
104
|
|
|
104
105
|
if (change.op === "pre" || change.op === "app") {
|
package/src/coValues/coMap.ts
CHANGED
|
@@ -4,15 +4,16 @@ import { CoID, ReadableCoValue, WriteableCoValue } from '../coValue.js';
|
|
|
4
4
|
import { CoValueCore, accountOrAgentIDfromSessionID } from '../coValueCore.js';
|
|
5
5
|
import { AccountID, isAccountID } from '../account.js';
|
|
6
6
|
import { Group } from '../group.js';
|
|
7
|
+
import { parseJSON } from '../jsonStringify.js';
|
|
7
8
|
|
|
8
|
-
type MapOp<K extends string, V extends JsonValue> = {
|
|
9
|
+
type MapOp<K extends string, V extends JsonValue | undefined> = {
|
|
9
10
|
txID: TransactionID;
|
|
10
11
|
madeAt: number;
|
|
11
12
|
changeIdx: number;
|
|
12
13
|
} & MapOpPayload<K, V>;
|
|
13
14
|
// TODO: add after TransactionID[] for conflicts/ordering
|
|
14
15
|
|
|
15
|
-
export type MapOpPayload<K extends string, V extends JsonValue> = {
|
|
16
|
+
export type MapOpPayload<K extends string, V extends JsonValue | undefined> = {
|
|
16
17
|
op: "set";
|
|
17
18
|
key: K;
|
|
18
19
|
value: V;
|
|
@@ -22,18 +23,16 @@ export type MapOpPayload<K extends string, V extends JsonValue> = {
|
|
|
22
23
|
key: K;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
export type MapK<M extends { [key: string]: JsonValue; }> = keyof M & string;
|
|
26
|
-
export type MapV<M extends { [key: string]: JsonValue; }> = M[MapK<M>];
|
|
27
|
-
|
|
28
|
-
[KK in MapK<M>]: M[KK];
|
|
29
|
-
}
|
|
26
|
+
export type MapK<M extends { [key: string]: JsonValue | undefined; }> = keyof M & string;
|
|
27
|
+
export type MapV<M extends { [key: string]: JsonValue | undefined; }> = M[MapK<M>];
|
|
28
|
+
|
|
30
29
|
|
|
31
30
|
/** A collaborative map with precise shape `M` and optional static metadata `Meta` */
|
|
32
31
|
export class CoMap<
|
|
33
|
-
M extends { [key: string]: JsonValue; },
|
|
32
|
+
M extends { [key: string]: JsonValue | undefined; },
|
|
34
33
|
Meta extends JsonObject | null = null,
|
|
35
34
|
> implements ReadableCoValue {
|
|
36
|
-
id: CoID<CoMap<
|
|
35
|
+
id: CoID<CoMap<M, Meta>>;
|
|
37
36
|
type = "comap" as const;
|
|
38
37
|
core: CoValueCore;
|
|
39
38
|
/** @internal */
|
|
@@ -43,7 +42,7 @@ export class CoMap<
|
|
|
43
42
|
|
|
44
43
|
/** @internal */
|
|
45
44
|
constructor(core: CoValueCore) {
|
|
46
|
-
this.id = core.id as CoID<CoMap<
|
|
45
|
+
this.id = core.id as CoID<CoMap<M, Meta>>;
|
|
47
46
|
this.core = core;
|
|
48
47
|
this.ops = {};
|
|
49
48
|
|
|
@@ -64,7 +63,7 @@ export class CoMap<
|
|
|
64
63
|
|
|
65
64
|
for (const { txID, changes, madeAt } of this.core.getValidSortedTransactions()) {
|
|
66
65
|
for (const [changeIdx, changeUntyped] of (
|
|
67
|
-
changes
|
|
66
|
+
parseJSON(changes)
|
|
68
67
|
).entries()) {
|
|
69
68
|
const change = changeUntyped as MapOpPayload<MapK<M>, MapV<M>>;
|
|
70
69
|
let entries = this.ops[change.key];
|
|
@@ -207,7 +206,7 @@ export class CoMap<
|
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
export class WriteableCoMap<
|
|
210
|
-
M extends { [key: string]: JsonValue; },
|
|
209
|
+
M extends { [key: string]: JsonValue | undefined; },
|
|
211
210
|
Meta extends JsonObject | null = null,
|
|
212
211
|
> extends CoMap<M, Meta> implements WriteableCoValue {
|
|
213
212
|
/** @internal */
|
package/src/coValues/coStream.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
2
2
|
import { CoID, ReadableCoValue, WriteableCoValue } from "../coValue.js";
|
|
3
|
-
import { CoValueCore } from "../coValueCore.js";
|
|
3
|
+
import { CoValueCore, accountOrAgentIDfromSessionID } from "../coValueCore.js";
|
|
4
4
|
import { Group } from "../group.js";
|
|
5
5
|
import { SessionID } from "../ids.js";
|
|
6
6
|
import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
|
|
7
|
+
import { AccountID } from "../index.js";
|
|
8
|
+
import { isAccountID } from "../account.js";
|
|
9
|
+
import { parseJSON } from "../jsonStringify.js";
|
|
7
10
|
|
|
8
11
|
export type BinaryChunkInfo = {
|
|
9
12
|
mimeType: string;
|
|
@@ -40,7 +43,7 @@ export class CoStream<
|
|
|
40
43
|
type = "costream" as const;
|
|
41
44
|
core: CoValueCore;
|
|
42
45
|
items: {
|
|
43
|
-
[key: SessionID]: T[];
|
|
46
|
+
[key: SessionID]: {item: T, madeAt: number}[];
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
constructor(core: CoValueCore) {
|
|
@@ -64,16 +67,17 @@ export class CoStream<
|
|
|
64
67
|
|
|
65
68
|
for (const {
|
|
66
69
|
txID,
|
|
70
|
+
madeAt,
|
|
67
71
|
changes,
|
|
68
72
|
} of this.core.getValidSortedTransactions()) {
|
|
69
|
-
for (const changeUntyped of changes) {
|
|
73
|
+
for (const changeUntyped of parseJSON(changes)) {
|
|
70
74
|
const change = changeUntyped as T;
|
|
71
75
|
let entries = this.items[txID.sessionID];
|
|
72
76
|
if (!entries) {
|
|
73
77
|
entries = [];
|
|
74
78
|
this.items[txID.sessionID] = entries;
|
|
75
79
|
}
|
|
76
|
-
entries.push(change);
|
|
80
|
+
entries.push({item: change, madeAt});
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -87,13 +91,57 @@ export class CoStream<
|
|
|
87
91
|
);
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
return Object.values(this.items)[0];
|
|
94
|
+
return Object.values(this.items)[0]?.map(item => item.item);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getLastItemsPerAccount(): {[account: AccountID]: T | undefined} {
|
|
98
|
+
const result: {[account: AccountID]: {item: T, madeAt: number} | undefined} = {};
|
|
99
|
+
|
|
100
|
+
for (const [sessionID, items] of Object.entries(this.items)) {
|
|
101
|
+
const account = accountOrAgentIDfromSessionID(sessionID as SessionID);
|
|
102
|
+
if (!isAccountID(account)) continue;
|
|
103
|
+
if (items.length > 0) {
|
|
104
|
+
const lastItemOfSession = items[items.length - 1]!;
|
|
105
|
+
if (!result[account] || lastItemOfSession.madeAt > result[account]!.madeAt) {
|
|
106
|
+
result[account] = lastItemOfSession;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Object.fromEntries(Object.entries(result).map(([account, item]) =>
|
|
112
|
+
[account, item?.item]
|
|
113
|
+
));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getLastItemFrom(account: AccountID): T | undefined {
|
|
117
|
+
let lastItem: {item: T, madeAt: number} | undefined;
|
|
118
|
+
|
|
119
|
+
for (const [sessionID, items] of Object.entries(this.items)) {
|
|
120
|
+
if (sessionID.startsWith(account)) {
|
|
121
|
+
if (items.length > 0) {
|
|
122
|
+
const lastItemOfSession = items[items.length - 1]!;
|
|
123
|
+
if (!lastItem || lastItemOfSession.madeAt > lastItem.madeAt) {
|
|
124
|
+
lastItem = lastItemOfSession;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return lastItem?.item;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getLastItemFromMe(): T | undefined {
|
|
134
|
+
const myAccountID = this.core.node.account.id;
|
|
135
|
+
if (!isAccountID(myAccountID)) return undefined;
|
|
136
|
+
return this.getLastItemFrom(myAccountID);
|
|
91
137
|
}
|
|
92
138
|
|
|
93
139
|
toJSON(): {
|
|
94
140
|
[key: SessionID]: T[];
|
|
95
141
|
} {
|
|
96
|
-
return this.items
|
|
142
|
+
return Object.fromEntries(Object.entries(this.items).map(([sessionID, items]) =>
|
|
143
|
+
[sessionID, items.map(item => item.item)]
|
|
144
|
+
));
|
|
97
145
|
}
|
|
98
146
|
|
|
99
147
|
subscribe(listener: (coMap: CoStream<T, Meta>) => void): () => void {
|
|
@@ -121,10 +169,10 @@ export class BinaryCoStream<
|
|
|
121
169
|
{
|
|
122
170
|
id!: CoID<BinaryCoStream<Meta>>;
|
|
123
171
|
|
|
124
|
-
getBinaryChunks():
|
|
172
|
+
getBinaryChunks(allowUnfinished?: boolean):
|
|
125
173
|
| (BinaryChunkInfo & { chunks: Uint8Array[]; finished: boolean })
|
|
126
174
|
| undefined {
|
|
127
|
-
const before = performance.now();
|
|
175
|
+
// const before = performance.now();
|
|
128
176
|
const items = this.getSingleStream();
|
|
129
177
|
|
|
130
178
|
if (!items) return;
|
|
@@ -136,10 +184,14 @@ export class BinaryCoStream<
|
|
|
136
184
|
return;
|
|
137
185
|
}
|
|
138
186
|
|
|
187
|
+
const end = items[items.length - 1];
|
|
188
|
+
|
|
189
|
+
if (end?.type !== "end" && !allowUnfinished) return;
|
|
190
|
+
|
|
139
191
|
const chunks: Uint8Array[] = [];
|
|
140
192
|
|
|
141
193
|
let finished = false;
|
|
142
|
-
let totalLength = 0;
|
|
194
|
+
// let totalLength = 0;
|
|
143
195
|
|
|
144
196
|
for (const item of items.slice(1)) {
|
|
145
197
|
if (item.type === "end") {
|
|
@@ -155,15 +207,15 @@ export class BinaryCoStream<
|
|
|
155
207
|
const chunk = base64URLtoBytes(
|
|
156
208
|
item.chunk.slice(binary_U_prefixLength)
|
|
157
209
|
);
|
|
158
|
-
totalLength += chunk.length;
|
|
210
|
+
// totalLength += chunk.length;
|
|
159
211
|
chunks.push(chunk);
|
|
160
212
|
}
|
|
161
213
|
|
|
162
|
-
const after = performance.now();
|
|
163
|
-
console.log(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
);
|
|
214
|
+
// const after = performance.now();
|
|
215
|
+
// console.log(
|
|
216
|
+
// "getBinaryChunks bandwidth in MB/s",
|
|
217
|
+
// (1000 * totalLength) / (after - before) / (1024 * 1024)
|
|
218
|
+
// );
|
|
167
219
|
|
|
168
220
|
return {
|
|
169
221
|
mimeType: start.mimeType,
|
|
@@ -238,7 +290,7 @@ export class WriteableBinaryCoStream<
|
|
|
238
290
|
chunk: Uint8Array,
|
|
239
291
|
privacy: "private" | "trusting" = "private"
|
|
240
292
|
) {
|
|
241
|
-
const before = performance.now();
|
|
293
|
+
// const before = performance.now();
|
|
242
294
|
this.push(
|
|
243
295
|
{
|
|
244
296
|
type: "chunk",
|
|
@@ -246,11 +298,11 @@ export class WriteableBinaryCoStream<
|
|
|
246
298
|
} satisfies BinaryStreamChunk,
|
|
247
299
|
privacy
|
|
248
300
|
);
|
|
249
|
-
const after = performance.now();
|
|
250
|
-
console.log(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
);
|
|
301
|
+
// const after = performance.now();
|
|
302
|
+
// console.log(
|
|
303
|
+
// "pushBinaryStreamChunk bandwidth in MB/s",
|
|
304
|
+
// (1000 * chunk.length) / (after - before) / (1024 * 1024)
|
|
305
|
+
// );
|
|
254
306
|
}
|
|
255
307
|
|
|
256
308
|
endBinaryStream(privacy: "private" | "trusting" = "private") {
|