@evolu/common 8.7.0 → 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 (300) 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/{Buffer.d.ts → Binary.d.ts} +85 -12
  7. package/dist/src/Binary.d.ts.map +1 -0
  8. package/dist/src/{Buffer.js → Binary.js} +206 -24
  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 +14 -76
  160. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  161. package/dist/src/local-first/Protocol.js +29 -206
  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 -4
  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/{Buffer.ts → Binary.ts} +273 -46
  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 +58 -261
  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.map +0 -1
@@ -11,15 +11,16 @@
11
11
  * @module
12
12
  */
13
13
 
14
+ import { bytesToUtf8, utf8ToBytes } from "@noble/ciphers/utils.js";
14
15
  import type { Result } from "./Result.ts";
15
- import type { JsonValue, NonNegativeInt } from "./Type.ts";
16
- export {
17
- bytesToHex,
18
- bytesToUtf8,
19
- concatBytes,
20
- hexToBytes,
21
- utf8ToBytes,
22
- } from "@noble/ciphers/utils.js";
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 };
23
24
 
24
25
  /**
25
26
  * Custom error for {@link Buffer}-related failures like premature end of data.
@@ -54,9 +55,9 @@ export class BufferError extends Error {
54
55
  *
55
56
  * ```ts
56
57
  * import {
57
- * assert,
58
58
  * assertEqual,
59
59
  * assertErr,
60
+ * assertInstanceOf,
60
61
  * createBuffer,
61
62
  * createIdFromString,
62
63
  * IdBytes,
@@ -65,11 +66,9 @@ export class BufferError extends Error {
65
66
  * idToIdBytes,
66
67
  * NonNegativeInt,
67
68
  * trySync,
68
- * } from "@evolu/common";
69
- * import {
70
69
  * decodeNonNegativeInt,
71
70
  * encodeNonNegativeInt,
72
- * } from "@evolu/common/local-first";
71
+ * } from "@evolu/common";
73
72
  *
74
73
  * const buffer = createBuffer();
75
74
  * const id = createIdFromString("buffer-example");
@@ -84,11 +83,8 @@ export class BufferError extends Error {
84
83
  * assertEqual(decodedId, id);
85
84
  * const result = trySync(() => decodeNonNegativeInt(decoder));
86
85
  * assertErr(result);
87
- * assert(
88
- * result.error instanceof Error &&
89
- * result.error.message === "Buffer parse ended prematurely",
90
- * "Expected the premature-buffer-end error.",
91
- * );
86
+ * assertInstanceOf(result.error, Error);
87
+ * assertEqual(result.error.message, "Buffer parse ended prematurely");
92
88
  * ```
93
89
  *
94
90
  * For more on exponential growth, see:
@@ -149,9 +145,7 @@ export const createBuffer = (
149
145
  const initialLength = arrayLike?.length ?? 0;
150
146
  assertNonNegativeInt(initialLength, "arrayLike.length");
151
147
 
152
- let value = arrayLike
153
- ? new globalThis.Uint8Array(arrayLike)
154
- : new globalThis.Uint8Array(512);
148
+ let value = arrayLike ? new Uint8Array(arrayLike) : new Uint8Array(512);
155
149
  let length = initialLength;
156
150
 
157
151
  const buffer: Buffer = {
@@ -169,7 +163,7 @@ export const createBuffer = (
169
163
  if (value.length < targetSize) {
170
164
  const oldValue = value;
171
165
  const newCapacity = Math.max(value.length * 2, targetSize);
172
- value = new globalThis.Uint8Array(newCapacity);
166
+ value = new Uint8Array(newCapacity);
173
167
  value.set(oldValue);
174
168
  }
175
169
  value.set(arg, length);
@@ -211,6 +205,244 @@ export const createBuffer = (
211
205
  return buffer;
212
206
  };
213
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
+
214
446
  // Inspired by msgpackr 2.0.5, licensed under the MIT License.
215
447
  // This implementation is specialized for Evolu's JsonValue domain.
216
448
  //
@@ -244,23 +476,25 @@ interface JsonKeyCacheEntry {
244
476
  const jsonKeyCacheSize = 4096;
245
477
  const maxCachedJsonKeyByteLength = 32;
246
478
  const maxJsonNestingDepth = 1_000;
247
- let jsonEncoderTarget = new globalThis.Uint8Array(8192);
248
- let jsonEncoderTargetView = new globalThis.DataView(jsonEncoderTarget.buffer);
479
+ let jsonEncoderTarget = new Uint8Array(8192);
480
+ let jsonEncoderTargetView = new DataView(jsonEncoderTarget.buffer);
249
481
  let jsonEncoderPosition = 0;
250
482
  let jsonEncoderDepth = 0;
251
483
  let jsonEncoderIsActive = false;
252
- const emptyJsonDecoderSource: Uint8Array = new globalThis.Uint8Array(0);
253
- const emptyJsonDecoderView: DataView = new globalThis.DataView(
484
+ const emptyJsonDecoderSource: Uint8Array = new Uint8Array(0);
485
+ const emptyJsonDecoderView: DataView = new DataView(
254
486
  emptyJsonDecoderSource.buffer,
255
487
  );
256
488
  let jsonDecoderSource: Uint8Array = emptyJsonDecoderSource;
257
489
  let jsonDecoderView: DataView = emptyJsonDecoderView;
258
490
  let jsonDecoderPosition = 0;
259
491
  let jsonDecoderDepth = 0;
260
- const jsonStringFromCharCode = globalThis.String.fromCharCode;
492
+ const jsonStringFromCharCode = String.fromCharCode;
261
493
  // Cache only short keys and use a fixed table to bound retained memory.
262
- const jsonKeyCache: Array<JsonKeyCacheEntry | undefined> =
263
- globalThis.Array.from({ length: jsonKeyCacheSize }, () => undefined);
494
+ const jsonKeyCache: Array<JsonKeyCacheEntry | undefined> = Array.from(
495
+ { length: jsonKeyCacheSize },
496
+ () => undefined,
497
+ );
264
498
 
265
499
  /**
266
500
  * Encodes a {@link JsonValue} using the MessagePack format.
@@ -337,7 +571,7 @@ export const encodeJsonValue = (buffer: Buffer, value: JsonValue): void => {
337
571
  export const decodeJsonValue = (buffer: Buffer): JsonValue => {
338
572
  const source = buffer.unwrap();
339
573
  jsonDecoderSource = source;
340
- jsonDecoderView = new globalThis.DataView(
574
+ jsonDecoderView = new DataView(
341
575
  source.buffer,
342
576
  source.byteOffset,
343
577
  source.byteLength,
@@ -372,7 +606,7 @@ const encodeJsonValueToTarget = (value: JsonValue): void => {
372
606
  encodeJsonStringToTarget(value);
373
607
  return;
374
608
  case "number": {
375
- if (globalThis.Object.is(value, -0)) {
609
+ if (Object.is(value, -0)) {
376
610
  ensureJsonEncoderCapacity(9);
377
611
  jsonEncoderTarget[jsonEncoderPosition++] = 0xcb;
378
612
  jsonEncoderTargetView.setFloat64(jsonEncoderPosition, value);
@@ -401,11 +635,7 @@ const encodeJsonValueToTarget = (value: JsonValue): void => {
401
635
  return;
402
636
  }
403
637
 
404
- if (
405
- globalThis.Number.isInteger(value) &&
406
- value >= -0x80000000 &&
407
- value < 0
408
- ) {
638
+ if (Number.isInteger(value) && value >= -0x80000000 && value < 0) {
409
639
  if (value >= -0x20) {
410
640
  writeJsonEncoderByte(0x100 + value);
411
641
  } else if (value >= -0x80) {
@@ -443,7 +673,7 @@ const encodeJsonValueToTarget = (value: JsonValue): void => {
443
673
  }
444
674
  jsonEncoderDepth++;
445
675
 
446
- if (globalThis.Array.isArray(value)) {
676
+ if (Array.isArray(value)) {
447
677
  const array = value as ReadonlyArray<JsonValue>;
448
678
  const length = array.length;
449
679
  writeJsonCollectionHeader(length, 0x90, 0xdc, 0xdd);
@@ -454,7 +684,7 @@ const encodeJsonValueToTarget = (value: JsonValue): void => {
454
684
  }
455
685
 
456
686
  const object = value as Readonly<Record<string, JsonValue>>;
457
- const keys = globalThis.Object.keys(object);
687
+ const keys = Object.keys(object);
458
688
  writeJsonCollectionHeader(keys.length, 0x80, 0xde, 0xdf);
459
689
 
460
690
  for (const key of keys) {
@@ -590,16 +820,13 @@ const ensureJsonEncoderCapacity = (additionalLength: number): void => {
590
820
 
591
821
  if (requiredLength <= jsonEncoderTarget.length) return;
592
822
 
593
- const newCapacity = globalThis.Math.max(
594
- jsonEncoderTarget.length * 2,
595
- requiredLength,
596
- );
823
+ const newCapacity = Math.max(jsonEncoderTarget.length * 2, requiredLength);
597
824
  assertNonNegativeInt(newCapacity, "JSON encoder capacity");
598
825
 
599
826
  const oldTarget = jsonEncoderTarget;
600
- jsonEncoderTarget = new globalThis.Uint8Array(newCapacity);
827
+ jsonEncoderTarget = new Uint8Array(newCapacity);
601
828
  jsonEncoderTarget.set(oldTarget.subarray(0, jsonEncoderPosition));
602
- jsonEncoderTargetView = new globalThis.DataView(jsonEncoderTarget.buffer);
829
+ jsonEncoderTargetView = new DataView(jsonEncoderTarget.buffer);
603
830
  };
604
831
 
605
832
  const assertMessagePackLength = (length: number, name: string): void => {
@@ -679,7 +906,7 @@ const decodeJsonFloat = (byteLength: 4 | 8): JsonValue => {
679
906
  : jsonDecoderView.getFloat64(jsonDecoderPosition);
680
907
  jsonDecoderPosition += byteLength;
681
908
 
682
- if (!globalThis.Number.isFinite(value)) {
909
+ if (!Number.isFinite(value)) {
683
910
  throw new BufferError("A decoded JSON number must be finite.");
684
911
  }
685
912
  return value as JsonValue;
@@ -1036,7 +1263,7 @@ const decodeJsonMap = (length: number): JsonValue => {
1036
1263
  const entryValue = decodeJsonValueFromSource();
1037
1264
 
1038
1265
  if (key === "__proto__") {
1039
- globalThis.Object.defineProperty(value, key, {
1266
+ Object.defineProperty(value, key, {
1040
1267
  value: entryValue,
1041
1268
  configurable: true,
1042
1269
  enumerable: true,
@@ -1135,7 +1362,7 @@ const assertNonNegativeInt: (
1135
1362
  value: number,
1136
1363
  name: string,
1137
1364
  ) => asserts value is NonNegativeInt = (value, name) => {
1138
- if (!globalThis.Number.isSafeInteger(value) || value < 0) {
1365
+ if (!Number.isSafeInteger(value) || value < 0) {
1139
1366
  throw new BufferError(`${name} must be a non-negative safe integer.`);
1140
1367
  }
1141
1368
  };
@@ -0,0 +1,90 @@
1
+ import { test } from "node:test";
2
+
3
+ import type { Brand, IsBranded } from "./Brand.ts";
4
+ import { constVoid } from "./Function.ts";
5
+ import { assertType } from "./Type.ts";
6
+
7
+ test("Brand", () => {
8
+ type UserId = string & Brand<"UserId">;
9
+ type ProductId = number & Brand<"ProductId">;
10
+
11
+ const validUserId: UserId = "user123" as UserId;
12
+ assertType<typeof validUserId, string & Brand<"UserId">>();
13
+
14
+ const validProductId: ProductId = 42 as ProductId;
15
+ assertType<typeof validProductId, number & Brand<"ProductId">>();
16
+
17
+ // Invalid assignment (string to ProductId)
18
+ // @ts-expect-error - Should not allow a string to be assigned to ProductId
19
+ const invalidProductId: ProductId = "not-a-number";
20
+
21
+ // Invalid assignment (unbranded string to UserId)
22
+ // @ts-expect-error - Should not allow unbranded string to be assigned to UserId
23
+ const invalidUserId: UserId = "user123";
24
+
25
+ // Invalid assignment (mixing different brands)
26
+ // @ts-expect-error - Should not allow assigning a ProductId to a UserId
27
+ const mismatchedId: UserId = validProductId;
28
+ });
29
+
30
+ test("Brand - multiple brands", () => {
31
+ type Min1 = string & Brand<"Min1">;
32
+ type Max100 = string & Brand<"Max100">;
33
+ type Min1Max100 = string & Brand<"Min1" | "Max100">;
34
+
35
+ const requiresMin1 = (_value: Min1) => constVoid;
36
+ const requiresMax100 = (_value: Max100) => constVoid;
37
+
38
+ const min1Value: Min1 = "hello" as Min1;
39
+ const max100Value: Max100 = "world" as Max100;
40
+ const min1Max100Value: Min1Max100 = "typescript" as Min1Max100;
41
+
42
+ requiresMin1(min1Value);
43
+ requiresMax100(max100Value);
44
+ requiresMin1(min1Max100Value);
45
+ requiresMax100(min1Max100Value);
46
+
47
+ // @ts-expect-error: brands works
48
+ requiresMin1("hello");
49
+ // @ts-expect-error: brands works
50
+ requiresMin1(max100Value);
51
+ // @ts-expect-error: brands works
52
+ requiresMax100("world");
53
+ // @ts-expect-error: brands works
54
+ requiresMax100(min1Value);
55
+ });
56
+
57
+ test("IsBranded", () => {
58
+ type UnbrandedString = string;
59
+ type BrandedString = string & Brand<"UserId">;
60
+ type BrandedNumber = number & Brand<"ProductId">;
61
+ type DoubleBranded = string & Brand<"Min"> & Brand<"Max">;
62
+
63
+ assertType<IsBranded<UnbrandedString>, false>();
64
+ assertType<IsBranded<BrandedString>, true>();
65
+ assertType<IsBranded<BrandedNumber>, true>();
66
+ assertType<IsBranded<DoubleBranded>, true>();
67
+ });
68
+
69
+ test("Brand - standalone (nominal type)", () => {
70
+ // Brand can be used alone without a base type for purely nominal typing.
71
+ // Useful for platform-specific values where type identity is based on name only.
72
+ type NativePort = Brand<"NativePort">;
73
+
74
+ const requiresNativePort = (_port: NativePort) => constVoid;
75
+
76
+ // Only branded values can be passed
77
+ const nativePort: NativePort = {} as NativePort;
78
+ requiresNativePort(nativePort);
79
+
80
+ // @ts-expect-error: plain unknown cannot be passed
81
+ requiresNativePort({} as unknown);
82
+
83
+ // @ts-expect-error: other types cannot be passed
84
+ requiresNativePort({});
85
+
86
+ // @ts-expect-error: null cannot be passed
87
+ requiresNativePort(null);
88
+
89
+ assertType<IsBranded<NativePort>, true>();
90
+ });