@evolu/common 6.0.1-preview.3 → 6.0.1-preview.31

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 (173) hide show
  1. package/dist/src/Array.d.ts +69 -5
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +64 -5
  4. package/dist/src/Assert.d.ts +6 -16
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +6 -18
  7. package/dist/src/Brand.d.ts +75 -0
  8. package/dist/src/Brand.d.ts.map +1 -0
  9. package/dist/src/Brand.js +1 -0
  10. package/dist/src/Buffer.d.ts +1 -1
  11. package/dist/src/Buffer.d.ts.map +1 -1
  12. package/dist/src/Buffer.js +8 -7
  13. package/dist/src/Cache.d.ts +44 -0
  14. package/dist/src/Cache.d.ts.map +1 -0
  15. package/dist/src/Cache.js +52 -0
  16. package/dist/src/Callbacks.d.ts +45 -12
  17. package/dist/src/Callbacks.d.ts.map +1 -1
  18. package/dist/src/Callbacks.js +14 -7
  19. package/dist/src/Console.d.ts +31 -6
  20. package/dist/src/Console.d.ts.map +1 -1
  21. package/dist/src/Console.js +72 -9
  22. package/dist/src/Crypto.d.ts +61 -34
  23. package/dist/src/Crypto.d.ts.map +1 -1
  24. package/dist/src/Crypto.js +32 -45
  25. package/dist/src/Evolu/Db.d.ts +161 -65
  26. package/dist/src/Evolu/Db.d.ts.map +1 -1
  27. package/dist/src/Evolu/Db.js +286 -694
  28. package/dist/src/Evolu/Diff.d.ts +3 -3
  29. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  30. package/dist/src/Evolu/Diff.js +7 -5
  31. package/dist/src/Evolu/Evolu.d.ts +208 -133
  32. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  33. package/dist/src/Evolu/Evolu.js +188 -183
  34. package/dist/src/Evolu/Internal.d.ts +0 -2
  35. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  36. package/dist/src/Evolu/Internal.js +0 -2
  37. package/dist/src/Evolu/LocalAuth.d.ts +150 -0
  38. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  39. package/dist/src/Evolu/LocalAuth.js +174 -0
  40. package/dist/src/Evolu/Owner.d.ts +273 -120
  41. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  42. package/dist/src/Evolu/Owner.js +130 -104
  43. package/dist/src/Evolu/Platform.d.ts +9 -7
  44. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  45. package/dist/src/Evolu/Protocol.d.ts +277 -232
  46. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  47. package/dist/src/Evolu/Protocol.js +603 -378
  48. package/dist/src/Evolu/Public.d.ts +6 -8
  49. package/dist/src/Evolu/Public.d.ts.map +1 -1
  50. package/dist/src/Evolu/Public.js +2 -3
  51. package/dist/src/Evolu/PublicKysely.js +3 -3
  52. package/dist/src/Evolu/Query.d.ts +2 -1
  53. package/dist/src/Evolu/Query.d.ts.map +1 -1
  54. package/dist/src/Evolu/Relay.d.ts +92 -7
  55. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  56. package/dist/src/Evolu/Relay.js +238 -76
  57. package/dist/src/Evolu/Schema.d.ts +129 -73
  58. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  59. package/dist/src/Evolu/Schema.js +169 -89
  60. package/dist/src/Evolu/Storage.d.ts +240 -26
  61. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  62. package/dist/src/Evolu/Storage.js +189 -91
  63. package/dist/src/Evolu/Sync.d.ts +67 -13
  64. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  65. package/dist/src/Evolu/Sync.js +441 -20
  66. package/dist/src/Evolu/Timestamp.d.ts +85 -27
  67. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  68. package/dist/src/Evolu/Timestamp.js +77 -18
  69. package/dist/src/Identicon.d.ts +35 -0
  70. package/dist/src/Identicon.d.ts.map +1 -0
  71. package/dist/src/Identicon.js +143 -0
  72. package/dist/src/Instances.d.ts +34 -0
  73. package/dist/src/Instances.d.ts.map +1 -0
  74. package/dist/src/Instances.js +44 -0
  75. package/dist/src/ManyToManyMap.d.ts +71 -10
  76. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  77. package/dist/src/ManyToManyMap.js +41 -6
  78. package/dist/src/Number.d.ts +4 -3
  79. package/dist/src/Number.d.ts.map +1 -1
  80. package/dist/src/Number.js +5 -4
  81. package/dist/src/Platform.d.ts +20 -0
  82. package/dist/src/Platform.d.ts.map +1 -0
  83. package/dist/src/Platform.js +22 -0
  84. package/dist/src/Random.d.ts +3 -2
  85. package/dist/src/Random.d.ts.map +1 -1
  86. package/dist/src/Resources.d.ts +118 -0
  87. package/dist/src/Resources.d.ts.map +1 -0
  88. package/dist/src/Resources.js +197 -0
  89. package/dist/src/Result.d.ts +184 -52
  90. package/dist/src/Result.d.ts.map +1 -1
  91. package/dist/src/Result.js +30 -241
  92. package/dist/src/Skiplist.js +2 -1
  93. package/dist/src/Sqlite.d.ts +63 -5
  94. package/dist/src/Sqlite.d.ts.map +1 -1
  95. package/dist/src/Sqlite.js +110 -9
  96. package/dist/src/Task.d.ts +586 -0
  97. package/dist/src/Task.d.ts.map +1 -0
  98. package/dist/src/Task.js +469 -0
  99. package/dist/src/Time.d.ts +66 -1
  100. package/dist/src/Time.d.ts.map +1 -1
  101. package/dist/src/Time.js +99 -5
  102. package/dist/src/Type.d.ts +622 -340
  103. package/dist/src/Type.d.ts.map +1 -1
  104. package/dist/src/Type.js +666 -464
  105. package/dist/src/Types.d.ts +1 -75
  106. package/dist/src/Types.d.ts.map +1 -1
  107. package/dist/src/WebSocket.d.ts +5 -2
  108. package/dist/src/WebSocket.d.ts.map +1 -1
  109. package/dist/src/WebSocket.js +12 -18
  110. package/dist/src/Worker.d.ts +39 -11
  111. package/dist/src/Worker.d.ts.map +1 -1
  112. package/dist/src/Worker.js +22 -4
  113. package/dist/src/index.d.ts +7 -2
  114. package/dist/src/index.d.ts.map +1 -1
  115. package/dist/src/index.js +7 -2
  116. package/package.json +14 -13
  117. package/src/Array.ts +90 -11
  118. package/src/Assert.ts +6 -24
  119. package/src/Brand.ts +75 -0
  120. package/src/Buffer.ts +7 -7
  121. package/src/Cache.ts +85 -0
  122. package/src/Callbacks.ts +62 -22
  123. package/src/Console.ts +91 -11
  124. package/src/Crypto.ts +97 -82
  125. package/src/Evolu/Db.ts +517 -1020
  126. package/src/Evolu/Diff.ts +7 -5
  127. package/src/Evolu/Evolu.ts +464 -355
  128. package/src/Evolu/Internal.ts +0 -2
  129. package/src/Evolu/LocalAuth.ts +463 -0
  130. package/src/Evolu/Owner.ts +355 -228
  131. package/src/Evolu/Platform.ts +9 -9
  132. package/src/Evolu/Protocol.ts +859 -676
  133. package/src/Evolu/Public.ts +7 -14
  134. package/src/Evolu/PublicKysely.ts +3 -3
  135. package/src/Evolu/Query.ts +2 -1
  136. package/src/Evolu/Relay.ts +437 -93
  137. package/src/Evolu/Schema.ts +391 -191
  138. package/src/Evolu/Storage.ts +532 -135
  139. package/src/Evolu/Sync.ts +766 -37
  140. package/src/Evolu/Timestamp.ts +88 -35
  141. package/src/Identicon.ts +197 -0
  142. package/src/Instances.ts +90 -0
  143. package/src/ManyToManyMap.ts +124 -24
  144. package/src/Number.ts +6 -10
  145. package/src/Platform.ts +26 -0
  146. package/src/Random.ts +3 -2
  147. package/src/Resources.ts +367 -0
  148. package/src/Result.ts +191 -54
  149. package/src/Skiplist.ts +1 -1
  150. package/src/Sqlite.ts +122 -17
  151. package/src/Task.ts +901 -0
  152. package/src/Time.ts +180 -5
  153. package/src/Type.ts +1084 -727
  154. package/src/Types.ts +1 -77
  155. package/src/WebSocket.ts +27 -25
  156. package/src/Worker.ts +72 -23
  157. package/src/index.ts +7 -2
  158. package/dist/src/Evolu/Config.d.ts +0 -69
  159. package/dist/src/Evolu/Config.d.ts.map +0 -1
  160. package/dist/src/Evolu/Config.js +0 -9
  161. package/dist/src/Evolu/Kysely.d.ts +0 -6
  162. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  163. package/dist/src/Evolu/Kysely.js +0 -21
  164. package/dist/src/NanoId.d.ts +0 -27
  165. package/dist/src/NanoId.d.ts.map +0 -1
  166. package/dist/src/NanoId.js +0 -6
  167. package/dist/src/Promise.d.ts +0 -180
  168. package/dist/src/Promise.d.ts.map +0 -1
  169. package/dist/src/Promise.js +0 -176
  170. package/src/Evolu/Config.ts +0 -83
  171. package/src/Evolu/Kysely.ts +0 -38
  172. package/src/NanoId.ts +0 -39
  173. package/src/Promise.ts +0 -295
