@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
@@ -170,29 +170,41 @@
170
170
  * initiator/non-initiator terminology instead, and consolidate into a single
171
171
  * `applyProtocolMessage` function with conditional arguments to reduce code
172
172
  * duplication.
173
- * - Replace try-catch with Result + new Error (to preserve stacktraces). Measure
174
- * Result overhead, it should be super small.
175
173
  * - Allow clients to broadcast messages that are not persisted by relays. This
176
174
  * would enable real-time ephemeral data (like cursor positions, typing
177
175
  * indicators) to be forwarded by relays without storage overhead.
178
176
  */
179
177
 
180
- import { Packr } from "msgpackr";
181
178
  import {
182
179
  createMutableArray,
183
180
  isNonEmptyArray,
184
181
  type NonEmptyReadonlyArray,
185
182
  } from "../Array.ts";
186
- import { assert, assertNonNullable, assertNotUndefined } from "../Assert.ts";
183
+ import {
184
+ assert,
185
+ assertNonNullable,
186
+ assertNotUndefined,
187
+ assertSame,
188
+ } from "../Assert.ts";
187
189
  import type { Brand } from "../Brand.ts";
188
190
  import {
189
191
  type Buffer,
190
- bytesToHex,
191
- bytesToUtf8,
192
192
  createBuffer,
193
- hexToBytes,
194
- utf8ToBytes,
195
- } from "../Buffer.ts";
193
+ createRunLengthEncoder,
194
+ decodeFlags,
195
+ decodeJsonValue,
196
+ decodeLength,
197
+ decodeNonNegativeInt,
198
+ decodeNumber,
199
+ decodeRle,
200
+ decodeString,
201
+ encodeFlags,
202
+ encodeJsonValue,
203
+ encodeLength,
204
+ encodeNonNegativeInt,
205
+ encodeNumber,
206
+ encodeString,
207
+ } from "../Binary.ts";
196
208
  import type { ConsoleDep } from "../Console.ts";
