@evolu/common 8.6.2 → 8.8.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.
Files changed (303) hide show
  1. package/dist/src/Array.d.ts +21 -21
  2. package/dist/src/Array.js +5 -5
  3. package/dist/src/Assert.d.ts +478 -141
  4. package/dist/src/Assert.d.ts.map +1 -1
  5. package/dist/src/Assert.js +644 -99
  6. package/dist/src/Binary.d.ts +254 -0
  7. package/dist/src/Binary.d.ts.map +1 -0
  8. package/dist/src/Binary.js +958 -0
  9. package/dist/src/Console.d.ts +2 -10
  10. package/dist/src/Console.d.ts.map +1 -1
  11. package/dist/src/Console.js +6 -12
  12. package/dist/src/Crypto.d.ts +5 -5
  13. package/dist/src/Crypto.js +1 -1
  14. package/dist/src/Eq.d.ts +35 -40
  15. package/dist/src/Eq.d.ts.map +1 -1
  16. package/dist/src/Eq.js +51 -87
  17. package/dist/src/Function.d.ts +9 -14
  18. package/dist/src/Function.d.ts.map +1 -1
  19. package/dist/src/Function.js +7 -9
  20. package/dist/src/Http.d.ts +6 -7
  21. package/dist/src/Http.d.ts.map +1 -1
  22. package/dist/src/LeakDetector.d.ts.map +1 -1
  23. package/dist/src/LeakDetector.js +1 -0
  24. package/dist/src/LockManager.d.ts +1 -1
  25. package/dist/src/LockManager.js +1 -1
  26. package/dist/src/Lookup.d.ts +3 -2
  27. package/dist/src/Lookup.d.ts.map +1 -1
  28. package/dist/src/Lookup.js +4 -3
  29. package/dist/src/Number.d.ts +1 -1
  30. package/dist/src/Number.d.ts.map +1 -1
  31. package/dist/src/Number.js +1 -1
  32. package/dist/src/Object.d.ts +4 -4
  33. package/dist/src/Object.d.ts.map +1 -1
  34. package/dist/src/Object.js +5 -4
  35. package/dist/src/Option.d.ts +1 -2
  36. package/dist/src/Option.d.ts.map +1 -1
  37. package/dist/src/Option.js +1 -2
  38. package/dist/src/Platform.js +2 -0
  39. package/dist/src/Polyfills.js +4 -1
  40. package/dist/src/Random.d.ts +1 -1
  41. package/dist/src/Result.d.ts +49 -54
  42. package/dist/src/Result.d.ts.map +1 -1
  43. package/dist/src/Result.js +17 -22
  44. package/dist/src/Set.d.ts +6 -6
  45. package/dist/src/Set.js +2 -2
  46. package/dist/src/Sqlite.js +1 -1
  47. package/dist/src/String.js +3 -2
  48. package/dist/src/Task.d.ts +32 -32
  49. package/dist/src/Task.d.ts.map +1 -1
  50. package/dist/src/Task.js +20 -22
  51. package/dist/src/Test.d.ts +31 -3
  52. package/dist/src/Test.d.ts.map +1 -1
  53. package/dist/src/Test.js +46 -3
  54. package/dist/src/Time.d.ts +3 -11
  55. package/dist/src/Time.d.ts.map +1 -1
  56. package/dist/src/Time.js +10 -18
  57. package/dist/src/Type.d.ts +87 -85
  58. package/dist/src/Type.d.ts.map +1 -1
  59. package/dist/src/Type.js +70 -63
  60. package/dist/src/Types.d.ts +12 -12
  61. package/dist/src/Types.js +1 -1
  62. package/dist/src/WebSocket.d.ts +4 -7
  63. package/dist/src/WebSocket.d.ts.map +1 -1
  64. package/dist/src/Worker.d.ts +1 -1
  65. package/dist/src/Worker.js +7 -7
  66. package/dist/src/index.d.ts +1 -1
  67. package/dist/src/index.js +1 -1
  68. package/dist/src/intl/_en.d.ts.map +1 -1
  69. package/dist/src/intl/_en.js +1 -1
  70. package/dist/src/intl/ar.d.ts.map +1 -1
  71. package/dist/src/intl/ar.js +1 -1
  72. package/dist/src/intl/bn.d.ts.map +1 -1
  73. package/dist/src/intl/bn.js +1 -1
  74. package/dist/src/intl/ca.d.ts.map +1 -1
  75. package/dist/src/intl/ca.js +1 -1
  76. package/dist/src/intl/cs.d.ts.map +1 -1
  77. package/dist/src/intl/cs.js +1 -1
  78. package/dist/src/intl/da.d.ts.map +1 -1
  79. package/dist/src/intl/da.js +1 -1
  80. package/dist/src/intl/de.d.ts.map +1 -1
  81. package/dist/src/intl/de.js +1 -1
  82. package/dist/src/intl/el.d.ts.map +1 -1
  83. package/dist/src/intl/el.js +1 -1
  84. package/dist/src/intl/es.d.ts.map +1 -1
  85. package/dist/src/intl/es.js +1 -1
  86. package/dist/src/intl/fa.d.ts.map +1 -1
  87. package/dist/src/intl/fa.js +1 -1
  88. package/dist/src/intl/fi.d.ts.map +1 -1
  89. package/dist/src/intl/fi.js +1 -1
  90. package/dist/src/intl/fil.d.ts.map +1 -1
  91. package/dist/src/intl/fil.js +1 -1
  92. package/dist/src/intl/fr.d.ts.map +1 -1
  93. package/dist/src/intl/fr.js +1 -1
  94. package/dist/src/intl/he.d.ts.map +1 -1
  95. package/dist/src/intl/he.js +1 -1
  96. package/dist/src/intl/hi.d.ts.map +1 -1
  97. package/dist/src/intl/hi.js +1 -1
  98. package/dist/src/intl/hr.d.ts.map +1 -1
  99. package/dist/src/intl/hr.js +1 -1
  100. package/dist/src/intl/hu.d.ts.map +1 -1
  101. package/dist/src/intl/hu.js +1 -1
  102. package/dist/src/intl/id.d.ts.map +1 -1
  103. package/dist/src/intl/id.js +1 -1
  104. package/dist/src/intl/it.d.ts.map +1 -1
  105. package/dist/src/intl/it.js +1 -1
  106. package/dist/src/intl/ja.d.ts.map +1 -1
  107. package/dist/src/intl/ja.js +1 -1
  108. package/dist/src/intl/ko.d.ts.map +1 -1
  109. package/dist/src/intl/ko.js +1 -1
  110. package/dist/src/intl/ml.d.ts.map +1 -1
  111. package/dist/src/intl/ml.js +1 -1
  112. package/dist/src/intl/mr.d.ts.map +1 -1
  113. package/dist/src/intl/mr.js +1 -1
  114. package/dist/src/intl/ms.d.ts.map +1 -1
  115. package/dist/src/intl/ms.js +1 -1
  116. package/dist/src/intl/nb.d.ts.map +1 -1
  117. package/dist/src/intl/nb.js +1 -1
  118. package/dist/src/intl/nl.d.ts.map +1 -1
  119. package/dist/src/intl/nl.js +1 -1
  120. package/dist/src/intl/pa.d.ts.map +1 -1
  121. package/dist/src/intl/pa.js +1 -1
  122. package/dist/src/intl/pl.d.ts.map +1 -1
  123. package/dist/src/intl/pl.js +1 -1
  124. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  125. package/dist/src/intl/pt-BR.js +1 -1
  126. package/dist/src/intl/pt.d.ts.map +1 -1
  127. package/dist/src/intl/pt.js +1 -1
  128. package/dist/src/intl/ro.d.ts.map +1 -1
  129. package/dist/src/intl/ro.js +1 -1
  130. package/dist/src/intl/sk.d.ts.map +1 -1
  131. package/dist/src/intl/sk.js +1 -1
  132. package/dist/src/intl/sl.d.ts.map +1 -1
  133. package/dist/src/intl/sl.js +1 -1
  134. package/dist/src/intl/sv.d.ts.map +1 -1
  135. package/dist/src/intl/sv.js +1 -1
  136. package/dist/src/intl/sw.d.ts.map +1 -1
  137. package/dist/src/intl/sw.js +1 -1
  138. package/dist/src/intl/ta.d.ts.map +1 -1
  139. package/dist/src/intl/ta.js +1 -1
  140. package/dist/src/intl/te.d.ts.map +1 -1
  141. package/dist/src/intl/te.js +1 -1
  142. package/dist/src/intl/th.d.ts.map +1 -1
  143. package/dist/src/intl/th.js +1 -1
  144. package/dist/src/intl/tr.d.ts.map +1 -1
  145. package/dist/src/intl/tr.js +1 -1
  146. package/dist/src/intl/uk.d.ts.map +1 -1
  147. package/dist/src/intl/uk.js +1 -1
  148. package/dist/src/intl/ur.d.ts.map +1 -1
  149. package/dist/src/intl/ur.js +1 -1
  150. package/dist/src/intl/vi.d.ts.map +1 -1
  151. package/dist/src/intl/vi.js +1 -1
  152. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  153. package/dist/src/intl/zh-CN.js +1 -1
  154. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  155. package/dist/src/intl/zh-TW.js +1 -1
  156. package/dist/src/local-first/Evolu.d.ts +12 -12
  157. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  158. package/dist/src/local-first/Evolu.js +2 -2
  159. package/dist/src/local-first/Protocol.d.ts +28 -76
  160. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  161. package/dist/src/local-first/Protocol.js +45 -229
  162. package/dist/src/local-first/Query.d.ts +11 -11
  163. package/dist/src/local-first/Query.js +6 -6
  164. package/dist/src/local-first/Relay.d.ts +2 -2
  165. package/dist/src/local-first/Schema.d.ts +3 -5
  166. package/dist/src/local-first/Schema.d.ts.map +1 -1
  167. package/dist/src/local-first/Schema.js +2 -4
  168. package/dist/src/local-first/Shared.d.ts.map +1 -1
  169. package/dist/src/local-first/Shared.js +5 -5
  170. package/dist/src/local-first/Storage.js +4 -4
  171. package/dist/src/local-first/Timestamp.d.ts +9 -0
  172. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  173. package/dist/src/local-first/Timestamp.js +13 -10
  174. package/package.json +7 -5
  175. package/src/Array.test.ts +809 -0
  176. package/src/Array.ts +21 -21
  177. package/src/Assert.test.ts +1181 -0
  178. package/src/Assert.ts +971 -227
  179. package/src/BigInt.test.ts +28 -0
  180. package/src/Binary.test.ts +1128 -0
  181. package/src/Binary.ts +1377 -0
  182. package/src/Brand.test.ts +90 -0
  183. package/src/Cache.test.ts +183 -0
  184. package/src/Callbacks.test.ts +66 -0
  185. package/src/Console.test.ts +809 -0
  186. package/src/Console.ts +9 -17
  187. package/src/Crypto.test.ts +138 -0
  188. package/src/Crypto.ts +5 -5
  189. package/src/Eq.test.ts +333 -0
  190. package/src/Eq.ts +110 -176
  191. package/src/Error.test.ts +118 -0
  192. package/src/Function.test.ts +204 -0
  193. package/src/Function.ts +10 -15
  194. package/src/Http.test.ts +816 -0
  195. package/src/Http.ts +6 -7
  196. package/src/LeakDetector.test.ts +210 -0
  197. package/src/LeakDetector.ts +1 -0
  198. package/src/LockManager.ts +2 -2
  199. package/src/Lookup.test.ts +418 -0
  200. package/src/Lookup.ts +4 -3
  201. package/src/Microtask.test.ts +140 -0
  202. package/src/Number.test.ts +218 -0
  203. package/src/Number.ts +2 -2
  204. package/src/Object.test.ts +293 -0
  205. package/src/Object.ts +5 -4
  206. package/src/Option.test.ts +104 -0
  207. package/src/Option.ts +1 -2
  208. package/src/Order.test.ts +56 -0
  209. package/src/Platform.ts +2 -0
  210. package/src/Polyfills.ts +3 -4
  211. package/src/Random.test.ts +89 -0
  212. package/src/Random.ts +1 -1
  213. package/src/Ref.test.ts +139 -0
  214. package/src/RefCount.test.ts +184 -0
  215. package/src/Relation.test.ts +505 -0
  216. package/src/Resource.test.ts +4623 -0
  217. package/src/Result.test.ts +1829 -0
  218. package/src/Result.ts +49 -54
  219. package/src/Schedule.test.ts +1554 -0
  220. package/src/Set.test.ts +279 -0
  221. package/src/Set.ts +6 -6
  222. package/src/Sqlite.ts +1 -1
  223. package/src/Store.test.ts +288 -0
  224. package/src/String.test.ts +54 -0
  225. package/src/String.ts +3 -3
  226. package/src/Task.test.ts +10820 -0
  227. package/src/Task.ts +38 -43
  228. package/src/Test.test.ts +88 -0
  229. package/src/Test.ts +49 -3
  230. package/src/Time.test.ts +926 -0
  231. package/src/Time.ts +13 -21
  232. package/src/Type.base64.test.ts +88 -0
  233. package/src/Type.test.ts +20465 -0
  234. package/src/Type.ts +114 -111
  235. package/src/Types.test.ts +324 -0
  236. package/src/Types.ts +13 -13
  237. package/src/WebSocket.ts +4 -7
  238. package/src/Worker.test.ts +755 -0
  239. package/src/Worker.ts +13 -18
  240. package/src/index.ts +1 -1
  241. package/src/intl/_en.ts +1 -1
  242. package/src/intl/ar.ts +1 -1
  243. package/src/intl/bn.ts +1 -1
  244. package/src/intl/ca.ts +1 -1
  245. package/src/intl/cs.ts +1 -1
  246. package/src/intl/da.ts +1 -1
  247. package/src/intl/de.ts +1 -1
  248. package/src/intl/el.ts +1 -1
  249. package/src/intl/es.ts +1 -1
  250. package/src/intl/fa.ts +1 -1
  251. package/src/intl/fi.ts +1 -1
  252. package/src/intl/fil.ts +1 -1
  253. package/src/intl/fr.ts +1 -1
  254. package/src/intl/he.ts +1 -1
  255. package/src/intl/hi.ts +1 -1
  256. package/src/intl/hr.ts +1 -1
  257. package/src/intl/hu.ts +1 -1
  258. package/src/intl/id.ts +1 -1
  259. package/src/intl/intl.test.ts +3106 -0
  260. package/src/intl/it.ts +1 -1
  261. package/src/intl/ja.ts +1 -1
  262. package/src/intl/ko.ts +1 -1
  263. package/src/intl/ml.ts +1 -1
  264. package/src/intl/mr.ts +1 -1
  265. package/src/intl/ms.ts +1 -1
  266. package/src/intl/nb.ts +1 -1
  267. package/src/intl/nl.ts +1 -1
  268. package/src/intl/pa.ts +1 -1
  269. package/src/intl/pl.ts +1 -1
  270. package/src/intl/pt-BR.ts +1 -1
  271. package/src/intl/pt.ts +1 -1
  272. package/src/intl/ro.ts +1 -1
  273. package/src/intl/sk.ts +1 -1
  274. package/src/intl/sl.ts +1 -1
  275. package/src/intl/sv.ts +1 -1
  276. package/src/intl/sw.ts +1 -1
  277. package/src/intl/ta.ts +1 -1
  278. package/src/intl/te.ts +1 -1
  279. package/src/intl/th.ts +1 -1
  280. package/src/intl/tr.ts +1 -1
  281. package/src/intl/uk.ts +1 -1
  282. package/src/intl/ur.ts +1 -1
  283. package/src/intl/vi.ts +1 -1
  284. package/src/intl/zh-CN.ts +1 -1
  285. package/src/intl/zh-TW.ts +1 -1
  286. package/src/local-first/Evolu.test.ts +1921 -0
  287. package/src/local-first/Evolu.ts +18 -14
  288. package/src/local-first/Owner.test.ts +83 -0
  289. package/src/local-first/Protocol.test.ts +1037 -0
  290. package/src/local-first/Protocol.ts +75 -290
  291. package/src/local-first/Query.test.ts +349 -0
  292. package/src/local-first/Query.ts +11 -11
  293. package/src/local-first/Relay.ts +2 -2
  294. package/src/local-first/Schema.ts +3 -5
  295. package/src/local-first/Shared.test.ts +1828 -0
  296. package/src/local-first/Shared.ts +9 -8
  297. package/src/local-first/Storage.ts +4 -4
  298. package/src/local-first/Timestamp.test.ts +374 -0
  299. package/src/local-first/Timestamp.ts +22 -10
  300. package/dist/src/Buffer.d.ts +0 -122
  301. package/dist/src/Buffer.d.ts.map +0 -1
  302. package/dist/src/Buffer.js +0 -69
  303. package/src/Buffer.ts +0 -206