@@ -1 +1 @@
1
- {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import { assert } from "../Assert.js";
2
+ import { bytesToHex } from "../Buffer.js";
3
+ import { createEqObject, eqNumber, eqString } from "../Eq.js";
2
4
  import { increment } from "../Number.js";
3
5
  import { orderUint8Array } from "../Order.js";
4
- import { err, getOrThrow, ok } from "../Result.js";
5
- import { brand, lessThanOrEqualTo, NonNegativeInt, object, regex, String, } from "../Type.js";
6
+ import { err, ok } from "../Result.js";
7
+ import { brand, lessThanOrEqualTo, NonNegativeInt, object, regex, String, Uint8Array, } from "../Type.js";
6
8
  /**
7
9
  * Millis is a timestamp in milliseconds, like `Date.now()`, but limited to the
8
10
  * maximum value representable in 6 bytes (281474976710655) minus 1 (reserved
@@ -34,14 +36,17 @@ export const maxCounter = 65535;
34
36
  *
35
37
  * https://lemire.me/blog/2019/12/12/are-64-bit-random-identifiers-free-from-collision
36
38
  *
37
- * What will happen if a different device generates the same NodeId?
39
+ * What happens if different devices generate the same NodeId?
38
40
  *
39
- * If the device belongs to a different owner, nothing will happen because
40
- * different owner have different owner IDs. Timestamps are partitioned by
41
- * OwnerId.
41
+ * If devices with the same NodeId use different owners, no issues occur.
42
42
  *
43
- * If the device belongs to the same owner, the other device will return
44
- * {@link TimestampDuplicateNodeError}.
43
+ * If devices with the same NodeId use the same owner, problems only arise when
44
+ * they generate CRDT messages with identical timestamps (same millis, counter,
45
+ * and NodeId). In this case, the protocol sync algorithm treats them as the
46
+ * same message: the first will be synced with the relay, while the affected
47
+ * message will not be delivered. The affected devices will see different data
48
+ * yet they will think they are synced. This is extremely rare and can be
49
+ * resolved by resetting one device to generate a new NodeId.
45
50
  */
46
51
  export const NodeId = regex("NodeId", /^[a-f0-9]{16}$/)(String);
47
52
  export const minNodeId = "0000000000000000";
@@ -49,19 +54,74 @@ export const maxNodeId = "ffffffffffffffff";
49
54
  /**
50
55
  * Hybrid Logical Clock timestamp.
51
56
  *
57
+ * Timestamps serve as globally unique, causally ordered identifiers for CRDT
58
+ * messages in Evolu's sync protocol.
59
+ *
60
+ * ### Why Hybrid Logical Clocks
61
+ *
62
+ * Evolu uses Hybrid Logical Clocks (HLC), which combine physical time (millis)
63
+ * with a logical counter. This hybrid approach preserves causality like logical
64
+ * clocks while staying close to physical time for better human
65
+ * interpretability.
66
+ *
67
+ * The counter component ensures causality is maintained even when physical
68
+ * clocks are imperfect. When clocks drift or operations occur concurrently, the
69
+ * counter increments to establish a total order. This means Evolu achieves
70
+ * well-defined, eventually-consistent behavior regardless of physical clock
71
+ * accuracy.
72
+ *
73
+ * Vector clocks can accurately track causality and detect concurrent
74
+ * operations, but they require unbounded space in peer-to-peer systems and
75
+ * crucially, still don't solve our fundamental problem: when they detect
76
+ * operations as concurrent, we still need a deterministic way to choose a
77
+ * winner. Additionally, any deterministic conflict resolution can be gamed by
78
+ * malicious actors.
79
+ *
80
+ * HLC timestamps work well in practice because modern device clocks accurately
81
+ * reflect the order of sequential edits in the common case. Evolu's `maxDrift`
82
+ * configuration protects against buggy clocks and prevents problematic
83
+ * future-dated entries from propagating through the network.
84
+ *
85
+ * ### References
86
+ *
52
87
  * - https://muratbuffalo.blogspot.com/2014/07/hybrid-logical-clocks.html
53
88
  * - https://sergeiturukin.com/2017/06/26/hybrid-logical-clocks.html
54
89
  * - https://jaredforsyth.com/posts/hybrid-logical-clocks/
90
+ * - https://willowprotocol.org/more/timestamps_really/index.html
91
+ *
92
+ * ### Privacy Considerations
93
+ *
94
+ * Timestamps are metadata visible to relays and collaborators. While it can be
95
+ * considered a privacy leak, let us explain why it's necessary, and how to
96
+ * avoid it if maximum privacy is required.
97
+ *
98
+ * With real-time communication, participants always see activity (receiving
99
+ * bytes). We cannot trust anyone not to store that information, so explicitly
100
+ * exposing timestamps doesn't add additional risk.
101
+ *
102
+ * If we really want not to leak user activity, we can implement a local write
103
+ * queue:
104
+ *
105
+ * 1. Write changes immediately to a local-only table
106
+ * 2. Periodically/randomly flush messages to sync tables
107
+ * 3. This decouples user activity from sync timing
108
+ *
109
+ * Tradeoff: It breaks real-time collaboration.
55
110
  */
56
111
  export const Timestamp = object({
57
112
  millis: Millis,
58
113
  counter: Counter,
59
114
  nodeId: NodeId,
60
115
  });
116
+ /** Equality function for comparing {@link Timestamp}. */
117
+ export const eqTimestamp = createEqObject({
118
+ millis: eqNumber,
119
+ counter: eqNumber,
120
+ nodeId: eqString,
121
+ });
61
122
  export const createTimestamp = ({ millis = minMillis, counter = minCounter, nodeId = minNodeId, } = {}) => ({ millis, counter, nodeId });
62
- const hexAlphabet = "0123456789abcdef";
63
123
  export const createInitialTimestamp = (deps) => {
64
- const nodeId = deps.nanoIdLib.customAlphabet(hexAlphabet, 16)();
124
+ const nodeId = bytesToHex(deps.randomBytes.create(8));
65
125
  return createTimestamp({ nodeId });
66
126
  };
67
127
  export const timestampToTimestampString = (t) => [
@@ -115,9 +175,6 @@ export const sendTimestamp = (deps) => (timestamp) => {
115
175
  });
116
176
  };
