@evolu/common 6.0.1-preview.19 → 6.0.1-preview.20

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 (140) hide show
  1. package/dist/src/Assert.d.ts.map +1 -1
  2. package/dist/src/Assert.js +1 -1
  3. package/dist/src/Buffer.d.ts +1 -1
  4. package/dist/src/Buffer.d.ts.map +1 -1
  5. package/dist/src/Buffer.js +1 -1
  6. package/dist/src/CallbackRegistry.d.ts +53 -0
  7. package/dist/src/CallbackRegistry.d.ts.map +1 -0
  8. package/dist/src/CallbackRegistry.js +25 -0
  9. package/dist/src/Console.d.ts +31 -6
  10. package/dist/src/Console.d.ts.map +1 -1
  11. package/dist/src/Console.js +72 -9
  12. package/dist/src/Crypto.d.ts +48 -37
  13. package/dist/src/Crypto.d.ts.map +1 -1
  14. package/dist/src/Crypto.js +27 -50
  15. package/dist/src/Evolu/Db.d.ts +138 -66
  16. package/dist/src/Evolu/Db.d.ts.map +1 -1
  17. package/dist/src/Evolu/Db.js +248 -645
  18. package/dist/src/Evolu/Diff.d.ts +3 -3
  19. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  20. package/dist/src/Evolu/Diff.js +7 -5
  21. package/dist/src/Evolu/Evolu.d.ts +79 -116
  22. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  23. package/dist/src/Evolu/Evolu.js +275 -132
  24. package/dist/src/Evolu/Internal.d.ts +0 -2
  25. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  26. package/dist/src/Evolu/Internal.js +0 -2
  27. package/dist/src/Evolu/LocalAuth.d.ts +144 -0
  28. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  29. package/dist/src/Evolu/LocalAuth.js +171 -0
  30. package/dist/src/Evolu/Owner.d.ts +129 -83
  31. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  32. package/dist/src/Evolu/Owner.js +80 -89
  33. package/dist/src/Evolu/Platform.d.ts +9 -7
  34. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  35. package/dist/src/Evolu/Protocol.d.ts +114 -191
  36. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  37. package/dist/src/Evolu/Protocol.js +409 -416
  38. package/dist/src/Evolu/Public.d.ts +6 -8
  39. package/dist/src/Evolu/Public.d.ts.map +1 -1
  40. package/dist/src/Evolu/Public.js +2 -3
  41. package/dist/src/Evolu/PublicKysely.js +3 -3
  42. package/dist/src/Evolu/Relay.d.ts +1 -2
  43. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  44. package/dist/src/Evolu/Relay.js +11 -9
  45. package/dist/src/Evolu/Schema.d.ts +88 -27
  46. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  47. package/dist/src/Evolu/Schema.js +141 -24
  48. package/dist/src/Evolu/Storage.d.ts +158 -14
  49. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  50. package/dist/src/Evolu/Storage.js +32 -32
  51. package/dist/src/Evolu/Sync.d.ts +77 -13
  52. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  53. package/dist/src/Evolu/Sync.js +453 -20
  54. package/dist/src/Evolu/Timestamp.d.ts +29 -27
  55. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  56. package/dist/src/Evolu/Timestamp.js +20 -18
  57. package/dist/src/ManyToManyMap.d.ts +74 -10
  58. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  59. package/dist/src/ManyToManyMap.js +41 -6
  60. package/dist/src/Random.d.ts +3 -2
  61. package/dist/src/Random.d.ts.map +1 -1
  62. package/dist/src/RefCountedResourceManager.d.ts +119 -0
  63. package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
  64. package/dist/src/RefCountedResourceManager.js +197 -0
  65. package/dist/src/Result.d.ts +144 -22
  66. package/dist/src/Result.d.ts.map +1 -1
  67. package/dist/src/Result.js +5 -2
  68. package/dist/src/Sqlite.d.ts +20 -4
  69. package/dist/src/Sqlite.d.ts.map +1 -1
  70. package/dist/src/Sqlite.js +50 -8
  71. package/dist/src/Task.d.ts +511 -0
  72. package/dist/src/Task.d.ts.map +1 -0
  73. package/dist/src/Task.js +410 -0
  74. package/dist/src/Time.d.ts +59 -0
  75. package/dist/src/Time.d.ts.map +1 -1
  76. package/dist/src/Time.js +87 -4
  77. package/dist/src/Type.d.ts +431 -341
  78. package/dist/src/Type.d.ts.map +1 -1
  79. package/dist/src/Type.js +458 -466
  80. package/dist/src/WebSocket.d.ts +5 -2
  81. package/dist/src/WebSocket.d.ts.map +1 -1
  82. package/dist/src/WebSocket.js +12 -13
  83. package/dist/src/Worker.d.ts +39 -11
  84. package/dist/src/Worker.d.ts.map +1 -1
  85. package/dist/src/Worker.js +22 -4
  86. package/dist/src/index.d.ts +2 -3
  87. package/dist/src/index.d.ts.map +1 -1
  88. package/dist/src/index.js +2 -3
  89. package/package.json +7 -7
  90. package/src/Assert.ts +2 -4
  91. package/src/Buffer.ts +1 -1
  92. package/src/CallbackRegistry.ts +84 -0
  93. package/src/Console.ts +91 -11
  94. package/src/Crypto.ts +78 -91
  95. package/src/Evolu/Db.ts +455 -947
  96. package/src/Evolu/Diff.ts +7 -5
  97. package/src/Evolu/Evolu.ts +545 -307
  98. package/src/Evolu/Internal.ts +0 -2
  99. package/src/Evolu/LocalAuth.ts +422 -0
  100. package/src/Evolu/Owner.ts +191 -131
  101. package/src/Evolu/Platform.ts +9 -9
  102. package/src/Evolu/Protocol.ts +536 -653
  103. package/src/Evolu/Public.ts +7 -9
  104. package/src/Evolu/PublicKysely.ts +3 -3
  105. package/src/Evolu/Relay.ts +17 -12
  106. package/src/Evolu/Schema.ts +271 -66
  107. package/src/Evolu/Storage.ts +263 -55
  108. package/src/Evolu/Sync.ts +758 -37
  109. package/src/Evolu/Timestamp.ts +30 -35
  110. package/src/ManyToManyMap.ts +127 -24
  111. package/src/Random.ts +3 -2
  112. package/src/RefCountedResourceManager.ts +368 -0
  113. package/src/Result.ts +149 -23
  114. package/src/Sqlite.ts +59 -24
  115. package/src/Task.ts +779 -0
  116. package/src/Time.ts +168 -4
  117. package/src/Type.ts +657 -695
  118. package/src/WebSocket.ts +23 -17
  119. package/src/Worker.ts +72 -23
  120. package/src/index.ts +2 -3
  121. package/dist/src/Callbacks.d.ts +0 -20
  122. package/dist/src/Callbacks.d.ts.map +0 -1
  123. package/dist/src/Callbacks.js +0 -18
  124. package/dist/src/Evolu/Config.d.ts +0 -82
  125. package/dist/src/Evolu/Config.d.ts.map +0 -1
  126. package/dist/src/Evolu/Config.js +0 -9
  127. package/dist/src/Evolu/Kysely.d.ts +0 -6
  128. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  129. package/dist/src/Evolu/Kysely.js +0 -21
  130. package/dist/src/NanoId.d.ts +0 -27
  131. package/dist/src/NanoId.d.ts.map +0 -1
  132. package/dist/src/NanoId.js +0 -6
  133. package/dist/src/Promise.d.ts +0 -180
  134. package/dist/src/Promise.d.ts.map +0 -1
  135. package/dist/src/Promise.js +0 -176
  136. package/src/Callbacks.ts +0 -43
  137. package/src/Evolu/Config.ts +0 -97
  138. package/src/Evolu/Kysely.ts +0 -38
  139. package/src/NanoId.ts +0 -39
  140. package/src/Promise.ts +0 -295
