cojson 0.4.8 → 0.5.0

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.
@@ -1,4 +1,3 @@
1
- import { randomBytes } from "@noble/hashes/utils";
2
1
  import { AnyCoValue, CoValue } from "./coValue.js";
3
2
  import {
4
3
  Encrypted,
@@ -28,11 +27,8 @@ import { Group } from "./coValues/group.js";
28
27
  import { LocalNode } from "./localNode.js";
29
28
  import { CoValueKnownState, NewContentMessage } from "./sync.js";
30
29
  import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
31
- import {
32
- AccountID,
33
- GeneralizedControlledAccount,
34
- } from "./coValues/account.js";
35
- import { Stringified, stableStringify } from "./jsonStringify.js";
30
+ import { AccountID, GeneralizedControlledAccount } from "./coValues/account.js";
31
+ import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
36
32
  import { coreToCoValue } from "./coreToCoValue.js";
37
33
  import { expectGroup } from "./typeUtils/expectGroup.js";
38
34
  import { isAccountID } from "./typeUtils/isAccountID.js";
@@ -54,7 +50,10 @@ export function idforHeader(header: CoValueHeader): RawCoID {
54
50
  }
55
51
 
56
52
  export function newRandomSessionID(accountID: AccountID | AgentID): SessionID {
57
- return `${accountID}_session_z${base58.encode(randomBytes(8))}`;
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ return `${accountID}_session_z${base58.encode(
55
+ (globalThis as any).crypto.getRandomValues(new Uint8Array(8))
56
+ )}`;
58
57
  }
59
58
 
60
59
  type SessionLog = {
@@ -85,7 +84,7 @@ export type Transaction = PrivateTransaction | TrustingTransaction;
85
84
 
86
85
  export type DecryptedTransaction = {
87
86
  txID: TransactionID;
88
- changes: Stringified<JsonValue[]>;
87
+ changes: JsonValue[];
89
88
  madeAt: number;
90
89
  };
91
90
 
@@ -95,23 +94,25 @@ export class CoValueCore {
95
94
  id: RawCoID;
96
95
  node: LocalNode;
97
96
  header: CoValueHeader;
98
- _sessions: { [key: SessionID]: SessionLog };
97
+ _sessionLogs: Map<SessionID, SessionLog>;
99
98
  _cachedContent?: CoValue;
100
99
  listeners: Set<(content?: CoValue) => void> = new Set();
101
100
  _decryptionCache: {
102
- [key: Encrypted<JsonValue[], JsonValue>]:
103
- | Stringified<JsonValue[]>
104
- | undefined;
101
+ [key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
105
102
  } = {};
103
+ currentlyAsyncApplyingTxDone?: Promise<void>;
104
+ _cachedKnownState?: CoValueKnownState;
105
+ _cachedDependentOn?: RawCoID[];
106
+ _cachedNewContentSinceEmpty?: NewContentMessage[] | undefined;
106
107
 
107
108
  constructor(
108
109
  header: CoValueHeader,
109
110
  node: LocalNode,
110
- internalInitSessions: { [key: SessionID]: SessionLog } = {}
111
+ internalInitSessions: Map<SessionID, SessionLog> = new Map()
111
112
  ) {
112
113
  this.id = idforHeader(header);
113
114
  this.header = header;
114
- this._sessions = internalInitSessions;
115
+ this._sessionLogs = internalInitSessions;
115
116
  this.node = node;
116
117
 
117
118
  if (header.ruleset.type == "ownedByGroup") {
@@ -127,8 +128,8 @@ export class CoValueCore {
127
128
  }
128
129
  }
129
130
 
130
- get sessions(): Readonly<{ [key: SessionID]: SessionLog }> {
131
- return this._sessions;
131
+ get sessionLogs(): Map<SessionID, SessionLog> {
132
+ return this._sessionLogs;
132
133
  }
133
134
 
134
135
  testWithDifferentAccount(
@@ -144,11 +145,22 @@ export class CoValueCore {
144
145
  }
145
146
 
146
147
  knownState(): CoValueKnownState {
148
+ if (this._cachedKnownState) {
149
+ return this._cachedKnownState;
150
+ } else {
151
+ const knownState = this.knownStateUncached();
152
+ this._cachedKnownState = knownState;
153
+ return knownState;
154
+ }
155
+ }
156
+
157
+ /** @internal */
158
+ knownStateUncached(): CoValueKnownState {
147
159
  return {
148
160
  id: this.id,
149
161
  header: true,
150
162
  sessions: Object.fromEntries(
151
- Object.entries(this.sessions).map(([k, v]) => [
163
+ [...this.sessionLogs.entries()].map(([k, v]) => [
152
164
  k,
153
165
  v.transactions.length,
154
166
  ])
@@ -172,7 +184,7 @@ export class CoValueCore {
172
184
 
173
185
  return {
174
186
  sessionID,
175
- txIndex: this.sessions[sessionID]?.transactions.length || 0,
187
+ txIndex: this.sessionLogs.get(sessionID)?.transactions.length || 0,
176
188
  };
177
189
  }
178
190
 
@@ -250,8 +262,17 @@ export class CoValueCore {
250
262
  givenExpectedNewHash: Hash | undefined,
251
263
  newSignature: Signature
252
264
  ): Promise<boolean> {
265
+ if (this.currentlyAsyncApplyingTxDone) {
266
+ await this.currentlyAsyncApplyingTxDone;
267
+ }
268
+ let resolveDone!: () => void;
269
+
270
+ this.currentlyAsyncApplyingTxDone = new Promise((resolve) => {
271
+ resolveDone = resolve;
272
+ });
273
+
253
274
  const signerID = getAgentSignerID(
254
- this.node.resolveAccountAgent(
275
+ await this.node.resolveAccountAgentAsync(
255
276
  accountOrAgentIDfromSessionID(sessionID),
256
277
  "Expected to know signer of transaction"
257
278
  )
@@ -262,10 +283,11 @@ export class CoValueCore {
262
283
  "Unknown agent",
263
284
  accountOrAgentIDfromSessionID(sessionID)
264
285
  );
286
+ resolveDone();
265
287
  return false;
266
288
  }
267
289
 
268
- const nTxBefore = this.sessions[sessionID]?.transactions.length ?? 0;
290
+ const nTxBefore = this.sessionLogs.get(sessionID)?.transactions.length ?? 0;
269
291
 
270
292
  // const beforeHash = performance.now();
271
293
  const { expectedNewHash, newStreamingHash } =
@@ -276,7 +298,7 @@ export class CoValueCore {
276
298
  // afterHash - beforeHash
277
299
  // );
278
300
 
279
- const nTxAfter = this.sessions[sessionID]?.transactions.length ?? 0;
301
+ const nTxAfter = this.sessionLogs.get(sessionID)?.transactions.length ?? 0;
280
302
 
281
303
  if (nTxAfter !== nTxBefore) {
282
304
  const newTransactionLengthBefore = newTransactions.length;
@@ -294,6 +316,7 @@ export class CoValueCore {
294
316
  expectedNewHash,
295
317
  givenExpectedNewHash,
296
318
  });
319
+ resolveDone();
297
320
  return false;
298
321
  }
299
322
 
@@ -306,6 +329,7 @@ export class CoValueCore {
306
329
  expectedNewHash,
307
330
  signerID
308
331
  );
332
+ resolveDone();
309
333
  return false;
310
334
  }
311
335
  // const afterVerify = performance.now();
@@ -322,6 +346,7 @@ export class CoValueCore {
322
346
  newStreamingHash
323
347
  );
324
348
 
349
+ resolveDone();
325
350
  return true;
326
351
  }
327
352
 
@@ -332,10 +357,10 @@ export class CoValueCore {
332
357
  expectedNewHash: Hash,
333
358
  newStreamingHash: StreamingHash
334
359
  ) {
335
- const transactions = this.sessions[sessionID]?.transactions ?? [];
360
+ const transactions = this.sessionLogs.get(sessionID)?.transactions ?? [];
336
361
  transactions.push(...newTransactions);
337
362
 
338
- const signatureAfter = this.sessions[sessionID]?.signatureAfter ?? {};
363
+ const signatureAfter = this.sessionLogs.get(sessionID)?.signatureAfter ?? {};
339
364
 
340
365
  const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
341
366
  (max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
@@ -363,15 +388,18 @@ export class CoValueCore {
363
388
  signatureAfter[transactions.length - 1] = newSignature;
364
389
  }
365
390
 
366
- this._sessions[sessionID] = {
391
+ this._sessionLogs.set(sessionID, {
367
392
  transactions,
368
393
  lastHash: expectedNewHash,
369
394
  streamingHash: newStreamingHash,
370
395
  lastSignature: newSignature,
371
396
  signatureAfter: signatureAfter,
372
- };
397
+ });
373
398
 
374
399
  this._cachedContent = undefined;
400
+ this._cachedKnownState = undefined;
401
+ this._cachedDependentOn = undefined;
402
+ this._cachedNewContentSinceEmpty = undefined;
375
403
 
376
404
  if (this.listeners.size > 0) {
377
405
  const content = this.getCurrentContent();
@@ -395,7 +423,7 @@ export class CoValueCore {
395
423
  newTransactions: Transaction[]
396
424
  ): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
397
425
  const streamingHash =
398
- this.sessions[sessionID]?.streamingHash.clone() ??
426
+ this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
399
427
  new StreamingHash();
400
428
  for (const transaction of newTransactions) {
401
429
  streamingHash.update(transaction);
@@ -414,7 +442,7 @@ export class CoValueCore {
414
442
  newTransactions: Transaction[]
415
443
  ): Promise<{ expectedNewHash: Hash; newStreamingHash: StreamingHash }> {
416
444
  const streamingHash =
417
- this.sessions[sessionID]?.streamingHash.clone() ??
445
+ this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
418
446
  new StreamingHash();
419
447
  let before = performance.now();
420
448
  for (const transaction of newTransactions) {
@@ -457,7 +485,7 @@ export class CoValueCore {
457
485
  tx: this.nextTransactionID(),
458
486
  });
459
487
 
460
- this._decryptionCache[encrypted] = stableStringify(changes);
488
+ this._decryptionCache[encrypted] = changes;
461
489
 
462
490
  transaction = {
463
491
  privacy: "private",
@@ -530,7 +558,7 @@ export class CoValueCore {
530
558
  return {
531
559
  txID,
532
560
  madeAt: tx.madeAt,
533
- changes: tx.changes,
561
+ changes: parseJSON(tx.changes),
534
562
  };
535
563
  } else {
536
564
  if (options?.ignorePrivateTransactions) {
@@ -545,7 +573,7 @@ export class CoValueCore {
545
573
  this._decryptionCache[tx.encryptedChanges];
546
574
 
547
575
  if (!decrytedChanges) {
548
- decrytedChanges = decryptRawForTransaction(
576
+ const decryptedString = decryptRawForTransaction(
549
577
  tx.encryptedChanges,
550
578
  readKey,
551
579
  {
@@ -553,6 +581,8 @@ export class CoValueCore {
553
581
  tx: txID,
554
582
  }
555
583
  );
584
+ decrytedChanges =
585
+ decryptedString && parseJSON(decryptedString);
556
586
  this._decryptionCache[tx.encryptedChanges] =
557
587
  decrytedChanges;
558
588
  }
@@ -724,12 +754,18 @@ export class CoValueCore {
724
754
  }
725
755
 
726
756
  getTx(txID: TransactionID): Transaction | undefined {
727
- return this.sessions[txID.sessionID]?.transactions[txID.txIndex];
757
+ return this.sessionLogs.get(txID.sessionID)?.transactions[txID.txIndex];
728
758
  }
729
759
 
730
760
  newContentSince(
731
761
  knownState: CoValueKnownState | undefined
732
762
  ): NewContentMessage[] | undefined {
763
+ const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
764
+
765
+ if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
766
+ return this._cachedNewContentSinceEmpty;
767
+ }
768
+
733
769
  let currentPiece: NewContentMessage = {
734
770
  action: "content",
735
771
  id: this.id,
@@ -739,44 +775,55 @@ export class CoValueCore {
739
775
 
740
776
  const pieces = [currentPiece];
741
777
 
742
- const sentState: CoValueKnownState["sessions"] = {
743
- ...knownState?.sessions,
744
- };
778
+ const sentState: CoValueKnownState["sessions"] = {};
745
779
 
746
- let newTxsWereAdded = true;
747
780
  let pieceSize = 0;
748
- while (newTxsWereAdded) {
749
- newTxsWereAdded = false;
750
-
751
- for (const [sessionID, log] of Object.entries(this.sessions) as [
752
- SessionID,
753
- SessionLog
754
- ][]) {
755
- const nextKnownSignatureIdx = Object.keys(log.signatureAfter)
756
- .map(Number)
757
- .sort((a, b) => a - b)
758
- .find((idx) => idx >= (sentState[sessionID] ?? -1));
759
-
760
- const txsToAdd = log.transactions.slice(
761
- sentState[sessionID] ?? 0,
762
- nextKnownSignatureIdx === undefined
763
- ? undefined
764
- : nextKnownSignatureIdx + 1
781
+
782
+ let sessionsTodoAgain: Set<SessionID> | undefined | "first" = "first";
783
+
784
+ while (sessionsTodoAgain === "first" || (sessionsTodoAgain?.size || 0 > 0)) {
785
+ if (sessionsTodoAgain === "first") {
786
+ sessionsTodoAgain = undefined;
787
+ }
788
+ const sessionsTodo = sessionsTodoAgain ?? this.sessionLogs.keys();
789
+
790
+ for (const sessionIDKey of sessionsTodo) {
791
+ const sessionID = sessionIDKey as SessionID;
792
+ const log = this.sessionLogs.get(sessionID)!;
793
+ const knownStateForSessionID = knownState?.sessions[sessionID];
794
+ const sentStateForSessionID = sentState[sessionID];
795
+ const nextKnownSignatureIdx = getNextKnownSignatureIdx(
796
+ log,
797
+ knownStateForSessionID,
798
+ sentStateForSessionID
765
799
  );
766
800
 
767
- if (txsToAdd.length === 0) continue;
801
+ const firstNewTxIdx = sentStateForSessionID ?? knownStateForSessionID ?? 0;
802
+ const afterLastNewTxIdx = nextKnownSignatureIdx === undefined
803
+ ? log.transactions.length
804
+ : nextKnownSignatureIdx + 1;
768
805
 
769
- newTxsWereAdded = true;
806
+ const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
807
+
808
+ if (nNewTx === 0) {
809
+ sessionsTodoAgain?.delete(sessionID);
810
+ continue;
811
+ }
812
+
813
+ if (afterLastNewTxIdx < log.transactions.length) {
814
+ if (!sessionsTodoAgain) {
815
+ sessionsTodoAgain = new Set();
816
+ }
817
+ sessionsTodoAgain.add(sessionID);
818
+ }
770
819
 
771
820
  const oldPieceSize = pieceSize;
772
- pieceSize += txsToAdd.reduce(
773
- (sum, tx) =>
774
- sum +
775
- (tx.privacy === "private"
776
- ? tx.encryptedChanges.length
777
- : tx.changes.length),
778
- 0
779
- );
821
+ for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
822
+ const tx = log.transactions[txIdx]!;
823
+ pieceSize += (tx.privacy === "private"
824
+ ? tx.encryptedChanges.length
825
+ : tx.changes.length);
826
+ }
780
827
 
781
828
  if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
782
829
  currentPiece = {
@@ -792,21 +839,26 @@ export class CoValueCore {
792
839
  let sessionEntry = currentPiece.new[sessionID];
793
840
  if (!sessionEntry) {
794
841
  sessionEntry = {
795
- after: sentState[sessionID] ?? 0,
842
+ after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
796
843
  newTransactions: [],
797
844
  lastSignature: "WILL_BE_REPLACED" as Signature,
798
845
  };
799
846
  currentPiece.new[sessionID] = sessionEntry;
800
847
  }
801
848
 
802
- sessionEntry.newTransactions.push(...txsToAdd);
849
+ for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
850
+ const tx = log.transactions[txIdx]!;
851
+ sessionEntry.newTransactions.push(tx);
852
+ }
853
+
854
+
803
855
  sessionEntry.lastSignature =
804
856
  nextKnownSignatureIdx === undefined
805
857
  ? log.lastSignature!
806
858
  : log.signatureAfter[nextKnownSignatureIdx]!;
807
859
 
808
860
  sentState[sessionID] =
809
- (sentState[sessionID] || 0) + txsToAdd.length;
861
+ (sentStateForSessionID ?? knownStateForSessionID ?? 0) + nNewTx;
810
862
  }
811
863
  }
812
864
 
@@ -818,10 +870,25 @@ export class CoValueCore {
818
870
  return undefined;
819
871
  }
820
872
 
873
+ if (isKnownStateEmpty) {
874
+ this._cachedNewContentSinceEmpty = piecesWithContent;
875
+ }
876
+
821
877
  return piecesWithContent;
822
878
  }
823
879
 
824
880
  getDependedOnCoValues(): RawCoID[] {
881
+ if (this._cachedDependentOn) {
882
+ return this._cachedDependentOn;
883
+ } else {
884
+ const dependentOn = this.getDependedOnCoValuesUncached();
885
+ this._cachedDependentOn = dependentOn;
886
+ return dependentOn;
887
+ }
888
+ }
889
+
890
+ /** @internal */
891
+ getDependedOnCoValuesUncached(): RawCoID[] {
825
892
  return this.header.ruleset.type === "group"
826
893
  ? expectGroup(this.getCurrentContent())
827
894
  .keys()
@@ -830,7 +897,7 @@ export class CoValueCore {
830
897
  ? [
831
898
  this.header.ruleset.group,
832
899
  ...new Set(
833
- Object.keys(this._sessions)
900
+ [...this.sessionLogs.keys()]
834
901
  .map((sessionID) =>
835
902
  accountOrAgentIDfromSessionID(
836
903
  sessionID as SessionID
@@ -845,3 +912,14 @@ export class CoValueCore {
845
912
  : [];
846
913
  }
847
914
  }
915
+
916
+ function getNextKnownSignatureIdx(
917
+ log: SessionLog,
918
+ knownStateForSessionID?: number,
919
+ sentStateForSessionID?: number,
920
+ ) {
921
+ return Object.keys(log.signatureAfter)
922
+ .map(Number)
923
+ .sort((a, b) => a - b)
924
+ .find((idx) => idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1));
925
+ }
@@ -5,7 +5,6 @@ import { CoValueCore } from "../coValueCore.js";
5
5
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
6
6
  import { AgentID, SessionID, TransactionID } from "../ids.js";
7
7
  import { AccountID } from "./account.js";
8
- import { parseJSON } from "../jsonStringify.js";
9
8
  import { Group } from "./group.js";
10
9
 
11
10
  type OpID = TransactionID & { changeIdx: number };
@@ -76,6 +75,13 @@ export class CoListView<
76
75
  /** @category 6. Meta */
77
76
  readonly _item!: Item;
78
77
 
78
+ /** @internal */
79
+ _cachedEntries?: {
80
+ value: Item;
81
+ madeAt: number;
82
+ opID: OpID;
83
+ }[];
84
+
79
85
  /** @internal */
80
86
  constructor(core: CoValueCore) {
81
87
  this.id = core.id as CoID<this>;
@@ -95,9 +101,7 @@ export class CoListView<
95
101
  changes,
96
102
  madeAt,
97
103
  } of this.core.getValidSortedTransactions()) {
98
- for (const [changeIdx, changeUntyped] of parseJSON(
99
- changes
100
- ).entries()) {
104
+ for (const [changeIdx, changeUntyped] of changes.entries()) {
101
105
  const change = changeUntyped as ListOpPayload<Item>;
102
106
 
103
107
  if (change.op === "pre" || change.op === "app") {
@@ -129,10 +133,11 @@ export class CoListView<
129
133
  change.before.txIndex
130
134
  ]?.[change.before.changeIdx];
131
135
  if (!beforeEntry) {
132
- throw new Error(
133
- "Not yet implemented: insertion before missing op " +
134
- change.before
135
- );
136
+ // console.error(
137
+ // "Insertion before missing op " +
138
+ // change.before
139
+ // );
140
+ continue;
136
141
  }
137
142
  beforeEntry.predecessors.splice(0, 0, {
138
143
  ...txID,
@@ -151,10 +156,10 @@ export class CoListView<
151
156
  change.after.txIndex
152
157
  ]?.[change.after.changeIdx];
153
158
  if (!afterEntry) {
154
- throw new Error(
155
- "Not yet implemented: insertion after missing op " +
156
- change.after
157
- );
159
+ // console.error(
160
+ // "Insertion after missing op " + change.after
161
+ // );
162
+ continue;
158
163
  }
159
164
  afterEntry.successors.push({
160
165
  ...txID,
@@ -244,6 +249,20 @@ export class CoListView<
244
249
  value: Item;
245
250
  madeAt: number;
246
251
  opID: OpID;
252
+ }[] {
253
+ if (this._cachedEntries) {
254
+ return this._cachedEntries;
255
+ }
256
+ const arr = this.entriesUncached();
257
+ this._cachedEntries = arr;
258
+ return arr;
259
+ }
260
+
261
+ /** @internal */
262
+ entriesUncached(): {
263
+ value: Item;
264
+ madeAt: number;
265
+ opID: OpID;
247
266
  }[] {
248
267
  const arr: {
249
268
  value: Item;
@@ -545,6 +564,7 @@ export class MutableCoList<
545
564
  this.beforeEnd = listAfter.beforeEnd;
546
565
  this.insertions = listAfter.insertions;
547
566
  this.deletionsByInsertion = listAfter.deletionsByInsertion;
567
+ this._cachedEntries = undefined;
548
568
  }
549
569
 
550
570
  /** Prepends `item` before the item currently at index `before`.
@@ -570,6 +590,7 @@ export class MutableCoList<
570
590
  this.beforeEnd = listAfter.beforeEnd;
571
591
  this.insertions = listAfter.insertions;
572
592
  this.deletionsByInsertion = listAfter.deletionsByInsertion;
593
+ this._cachedEntries = undefined;
573
594
  }
574
595
 
575
596
  /** Deletes the item at index `at` from the list.
@@ -590,5 +611,6 @@ export class MutableCoList<
590
611
  this.beforeEnd = listAfter.beforeEnd;
591
612
  this.insertions = listAfter.insertions;
592
613
  this.deletionsByInsertion = listAfter.deletionsByInsertion;
614
+ this._cachedEntries = undefined;
593
615
  }
594
616
  }
@@ -5,7 +5,6 @@ import { isCoValue } from "../typeUtils/isCoValue.js";
5
5
  import { CoValueCore } from "../coValueCore.js";
6
6
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
7
7
  import { AccountID } from "./account.js";
8
- import { parseJSON } from "../jsonStringify.js";
9
8
  import type { Group } from "./group.js";
10
9
 
11
10
  type MapOp<K extends string, V extends JsonValue | undefined> = {
@@ -60,9 +59,7 @@ export class CoMapView<
60
59
  for (const { txID, changes, madeAt } of core.getValidSortedTransactions(
61
60
  options
62
61
  )) {
63
- for (const [changeIdx, changeUntyped] of parseJSON(
64
- changes
65
- ).entries()) {
62
+ for (const [changeIdx, changeUntyped] of changes.entries()) {
66
63
  const change = changeUntyped as MapOpPayload<
67
64
  keyof Shape & string,
68
65
  Shape[keyof Shape & string]
@@ -123,7 +120,7 @@ export class CoMapView<
123
120
  * Get all keys currently in the map.
124
121
  *
125
122
  * @category 1. Reading */
126
- keys<K extends (keyof Shape & string) = (keyof Shape & string)>(): K[] {
123
+ keys<K extends keyof Shape & string = keyof Shape & string>(): K[] {
127
124
  const keys = Object.keys(this.ops) as K[];
128
125
 
129
126
  if (this.atTimeFilter) {