@evolu/common 6.0.1-preview.2 → 6.0.1-preview.21

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 (154) hide show
  1. package/dist/src/Assert.d.ts +6 -3
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +7 -4
  4. package/dist/src/Brand.d.ts +75 -0
  5. package/dist/src/Brand.d.ts.map +1 -0
  6. package/dist/src/Brand.js +1 -0
  7. package/dist/src/Buffer.d.ts +1 -1
  8. package/dist/src/Buffer.d.ts.map +1 -1
  9. package/dist/src/Buffer.js +1 -1
  10. package/dist/src/CallbackRegistry.d.ts +53 -0
  11. package/dist/src/CallbackRegistry.d.ts.map +1 -0
  12. package/dist/src/CallbackRegistry.js +25 -0
  13. package/dist/src/Console.d.ts +31 -6
  14. package/dist/src/Console.d.ts.map +1 -1
  15. package/dist/src/Console.js +72 -9
  16. package/dist/src/Crypto.d.ts +61 -34
  17. package/dist/src/Crypto.d.ts.map +1 -1
  18. package/dist/src/Crypto.js +29 -42
  19. package/dist/src/Evolu/Db.d.ts +159 -66
  20. package/dist/src/Evolu/Db.d.ts.map +1 -1
  21. package/dist/src/Evolu/Db.js +300 -705
  22. package/dist/src/Evolu/Diff.d.ts +3 -3
  23. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  24. package/dist/src/Evolu/Diff.js +7 -5
  25. package/dist/src/Evolu/Evolu.d.ts +200 -115
  26. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  27. package/dist/src/Evolu/Evolu.js +287 -157
  28. package/dist/src/Evolu/Internal.d.ts +0 -2
  29. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  30. package/dist/src/Evolu/Internal.js +0 -2
  31. package/dist/src/Evolu/LocalAuth.d.ts +144 -0
  32. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  33. package/dist/src/Evolu/LocalAuth.js +171 -0
  34. package/dist/src/Evolu/Owner.d.ts +209 -124
  35. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  36. package/dist/src/Evolu/Owner.js +118 -104
  37. package/dist/src/Evolu/Platform.d.ts +9 -7
  38. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  39. package/dist/src/Evolu/Protocol.d.ts +256 -211
  40. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  41. package/dist/src/Evolu/Protocol.js +558 -359
  42. package/dist/src/Evolu/Public.d.ts +6 -8
  43. package/dist/src/Evolu/Public.d.ts.map +1 -1
  44. package/dist/src/Evolu/Public.js +2 -3
  45. package/dist/src/Evolu/PublicKysely.js +3 -3
  46. package/dist/src/Evolu/Query.d.ts +2 -1
  47. package/dist/src/Evolu/Query.d.ts.map +1 -1
  48. package/dist/src/Evolu/Relay.d.ts +72 -4
  49. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  50. package/dist/src/Evolu/Relay.js +124 -13
  51. package/dist/src/Evolu/Schema.d.ts +129 -73
  52. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  53. package/dist/src/Evolu/Schema.js +169 -89
  54. package/dist/src/Evolu/Storage.d.ts +159 -14
  55. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  56. package/dist/src/Evolu/Storage.js +42 -32
  57. package/dist/src/Evolu/Sync.d.ts +68 -13
  58. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  59. package/dist/src/Evolu/Sync.js +414 -20
  60. package/dist/src/Evolu/Timestamp.d.ts +35 -27
  61. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  62. package/dist/src/Evolu/Timestamp.js +27 -18
  63. package/dist/src/ManyToManyMap.d.ts +74 -10
  64. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  65. package/dist/src/ManyToManyMap.js +41 -6
  66. package/dist/src/Number.d.ts +2 -1
  67. package/dist/src/Number.d.ts.map +1 -1
  68. package/dist/src/Random.d.ts +3 -2
  69. package/dist/src/Random.d.ts.map +1 -1
  70. package/dist/src/RefCountedResourceManager.d.ts +119 -0
  71. package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
  72. package/dist/src/RefCountedResourceManager.js +197 -0
  73. package/dist/src/Result.d.ts +176 -51
  74. package/dist/src/Result.d.ts.map +1 -1
  75. package/dist/src/Result.js +30 -241
  76. package/dist/src/Sqlite.d.ts +21 -4
  77. package/dist/src/Sqlite.d.ts.map +1 -1
  78. package/dist/src/Sqlite.js +50 -8
  79. package/dist/src/Task.d.ts +511 -0
  80. package/dist/src/Task.d.ts.map +1 -0
  81. package/dist/src/Task.js +410 -0
  82. package/dist/src/Time.d.ts +59 -0
  83. package/dist/src/Time.d.ts.map +1 -1
  84. package/dist/src/Time.js +87 -4
  85. package/dist/src/Type.d.ts +474 -327
  86. package/dist/src/Type.d.ts.map +1 -1
  87. package/dist/src/Type.js +502 -436
  88. package/dist/src/Types.d.ts +0 -74
  89. package/dist/src/Types.d.ts.map +1 -1
  90. package/dist/src/WebSocket.d.ts +5 -2
  91. package/dist/src/WebSocket.d.ts.map +1 -1
  92. package/dist/src/WebSocket.js +12 -13
  93. package/dist/src/Worker.d.ts +39 -11
  94. package/dist/src/Worker.d.ts.map +1 -1
  95. package/dist/src/Worker.js +22 -4
  96. package/dist/src/index.d.ts +3 -3
  97. package/dist/src/index.d.ts.map +1 -1
  98. package/dist/src/index.js +3 -3
  99. package/package.json +14 -13
  100. package/src/Assert.ts +8 -7
  101. package/src/Brand.ts +75 -0
  102. package/src/Buffer.ts +1 -1
  103. package/src/CallbackRegistry.ts +84 -0
  104. package/src/Console.ts +91 -11
  105. package/src/Crypto.ts +94 -79
  106. package/src/Evolu/Db.ts +528 -1027
  107. package/src/Evolu/Diff.ts +7 -5
  108. package/src/Evolu/Evolu.ts +682 -329
  109. package/src/Evolu/Internal.ts +0 -2
  110. package/src/Evolu/LocalAuth.ts +422 -0
  111. package/src/Evolu/Owner.ts +282 -227
  112. package/src/Evolu/Platform.ts +9 -9
  113. package/src/Evolu/Protocol.ts +788 -640
  114. package/src/Evolu/Public.ts +7 -14
  115. package/src/Evolu/PublicKysely.ts +3 -3
  116. package/src/Evolu/Query.ts +2 -1
  117. package/src/Evolu/Relay.ts +238 -18
  118. package/src/Evolu/Schema.ts +391 -191
  119. package/src/Evolu/Storage.ts +275 -55
  120. package/src/Evolu/Sync.ts +708 -37
  121. package/src/Evolu/Timestamp.ts +38 -35
  122. package/src/ManyToManyMap.ts +127 -24
  123. package/src/Number.ts +2 -6
  124. package/src/Random.ts +3 -2
  125. package/src/RefCountedResourceManager.ts +368 -0
  126. package/src/Result.ts +183 -53
  127. package/src/Sqlite.ts +59 -23
  128. package/src/Task.ts +779 -0
  129. package/src/Time.ts +168 -4
  130. package/src/Type.ts +708 -660
  131. package/src/Types.ts +0 -76
  132. package/src/WebSocket.ts +23 -17
  133. package/src/Worker.ts +72 -23
  134. package/src/index.ts +3 -3
  135. package/dist/src/Callbacks.d.ts +0 -20
  136. package/dist/src/Callbacks.d.ts.map +0 -1
  137. package/dist/src/Callbacks.js +0 -18
  138. package/dist/src/Evolu/Config.d.ts +0 -69
  139. package/dist/src/Evolu/Config.d.ts.map +0 -1
  140. package/dist/src/Evolu/Config.js +0 -9
  141. package/dist/src/Evolu/Kysely.d.ts +0 -6
  142. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  143. package/dist/src/Evolu/Kysely.js +0 -21
  144. package/dist/src/NanoId.d.ts +0 -27
  145. package/dist/src/NanoId.d.ts.map +0 -1
  146. package/dist/src/NanoId.js +0 -6
  147. package/dist/src/Promise.d.ts +0 -180
  148. package/dist/src/Promise.d.ts.map +0 -1
  149. package/dist/src/Promise.js +0 -176
  150. package/src/Callbacks.ts +0 -43
  151. package/src/Evolu/Config.ts +0 -83
  152. package/src/Evolu/Kysely.ts +0 -38
  153. package/src/NanoId.ts +0 -39
  154. package/src/Promise.ts +0 -295