@@ -1,19 +1,22 @@
1
1
  import { assert } from "../Assert.js";
2
+ import { Brand } from "../Brand.js";
3
+ import { bytesToHex } from "../Buffer.js";
4
+ import { RandomBytesDep } from "../Crypto.js";
2
5
  import { createEqObject, eqNumber, eqString } from "../Eq.js";
3
- import { NanoIdLibDep } from "../NanoId.js";
4
6
  import { increment } from "../Number.js";
5
7
  import { Order, orderUint8Array } from "../Order.js";
6
- import { err, getOrThrow, ok, Result } from "../Result.js";
8
+ import { err, ok, Result } from "../Result.js";
7
9
  import { TimeDep } from "../Time.js";
8
10
  import {
9
11
  brand,
12
+ InferType,
10
13
  lessThanOrEqualTo,
11
14
  NonNegativeInt,
12
15
  object,
13
16
  regex,
14
17
  String,
18
+ Uint8Array,
15
19
  } from "../Type.js";
16
- import { Brand } from "../Brand.js";
17
20
 
18
21
  export interface TimestampConfig {
19
22
  /**
@@ -31,7 +34,6 @@ export interface TimestampConfigDep {
31
34
  export type TimestampError =
32
35
  | TimestampDriftError
33
36
  | TimestampCounterOverflowError
34
- | TimestampDuplicateNodeError
35
37
  | TimestampTimeOutOfRangeError;
36
38
 
37
39
  export interface TimestampDriftError {
@@ -44,11 +46,6 @@ export interface TimestampCounterOverflowError {
44
46
  readonly type: "TimestampCounterOverflowError";
45
47
  }
46
48
 
47
- export interface TimestampDuplicateNodeError {
48
- readonly type: "TimestampDuplicateNodeError";
49
- readonly nodeId: NodeId;
50
- }
51
-
52
49
  export interface TimestampTimeOutOfRangeError {
53
50
  readonly type: "TimestampTimeOutOfRangeError";
54
51
  }
@@ -96,14 +93,17 @@ export const maxCounter = 65535 as Counter;
96
93
  *
97
94
  * https://lemire.me/blog/2019/12/12/are-64-bit-random-identifiers-free-from-collision
98
95
  *
99
- * What will happen if a different device generates the same NodeId?
96
+ * What happens if different devices generate the same NodeId?
100
97
  *
101
- * If the device belongs to a different owner, nothing will happen because
102
- * different owner have different owner IDs. Timestamps are partitioned by
103
- * OwnerId.
98
+ * If devices with the same NodeId use different owners, no issues occur.
104
99
  *
105
- * If the device belongs to the same owner, the other device will return
106
- * {@link TimestampDuplicateNodeError}.
100
+ * If devices with the same NodeId use the same owner, problems only arise when
101
+ * they generate CRDT messages with identical timestamps (same millis, counter,
102
+ * and NodeId). In this case, the protocol sync algorithm treats them as the
103
+ * same message: the first will be synced with the relay, while the affected
104
+ * message will not be delivered. The affected devices will see different data
105
+ * yet they will think they are synced. This is extremely rare and can be
106
+ * resolved by resetting one device to generate a new NodeId.
107
107
  */
108
108
  export const NodeId = regex("NodeId", /^[a-f0-9]{16}$/)(String);
109
109
  export type NodeId = typeof NodeId.Type;
@@ -123,7 +123,7 @@ export const Timestamp = object({
123
123
  counter: Counter,
124
124
  nodeId: NodeId,
125
125
  });
126
- export type Timestamp = typeof Timestamp.Type;
126
+ export interface Timestamp extends InferType<typeof Timestamp> {}
127
127
 
128
128
  /** Equality function for comparing {@link Timestamp}. */
129
129
  export const eqTimestamp = createEqObject<Timestamp>({
@@ -138,14 +138,12 @@ export const createTimestamp = ({
138
138
  nodeId = minNodeId,
139
139
  }: Partial<Timestamp> = {}): Timestamp => ({ millis, counter, nodeId });
140
140
 
141
- const hexAlphabet = "0123456789abcdef";
142
-
143
- export const createInitialTimestamp = (deps: NanoIdLibDep): Timestamp => {
144
- const nodeId = deps.nanoIdLib.customAlphabet(hexAlphabet, 16)() as NodeId;
141
+ export const createInitialTimestamp = (deps: RandomBytesDep): Timestamp => {
142
+ const nodeId = bytesToHex(deps.randomBytes.create(8)) as NodeId;
145
143
  return createTimestamp({ nodeId });
146
144
  };
147
145
 
148
- /** TimestampString is a sortable string version of {@link Timestamp}. */
146
+ /** Sortable string representation of {@link Timestamp}. */
149
147
  export type TimestampString = string & Brand<"TimestampString">;
150
148
 
151
149
  export const timestampToTimestampString = (t: Timestamp): TimestampString =>
@@ -230,12 +228,8 @@ export const receiveTimestamp =
230
228
  Timestamp,
231
229
  | TimestampDriftError
232
230
  | TimestampCounterOverflowError
233
- | TimestampDuplicateNodeError
234
231
  | TimestampTimeOutOfRangeError
235
232
  > => {
236
- if (local.nodeId === remote.nodeId) {
237
- return err({ type: "TimestampDuplicateNodeError", nodeId: local.nodeId });
238
- }
239
233
  const millis = getNextMillis(deps)([local.millis, remote.millis]);
240
234
  if (!millis.ok) return millis;
241
235
 
@@ -257,18 +251,19 @@ export const receiveTimestamp =
257
251
  });
258
252
  };
259
253
 
260
- /** BinaryTimestamp is a binary and sortable version of {@link Timestamp} for DB. */
261
- export type BinaryTimestamp = Uint8Array & Brand<"BinaryTimestamp">;
254
+ /** Sortable bytes representation of {@link Timestamp}. */
255
+ export const TimestampBytes = brand("TimestampBytes", Uint8Array);
256
+ export type TimestampBytes = typeof TimestampBytes.Type;
262
257
 
263
- export const binaryTimestampLength = getOrThrow(NonNegativeInt.from(16));
258
+ export const timestampBytesLength = NonNegativeInt.orThrow(16);
264
259
 
265
- export const timestampToBinaryTimestamp = (
260
+ export const timestampToTimestampBytes = (
266
261
  timestamp: Timestamp,
267
- ): BinaryTimestamp => {
262
+ ): TimestampBytes => {
268
263
  const { millis, counter, nodeId } = timestamp;
269
264
 
270
265
  // 6 bytes for millis, 2 bytes for counter, 8 bytes for nodeId.
271
- const value = new Uint8Array(16);
266
+ const value = new globalThis.Uint8Array(16);
272
267
 
273
268
  // Encode `millis` into the first 6 bytes.
274
269
  const millisBigInt = BigInt(millis);
@@ -289,11 +284,11 @@ export const timestampToBinaryTimestamp = (
289
284
  value[8 + i] = byte;
290
285
  }
291
286
 
292
- return value as BinaryTimestamp;
287
+ return value as TimestampBytes;
293
288
  };
294
289
 
295
- export const binaryTimestampToTimestamp = (
296
- timestamp: BinaryTimestamp,
290
+ export const timestampBytesToTimestamp = (
291
+ timestamp: TimestampBytes,
297
292
  ): Timestamp => {
298
293
  // Decode `millis` from the first 6 bytes.
299
294
  const millis =
@@ -316,4 +311,4 @@ export const binaryTimestampToTimestamp = (
316
311
  return { millis: Number(millis), counter, nodeId } as Timestamp;
317
312
  };
318
313
 
319
- export const orderBinaryTimestamp: Order<BinaryTimestamp> = orderUint8Array;
314
+ export const orderTimestampBytes: Order<TimestampBytes> = orderUint8Array;
@@ -1,22 +1,84 @@
1
- /** Represents a many-to-many mapping between keys and values. */
1
+ import { assert } from "./Assert.js";
2
+
3
+ /**
4
+ * Bidirectional many-to-many map between keys and values.
5
+ *
6
+ * Why useful:
7
+ *
8
+ * - Provides O(1) (amortized) forward lookup (key -> values) and reverse lookup
9
+ * (value -> keys) without maintaining two maps manually and risking them
10
+ * diverging.
11
+ * - Natural fit for pub/sub style relations such as ownerId ↔ WebSocket, tag ↔
12
+ * item, user ↔ role, entity ↔ subscription where both directions are
13
+ * frequently queried.
14
+ * - Supports fast membership tests via `hasPair`, `hasKey`, and `hasValue`.
15
+ * - Iteration helpers (`forEach`, iterator) allow treating the structure as an
16
+ * edge list when needed.
17
+ *
18
+ * Complexity:
19
+ *
20
+ * - `add` / `remove` / `has*` / `get*` each perform a constant number of Map/Set
21
+ * operations (O(1) expected).
22
+ * - `deleteKey` and `deleteValue` are O(d) where d is the number of associated
23
+ * values / keys (the degree). This is optimal because every associated pair
24
+ * must be touched once.
25
+ * - In the Relay use case a socket (value) typically has only dozens of owners
26
+ * (degree small), and connection closes (triggering deleteValue) are
27
+ * relatively infrequent, so O(d) is acceptable.
28
+ *
29
+ * Object identity:
30
+ *
31
+ * - Keys and values are compared by reference (standard Map / Set semantics).
32
+ * Structural hashing of objects in JavaScript is non-trivial, can be
33
+ * expensive, and collision-prone if done naively. Prefer using stable
34
+ * primitive identifiers (ids, strings) instead of attempting to hash full
35
+ * object structures.
36
+ * - If structural equivalence is truly required, wrap objects in an adapter that
37
+ * supplies a canonical hash/id and stores/retrieves the original objects
38
+ * separately. This is a rare need; avoid unless you have clear requirements.
39
+ */
2
40
  export interface ManyToManyMap<K, V> {
3
- /** Adds a key-value association to the map. */
4
- add: (key: K, value: V) => ManyToManyMap<K, V>;
5
-
6
- /** Removes a specific key-value association. */
41
+ /**
42
+ * Adds a key-value association to the map. Returns true if the pair was newly
43
+ * added, false if it already existed.
44
+ */
45
+ add: (key: K, value: V) => boolean;
46
+
47
+ /**
48
+ * Removes a specific key-value association. Returns true if the pair existed
49
+ * and was removed, false if it was not present.
50
+ */
7
51
  remove: (key: K, value: V) => boolean;
8
52
 
9
- /** Gets all values associated with a key. */
10
- getValues: (key: K) => Set<V> | undefined;
11
-
12
- /** Gets all keys associated with a value. */
13
- getKeys: (value: V) => Set<K> | undefined;
53
+ /**
54
+ * Gets all values associated with a key. Returned set is the internal Set
55
+ * instance typed as ReadonlySet. Do not mutate.
56
+ */
57
+ getValues: (key: K) => ReadonlySet<V> | undefined;
58
+
59
+ /**
60
+ * Gets all keys associated with a value. Returned set is the internal Set
61
+ * instance typed as ReadonlySet. Do not mutate.
62
+ */
63
+ getKeys: (value: V) => ReadonlySet<K> | undefined;
64
+
65
+ /**
66
+ * Iterates over each key-value pair (in insertion order of keys, then values
67
+ * per key).
68
+ */
69
+ forEach: (callback: (key: K, value: V) => void) => void;
70
+
71
+ /**
72
+ * Iterator over all key-value pairs enabling for..of and spread. Yields
73
+ * readonly [key, value] tuples.
74
+ */
75
+ readonly [Symbol.iterator]: () => IterableIterator<readonly [K, V]>;
14
76
 
15
77
  /** Checks if a specific key-value pair exists. */
16
78
  hasPair: (key: K, value: V) => boolean;
17
79
 
18
80
  /** Checks if a key exists in the map. */
19
- hasKey(key: K): boolean;
81
+ hasKey: (key: K) => boolean;
20
82
 
21
83
  /** Checks if a value exists in the map. */
22
84
  hasValue: (value: V) => boolean;
@@ -28,17 +90,26 @@ export interface ManyToManyMap<K, V> {
28
90
  deleteValue: (value: V) => boolean;
29
91
 
30
92
  /** Clears all associations in the map. */
31
- clear(): void;
93
+ clear: () => void;
94
+
95
+ /** Number of distinct keys currently present. */
96
+ keyCount: () => number;
97
+ /** Number of distinct values currently present. */
98
+ valueCount: () => number;
99
+ /** Number of key-value pairs (associations) currently stored. */
100
+ pairCount: () => number;
32
101
  }
33
102
 
34
103
  /** Creates a {@link ManyToManyMap}. */
35
104
  export const createManyToManyMap = <K, V>(): ManyToManyMap<K, V> => {
36
105
  const forwardMap = new Map<K, Set<V>>();
37
106
  const reverseMap = new Map<V, Set<K>>();
107
+ let pairCountInternal = 0;
38
108
 
39
109
  const map: ManyToManyMap<K, V> = {
40
110
  add(key: K, value: V) {
41
111
  let values = forwardMap.get(key);
112
+ if (values?.has(value)) return false;
42
113
  if (!values) {
43
114
  values = new Set<V>();
44
115
  forwardMap.set(key, values);
@@ -51,8 +122,8 @@ export const createManyToManyMap = <K, V>(): ManyToManyMap<K, V> => {
51
122
  reverseMap.set(value, keys);
52
123
  }
53
124
  keys.add(key);
54
-
55
- return map;
125
+ pairCountInternal++;
126
+ return true;
56
127
  },
57
128
 
58
129
  remove(key: K, value: V) {
@@ -65,24 +136,41 @@ export const createManyToManyMap = <K, V>(): ManyToManyMap<K, V> => {
65
136
  }
66
137
 
67
138
  const keys = reverseMap.get(value);
68
- if (keys?.size) {
69
- keys.delete(key);
70
- if (keys.size === 0) {
71
- reverseMap.delete(value);
72
- }
73
- }
139
+ assert(keys, "Key-value mapping inconsistency");
74
140
 
141
+ keys.delete(key);
142
+ if (keys.size === 0) {
143
+ reverseMap.delete(value);
144
+ }
145
+ pairCountInternal--;
75
146
  return true;
76
147
  },
77
148
 
78
- getValues(key: K) {
149
+ getValues(key: K): ReadonlySet<V> | undefined {
79
150
  return forwardMap.get(key);
80
151
  },
81
152
 
82
- getKeys(value: V) {
153
+ getKeys(value: V): ReadonlySet<K> | undefined {
83
154
  return reverseMap.get(value);
84
155
  },
85
156
 
157
+ forEach(callback: (key: K, value: V) => void) {
158
+ for (const [key, values] of forwardMap) {
159
+ for (const value of values) callback(key, value);
160
+ }
161
+ },
162
+
163
+ [Symbol.iterator](): IterableIterator<readonly [K, V]> {
164
+ const iterator = function* () {
165
+ for (const [key, values] of forwardMap) {
166
+ for (const value of values) {
167
+ yield [key, value] as const;
168
+ }
169
+ }
170
+ };
171
+ return iterator();
172
+ },
173
+
86
174
  hasPair(key: K, value: V) {
87
175
  const values = forwardMap.get(key);
88
176
  return values?.has(value) ?? false;
@@ -99,7 +187,7 @@ export const createManyToManyMap = <K, V>(): ManyToManyMap<K, V> => {
99
187
  deleteKey(key: K) {
100
188
  const values = forwardMap.get(key);
101
189
  if (!values) return false;
102
-
190
+ const removed = values.size;
103
191
  for (const value of values) {
104
192
  const keys = reverseMap.get(value);
105
193
  if (keys) {
@@ -110,13 +198,14 @@ export const createManyToManyMap = <K, V>(): ManyToManyMap<K, V> => {
110
198
  }
111
199
  }
112
200
  forwardMap.delete(key);
201
+ pairCountInternal -= removed;
113
202
  return true;
114
203
  },
115
204
 
116
205
  deleteValue(value: V) {
117
206
  const keys = reverseMap.get(value);
118
207
  if (!keys) return false;
119
-
208
+ const removed = keys.size;
120
209
  for (const key of keys) {
121
210
  const values = forwardMap.get(key);
122
211
  if (values) {
@@ -127,12 +216,26 @@ export const createManyToManyMap = <K, V>(): ManyToManyMap<K, V> => {
127
216
  }
128
217
  }
129
218
  reverseMap.delete(value);
219
+ pairCountInternal -= removed;
130
220
  return true;
131
221
  },
132
222
 
133
223
  clear() {
134
224
  forwardMap.clear();
135
225
  reverseMap.clear();
226
+ pairCountInternal = 0;
227
+ },
228
+
229
+ keyCount() {
230
+ return forwardMap.size;
231
+ },
232
+
233
+ valueCount() {
234
+ return reverseMap.size;
235
+ },
236
+
237
+ pairCount() {
238
+ return pairCountInternal;
136
239
  },
137
240
  };
138
241
 
package/src/Random.ts CHANGED
@@ -7,8 +7,9 @@
7
7
  import { Random as RandomLib } from "random";
8
8
 
9
9
  /**
10
- * A simple wrapper around Math.random(). Most apps need only this. For more
11
- * complex needs check {@link RandomLibDep}.
10
+ * A simple wrapper around Math.random().
11
+ *
12
+ * For more complex needs check {@link RandomLibDep}.
12
13
  *
13
14
  * ### Example
14
15
  *