package/src/Binary.ts ADDED
@@ -0,0 +1,1377 @@
1
+ /**
2
+ * Binary data handling and byte array utilities.
3
+ *
4
+ * Buffer-based decoding functions intentionally throw errors instead of
5
+ * returning {@link Result}. This is a deliberate micro-optimization for Evolu
6
+ * Protocol's hot paths: Result is inexpensive, but returning decoded values
7
+ * directly avoids its success-case allocation, and using `Error` objects
8
+ * preserves stack traces. In the future, we will try returning `Error` objects
9
+ * in `Result` values to measure the real-world performance impact.
10
+ *
11
+ * @module
12
+ */
13
+
14
+ import { bytesToUtf8, utf8ToBytes } from "@noble/ciphers/utils.js";
15
+ import type { Result } from "./Result.ts";
16
+ import type {
17
+ FiniteNumber,
18
+ JsonValue,
19
+ NonNegativeInt,
20
+ PositiveInt,
21
+ } from "./Type.ts";
22
+ export { bytesToHex, concatBytes, hexToBytes } from "@noble/ciphers/utils.js";
23
+ export { bytesToUtf8, utf8ToBytes };
24
+
25
+ /**
26
+ * Custom error for {@link Buffer}-related failures like premature end of data.
27
+ * Provides better stack traces for debugging binary protocol issues.
28
+ */
29
+ export class BufferError extends Error {
30
+ constructor(message: string) {
31
+ super(message);
32
+ this.name = this.constructor.name;
33
+
34
+ Error.captureStackTrace(this, this.constructor);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * A Buffer is a dynamic, resizable container for binary data, optimized for
40
+ * scenarios where the final size is unknown. It grows exponentially (doubling
41
+ * its capacity) to minimize memory reallocations and uses `subarray` for
42
+ * efficient, copy-free data access in methods like `unwrap` and `shift`.
43
+ *
44
+ * ## Recommended Usage
45
+ *
46
+ * Create as few Buffers as possible—typically one main Buffer for the final
47
+ * output. Temporary Buffers are allowed when necessary (e.g., for
48
+ * variable-length headers), but avoid excessive allocations. Buffers can be
49
+ * reused within functions by leveraging `reset` to clear contents while
50
+ * preserving capacity, or `truncate` to adjust the length to a specific size,
51
+ * reducing the need for new allocations. Pass Buffers to `encode*` functions to
52
+ * append serialized data and use `decode*` functions to extract data.
53
+ *
54
+ * ### Example
55
+ *
56
+ * ```ts
57
+ * import {
58
+ * assertEqual,
59
+ * assertErr,
60
+ * assertInstanceOf,
61
+ * createBuffer,
62
+ * createIdFromString,
63
+ * IdBytes,
64
+ * idBytesToId,
65
+ * idBytesTypeValueLength,
66
+ * idToIdBytes,
67
+ * NonNegativeInt,
68
+ * trySync,
69
+ * decodeNonNegativeInt,
70
+ * encodeNonNegativeInt,
71
+ * } from "@evolu/common";
72
+ *
73
+ * const buffer = createBuffer();
74
+ * const id = createIdFromString("buffer-example");
75
+ * encodeNonNegativeInt(buffer, NonNegativeInt.orThrow(300));
76
+ * buffer.extend(idToIdBytes(id));
77
+ *
78
+ * const decoder = createBuffer(buffer.unwrap());
79
+ * assertEqual(decodeNonNegativeInt(decoder), 300);
80
+ * const decodedId = idBytesToId(
81
+ * IdBytes.orThrow(decoder.shiftN(idBytesTypeValueLength)),
82
+ * );
83
+ * assertEqual(decodedId, id);
84
+ * const result = trySync(() => decodeNonNegativeInt(decoder));
85
+ * assertErr(result);
86
+ * assertInstanceOf(result.error, Error);
87
+ * assertEqual(result.error.message, "Buffer parse ended prematurely");
88
+ * ```
89
+ *
90
+ * For more on exponential growth, see:
91
+ * https://blog.mozilla.org/nnethercote/2014/11/04/please-grow-your-buffers-exponentially
92
+ */
93
+ export interface Buffer {
94
+ /** Returns the current capacity of the buffer. */
95
+ getCapacity: () => NonNegativeInt;
96
+
97
+ /** Returns the current number of bytes stored in the buffer. */
98
+ getLength: () => NonNegativeInt;
99
+
100
+ /**
101
+ * Appends binary data to the buffer, resizing if necessary. Throws if
102
+ * `arg.length` is not a non-negative safe integer.
103
+ */
104
+ extend: (arg: Uint8Array | ArrayLike<number>) => void;
105
+
106
+ /**
107
+ * Removes and returns the first byte. Throws an `Error` with message "Buffer
108
+ * parse ended prematurely" if the buffer is empty.
109
+ */
110
+ shift: () => NonNegativeInt;
111
+
112
+ /**
113
+ * Removes and returns the first `n` bytes. Throws an `Error` with message
114
+ * "Buffer parse ended prematurely" if fewer than `n` bytes remain.
115
+ */
116
+ shiftN: (n: NonNegativeInt) => Uint8Array;
117
+
118
+ /**
119
+ * Truncates the buffer to the specified length, discarding data from the end.
120
+ * Throws if the new length is greater than the current length.
121
+ */
122
+ truncate: (length: NonNegativeInt) => void;
123
+
124
+ /**
125
+ * Resets the buffer to its initial empty state, preserving its capacity.
126
+ *
127
+ * This allows efficient buffer reuse without reallocating memory. Use this
128
+ * when you want to clear the buffer and write new data, avoiding unnecessary
129
+ * allocations.
130
+ */
131
+ reset: () => void;
132
+
133
+ /**
134
+ * Returns a view of the buffer’s current data. Do not modify this array, as
135
+ * it directly alters the buffer’s internal state, potentially breaking
136
+ * subsequent operations.
137
+ */
138
+ unwrap: () => Uint8Array;
139
+ }
140
+
141
+ /** Creates a {@link Buffer} for efficient byte operations. */
142
+ export const createBuffer = (
143
+ arrayLike?: Uint8Array | ArrayLike<number>,
144
+ ): Buffer => {
145
+ const initialLength = arrayLike?.length ?? 0;
146
+ assertNonNegativeInt(initialLength, "arrayLike.length");
147
+
148
+ let value = arrayLike ? new Uint8Array(arrayLike) : new Uint8Array(512);
149
+ let length = initialLength;
150
+
151
+ const buffer: Buffer = {
152
+ getCapacity: () => value.length as NonNegativeInt,
153
+
154
+ getLength: () => length,
155
+
156
+ extend: (arg) => {
157
+ const argLength = arg.length;
158
+ assertNonNegativeInt(argLength, "arg.length");
159
+
160
+ const targetSize = length + argLength;
161
+ assertNonNegativeInt(targetSize, "Buffer length");
162
+
163
+ if (value.length < targetSize) {
164
+ const oldValue = value;
165
+ const newCapacity = Math.max(value.length * 2, targetSize);
166
+ value = new Uint8Array(newCapacity);
167
+ value.set(oldValue);
168
+ }
169
+ value.set(arg, length);
170
+ length = targetSize;
171
+ },
172
+
173
+ shift: () => {
174
+ assertBufferHasRemainingBytes(length, 1);
175
+ const first = value[0];
176
+ value = value.subarray(1);
177
+ length--;
178
+ return first as NonNegativeInt;
179
+ },
180
+
181
+ shiftN: (n) => {
182
+ assertBufferHasRemainingBytes(length, n);
183
+ const subarray = value.subarray(0, n);
184
+ value = value.subarray(n);
185
+ length = (length - n) as NonNegativeInt;
186
+ return subarray;
187
+ },
188
+
189
+ truncate: (newLength) => {
190
+ if (newLength > length) {
191
+ throw new BufferError(
192
+ "Cannot truncate to a length greater than current",
193
+ );
194
+ }
195
+ length = newLength;
196
+ },
197
+
198
+ reset: () => {
199
+ length = 0 as NonNegativeInt;
200
+ },
201
+
202
+ unwrap: () => (value.length === length ? value : value.subarray(0, length)),
203
+ };
204
+
205
+ return buffer;
206
+ };
207
+
208
+ /**
209
+ * Evolu uses MessagePack to handle finite numbers except for NonNegativeInt.
210
+ * For NonNegativeInt, Evolu provides more efficient encoding.
211
+ */
212
+ export const encodeNumber = (buffer: Buffer, number: FiniteNumber): void => {
213
+ encodeJsonValue(buffer, number);
214
+ };
215
+
216
+ export const decodeNumber = (buffer: Buffer): FiniteNumber => {
217
+ const value = decodeJsonValue(buffer);
218
+ if (typeof value !== "number") {
219
+ throw new BufferError("Expected an encoded number.");
220
+ }
221
+ return value;
222
+ };
223
+
224
+ /**
225
+ * Encodes an array of boolean flags into a single byte.
226
+ *
227
+ * Each element in the array corresponds to a bit (0-7). Array can have 0-8
228
+ * elements.
229
+ *
230
+ * ### Example
231
+ *
232
+ * ```ts
233
+ * import { assertEqual, createBuffer, encodeFlags } from "@evolu/common";
234
+ *
235
+ * const buffer = createBuffer();
236
+ * encodeFlags(buffer, [true, false, true]);
237
+ *
238
+ * assertEqual(buffer.unwrap(), new Uint8Array([0b101]));
239
+ * ```
240
+ */
241
+ export const encodeFlags = (
242
+ buffer: Buffer,
243
+ flags: ReadonlyArray<boolean>,
244
+ ): void => {
245
+ let byte = 0;
246
+ for (let i = 0; i < flags.length && i < 8; i++) {
247
+ if (flags[i]) {
248
+ byte |= 1 << i;
249
+ }
250
+ }
251
+ buffer.extend([byte]);
252
+ };
253
+
254
+ /**
255
+ * Decodes a byte into an array of boolean flags.
256
+ *
257
+ * ### Example
258
+ *
259
+ * ```ts
260
+ * import {
261
+ * assertEqual,
262
+ * createBuffer,
263
+ * decodeFlags,
264
+ * PositiveInt,
265
+ * } from "@evolu/common";
266
+ *
267
+ * const buffer = createBuffer([0b101]);
268
+ * const flags = decodeFlags(buffer, PositiveInt.orThrow(3));
269
+ *
270
+ * assertEqual(flags, [true, false, true]);
271
+ * assertEqual(buffer.getLength(), 0);
272
+ * ```
273
+ */
274
+ export const decodeFlags = (
275
+ buffer: Buffer,
276
+ count: PositiveInt,
277
+ ): ReadonlyArray<boolean> => {
278
+ const byte = buffer.shift();
279
+ const length = Math.min(count, 8);
280
+ // oxlint-disable-next-line unicorn/no-new-array -- Preallocation is intentional in this decoding hot path.
281
+ const flags = new Array<boolean>(length);
282
+ for (let i = 0; i < length; i++) {
283
+ flags[i] = (byte & (1 << i)) !== 0;
284
+ }
285
+ return flags;
286
+ };
287
+
288
+ /**
289
+ * Encodes a non-negative integer into a variable-length integer format. It's
290
+ * more efficient than encoding via {@link encodeNumber}.
291
+ *
292
+ * https://en.wikipedia.org/wiki/Variable-length_quantity
293
+ */
294
+ export const encodeNonNegativeInt = (
295
+ buffer: Buffer,
296
+ value: NonNegativeInt,
297
+ ): void => {
298
+ if (value === 0) {
299
+ buffer.extend([0]);
300
+ return;
301
+ }
302
+
303
+ let remaining = BigInt(value);
304
+ const bytes: Array<number> = [];
305
+
306
+ while (remaining !== 0n) {
307
+ const byte = Number(remaining & 127n);
308
+ bytes.push(byte);
309
+ remaining >>= 7n;
310
+ }
311
+
312
+ for (let i = 0; i < bytes.length - 1; i++) {
313
+ bytes[i] |= 128;
314
+ }
315
+
316
+ buffer.extend(bytes);
317
+ };
318
+
319
+ /**
320
+ * Decodes a non-negative integer from a variable-length integer format.
321
+ *
322
+ * https://en.wikipedia.org/wiki/Variable-length_quantity
323
+ */
324
+ export const decodeNonNegativeInt = (buffer: Buffer): NonNegativeInt => {
325
+ let result = 0n;
326
+ let shift = 0n;
327
+ let byte = 0;
328
+
329
+ // 8 is the smallest required count
330
+ for (let byteCount = 0; byteCount < 8; byteCount++) {
331
+ byte = buffer.shift();
332
+ result |= BigInt(byte & 127) << shift;
333
+ if ((byte & 128) === 0) break;
334
+ shift += 7n;
335
+ }
336
+
337
+ if ((byte & 128) !== 0) {
338
+ throw new BufferError(
339
+ "Variable-length quantity must terminate within 8 bytes.",
340
+ );
341
+ }
342
+
343
+ const value = Number(result);
344
+ assertNonNegativeInt(value, "Decoded integer");
345
+ return value;
346
+ };
347
+
348
+ /** Encodes the length of an array-like value. */
349
+ export const encodeLength = (
350
+ buffer: Buffer,
351
+ value: ArrayLike<unknown>,
352
+ ): void => {
353
+ assertNonNegativeInt(value.length, "Array-like length");
354
+ encodeNonNegativeInt(buffer, value.length);
355
+ };
356
+
357
+ /** Decodes an array-like value length. */
358
+ export const decodeLength = decodeNonNegativeInt;
359
+
360
+ /** Encodes a length-prefixed UTF-8 string. */
361
+ export const encodeString = (buffer: Buffer, value: string): void => {
362
+ const bytes = utf8ToBytes(value);
363
+ encodeLength(buffer, bytes);
364
+ buffer.extend(bytes);
365
+ };
366
+
367
+ /** Decodes a length-prefixed UTF-8 string. */
368
+ export const decodeString = (buffer: Buffer): string => {
369
+ const length = decodeLength(buffer);
370
+ const bytes = buffer.shiftN(length);
371
+ return bytesToUtf8(bytes);
372
+ };
373
+
374
+ /** Incrementally encodes consecutive equal values using run-length encoding. */
375
+ export interface RunLengthEncoder<T> {
376
+ readonly add: (value: T) => void;
377
+ readonly getLength: () => NonNegativeInt;
378
+ readonly unwrap: () => Uint8Array;
379
+ }
380
+
381
+ /** Creates an incremental run-length encoder. */
382
+ export const createRunLengthEncoder = <T>(
383
+ encodeValue: (buffer: Buffer, value: T) => void,
384
+ ): RunLengthEncoder<T> => {
385
+ const buffer = createBuffer();
386
+ let previousLength = 0 as NonNegativeInt;
387
+ let previousValue = null as T | null;
388
+ let runLength = 0 as NonNegativeInt;
389
+
390
+ return {
391
+ add: (value) => {
392
+ if (Object.is(value, previousValue)) {
393
+ runLength++;
394
+ buffer.truncate(previousLength);
395
+ } else {
396
+ previousValue = value;
397
+ runLength = 1 as NonNegativeInt;
398
+ }
399
+ previousLength = buffer.getLength();
400
+ encodeValue(buffer, value);
401
+ encodeNonNegativeInt(buffer, runLength);
402
+ },
403
+
404
+ getLength: () => buffer.getLength(),
405
+
406
+ unwrap: () => buffer.unwrap(),
407
+ };
408
+ };
409
+
410
+ /** Decodes a run-length encoded sequence. */
411
+ export const decodeRle = <T>(
412
+ buffer: Buffer,
413
+ length: NonNegativeInt,
414
+ decodeValue: () => T,
415
+ ): ReadonlyArray<T> => {
416
+ // oxlint-disable-next-line unicorn/no-new-array -- Preallocation is intentional in this decoding hot path.
417
+ const values = new Array<T>(length);
418
+ let index = 0;
419
+ while (index < length) {
420
+ const value = decodeValue();
421
+ const runLength = decodeNonNegativeInt(buffer);
422
+
423
+ // Prevent infinite loop on malformed input.
424
+ if (runLength === 0) {
425
+ throw new BufferError("Invalid RLE encoding: runLength must be positive");
426
+ }
427
+
428
+ const remaining = length - index;
429
+
430
+ // Prevent CPU/memory amplification via oversized runLength.
431
+ if (runLength > remaining) {
432
+ throw new BufferError(
433
+ `Invalid RLE encoding: runLength ${runLength} exceeds remaining ${remaining}`,
434
+ );
435
+ }
436
+
437
+ for (let i = 0; i < runLength; i++) {
438
+ values[index] = value;
439
+ index++;
440
+ }
441
+ }
442
+
443
+ return values;
444
+ };
445
+
446
+ // Inspired by msgpackr 2.0.5, licensed under the MIT License.
447
+ // This implementation is specialized for Evolu's JsonValue domain.
448
+ //
449
+ // MIT License
450
+ //
451
+ // Copyright (c) 2020 Kris Zyp
452
+ //
453
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
454
+ // of this software and associated documentation files (the "Software"), to deal
455
+ // in the Software without restriction, including without limitation the rights
456
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
457
+ // copies of the Software, and to permit persons to whom the Software is
458
+ // furnished to do so, subject to the following conditions:
459
+ //
460
+ // The above copyright notice and this permission notice shall be included in all
461
+ // copies or substantial portions of the Software.
462
+ //
463
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
464
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
465
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
466
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
467
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
468
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
469
+ // SOFTWARE.
470
+
471
+ interface JsonKeyCacheEntry {
472
+ readonly bytes: Uint8Array;
473
+ readonly value: string;
474
+ }
475
+
476
+ const jsonKeyCacheSize = 4096;
477
+ const maxCachedJsonKeyByteLength = 32;
478
+ const maxJsonNestingDepth = 1_000;
479
+ let jsonEncoderTarget = new Uint8Array(8192);
480
+ let jsonEncoderTargetView = new DataView(jsonEncoderTarget.buffer);
481
+ let jsonEncoderPosition = 0;
482
+ let jsonEncoderDepth = 0;
483
+ let jsonEncoderIsActive = false;
484
+ const emptyJsonDecoderSource: Uint8Array = new Uint8Array(0);
485
+ const emptyJsonDecoderView: DataView = new DataView(
486
+ emptyJsonDecoderSource.buffer,
487
+ );
488
+ let jsonDecoderSource: Uint8Array = emptyJsonDecoderSource;
489
+ let jsonDecoderView: DataView = emptyJsonDecoderView;
490
+ let jsonDecoderPosition = 0;
491
+ let jsonDecoderDepth = 0;
492
+ const jsonStringFromCharCode = String.fromCharCode;
493
+ // Cache only short keys and use a fixed table to bound retained memory.
494
+ const jsonKeyCache: Array<JsonKeyCacheEntry | undefined> = Array.from(
495
+ { length: jsonKeyCacheSize },
496
+ () => undefined,
497
+ );
498
+
499
+ /**
500
+ * Encodes a {@link JsonValue} using the MessagePack format.
501
+ *
502
+ * ### Example
503
+ *
504
+ * ```ts
505
+ * import {
506
+ * assertEqual,
507
+ * createBuffer,
508
+ * encodeJsonValue,
509
+ * JsonValue,
510
+ * } from "@evolu/common";
511
+ *
512
+ * const buffer = createBuffer();
513
+ * const value = JsonValue.orThrow({ name: "Ada" });
514
+ *
515
+ * encodeJsonValue(buffer, value);
516
+ *
517
+ * assertEqual(
518
+ * buffer.unwrap(),
519
+ * new Uint8Array([
520
+ * 0x81, 0xa4, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x41, 0x64, 0x61,
521
+ * ]),
522
+ * );
523
+ * ```
524
+ *
525
+ * Encoding is artificially limited to 1,000 nested arrays or objects to keep
526
+ * recursive encoding and decoding safe and symmetric. JSON data should not
527
+ * require such depth; flatten or split deeply nested data, or use a
528
+ * purpose-built serialization format.
529
+ */
530
+ export const encodeJsonValue = (buffer: Buffer, value: JsonValue): void => {
531
+ if (jsonEncoderIsActive) {
532
+ throw new BufferError("Reentrant JSON encoding is not supported.");
533
+ }
534
+ jsonEncoderIsActive = true;
535
+ jsonEncoderPosition = 0;
536
+
537
+ try {
538
+ encodeJsonValueToTarget(value);
539
+ buffer.extend(jsonEncoderTarget.subarray(0, jsonEncoderPosition));
540
+ } finally {
541
+ jsonEncoderPosition = 0;
542
+ jsonEncoderDepth = 0;
543
+ jsonEncoderIsActive = false;
544
+ }
545
+ };
546
+
547
+ /**
548
+ * Decodes a {@link JsonValue} using the MessagePack format.
549
+ *
550
+ * Throws a {@link BufferError} without modifying the Buffer if the encoded value
551
+ * is malformed, truncated, unsupported, outside the JsonValue domain, or
552
+ * exceeds 1,000 nested arrays or objects.
553
+ *
554
+ * ### Example
555
+ *
556
+ * ```ts
557
+ * import {
558
+ * assertEqual,
559
+ * createBuffer,
560
+ * decodeJsonValue,
561
+ * } from "@evolu/common";
562
+ *
563
+ * const buffer = createBuffer([
564
+ * 0x81, 0xa4, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x41, 0x64, 0x61,
565
+ * ]);
566
+ *
567
+ * assertEqual(decodeJsonValue(buffer), { name: "Ada" });
568
+ * assertEqual(buffer.unwrap(), new Uint8Array());
569
+ * ```
570
+ */
571
+ export const decodeJsonValue = (buffer: Buffer): JsonValue => {
572
+ const source = buffer.unwrap();
573
+ jsonDecoderSource = source;
574
+ jsonDecoderView = new DataView(
575
+ source.buffer,
576
+ source.byteOffset,
577
+ source.byteLength,
578
+ );
579
+ jsonDecoderPosition = 0;
580
+
581
+ try {
582
+ const value = decodeJsonValueFromSource();
583
+ buffer.shiftN(jsonDecoderPosition as NonNegativeInt);
584
+ return value;
585
+ } catch (error) {
586
+ if (error instanceof BufferError) throw error;
587
+
588
+ throw new BufferError("Invalid MessagePack data");
589
+ } finally {
590
+ jsonDecoderSource = emptyJsonDecoderSource;
591
+ jsonDecoderView = emptyJsonDecoderView;
592
+ jsonDecoderPosition = 0;
593
+ jsonDecoderDepth = 0;
594
+ }
595
+ };
596
+
597
+ const encodeJsonValueToTarget = (value: JsonValue): void => {
598
+ if (value === null) {
599
+ writeJsonEncoderByte(0xc0);
600
+ return;
601
+ }
602
+
603
+ // oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
604
+ switch (typeof value) {
605
+ case "string":
606
+ encodeJsonStringToTarget(value);
607
+ return;
608
+ case "number": {
609
+ if (Object.is(value, -0)) {
610
+ ensureJsonEncoderCapacity(9);
611
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xcb;
612
+ jsonEncoderTargetView.setFloat64(jsonEncoderPosition, value);
613
+ jsonEncoderPosition += 8;
614
+ return;
615
+ }
616
+
617
+ if (value >>> 0 === value) {
618
+ if (value < 0x80) {
619
+ writeJsonEncoderByte(value);
620
+ } else if (value < 0x100) {
621
+ ensureJsonEncoderCapacity(2);
622
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xcc;
623
+ jsonEncoderTarget[jsonEncoderPosition++] = value;
624
+ } else if (value < 0x10000) {
625
+ ensureJsonEncoderCapacity(3);
626
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xcd;
627
+ jsonEncoderTargetView.setUint16(jsonEncoderPosition, value);
628
+ jsonEncoderPosition += 2;
629
+ } else {
630
+ ensureJsonEncoderCapacity(5);
631
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xce;
632
+ jsonEncoderTargetView.setUint32(jsonEncoderPosition, value);
633
+ jsonEncoderPosition += 4;
634
+ }
635
+ return;
636
+ }
637
+
638
+ if (Number.isInteger(value) && value >= -0x80000000 && value < 0) {
639
+ if (value >= -0x20) {
640
+ writeJsonEncoderByte(0x100 + value);
641
+ } else if (value >= -0x80) {
642
+ ensureJsonEncoderCapacity(2);
643
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xd0;
644
+ jsonEncoderTargetView.setInt8(jsonEncoderPosition++, value);
645
+ } else if (value >= -0x8000) {
646
+ ensureJsonEncoderCapacity(3);
647
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xd1;
648
+ jsonEncoderTargetView.setInt16(jsonEncoderPosition, value);
649
+ jsonEncoderPosition += 2;
650
+ } else {
651
+ ensureJsonEncoderCapacity(5);
652
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xd2;
653
+ jsonEncoderTargetView.setInt32(jsonEncoderPosition, value);
654
+ jsonEncoderPosition += 4;
655
+ }
656
+ return;
657
+ }
658
+
659
+ ensureJsonEncoderCapacity(9);
660
+ jsonEncoderTarget[jsonEncoderPosition++] = 0xcb;
661
+ jsonEncoderTargetView.setFloat64(jsonEncoderPosition, value);
662
+ jsonEncoderPosition += 8;
663
+ return;
664
+ }
665
+ case "boolean":
666
+ writeJsonEncoderByte(value ? 0xc3 : 0xc2);
667
+ return;
668
+ case "object": {
669
+ if (jsonEncoderDepth >= maxJsonNestingDepth) {
670
+ throw new BufferError(
671
+ `JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`,
672
+ );
673
+ }
674
+ jsonEncoderDepth++;
675
+
676
+ if (Array.isArray(value)) {
677
+ const array = value as ReadonlyArray<JsonValue>;
678
+ const length = array.length;
679
+ writeJsonCollectionHeader(length, 0x90, 0xdc, 0xdd);
680
+
681
+ for (const item of array) encodeJsonValueToTarget(item);
682
+ jsonEncoderDepth--;
683
+ return;
684
+ }
685
+
686
+ const object = value as Readonly<Record<string, JsonValue>>;
687
+ const keys = Object.keys(object);
688
+ writeJsonCollectionHeader(keys.length, 0x80, 0xde, 0xdf);
689
+
690
+ for (const key of keys) {
691
+ encodeJsonStringToTarget(key);
692
+ encodeJsonValueToTarget(object[key]);
693
+ }
694
+ jsonEncoderDepth--;
695
+ }
696
+ }
697
+ };
698
+
699
+ const encodeJsonStringToTarget = (value: string): void => {
700
+ const valueLength = value.length;
701
+ const headerLength =
702
+ valueLength < 0x20
703
+ ? 1
704
+ : valueLength < 0x100
705
+ ? 2
706
+ : valueLength < 0x10000
707
+ ? 3
708
+ : 5;
709
+ ensureJsonEncoderCapacity(5 + valueLength * 3);
710
+
711
+ const headerPosition = jsonEncoderPosition;
712
+ jsonEncoderPosition += headerLength;
713
+
714
+ for (let index = 0; index < valueLength; index++) {
715
+ let first = value.charCodeAt(index);
716
+
717
+ if (first < 0x80) {
718
+ jsonEncoderTarget[jsonEncoderPosition++] = first;
719
+ } else if (first < 0x800) {
720
+ jsonEncoderTarget[jsonEncoderPosition++] = (first >> 6) | 0xc0;
721
+ jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
722
+ } else if (
723
+ (first & 0xfc00) === 0xd800 &&
724
+ (value.charCodeAt(index + 1) & 0xfc00) === 0xdc00
725
+ ) {
726
+ const second = value.charCodeAt(++index);
727
+ first = 0x10000 + ((first & 0x03ff) << 10) + (second & 0x03ff);
728
+ jsonEncoderTarget[jsonEncoderPosition++] = (first >> 18) | 0xf0;
729
+ jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 12) & 0x3f) | 0x80;
730
+ jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 6) & 0x3f) | 0x80;
731
+ jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
732
+ } else {
733
+ jsonEncoderTarget[jsonEncoderPosition++] = (first >> 12) | 0xe0;
734
+ jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 6) & 0x3f) | 0x80;
735
+ jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
736
+ }
737
+ }
738
+
739
+ const byteLength = jsonEncoderPosition - headerPosition - headerLength;
740
+ assertMessagePackLength(byteLength, "String byte length");
741
+
742
+ if (byteLength < 0x20) {
743
+ jsonEncoderTarget[headerPosition] = 0xa0 | byteLength;
744
+ return;
745
+ }
746
+
747
+ if (byteLength < 0x100) {
748
+ if (headerLength === 1) {
749
+ jsonEncoderTarget.copyWithin(
750
+ headerPosition + 2,
751
+ headerPosition + 1,
752
+ jsonEncoderPosition,
753
+ );
754
+ jsonEncoderPosition++;
755
+ }
756
+ jsonEncoderTarget[headerPosition] = 0xd9;
757
+ jsonEncoderTarget[headerPosition + 1] = byteLength;
758
+ return;
759
+ }
760
+
761
+ if (byteLength < 0x10000) {
762
+ if (headerLength < 3) {
763
+ const additionalHeaderLength = 3 - headerLength;
764
+ jsonEncoderTarget.copyWithin(
765
+ headerPosition + 3,
766
+ headerPosition + headerLength,
767
+ jsonEncoderPosition,
768
+ );
769
+ jsonEncoderPosition += additionalHeaderLength;
770
+ }
771
+ jsonEncoderTarget[headerPosition] = 0xda;
772
+ jsonEncoderTargetView.setUint16(headerPosition + 1, byteLength);
773
+ return;
774
+ }
775
+
776
+ if (headerLength < 5) {
777
+ const additionalHeaderLength = 5 - headerLength;
778
+ jsonEncoderTarget.copyWithin(
779
+ headerPosition + 5,
780
+ headerPosition + headerLength,
781
+ jsonEncoderPosition,
782
+ );
783
+ jsonEncoderPosition += additionalHeaderLength;
784
+ }
785
+ jsonEncoderTarget[headerPosition] = 0xdb;
786
+ jsonEncoderTargetView.setUint32(headerPosition + 1, byteLength);
787
+ };
788
+
789
+ const writeJsonCollectionHeader = (
790
+ length: number,
791
+ fixedMarker: number,
792
+ marker16: number,
793
+ marker32: number,
794
+ ): void => {
795
+ assertMessagePackLength(length, "Collection length");
796
+
797
+ if (length < 0x10) {
798
+ writeJsonEncoderByte(fixedMarker | length);
799
+ } else if (length < 0x10000) {
800
+ ensureJsonEncoderCapacity(3);
801
+ jsonEncoderTarget[jsonEncoderPosition++] = marker16;
802
+ jsonEncoderTargetView.setUint16(jsonEncoderPosition, length);
803
+ jsonEncoderPosition += 2;
804
+ } else {
805
+ ensureJsonEncoderCapacity(5);
806
+ jsonEncoderTarget[jsonEncoderPosition++] = marker32;
807
+ jsonEncoderTargetView.setUint32(jsonEncoderPosition, length);
808
+ jsonEncoderPosition += 4;
809
+ }
810
+ };
811
+
812
+ const writeJsonEncoderByte = (value: number): void => {
813
+ ensureJsonEncoderCapacity(1);
814
+ jsonEncoderTarget[jsonEncoderPosition++] = value;
815
+ };
816
+
817
+ const ensureJsonEncoderCapacity = (additionalLength: number): void => {
818
+ const requiredLength = jsonEncoderPosition + additionalLength;
819
+ assertNonNegativeInt(requiredLength, "Encoded JSON value length");
820
+
821
+ if (requiredLength <= jsonEncoderTarget.length) return;
822
+
823
+ const newCapacity = Math.max(jsonEncoderTarget.length * 2, requiredLength);
824
+ assertNonNegativeInt(newCapacity, "JSON encoder capacity");
825
+
826
+ const oldTarget = jsonEncoderTarget;
827
+ jsonEncoderTarget = new Uint8Array(newCapacity);
828
+ jsonEncoderTarget.set(oldTarget.subarray(0, jsonEncoderPosition));
829
+ jsonEncoderTargetView = new DataView(jsonEncoderTarget.buffer);
830
+ };
831
+
832
+ const assertMessagePackLength = (length: number, name: string): void => {
833
+ if (length > 0xffffffff) {
834
+ throw new BufferError(`${name} exceeds the MessagePack uint32 limit.`);
835
+ }
836
+ };
837
+
838
+ const decodeJsonValueFromSource = (): JsonValue => {
839
+ const marker = readJsonDecoderByte();
840
+
841
+ if (marker <= 0x7f) return marker as JsonValue;
842
+ if (marker <= 0x8f) return decodeJsonMap(marker - 0x80);
843
+ if (marker <= 0x9f) return decodeJsonArray(marker - 0x90);
844
+ if (marker <= 0xbf) return decodeJsonString(marker - 0xa0);
845
+ if (marker >= 0xe0) return (marker - 0x100) as JsonValue;
846
+
847
+ switch (marker) {
848
+ case 0xc0:
849
+ return null;
850
+ case 0xc2:
851
+ return false;
852
+ case 0xc3:
853
+ return true;
854
+ case 0xca:
855
+ return decodeJsonFloat(4);
856
+ case 0xcb:
857
+ return decodeJsonFloat(8);
858
+ case 0xcc:
859
+ return readJsonDecoderByte() as JsonValue;
860
+ case 0xcd:
861
+ return readJsonUint16() as JsonValue;
862
+ case 0xce:
863
+ return readJsonUint32() as JsonValue;
864
+ case 0xd0: {
865
+ assertJsonDecoderHasRemainingBytes(1);
866
+ return jsonDecoderView.getInt8(jsonDecoderPosition++) as JsonValue;
867
+ }
868
+ case 0xd1: {
869
+ assertJsonDecoderHasRemainingBytes(2);
870
+ const value = jsonDecoderView.getInt16(jsonDecoderPosition);
871
+ jsonDecoderPosition += 2;
872
+ return value as JsonValue;
873
+ }
874
+ case 0xd2: {
875
+ assertJsonDecoderHasRemainingBytes(4);
876
+ const value = jsonDecoderView.getInt32(jsonDecoderPosition);
877
+ jsonDecoderPosition += 4;
878
+ return value as JsonValue;
879
+ }
880
+ case 0xd9:
881
+ return decodeJsonString(readJsonDecoderByte());
882
+ case 0xda:
883
+ return decodeJsonString(readJsonUint16());
884
+ case 0xdb:
885
+ return decodeJsonString(readJsonUint32());
886
+ case 0xdc:
887
+ return decodeJsonArray(readJsonUint16());
888
+ case 0xdd:
889
+ return decodeJsonArray(readJsonUint32());
890
+ case 0xde:
891
+ return decodeJsonMap(readJsonUint16());
892
+ case 0xdf:
893
+ return decodeJsonMap(readJsonUint32());
894
+ default:
895
+ throw new BufferError(
896
+ `Unsupported MessagePack marker 0x${marker.toString(16).padStart(2, "0")}.`,
897
+ );
898
+ }
899
+ };
900
+
901
+ const decodeJsonFloat = (byteLength: 4 | 8): JsonValue => {
902
+ assertJsonDecoderHasRemainingBytes(byteLength);
903
+ const value =
904
+ byteLength === 4
905
+ ? jsonDecoderView.getFloat32(jsonDecoderPosition)
906
+ : jsonDecoderView.getFloat64(jsonDecoderPosition);
907
+ jsonDecoderPosition += byteLength;
908
+
909
+ if (!Number.isFinite(value)) {
910
+ throw new BufferError("A decoded JSON number must be finite.");
911
+ }
912
+ return value as JsonValue;
913
+ };
914
+
915
+ const decodeJsonString = (byteLength: number): string => {
916
+ assertJsonDecoderHasRemainingBytes(byteLength);
917
+
918
+ shortAscii: if (byteLength < 16) {
919
+ if (byteLength === 0) return "";
920
+
921
+ const start = jsonDecoderPosition;
922
+ const first = jsonDecoderSource[jsonDecoderPosition++];
923
+
924
+ if ((first & 0x80) !== 0) {
925
+ jsonDecoderPosition = start;
926
+ break shortAscii;
927
+ }
928
+ if (byteLength === 1) return jsonStringFromCharCode(first);
929
+
930
+ const second = jsonDecoderSource[jsonDecoderPosition++];
931
+ if ((second & 0x80) !== 0) {
932
+ jsonDecoderPosition = start;
933
+ break shortAscii;
934
+ }
935
+ if (byteLength === 2) return jsonStringFromCharCode(first, second);
936
+
937
+ const third = jsonDecoderSource[jsonDecoderPosition++];
938
+ if ((third & 0x80) !== 0) {
939
+ jsonDecoderPosition = start;
940
+ break shortAscii;
941
+ }
942
+ if (byteLength === 3) return jsonStringFromCharCode(first, second, third);
943
+
944
+ const fourth = jsonDecoderSource[jsonDecoderPosition++];
945
+ if ((fourth & 0x80) !== 0) {
946
+ jsonDecoderPosition = start;
947
+ break shortAscii;
948
+ }
949
+ if (byteLength === 4) {
950
+ return jsonStringFromCharCode(first, second, third, fourth);
951
+ }
952
+
953
+ const fifth = jsonDecoderSource[jsonDecoderPosition++];
954
+ if ((fifth & 0x80) !== 0) {
955
+ jsonDecoderPosition = start;
956
+ break shortAscii;
957
+ }
958
+ if (byteLength === 5) {
959
+ return jsonStringFromCharCode(first, second, third, fourth, fifth);
960
+ }
961
+
962
+ const sixth = jsonDecoderSource[jsonDecoderPosition++];
963
+ if ((sixth & 0x80) !== 0) {
964
+ jsonDecoderPosition = start;
965
+ break shortAscii;
966
+ }
967
+ if (byteLength === 6) {
968
+ return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth);
969
+ }
970
+
971
+ const seventh = jsonDecoderSource[jsonDecoderPosition++];
972
+ if ((seventh & 0x80) !== 0) {
973
+ jsonDecoderPosition = start;
974
+ break shortAscii;
975
+ }
976
+ if (byteLength === 7) {
977
+ return jsonStringFromCharCode(
978
+ first,
979
+ second,
980
+ third,
981
+ fourth,
982
+ fifth,
983
+ sixth,
984
+ seventh,
985
+ );
986
+ }
987
+
988
+ const eighth = jsonDecoderSource[jsonDecoderPosition++];
989
+ if ((eighth & 0x80) !== 0) {
990
+ jsonDecoderPosition = start;
991
+ break shortAscii;
992
+ }
993
+ if (byteLength === 8) {
994
+ return jsonStringFromCharCode(
995
+ first,
996
+ second,
997
+ third,
998
+ fourth,
999
+ fifth,
1000
+ sixth,
1001
+ seventh,
1002
+ eighth,
1003
+ );
1004
+ }
1005
+
1006
+ const ninth = jsonDecoderSource[jsonDecoderPosition++];
1007
+ if ((ninth & 0x80) !== 0) {
1008
+ jsonDecoderPosition = start;
1009
+ break shortAscii;
1010
+ }
1011
+ if (byteLength === 9) {
1012
+ return jsonStringFromCharCode(
1013
+ first,
1014
+ second,
1015
+ third,
1016
+ fourth,
1017
+ fifth,
1018
+ sixth,
1019
+ seventh,
1020
+ eighth,
1021
+ ninth,
1022
+ );
1023
+ }
1024
+
1025
+ const tenth = jsonDecoderSource[jsonDecoderPosition++];
1026
+ if ((tenth & 0x80) !== 0) {
1027
+ jsonDecoderPosition = start;
1028
+ break shortAscii;
1029
+ }
1030
+ if (byteLength === 10) {
1031
+ return jsonStringFromCharCode(
1032
+ first,
1033
+ second,
1034
+ third,
1035
+ fourth,
1036
+ fifth,
1037
+ sixth,
1038
+ seventh,
1039
+ eighth,
1040
+ ninth,
1041
+ tenth,
1042
+ );
1043
+ }
1044
+
1045
+ const eleventh = jsonDecoderSource[jsonDecoderPosition++];
1046
+ if ((eleventh & 0x80) !== 0) {
1047
+ jsonDecoderPosition = start;
1048
+ break shortAscii;
1049
+ }
1050
+ if (byteLength === 11) {
1051
+ return jsonStringFromCharCode(
1052
+ first,
1053
+ second,
1054
+ third,
1055
+ fourth,
1056
+ fifth,
1057
+ sixth,
1058
+ seventh,
1059
+ eighth,
1060
+ ninth,
1061
+ tenth,
1062
+ eleventh,
1063
+ );
1064
+ }
1065
+
1066
+ const twelfth = jsonDecoderSource[jsonDecoderPosition++];
1067
+ if ((twelfth & 0x80) !== 0) {
1068
+ jsonDecoderPosition = start;
1069
+ break shortAscii;
1070
+ }
1071
+ if (byteLength === 12) {
1072
+ return jsonStringFromCharCode(
1073
+ first,
1074
+ second,
1075
+ third,
1076
+ fourth,
1077
+ fifth,
1078
+ sixth,
1079
+ seventh,
1080
+ eighth,
1081
+ ninth,
1082
+ tenth,
1083
+ eleventh,
1084
+ twelfth,
1085
+ );
1086
+ }
1087
+
1088
+ const thirteenth = jsonDecoderSource[jsonDecoderPosition++];
1089
+ if ((thirteenth & 0x80) !== 0) {
1090
+ jsonDecoderPosition = start;
1091
+ break shortAscii;
1092
+ }
1093
+ if (byteLength === 13) {
1094
+ return jsonStringFromCharCode(
1095
+ first,
1096
+ second,
1097
+ third,
1098
+ fourth,
1099
+ fifth,
1100
+ sixth,
1101
+ seventh,
1102
+ eighth,
1103
+ ninth,
1104
+ tenth,
1105
+ eleventh,
1106
+ twelfth,
1107
+ thirteenth,
1108
+ );
1109
+ }
1110
+
1111
+ const fourteenth = jsonDecoderSource[jsonDecoderPosition++];
1112
+ if ((fourteenth & 0x80) !== 0) {
1113
+ jsonDecoderPosition = start;
1114
+ break shortAscii;
1115
+ }
1116
+ if (byteLength === 14) {
1117
+ return jsonStringFromCharCode(
1118
+ first,
1119
+ second,
1120
+ third,
1121
+ fourth,
1122
+ fifth,
1123
+ sixth,
1124
+ seventh,
1125
+ eighth,
1126
+ ninth,
1127
+ tenth,
1128
+ eleventh,
1129
+ twelfth,
1130
+ thirteenth,
1131
+ fourteenth,
1132
+ );
1133
+ }
1134
+
1135
+ const fifteenth = jsonDecoderSource[jsonDecoderPosition++];
1136
+ if ((fifteenth & 0x80) !== 0) {
1137
+ jsonDecoderPosition = start;
1138
+ break shortAscii;
1139
+ }
1140
+ return jsonStringFromCharCode(
1141
+ first,
1142
+ second,
1143
+ third,
1144
+ fourth,
1145
+ fifth,
1146
+ sixth,
1147
+ seventh,
1148
+ eighth,
1149
+ ninth,
1150
+ tenth,
1151
+ eleventh,
1152
+ twelfth,
1153
+ thirteenth,
1154
+ fourteenth,
1155
+ fifteenth,
1156
+ );
1157
+ }
1158
+
1159
+ const end = jsonDecoderPosition + byteLength;
1160
+ const units: Array<number> = [];
1161
+ let result = "";
1162
+
1163
+ while (jsonDecoderPosition < end) {
1164
+ const first = jsonDecoderSource[jsonDecoderPosition++];
1165
+
1166
+ if (first < 0x80) {
1167
+ units.push(first);
1168
+ } else if (first >= 0xc2 && first <= 0xdf) {
1169
+ assertJsonStringHasRemainingBytes(end, 1);
1170
+ const second = readJsonContinuationByte();
1171
+ units.push(((first & 0x1f) << 6) | second);
1172
+ } else if (first >= 0xe0 && first <= 0xef) {
1173
+ assertJsonStringHasRemainingBytes(end, 2);
1174
+ const secondByte = jsonDecoderSource[jsonDecoderPosition];
1175
+ if (first === 0xe0 && secondByte < 0xa0) {
1176
+ throw new BufferError("Invalid UTF-8 string encoding.");
1177
+ }
1178
+ const second = readJsonContinuationByte();
1179
+ const third = readJsonContinuationByte();
1180
+ units.push(((first & 0x0f) << 12) | (second << 6) | third);
1181
+ } else if (first >= 0xf0 && first <= 0xf4) {
1182
+ assertJsonStringHasRemainingBytes(end, 3);
1183
+ const secondByte = jsonDecoderSource[jsonDecoderPosition];
1184
+ if (
1185
+ (first === 0xf0 && secondByte < 0x90) ||
1186
+ (first === 0xf4 && secondByte > 0x8f)
1187
+ ) {
1188
+ throw new BufferError("Invalid UTF-8 string encoding.");
1189
+ }
1190
+ const second = readJsonContinuationByte();
1191
+ const third = readJsonContinuationByte();
1192
+ const fourth = readJsonContinuationByte();
1193
+ const codePoint =
1194
+ ((first & 0x07) << 18) | (second << 12) | (third << 6) | fourth;
1195
+ const pair = codePoint - 0x10000;
1196
+ units.push(0xd800 | (pair >> 10), 0xdc00 | (pair & 0x3ff));
1197
+ } else {
1198
+ throw new BufferError("Invalid UTF-8 string encoding.");
1199
+ }
1200
+
1201
+ if (units.length >= 0x1000) {
1202
+ result += jsonStringFromCharCode(...units);
1203
+ units.length = 0;
1204
+ }
1205
+ }
1206
+
1207
+ if (units.length > 0) {
1208
+ result += jsonStringFromCharCode(...units);
1209
+ }
1210
+ return result;
1211
+ };
1212
+
1213
+ const decodeJsonArray = (length: number): JsonValue => {
1214
+ if (jsonDecoderDepth >= maxJsonNestingDepth) {
1215
+ throw new BufferError(
1216
+ `JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`,
1217
+ );
1218
+ }
1219
+ if (length > jsonDecoderSource.length - jsonDecoderPosition) {
1220
+ throw new BufferError("Buffer parse ended prematurely");
1221
+ }
1222
+
1223
+ // oxlint-disable-next-line unicorn/no-new-array -- Preallocation is intentional in this decoding hot path.
1224
+ const value = new Array<JsonValue>(length);
1225
+ jsonDecoderDepth++;
1226
+ for (let index = 0; index < length; index++) {
1227
+ value[index] = decodeJsonValueFromSource();
1228
+ }
1229
+ jsonDecoderDepth--;
1230
+ return value;
1231
+ };
1232
+
1233
+ const decodeJsonMap = (length: number): JsonValue => {
1234
+ if (jsonDecoderDepth >= maxJsonNestingDepth) {
1235
+ throw new BufferError(
1236
+ `JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`,
1237
+ );
1238
+ }
1239
+ if (length > (jsonDecoderSource.length - jsonDecoderPosition) / 2) {
1240
+ throw new BufferError("Buffer parse ended prematurely");
1241
+ }
1242
+
1243
+ const value: Record<string, JsonValue> = {};
1244
+ jsonDecoderDepth++;
1245
+ for (let index = 0; index < length; index++) {
1246
+ const marker = readJsonDecoderByte();
1247
+ let key: string;
1248
+
1249
+ if (marker >= 0xa0 && marker <= 0xbf) {
1250
+ key = decodeCachedJsonKey(marker - 0xa0);
1251
+ } else if (marker === 0xd9) {
1252
+ key = decodeCachedJsonKey(readJsonDecoderByte());
1253
+ } else if (marker === 0xda) {
1254
+ key = decodeCachedJsonKey(readJsonUint16());
1255
+ } else if (marker === 0xdb) {
1256
+ key = decodeCachedJsonKey(readJsonUint32());
1257
+ } else {
1258
+ jsonDecoderPosition--;
1259
+ decodeJsonValueFromSource();
1260
+ throw new BufferError("A decoded JSON object key must be a string.");
1261
+ }
1262
+
1263
+ const entryValue = decodeJsonValueFromSource();
1264
+
1265
+ if (key === "__proto__") {
1266
+ Object.defineProperty(value, key, {
1267
+ value: entryValue,
1268
+ configurable: true,
1269
+ enumerable: true,
1270
+ writable: true,
1271
+ });
1272
+ } else {
1273
+ value[key] = entryValue;
1274
+ }
1275
+ }
1276
+ jsonDecoderDepth--;
1277
+ return value;
1278
+ };
1279
+
1280
+ const decodeCachedJsonKey = (byteLength: number): string => {
1281
+ if (byteLength > maxCachedJsonKeyByteLength) {
1282
+ return decodeJsonString(byteLength);
1283
+ }
1284
+
1285
+ assertJsonDecoderHasRemainingBytes(byteLength);
1286
+ const start = jsonDecoderPosition;
1287
+ const end = start + byteLength;
1288
+ const firstBytes =
1289
+ byteLength > 1
1290
+ ? jsonDecoderView.getUint16(start)
1291
+ : byteLength === 1
1292
+ ? jsonDecoderSource[start]
1293
+ : 0;
1294
+ const cacheIndex = ((byteLength << 5) ^ firstBytes) & (jsonKeyCacheSize - 1);
1295
+ const entry = jsonKeyCache[cacheIndex];
1296
+
1297
+ if (entry?.bytes.length === byteLength) {
1298
+ let index = 0;
1299
+ while (
1300
+ index < byteLength &&
1301
+ entry.bytes[index] === jsonDecoderSource[start + index]
1302
+ ) {
1303
+ index++;
1304
+ }
1305
+ if (index === byteLength) {
1306
+ jsonDecoderPosition = end;
1307
+ return entry.value;
1308
+ }
1309
+ }
1310
+
1311
+ const value = decodeJsonString(byteLength);
1312
+ jsonKeyCache[cacheIndex] = {
1313
+ bytes: jsonDecoderSource.slice(start, end),
1314
+ value,
1315
+ };
1316
+ return value;
1317
+ };
1318
+
1319
+ const readJsonContinuationByte = (): number => {
1320
+ const byte = jsonDecoderSource[jsonDecoderPosition++];
1321
+ if ((byte & 0xc0) !== 0x80) {
1322
+ throw new BufferError("Invalid UTF-8 string encoding.");
1323
+ }
1324
+ return byte & 0x3f;
1325
+ };
1326
+
1327
+ const assertJsonStringHasRemainingBytes = (
1328
+ end: number,
1329
+ requiredBytes: number,
1330
+ ): void => {
1331
+ if (end - jsonDecoderPosition < requiredBytes) {
1332
+ throw new BufferError("Invalid UTF-8 string encoding.");
1333
+ }
1334
+ };
1335
+
1336
+ const readJsonDecoderByte = (): number => {
1337
+ assertJsonDecoderHasRemainingBytes(1);
1338
+ return jsonDecoderSource[jsonDecoderPosition++];
1339
+ };
1340
+
1341
+ const readJsonUint16 = (): number => {
1342
+ assertJsonDecoderHasRemainingBytes(2);
1343
+ const value = jsonDecoderView.getUint16(jsonDecoderPosition);
1344
+ jsonDecoderPosition += 2;
1345
+ return value;
1346
+ };
1347
+
1348
+ const readJsonUint32 = (): number => {
1349
+ assertJsonDecoderHasRemainingBytes(4);
1350
+ const value = jsonDecoderView.getUint32(jsonDecoderPosition);
1351
+ jsonDecoderPosition += 4;
1352
+ return value;
1353
+ };
1354
+
1355
+ const assertJsonDecoderHasRemainingBytes = (requiredBytes: number): void => {
1356
+ if (jsonDecoderSource.length - jsonDecoderPosition < requiredBytes) {
1357
+ throw new BufferError("Buffer parse ended prematurely");
1358
+ }
1359
+ };
1360
+
1361
+ const assertNonNegativeInt: (
1362
+ value: number,
1363
+ name: string,
1364
+ ) => asserts value is NonNegativeInt = (value, name) => {
1365
+ if (!Number.isSafeInteger(value) || value < 0) {
1366
+ throw new BufferError(`${name} must be a non-negative safe integer.`);
1367
+ }
1368
+ };
1369
+
1370
+ const assertBufferHasRemainingBytes = (
1371
+ remainingBytes: number,
1372
+ requiredBytes: number,
1373
+ ): void => {
1374
+ if (remainingBytes < requiredBytes) {
1375
+ throw new BufferError("Buffer parse ended prematurely");
1376
+ }
1377
+ };