@@ -1,18 +1,22 @@
1
1
  import { assert } from "../Assert.js";
2
- import { NanoIdLibDep } from "../NanoId.js";
2
+ import { Brand } from "../Brand.js";
3
+ import { bytesToHex } from "../Buffer.js";
4
+ import { RandomBytesDep } from "../Crypto.js";
5
+ import { createEqObject, eqNumber, eqString } from "../Eq.js";
3
6
  import { increment } from "../Number.js";
4
7
  import { Order, orderUint8Array } from "../Order.js";
5
- import { err, getOrThrow, ok, Result } from "../Result.js";
8
+ import { err, ok, Result } from "../Result.js";
6
9
  import { TimeDep } from "../Time.js";
7
10
  import {
8
11
  brand,
12
+ InferType,
9
13
  lessThanOrEqualTo,
10
14
  NonNegativeInt,
11
15
  object,
12
16
  regex,
13
17
  String,
18
+ Uint8Array,
14
19
  } from "../Type.js";
15
- import { Brand } from "../Types.js";
16
20
 
17
21
  export interface TimestampConfig {
18
22
  /**
@@ -30,7 +34,6 @@ export interface TimestampConfigDep {
30
34
  export type TimestampError =
31
35
  | TimestampDriftError
32
36
  | TimestampCounterOverflowError
33
- | TimestampDuplicateNodeError
34
37
  | TimestampTimeOutOfRangeError;
35
38
 
36
39
  export interface TimestampDriftError {
@@ -43,11 +46,6 @@ export interface TimestampCounterOverflowError {
43
46
  readonly type: "TimestampCounterOverflowError";
44
47
  }
45
48
 
46
- export interface TimestampDuplicateNodeError {
47
- readonly type: "TimestampDuplicateNodeError";
48
- readonly nodeId: NodeId;
49
- }
50
-
51
49
  export interface TimestampTimeOutOfRangeError {
52
50
  readonly type: "TimestampTimeOutOfRangeError";
53
51
  }
@@ -95,14 +93,17 @@ export const maxCounter = 65535 as Counter;
95
93
  *
96
94
  * https://lemire.me/blog/2019/12/12/are-64-bit-random-identifiers-free-from-collision
97
95
  *
98
- * What will happen if a different device generates the same NodeId?
96
+ * What happens if different devices generate the same NodeId?
99
97
  *
100
- * If the device belongs to a different owner, nothing will happen because
101
- * different owner have different owner IDs. Timestamps are partitioned by
102
- * OwnerId.
98
+ * If devices with the same NodeId use different owners, no issues occur.
103
99
  *
104
- * If the device belongs to the same owner, the other device will return
105
- * {@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.
106
107
  */
107
108
  export const NodeId = regex("NodeId", /^[a-f0-9]{16}$/)(String);
108
109
  export type NodeId = typeof NodeId.Type;
@@ -122,7 +123,14 @@ export const Timestamp = object({
122
123
  counter: Counter,
123
124
  nodeId: NodeId,
124
125
  });
125
- export type Timestamp = typeof Timestamp.Type;
126
+ export interface Timestamp extends InferType<typeof Timestamp> {}
127
+
128
+ /** Equality function for comparing {@link Timestamp}. */
129
+ export const eqTimestamp = createEqObject<Timestamp>({
130
+ millis: eqNumber,
131
+ counter: eqNumber,
132
+ nodeId: eqString,
133
+ });
126
134
 
127
135
  export const createTimestamp = ({
128
136
  millis = minMillis,
@@ -130,14 +138,12 @@ export const createTimestamp = ({
130
138
  nodeId = minNodeId,
131
139
  }: Partial<Timestamp> = {}): Timestamp => ({ millis, counter, nodeId });
132
140
 
133
- const hexAlphabet = "0123456789abcdef";
134
-
135
- export const createInitialTimestamp = (deps: NanoIdLibDep): Timestamp => {
136
- 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;
137
143
  return createTimestamp({ nodeId });
138
144
  };
139
145
 
140
- /** TimestampString is a sortable string version of {@link Timestamp}. */
146
+ /** Sortable string representation of {@link Timestamp}. */
141
147
  export type TimestampString = string & Brand<"TimestampString">;
142
148
 
143
149
  export const timestampToTimestampString = (t: Timestamp): TimestampString =>
@@ -222,12 +228,8 @@ export const receiveTimestamp =
222
228
  Timestamp,
223
229
  | TimestampDriftError
224
230
  | TimestampCounterOverflowError
225
- | TimestampDuplicateNodeError
226
231
  | TimestampTimeOutOfRangeError
227
232
  > => {
228
- if (local.nodeId === remote.nodeId) {
229
- return err({ type: "TimestampDuplicateNodeError", nodeId: local.nodeId });
230
- }
231
233
  const millis = getNextMillis(deps)([local.millis, remote.millis]);
232
234
  if (!millis.ok) return millis;
233
235
 
@@ -249,18 +251,19 @@ export const receiveTimestamp =
249
251
  });
250
252
  };
251
253
 
252
- /** BinaryTimestamp is a binary and sortable version of {@link Timestamp} for DB. */
253
- 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;
254
257
 
255
- export const binaryTimestampLength = getOrThrow(NonNegativeInt.from(16));
258
+ export const timestampBytesLength = NonNegativeInt.orThrow(16);
256
259
 
257
- export const timestampToBinaryTimestamp = (
260
+ export const timestampToTimestampBytes = (
258
261
  timestamp: Timestamp,
259
- ): BinaryTimestamp => {
262
+ ): TimestampBytes => {
260
263
  const { millis, counter, nodeId } = timestamp;
261
264
 
262
265
  // 6 bytes for millis, 2 bytes for counter, 8 bytes for nodeId.
263
- const value = new Uint8Array(16);
266
+ const value = new globalThis.Uint8Array(16);
264
267
 
265
268
  // Encode `millis` into the first 6 bytes.
266
269
  const millisBigInt = BigInt(millis);
@@ -281,11 +284,11 @@ export const timestampToBinaryTimestamp = (
281
284
  value[8 + i] = byte;
282
285
  }
283
286
 
284
- return value as BinaryTimestamp;
287
+ return value as TimestampBytes;
285
288
  };
286
289
 
287
- export const binaryTimestampToTimestamp = (
288
- timestamp: BinaryTimestamp,
290
+ export const timestampBytesToTimestamp = (
291
+ timestamp: TimestampBytes,
289
292
  ): Timestamp => {
290
293
  // Decode `millis` from the first 6 bytes.
291
294
  const millis =
@@ -308,4 +311,4 @@ export const binaryTimestampToTimestamp = (
308
311
  return { millis: Number(millis), counter, nodeId } as Timestamp;
309
312
  };
310
313
 
311
- 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/Number.ts CHANGED
@@ -2,12 +2,8 @@ import { NonEmptyReadonlyArray } from "./Array.js";
2
2
  import { assertNonEmptyReadonlyArray } from "./Assert.js";
3
3
  import { err, ok, Result } from "./Result.js";
4
4
  import { NonNegativeInt, PositiveInt } from "./Type.js";
5
- import {
6
- IntentionalNever,
7
- IsBranded,
8
- Predicate,
9
- WidenLiteral,
10
- } from "./Types.js";
5
+ import { IntentionalNever, Predicate, WidenLiteral } from "./Types.js";
6
+ import { IsBranded } from "./Brand.js";
11
7
 
12
8
  export const increment = (n: number): number => n + 1;
13
9
 
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
  *