117
177
  export const receiveTimestamp = (deps) => (local, remote) => {
118
- if (local.nodeId === remote.nodeId) {
119
- return err({ type: "TimestampDuplicateNodeError", nodeId: local.nodeId });
120
- }
121
178
  const millis = getNextMillis(deps)([local.millis, remote.millis]);
122
179
  if (!millis.ok)
123
180
  return millis;
@@ -136,11 +193,13 @@ export const receiveTimestamp = (deps) => (local, remote) => {
136
193
  nodeId: local.nodeId,
137
194
  });
138
195
  };
139
- export const binaryTimestampLength = getOrThrow(NonNegativeInt.from(16));
140
- export const timestampToBinaryTimestamp = (timestamp) => {
196
+ /** Sortable bytes representation of {@link Timestamp}. */
197
+ export const TimestampBytes = brand("TimestampBytes", Uint8Array);
198
+ export const timestampBytesLength = NonNegativeInt.orThrow(16);
199
+ export const timestampToTimestampBytes = (timestamp) => {
141
200
  const { millis, counter, nodeId } = timestamp;
142
201
  // 6 bytes for millis, 2 bytes for counter, 8 bytes for nodeId.
143
- const value = new Uint8Array(16);
202
+ const value = new globalThis.Uint8Array(16);
144
203
  // Encode `millis` into the first 6 bytes.
145
204
  const millisBigInt = BigInt(millis);
146
205
  value[0] = Number((millisBigInt >> 40n) & 0xffn);
@@ -159,7 +218,7 @@ export const timestampToBinaryTimestamp = (timestamp) => {
159
218
  }
160
219
  return value;
161
220
  };
162
- export const binaryTimestampToTimestamp = (timestamp) => {
221
+ export const timestampBytesToTimestamp = (timestamp) => {
163
222
  // Decode `millis` from the first 6 bytes.
164
223
  const millis = (BigInt(timestamp[0]) << 40n) |
165
224
  (BigInt(timestamp[1]) << 32n) |
@@ -176,4 +235,4 @@ export const binaryTimestampToTimestamp = (timestamp) => {
176
235
  }
177
236
  return { millis: Number(millis), counter, nodeId };
178
237
  };
179
- export const orderBinaryTimestamp = orderUint8Array;
238
+ export const orderTimestampBytes = orderUint8Array;
@@ -0,0 +1,35 @@
1
+ import type { Brand } from "./Brand.js";
2
+ import { Id } from "./Type.js";
3
+ /**
4
+ * SVG string representing a visual identicon for an {@link Id}, created with
5
+ * {@link createIdenticon}.
6
+ */
7
+ export type Identicon = string & Brand<"Identicon">;
8
+ /** {@link Identicon} style. */
9
+ export type IdenticonStyle = "github" | "quadrant" | "gradient" | "sutnar";
10
+ /**
11
+ * Creates a deterministic identicon SVG from an {@link Id}.
12
+ *
13
+ * Works with any {@link Id} including branded IDs like `OwnerId`, etc.
14
+ *
15
+ * Available styles:
16
+ *
17
+ * - `"github"` (default): 5x5 grid with horizontal mirroring (GitHub-style)
18
+ * - `"quadrant"`: 2x2 grid with direct RGB color mapping from bytes
19
+ * - `"gradient"`: Diagonal stripes with smooth color gradients
20
+ * - `"sutnar"`: Three compositional variants with adaptive colors
21
+ *
22
+ * ### Example
23
+ *
24
+ * ```ts
25
+ * const svg = createIdenticon(id);
26
+ * const quadrantStyle = createIdenticon(id, "quadrant");
27
+ * const gradientStyle = createIdenticon(id, "gradient");
28
+ * const sutnarStyle = createIdenticon(id, "sutnar");
29
+ *
30
+ * // Works with branded IDs
31
+ * const ownerSvg = createIdenticon(ownerId);
32
+ * ```
33
+ */
34
+ export declare const createIdenticon: (id: Id, style?: IdenticonStyle) => Identicon;
35
+ //# sourceMappingURL=Identicon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Identicon.d.ts","sourceRoot":"","sources":["../../src/Identicon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,EAAE,EAAe,MAAM,WAAW,CAAC;AAG5C;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;AAEpD,+BAA+B;AAC/B,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,eAAe,GAC1B,IAAI,EAAE,EACN,QAAO,cAAyB,KAC/B,SA4JF,CAAC"}
@@ -0,0 +1,143 @@
1
+ import { idToIdBytes } from "./Type.js";
2
+ import { md5 } from "@noble/hashes/legacy.js";
3
+ /**
4
+ * Creates a deterministic identicon SVG from an {@link Id}.
5
+ *
6
+ * Works with any {@link Id} including branded IDs like `OwnerId`, etc.
7
+ *
8
+ * Available styles:
9
+ *
10
+ * - `"github"` (default): 5x5 grid with horizontal mirroring (GitHub-style)
11
+ * - `"quadrant"`: 2x2 grid with direct RGB color mapping from bytes
12
+ * - `"gradient"`: Diagonal stripes with smooth color gradients
13
+ * - `"sutnar"`: Three compositional variants with adaptive colors
14
+ *
15
+ * ### Example
16
+ *
17
+ * ```ts
18
+ * const svg = createIdenticon(id);
19
+ * const quadrantStyle = createIdenticon(id, "quadrant");
20
+ * const gradientStyle = createIdenticon(id, "gradient");
21
+ * const sutnarStyle = createIdenticon(id, "sutnar");
22
+ *
23
+ * // Works with branded IDs
24
+ * const ownerSvg = createIdenticon(ownerId);
25
+ * ```
26
+ */
27
+ export const createIdenticon = (id, style = "github") => {
28
+ const bytes = idToIdBytes(id);
29
+ switch (style) {
30
+ case "github": {
31
+ // GitHub-style identicon: MD5 hash the bytes first
32
+ const hashedBytes = md5(bytes);
33
+ // Map function for value ranges
34
+ const map = (value, inMin, inMax, outMin, outMax) => ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
35
+ // Extract 12-bit hue from bytes[12] (lower 4 bits) + bytes[13]
36
+ const h = ((hashedBytes[12] & 0x0f) << 8) | hashedBytes[13];
37
+ const hue = map(h, 0, 4095, 0, 360);
38
+ const saturation = 65 - map(hashedBytes[14], 0, 255, 0, 20);
39
+ const lightness = 75 - map(hashedBytes[15], 0, 255, 0, 20);
40
+ const fgColor = `hsl(${hue},${saturation}%,${lightness}%)`;
41
+ const bgColor = `hsl(${hue},${saturation}%,90%)`;
42
+ let rects = `<rect width="5" height="5" fill="${bgColor}"/>`;
43
+ // Extract nibbles and generate pattern
44
+ let nibbleIndex = 0;
45
+ for (let x = 2; x >= 0; x--) {
46
+ for (let y = 0; y < 5; y++) {
47
+ const byte = hashedBytes[Math.floor(nibbleIndex / 2)];
48
+ const nibble = nibbleIndex % 2 === 0 ? byte >> 4 : byte & 0x0f;
49
+ const paint = nibble % 2 === 0;
50
+ nibbleIndex++;
51
+ if (paint) {
52
+ rects += `<rect x="${x}" y="${y}" width="1" height="1" fill="${fgColor}"/>`;
53
+ const mx = 4 - x;
54
+ if (mx !== x) {
55
+ rects += `<rect x="${mx}" y="${y}" width="1" height="1" fill="${fgColor}"/>`;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 5" shape-rendering="crispEdges">${rects}</svg>`;
61
+ }
62
+ case "quadrant": {
63
+ const toHex = (b) => b.toString(16).padStart(2, "0");
64
+ let rects = "";
65
+ for (let i = 0; i < 4; i++) {
66
+ const x = i % 2;
67
+ const y = Math.floor(i / 2);
68
+ const r = bytes[i * 3];
69
+ const g = bytes[i * 3 + 1];
70
+ const b = bytes[i * 3 + 2];
71
+ const color = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
72
+ rects += `<rect x="${x}" y="${y}" width="1" height="1" fill="${color}"/>`;
73
+ }
74
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2">${rects}</svg>`;
75
+ }
76
+ case "gradient": {
77
+ // Smooth color gradients with diagonal stripes.
78
+ const toHex = (b) => b.toString(16).padStart(2, "0");
79
+ // Generate colors from bytes.
80
+ const color1 = `#${toHex(bytes[0])}${toHex(bytes[1])}${toHex(bytes[2])}`;
81
+ const color2 = `#${toHex(bytes[3])}${toHex(bytes[4])}${toHex(bytes[5])}`;
82
+ const color3 = `#${toHex(bytes[6])}${toHex(bytes[7])}${toHex(bytes[8])}`;
83
+ let defs = "";
84
+ let shapes = "";
85
+ // Diagonal stripes with gradient.
86
+ defs += `<linearGradient id="grad1-${id}" x1="0%" y1="0%" x2="0%" y2="100%">`;
87
+ defs += `<stop offset="0%" style="stop-color:${color1};stop-opacity:1" />`;
88
+ defs += `<stop offset="100%" style="stop-color:${color2};stop-opacity:1" />`;
89
+ defs += `</linearGradient>`;
90
+ defs += `<linearGradient id="grad2-${id}" x1="0%" y1="0%" x2="0%" y2="100%">`;
91
+ defs += `<stop offset="0%" style="stop-color:${color2};stop-opacity:1" />`;
92
+ defs += `<stop offset="100%" style="stop-color:${color3};stop-opacity:1" />`;
93
+ defs += `</linearGradient>`;
94
+ shapes += `<rect width="100" height="100" fill="url(#grad1-${id})"/>`;
95
+ const stripeWidth = 15 + (bytes[9] / 255) * 20;
96
+ const angle = 30 + (bytes[10] / 255) * 60;
97
+ shapes += `<rect x="20" y="-50" width="${stripeWidth}" height="200" fill="url(#grad2-${id})" transform="rotate(${angle} 50 50)" opacity="0.7"/>`;
98
+ shapes += `<rect x="60" y="-50" width="${stripeWidth}" height="200" fill="url(#grad2-${id})" transform="rotate(${angle} 50 50)" opacity="0.5"/>`;
99
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs>${defs}</defs>${shapes}</svg>`;
100
+ }
101
+ case "sutnar": {
102
+ // Three compositional variants with adaptive colors.
103
+ const hue = (bytes[0] / 255) * 360;
104
+ const saturation = 50 + (bytes[1] / 255) * 30;
105
+ const lightness = 50 + (bytes[2] / 255) * 20;
106
+ // Generate palette from base hue with variations
107
+ const toHsl = (h, s, l) => `hsl(${h},${s}%,${l}%)`;
108
+ const color1 = toHsl(hue, saturation, lightness);
109
+ const color2 = toHsl((hue + 120) % 360, saturation, lightness);
110
+ const color3 = toHsl((hue + 240) % 360, saturation, lightness);
111
+ const color4 = toHsl(hue, saturation * 0.3, lightness * 0.5);
112
+ const color5 = toHsl(hue, saturation * 0.5, Math.min(lightness * 1.3, 90));
113
+ const palette = [color1, color2, color3, color4, color5];
114
+ // Layout variant based on first byte.
115
+ const variant = bytes[3] % 3;
116
+ let shapes = "";
117
+ // Almost white background with subtle tint.
118
+ shapes += `<rect width="100" height="100" fill="${toHsl(hue, 10, 95)}"/>`;
119
+ if (variant === 0) {
120
+ // Composition A: Circle + horizontal bar.
121
+ const circleColor = palette[bytes[4] % palette.length];
122
+ const barColor = palette[(bytes[4] + 1) % palette.length];
123
+ shapes += `<circle cx="30" cy="50" r="22" fill="${circleColor}"/>`;
124
+ shapes += `<rect x="60" y="40" width="35" height="20" fill="${barColor}"/>`;
125
+ }
126
+ else if (variant === 1) {
127
+ // Composition B: Vertical bar + circle.
128
+ const barColor = palette[bytes[5] % palette.length];
129
+ const circleColor = palette[(bytes[5] + 1) % palette.length];
130
+ shapes += `<rect x="15" y="10" width="18" height="80" fill="${barColor}"/>`;
131
+ shapes += `<circle cx="70" cy="50" r="15" fill="${circleColor}"/>`;
132
+ }
133
+ else {
134
+ // Composition C: Square + circle.
135
+ const squareColor = palette[bytes[6] % palette.length];
136
+ const circleColor = palette[(bytes[6] + 1) % palette.length];
137
+ shapes += `<rect x="20" y="20" width="30" height="30" fill="${squareColor}"/>`;
138
+ shapes += `<circle cx="70" cy="70" r="18" fill="${circleColor}"/>`;
139
+ }
140
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">${shapes}</svg>`;
141
+ }
142
+ }
143
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Manages disposable instances by key, ensuring exactly one instance per key.
3
+ *
4
+ * Use cases:
5
+ *
6
+ * - One mutex per key to prevent concurrent writes
7
+ * - Preserving state during hot module reloading
8
+ *
9
+ * **Important:** Do not use this as global shared state. Use it locally or pass
10
+ * it as a dependency instead. The only exception is for hot reloading support,
11
+ * where Evolu uses it to ensure only one instance exists across module reloads
12
+ * (having two Evolu instances with the same name would mean two SQLite
13
+ * connections to the same file, which could corrupt data).
14
+ */
15
+ export interface Instances<K extends string, T extends Disposable> extends Disposable {
16
+ /**
17
+ * Ensures an instance exists for the given key, creating it if necessary. If
18
+ * the instance already exists, the optional `onCacheHit` callback is invoked
19
+ * to update the existing instance.
20
+ */
21
+ readonly ensure: (key: K, create: () => T, onCacheHit?: (instance: T) => void) => T;
22
+ /** Gets an instance by key, or returns `null` if it doesn't exist. */
23
+ readonly get: (key: K) => T | null;
24
+ /** Checks if an instance exists for the given key. */
25
+ readonly has: (key: K) => boolean;
26
+ /**
27
+ * Deletes and disposes an instance by key. Returns `true` if the instance
28
+ * existed and was deleted, `false` otherwise.
29
+ */
30
+ readonly delete: (key: K) => boolean;
31
+ }
32
+ /** Creates an {@link Instances}. */
33
+ export declare const createInstances: <K extends string, T extends Disposable>() => Instances<K, T>;
34
+ //# sourceMappingURL=Instances.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Instances.d.ts","sourceRoot":"","sources":["../../src/Instances.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,UAAU,CAC/D,SAAQ,UAAU;IAClB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,CACf,GAAG,EAAE,CAAC,EACN,MAAM,EAAE,MAAM,CAAC,EACf,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,KAC/B,CAAC,CAAC;IAEP,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAEnC,sDAAsD;IACtD,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAElC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;CACtC;AAED,oCAAoC;AACpC,eAAO,MAAM,eAAe,GAC1B,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,UAAU,OACjB,SAAS,CAAC,CAAC,EAAE,CAAC,CA6ClB,CAAC"}
@@ -0,0 +1,44 @@
1
+ /** Creates an {@link Instances}. */
2
+ export const createInstances = () => {
3
+ const instances = new Map();
4
+ return {
5
+ ensure: (key, create, onCacheHit) => {
6
+ let instance = instances.get(key);
7
+ if (instance == null) {
8
+ instance = create();
9
+ instances.set(key, instance);
10
+ }
11
+ else if (onCacheHit) {
12
+ onCacheHit(instance);
13
+ }
14
+ return instance;
15
+ },
16
+ get: (key) => instances.get(key) ?? null,
17
+ has: (key) => instances.has(key),
18
+ delete: (key) => {
19
+ const instance = instances.get(key);
20
+ if (instance == null)
21
+ return false;
22
+ instances.delete(key);
23
+ instance[Symbol.dispose]();
24
+ return true;
25
+ },
26
+ [Symbol.dispose]: () => {
27
+ const errors = [];
28
+ for (const instance of instances.values()) {
29
+ try {
30
+ instance[Symbol.dispose]();
31
+ }
32
+ catch (error) {
33
+ errors.push(error);
34
+ }
35
+ }
36
+ instances.clear();
37
+ if (errors.length === 1)
38
+ throw errors[0];
39
+ if (errors.length > 1) {
40
+ throw new AggregateError(errors, "Multiple disposal errors occurred");
41
+ }
42
+ },
43
+ };
44
+ };
@@ -1,17 +1,72 @@
1
- /** Represents a many-to-many mapping between keys and values. */
1
+ /**
2
+ * Bidirectional many-to-many map between keys and values.
3
+ *
4
+ * Why useful:
5
+ *
6
+ * - Provides O(1) (amortized) forward lookup (key -> values) and reverse lookup
7
+ * (value -> keys) without maintaining two maps manually and risking them
8
+ * diverging.
9
+ * - Natural fit for pub/sub style relations such as ownerId ↔ WebSocket, tag ↔
10
+ * item, user ↔ role, entity ↔ subscription where both directions are
11
+ * frequently queried.
12
+ * - Supports fast membership tests via `hasPair`, `hasKey`, and `hasValue`.
13
+ * - Iteration helpers (`forEach`, iterator) allow treating the structure as an
14
+ * edge list when needed.
15
+ *
16
+ * Complexity:
17
+ *
18
+ * - `add` / `remove` / `has*` / `get*` each perform a constant number of Map/Set
19
+ * operations (O(1) expected).
20
+ * - `deleteKey` and `deleteValue` are O(d) where d is the number of associated
21
+ * values / keys (the degree). This is optimal because every associated pair
22
+ * must be touched once.
23
+ *
24
+ * Object identity:
25
+ *
26
+ * - Keys and values are compared by reference (standard Map / Set semantics).
27
+ * Structural hashing of objects in JavaScript is non-trivial, can be
28
+ * expensive, and collision-prone if done naively. Prefer using stable
29
+ * primitive identifiers (ids, strings) instead of attempting to hash full
30
+ * object structures.
31
+ * - If structural equivalence is truly required, wrap objects in an adapter that
32
+ * supplies a canonical hash/id and stores/retrieves the original objects
33
+ * separately. This is a rare need; avoid unless you have clear requirements.
34
+ */
2
35
  export interface ManyToManyMap<K, V> {
3
- /** Adds a key-value association to the map. */
4
- add: (key: K, value: V) => ManyToManyMap<K, V>;
5
- /** Removes a specific key-value association. */
36
+ /**
37
+ * Adds a key-value association to the map. Returns true if the pair was newly
38
+ * added, false if it already existed.
39
+ */
40
+ add: (key: K, value: V) => boolean;
41
+ /**
42
+ * Removes a specific key-value association. Returns true if the pair existed
43
+ * and was removed, false if it was not present.
44
+ */
6
45
  remove: (key: K, value: V) => boolean;
7
- /** Gets all values associated with a key. */
8
- getValues: (key: K) => Set<V> | undefined;
9
- /** Gets all keys associated with a value. */
10
- getKeys: (value: V) => Set<K> | undefined;
46
+ /**
47
+ * Gets all values associated with a key. Returned set is the internal Set
48
+ * instance typed as ReadonlySet. Do not mutate.
49
+ */
50
+ getValues: (key: K) => ReadonlySet<V> | undefined;
51
+ /**
52
+ * Gets all keys associated with a value. Returned set is the internal Set
53
+ * instance typed as ReadonlySet. Do not mutate.
54
+ */
55
+ getKeys: (value: V) => ReadonlySet<K> | undefined;
56
+ /**
57
+ * Iterates over each key-value pair (in insertion order of keys, then values
58
+ * per key).
59
+ */
60
+ forEach: (callback: (key: K, value: V) => void) => void;
61
+ /**
62
+ * Iterator over all key-value pairs enabling for..of and spread. Yields
63
+ * readonly [key, value] tuples.
64
+ */
65
+ readonly [Symbol.iterator]: () => IterableIterator<readonly [K, V]>;
11
66
  /** Checks if a specific key-value pair exists. */
12
67
  hasPair: (key: K, value: V) => boolean;
13
68
  /** Checks if a key exists in the map. */
14
- hasKey(key: K): boolean;
69
+ hasKey: (key: K) => boolean;
15
70
  /** Checks if a value exists in the map. */
16
71
  hasValue: (value: V) => boolean;
17
72
  /** Deletes all associations for a key. */
@@ -19,7 +74,13 @@ export interface ManyToManyMap<K, V> {
19
74
  /** Deletes all associations for a value. */
20
75
  deleteValue: (value: V) => boolean;
21
76
  /** Clears all associations in the map. */
22
- clear(): void;
77
+ clear: () => void;
78
+ /** Number of distinct keys currently present. */
79
+ keyCount: () => number;
80
+ /** Number of distinct values currently present. */
81
+ valueCount: () => number;
82
+ /** Number of key-value pairs (associations) currently stored. */
83
+ pairCount: () => number;
23
84
  }
24
85
  /** Creates a {@link ManyToManyMap}. */
25
86
  export declare const createManyToManyMap: <K, V>() => ManyToManyMap<K, V>;
@@ -1 +1 @@
1
- {"version":3,"file":"ManyToManyMap.d.ts","sourceRoot":"","sources":["../../src/ManyToManyMap.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC,+CAA+C;IAC/C,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/C,gDAAgD;IAChD,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEtC,6CAA6C;IAC7C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAE1C,6CAA6C;IAC7C,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAE1C,kDAAkD;IAClD,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEvC,yCAAyC;IACzC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IAExB,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEhC,0CAA0C;IAC1C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAE/B,4CAA4C;IAC5C,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEnC,0CAA0C;IAC1C,KAAK,IAAI,IAAI,CAAC;CACf;AAED,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,OAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CAyG9D,CAAC"}
1
+ {"version":3,"file":"ManyToManyMap.d.ts","sourceRoot":"","sources":["../../src/ManyToManyMap.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC;;;OAGG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEnC;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEtC;;;OAGG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAElD;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAElD;;;OAGG;IACH,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC;IAExD;;;OAGG;IACH,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEpE,kDAAkD;IAClD,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEvC,yCAAyC;IACzC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAE5B,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEhC,0CAA0C;IAC1C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAE/B,4CAA4C;IAC5C,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEnC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;IAElB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,mDAAmD;IACnD,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,iEAAiE;IACjE,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB;AAED,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,OAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CA2I9D,CAAC"}
@@ -1,10 +1,14 @@
1
+ import { assert } from "./Assert.js";
1
2
  /** Creates a {@link ManyToManyMap}. */
2
3
  export const createManyToManyMap = () => {
3
4
  const forwardMap = new Map();
4
5
  const reverseMap = new Map();
6
+ let pairCountInternal = 0;
5
7
  const map = {
6
8
  add(key, value) {
7
9
  let values = forwardMap.get(key);
10
+ if (values?.has(value))
11
+ return false;
8
12
  if (!values) {
9
13
  values = new Set();
10
14
  forwardMap.set(key, values);
@@ -16,7 +20,8 @@ export const createManyToManyMap = () => {
16
20
  reverseMap.set(value, keys);
17
21
  }
18
22
  keys.add(key);
19
- return map;
23
+ pairCountInternal++;
24
+ return true;
20
25
  },
21
26
  remove(key, value) {
22
27
  const values = forwardMap.get(key);
@@ -27,12 +32,12 @@ export const createManyToManyMap = () => {
27
32
  forwardMap.delete(key);
28
33
  }
29
34
  const keys = reverseMap.get(value);
30
- if (keys?.size) {
31
- keys.delete(key);
32
- if (keys.size === 0) {
33
- reverseMap.delete(value);
34
- }
35
+ assert(keys, "Key-value mapping inconsistency");
36
+ keys.delete(key);
37
+ if (keys.size === 0) {
38
+ reverseMap.delete(value);
35
39
  }
40
+ pairCountInternal--;
36
41
  return true;
37
42
  },
38
43
  getValues(key) {
@@ -41,6 +46,22 @@ export const createManyToManyMap = () => {
41
46
  getKeys(value) {
42
47
  return reverseMap.get(value);
43
48
  },
49
+ forEach(callback) {
50
+ for (const [key, values] of forwardMap) {
51
+ for (const value of values)
52
+ callback(key, value);
53
+ }
54
+ },
55
+ [Symbol.iterator]() {
56
+ const iterator = function* () {
57
+ for (const [key, values] of forwardMap) {
58
+ for (const value of values) {
59
+ yield [key, value];
60
+ }
61
+ }
62
+ };
63
+ return iterator();
64
+ },
44
65
  hasPair(key, value) {
45
66
  const values = forwardMap.get(key);
46
67
  return values?.has(value) ?? false;
@@ -55,6 +76,7 @@ export const createManyToManyMap = () => {
55
76
  const values = forwardMap.get(key);
56
77
  if (!values)
57
78
  return false;
79
+ const removed = values.size;
58
80
  for (const value of values) {
59
81
  const keys = reverseMap.get(value);
60
82
  if (keys) {
@@ -65,12 +87,14 @@ export const createManyToManyMap = () => {
65
87
  }
66
88
  }
67
89
  forwardMap.delete(key);
90
+ pairCountInternal -= removed;
68
91
  return true;
69
92
  },
70
93
  deleteValue(value) {
71
94
  const keys = reverseMap.get(value);
72
95
  if (!keys)
73
96
  return false;
97
+ const removed = keys.size;
74
98
  for (const key of keys) {
75
99
  const values = forwardMap.get(key);
76
100
  if (values) {
@@ -81,11 +105,22 @@ export const createManyToManyMap = () => {
81
105
  }
82
106
  }
83
107
  reverseMap.delete(value);
108
+ pairCountInternal -= removed;
84
109
  return true;
85
110
  },
86
111
  clear() {
87
112
  forwardMap.clear();
88
113
  reverseMap.clear();
114
+ pairCountInternal = 0;
115
+ },
116
+ keyCount() {
117
+ return forwardMap.size;
118
+ },
119
+ valueCount() {
120
+ return reverseMap.size;
121
+ },
122
+ pairCount() {
123
+ return pairCountInternal;
89
124
  },
90
125
  };
91
126
  return map;