197
209
  import {
198
210
  createPadmePadding,
@@ -209,10 +221,11 @@ import { eqArrayNumber } from "../Eq.ts";
209
221
  import { computeBalancedBuckets } from "../Number.ts";
210
222
  import { createMutableRecord, objectToEntries } from "../Object.ts";
211
223
  import { err, ok, type Result } from "../Result.ts";
212
- import { SqliteValue } from "../Sqlite.ts";
224
+ import type { SqliteValue } from "../Sqlite.ts";
213
225
  import { AbortError, type Task } from "../Task.ts";
214
226
  import { Millis } from "../Time.ts";
215
227
  import {
228
+ assertType,
216
229
  Base64Url,
217
230
  base64UrlToUint8Array,
218
231
  between,
@@ -264,6 +277,10 @@ import {
264
277
  Counter,
265
278
  eqTimestamp,
266
279
  NodeId,
280
+ type NodeIdBytes,
281
+ nodeIdBytesLength,
282
+ nodeIdBytesToNodeId,
283
+ nodeIdToNodeIdBytes,
267
284
  Timestamp,
268
285
  TimestampBytes,
269
286
  timestampBytesLength,
@@ -271,14 +288,6 @@ import {
271
288
  timestampToTimestampBytes,
272
289
  } from "./Timestamp.ts";
273
290
 
274
- /**
275
- * Evolu uses MessagePack for numbers and JSONs.
276
- *
277
- * - `variableMapSize: true` - More compact maps, ~5-10% slower encoding
278
- * - `useRecords: false` - Standard MessagePack without extensions
279
- */
280
- const packr = new Packr({ variableMapSize: true, useRecords: false });
281
-
282
291
  const minProtocolMessageMaxSize = 1_000_000;
283
292
  const maxProtocolMessageMaxSize = 100_000_000;
284
293
 
@@ -434,7 +443,7 @@ export interface ProtocolVersionError
434
443
 
435
444
  /** Error for invalid or corrupted protocol message data. */
436
445
  export interface ProtocolInvalidDataError extends Typed<"ProtocolInvalidDataError"> {
437
- readonly data: globalThis.Uint8Array;
446
+ readonly data: Uint8Array;
438
447
  readonly error: unknown;
439
448
  }
440
449
 
@@ -843,13 +852,13 @@ export const createTimestampsBuffer = (): TimestampsBuffer => {
843
852
  encodeNonNegativeInt(buffer, value);
844
853
  });
845
854
  const nodeIdEncoder = createRunLengthEncoder<NodeId>((buffer, value) => {
846
- encodeNodeId(buffer, value);
855
+ buffer.extend(nodeIdToNodeIdBytes(value));
847
856
  });
848
857
 
849
858
  return {
850
859
  add: (timestamp) => {
851
860
  const delta = timestamp.millis - previousMillis;
852
- assert(NonNegativeInt.is(delta), "The delta must be NonNegativeInt");
861
+ assertType(NonNegativeInt, delta);
853
862
 
854
863
  count++;
855
864
  syncCount();
@@ -883,40 +892,6 @@ export const createTimestampsBuffer = (): TimestampsBuffer => {
883
892
  };
884
893
  };
885
894
 
886
- interface RunLengthEncoder<T> {
887
- add: (value: T) => void;
888
- getLength: () => NonNegativeInt;
889
- unwrap: () => Uint8Array;
890
- }
891
-
892
- const createRunLengthEncoder = <T>(
893
- encodeValue: (buffer: Buffer, value: T) => void,
894
- ): RunLengthEncoder<T> => {
895
- const buffer = createBuffer();
896
- let previousLength = zeroNonNegativeInt;
897
- let previousValue = null as T | null;
898
- let runLength = zeroNonNegativeInt;
899
-
900
- return {
901
- add: (value) => {
902
- if (value === previousValue) {
903
- runLength++;
904
- buffer.truncate(previousLength);
905
- } else {
906
- previousValue = value;
907
- runLength = NonNegativeInt.orThrow(1);
908
- }
909
- previousLength = buffer.getLength();
910
- encodeValue(buffer, value);
911
- encodeNonNegativeInt(buffer, runLength);
912
- },
913
-
914
- getLength: () => buffer.getLength(),
915
-
916
- unwrap: () => buffer.unwrap(),
917
- };
918
- };
919
-
920
895
  export interface ApplyProtocolMessageAsClientOptions {
921
896
  writeKey?: OwnerWriteKey;
922
897
 
@@ -1130,7 +1105,7 @@ export const applyProtocolMessageAsRelay =
1130
1105
  }
1131
1106
 
1132
1107
  const messageType = input.shift() as MessageType;
1133
- assert(messageType === MessageType.Request, "Invalid MessageType");
1108
+ assertSame(messageType, MessageType.Request);
1134
1109
 
1135
1110
  const hasWriteKey = input.shift();
1136
1111
  let writeKey: OwnerWriteKey | undefined;
@@ -1722,7 +1697,9 @@ const decodeTimestamps = (
1722
1697
  return counter.value;
1723
1698
  });
1724
1699
 
1725
- const nodeIds = decodeRle(buffer, length, (): NodeId => decodeNodeId(buffer));
1700
+ const nodeIds = decodeRle(buffer, length, (): NodeId =>
1701
+ nodeIdBytesToNodeId(buffer.shiftN(nodeIdBytesLength) as NodeIdBytes),
1702
+ );
1726
1703
 
1727
1704
  const timestamps = createMutableArray<Timestamp>(length);
1728
1705
  for (let i = 0; i < length; i++) {
@@ -1736,135 +1713,11 @@ const decodeTimestamps = (
1736
1713
  return timestamps;
1737
1714
  };
1738
1715
 
1739
- export const decodeRle = <T>(
1740
- buffer: Buffer,
1741
- length: NonNegativeInt,
1742
- decodeValue: () => T,
1743
- ): ReadonlyArray<T> => {
1744
- const values = createMutableArray<T>(length);
1745
- let index = 0;
1746
- while (index < length) {
1747
- const value = decodeValue();
1748
- const runLength = decodeNonNegativeInt(buffer);
1749
-
1750
- // Prevent infinite loop on malformed input.
1751
- if (runLength === 0) {
1752
- throw new ProtocolDecodeError(
1753
- "Invalid RLE encoding: runLength must be positive",
1754
- );
1755
- }
1756
- const remaining = length - index;
1757
-
1758
- // Prevent CPU/memory amplification via oversized runLength.
1759
- if (runLength > remaining) {
1760
- throw new ProtocolDecodeError(
1761
- `Invalid RLE encoding: runLength ${runLength} exceeds remaining ${remaining}`,
1762
- );
1763
- }
1764
- for (let i = 0; i < runLength; i++) {
1765
- values[index] = value;
1766
- index++;
1767
- }
1768
- }
1769
- return values;
1770
- };
1771
-
1772
1716
  const decodeId = (buffer: Buffer): Id => {
1773
1717
  const bytes = buffer.shiftN(idBytesTypeValueLength);
1774
1718
  return idBytesToId(bytes as IdBytes);
1775
1719
  };
1776
1720
 
1777
- /**
1778
- * Evolu uses MessagePack to handle finite numbers except for NonNegativeInt.
1779
- * For NonNegativeInt, Evolu provides more efficient encoding.
1780
- */
1781
- export const encodeNumber = (buffer: Buffer, number: FiniteNumber): void => {
1782
- buffer.extend(packr.pack(number));
1783
- };
1784
-
1785
- export const decodeNumber = (buffer: Buffer): FiniteNumber => {
1786
- let number: unknown;
1787
- let end: unknown;
1788
-
1789
- packr.unpackMultiple(
1790
- buffer.unwrap(),
1791
- (n: unknown, _: unknown, e: unknown) => {
1792
- number = n;
1793
- end = e;
1794
- return false;
1795
- },
1796
- );
1797
-
1798
- const endResult = NonNegativeInt.fromUnknown(end);
1799
- if (!endResult.ok) throw new ProtocolDecodeError(endResult.error.type);
1800
-
1801
- const numberResult = FiniteNumber.fromUnknown(number);
1802
- if (!numberResult.ok) throw new ProtocolDecodeError(numberResult.error.type);
1803
-
1804
- buffer.shiftN(endResult.value);
1805
- return numberResult.value;
1806
- };
1807
-
1808
- /**
1809
- * Encodes an array of boolean flags into a single byte.
1810
- *
1811
- * Each element in the array corresponds to a bit (0-7). Array can have 0-8
1812
- * elements.
1813
- *
1814
- * ### Example
1815
- *
1816
- * ```ts
1817
- * import { assertEqual, createBuffer } from "@evolu/common";
1818
- * import { encodeFlags } from "@evolu/common/local-first";
1819
- *
1820
- * const buffer = createBuffer();
1821
- * encodeFlags(buffer, [true, false, true]);
1822
- *
1823
- * assertEqual(buffer.unwrap(), new Uint8Array([0b101]));
1824
- * ```
1825
- */
1826
- export const encodeFlags = (
1827
- buffer: Buffer,
1828
- flags: ReadonlyArray<boolean>,
1829
- ): void => {
1830
- let byte = 0;
1831
- for (let i = 0; i < flags.length && i < 8; i++) {
1832
- if (flags[i]) {
1833
- byte |= 1 << i;
1834
- }
1835
- }
1836
- buffer.extend([byte]);
1837
- };
1838
-
1839
- /**
1840
- * Decodes a byte into an array of boolean flags.
1841
- *
1842
- * ### Example
1843
- *
1844
- * ```ts
1845
- * import { assertEqual, createBuffer, PositiveInt } from "@evolu/common";
1846
- * import { decodeFlags } from "@evolu/common/local-first";
1847
- *
1848
- * const buffer = createBuffer([0b101]);
1849
- * const flags = decodeFlags(buffer, PositiveInt.orThrow(3));
1850
- *
1851
- * assertEqual(flags, [true, false, true]);
1852
- * assertEqual(buffer.getLength(), 0);
1853
- * ```
1854
- */
1855
- export const decodeFlags = (
1856
- buffer: Buffer,
1857
- count: PositiveInt,
1858
- ): ReadonlyArray<boolean> => {
1859
- const byte = buffer.shift();
1860
- const length = globalThis.Math.min(count, 8);
1861
- const flags = createMutableArray<boolean>(length);
1862
- for (let i = 0; i < length; i++) {
1863
- flags[i] = (byte & (1 << i)) !== 0;
1864
- }
1865
- return flags;
1866
- };
1867
-
1868
1721
  /**
1869
1722
  * Encodes and encrypts a {@link DbChange} using the provided owner's encryption
1870
1723
  * key. Returns an encrypted binary representation as {@link EncryptedDbChange}.
@@ -1996,91 +1849,30 @@ export const decryptAndDecodeDbChange = (
1996
1849
  };
1997
1850
 
1998
1851
  /**
1999
- * Encodes a non-negative integer into a variable-length integer format. It's
2000
- * more efficient than encoding via {@link encodeNumber}.
1852
+ * Decodes a ProtocolMessage into a readable JSON object for debugging.
2001
1853
  *
2002
- * https://en.wikipedia.org/wiki/Variable-length_quantity
2003
- */
2004
- export const encodeNonNegativeInt = (
2005
- buffer: Buffer,
2006
- int: NonNegativeInt,
2007
- ): void => {
2008
- if (int === 0) {
2009
- buffer.extend([0]);
2010
- return;
2011
- }
2012
-
2013
- let remaining = BigInt(int);
2014
- const bytes: Array<number> = [];
2015
-
2016
- while (remaining !== 0n) {
2017
- const byte = globalThis.Number(remaining & 127n);
2018
- bytes.push(byte);
2019
- remaining >>= 7n;
2020
- }
2021
-
2022
- for (let i = 0; i < bytes.length - 1; i++) {
2023
- bytes[i] |= 128;
2024
- }
2025
-
2026
- buffer.extend(bytes);
2027
- };
2028
-
2029
- /**
2030
- * Decodes a non-negative integer from a variable-length integer format.
1854
+ * Note: This is a stub for future implementation. It should use:
1855
+ *
1856
+ * - DecodeVersionAndOwner
1857
+ * - DecodeError or decodeWriteKeys (depending on context)
1858
+ * - DecodeMessages
1859
+ * - DecodeRanges
2031
1860
  *
2032
- * https://en.wikipedia.org/wiki/Variable-length_quantity
1861
+ * If you want to help, please contribute to this function.
2033
1862
  */
2034
- export const decodeNonNegativeInt = (buffer: Buffer): NonNegativeInt => {
2035
- let result = 0n;
2036
- let shift = 0n;
2037
- let byte;
2038
-
2039
- // 8 is the smallest required count
2040
- for (let byteCount = 0; byteCount < 8; byteCount++) {
2041
- byte = buffer.shift();
2042
- result |= BigInt(byte & 127) << shift;
2043
- if ((byte & 128) === 0) break;
2044
- shift += 7n;
2045
- }
2046
-
2047
- const int = NonNegativeInt.fromUnknown(globalThis.Number(result));
2048
- if (!int.ok) throw new ProtocolDecodeError(int.error.type);
2049
-
2050
- return int.value;
2051
- };
2052
-
2053
- export const encodeLength = (buffer: Buffer, value: ArrayLike<any>): void => {
2054
- encodeNonNegativeInt(buffer, NonNegativeInt.orThrow(value.length));
2055
- };
2056
-
2057
- export const decodeLength = decodeNonNegativeInt;
2058
-
2059
- export const encodeString = (buffer: Buffer, value: string): void => {
2060
- const bytes = utf8ToBytes(value);
2061
- encodeLength(buffer, bytes);
2062
- buffer.extend(bytes);
2063
- };
2064
-
2065
- export const decodeString = (buffer: Buffer): string => {
2066
- const length = decodeLength(buffer);
2067
- const bytes = buffer.shiftN(length);
2068
- return bytesToUtf8(bytes);
2069
- };
2070
-
2071
- export const encodeNodeId = (buffer: Buffer, nodeId: NodeId): void => {
2072
- buffer.extend(hexToBytes(nodeId));
2073
- };
2074
-
2075
- export const decodeNodeId = (buffer: Buffer): NodeId => {
2076
- const bytes = buffer.shiftN(NonNegativeInt.orThrow(8));
2077
- return bytesToHex(bytes) as NodeId;
1863
+ export const decodeProtocolMessageToJson = (
1864
+ _protocolMessage: ProtocolMessage,
1865
+ _isInitiator: boolean,
1866
+ ): unknown => {
1867
+ // TODO: Implement using
1868
+ // - decodeVersionAndOwner
1869
+ // -- decodeError or decodeWriteKeys (should be refactored out),
1870
+ // -- decodeMessages, and decodeRanges.
1871
+ // This is a stub for PRs and community contributions.
1872
+ throw new Error("decodeProtocolMessageToJson is not implemented yet.");
2078
1873
  };
2079
1874
 
2080
1875
  // Small ints are encoded into ProtocolValueType, saving one byte per int.
2081
- const isSmallInt: Predicate<number> = (value: number) =>
2082
- value >= 0 && value < 20;
2083
-
2084
1876
  export const ProtocolValueType = {
2085
1877
  // 0-19 small ints
2086
1878
 
@@ -2157,10 +1949,16 @@ export const encodeSqliteValue = (buffer: Buffer, value: SqliteValue): void => {
2157
1949
  // Some valid JSON strings like "-0E0" get normalized to "0" during parsing,
2158
1950
  // which would cause data corruption if we don't verify round-trip safety.
2159
1951
  if (json.ok && JSON.stringify(jsonToJsonValue(json.value)) === value) {
2160
- const jsonBytes = packr.pack(jsonToJsonValue(json.value));
2161
- encodeNonNegativeInt(buffer, ProtocolValueType.Json);
2162
- encodeLength(buffer, jsonBytes);
2163
- buffer.extend(jsonBytes);
1952
+ jsonBuffer.reset();
1953
+ try {
1954
+ encodeJsonValue(jsonBuffer, jsonToJsonValue(json.value));
1955
+ const jsonBytes = jsonBuffer.unwrap();
1956
+ encodeNonNegativeInt(buffer, ProtocolValueType.Json);
1957
+ encodeLength(buffer, jsonBytes);
1958
+ buffer.extend(jsonBytes);
1959
+ } finally {
1960
+ jsonBuffer.reset();
1961
+ }
2164
1962
  return;
2165
1963
  }
2166
1964
 
@@ -2179,7 +1977,9 @@ export const encodeSqliteValue = (buffer: Buffer, value: SqliteValue): void => {
2179
1977
  }
2180
1978
 
2181
1979
  case "number": {
2182
- if (NonNegativeInt.is(value)) {
1980
+ // Negative zero is a non-negative integer in JavaScript, but integer
1981
+ // encoding would lose its observable sign.
1982
+ if (!Object.is(value, -0) && NonNegativeInt.is(value)) {
2183
1983
  if (isSmallInt(value)) {
2184
1984
  encodeNonNegativeInt(buffer, value);
2185
1985
  return;
@@ -2229,8 +2029,12 @@ export const decodeSqliteValue = (buffer: Buffer): SqliteValue => {
2229
2029
 
2230
2030
  case ProtocolValueType.Json: {
2231
2031
  const length = decodeLength(buffer);
2232
- const bytes = buffer.shiftN(length);
2233
- return JSON.stringify(packr.unpack(bytes));
2032
+ const jsonValueBuffer = createBuffer(buffer.shiftN(length));
2033
+ const value = decodeJsonValue(jsonValueBuffer);
2034
+ if (jsonValueBuffer.getLength() !== 0) {
2035
+ throw new ProtocolDecodeError("Invalid JSON MessagePack length");
2036
+ }
2037
+ return JSON.stringify(value);
2234
2038
  }
2235
2039
 
2236
2040
  case ProtocolValueType.DateIsoWithNonNegativeTime:
@@ -2258,26 +2062,7 @@ export const decodeSqliteValue = (buffer: Buffer): SqliteValue => {
2258
2062
  }
2259
2063
  };
2260
2064
 
2261
- /**
2262
- * Decodes a ProtocolMessage into a readable JSON object for debugging.
2263
- *
2264
- * Note: This is a stub for future implementation. It should use:
2265
- *
2266
- * - DecodeVersionAndOwner
2267
- * - DecodeError or decodeWriteKeys (depending on context)
2268
- * - DecodeMessages
2269
- * - DecodeRanges
2270
- *
2271
- * If you want to help, please contribute to this function.
2272
- */
2273
- export const decodeProtocolMessageToJson = (
2274
- _protocolMessage: ProtocolMessage,
2275
- _isInitiator: boolean,
2276
- ): unknown => {
2277
- // TODO: Implement using
2278
- // - decodeVersionAndOwner
2279
- // -- decodeError or decodeWriteKeys (should be refactored out),
2280
- // -- decodeMessages, and decodeRanges.
2281
- // This is a stub for PRs and community contributions.
2282
- throw new Error("decodeProtocolMessageToJson is not implemented yet.");
2283
- };
2065
+ const jsonBuffer = createBuffer();
2066
+
2067
+ const isSmallInt: Predicate<number> = (value: number) =>
2068
+ value >= 0 && value < 20;