@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.
- package/dist/src/Array.d.ts +21 -21
- package/dist/src/Array.js +5 -5
- package/dist/src/Assert.d.ts +478 -141
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +644 -99
- package/dist/src/Binary.d.ts +254 -0
- package/dist/src/Binary.d.ts.map +1 -0
- package/dist/src/Binary.js +958 -0
- package/dist/src/Console.d.ts +2 -10
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +6 -12
- package/dist/src/Crypto.d.ts +5 -5
- package/dist/src/Crypto.js +1 -1
- package/dist/src/Eq.d.ts +35 -40
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +51 -87
- package/dist/src/Function.d.ts +9 -14
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +7 -9
- package/dist/src/Http.d.ts +6 -7
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +1 -0
- package/dist/src/LockManager.d.ts +1 -1
- package/dist/src/LockManager.js +1 -1
- package/dist/src/Lookup.d.ts +3 -2
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +4 -3
- package/dist/src/Number.d.ts +1 -1
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +1 -1
- package/dist/src/Object.d.ts +4 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +5 -4
- package/dist/src/Option.d.ts +1 -2
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +1 -2
- package/dist/src/Platform.js +2 -0
- package/dist/src/Polyfills.js +4 -1
- package/dist/src/Random.d.ts +1 -1
- package/dist/src/Result.d.ts +49 -54
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +17 -22
- package/dist/src/Set.d.ts +6 -6
- package/dist/src/Set.js +2 -2
- package/dist/src/Sqlite.js +1 -1
- package/dist/src/String.js +3 -2
- package/dist/src/Task.d.ts +32 -32
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +20 -22
- package/dist/src/Test.d.ts +31 -3
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +46 -3
- package/dist/src/Time.d.ts +3 -11
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +10 -18
- package/dist/src/Type.d.ts +87 -85
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +70 -63
- package/dist/src/Types.d.ts +12 -12
- package/dist/src/Types.js +1 -1
- package/dist/src/WebSocket.d.ts +4 -7
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/Worker.d.ts +1 -1
- package/dist/src/Worker.js +7 -7
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +1 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +1 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +1 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +1 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +1 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +1 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +1 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +1 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +1 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +1 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +1 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +1 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +1 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +1 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +1 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +1 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +1 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +1 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +1 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +1 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +1 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +1 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +1 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +1 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +1 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +1 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +1 -1
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +1 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +1 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +1 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +1 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +1 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +1 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +1 -1
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +1 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +1 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +1 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +1 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +1 -1
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +1 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +1 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +1 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +1 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +1 -1
- package/dist/src/local-first/Evolu.d.ts +12 -12
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +2 -2
- package/dist/src/local-first/Protocol.d.ts +28 -76
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +45 -229
- package/dist/src/local-first/Query.d.ts +11 -11
- package/dist/src/local-first/Query.js +6 -6
- package/dist/src/local-first/Relay.d.ts +2 -2
- package/dist/src/local-first/Schema.d.ts +3 -5
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +2 -4
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +5 -5
- package/dist/src/local-first/Storage.js +4 -4
- package/dist/src/local-first/Timestamp.d.ts +9 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +13 -10
- package/package.json +7 -5
- package/src/Array.test.ts +809 -0
- package/src/Array.ts +21 -21
- package/src/Assert.test.ts +1181 -0
- package/src/Assert.ts +971 -227
- package/src/BigInt.test.ts +28 -0
- package/src/Binary.test.ts +1128 -0
- package/src/Binary.ts +1377 -0
- package/src/Brand.test.ts +90 -0
- package/src/Cache.test.ts +183 -0
- package/src/Callbacks.test.ts +66 -0
- package/src/Console.test.ts +809 -0
- package/src/Console.ts +9 -17
- package/src/Crypto.test.ts +138 -0
- package/src/Crypto.ts +5 -5
- package/src/Eq.test.ts +333 -0
- package/src/Eq.ts +110 -176
- package/src/Error.test.ts +118 -0
- package/src/Function.test.ts +204 -0
- package/src/Function.ts +10 -15
- package/src/Http.test.ts +816 -0
- package/src/Http.ts +6 -7
- package/src/LeakDetector.test.ts +210 -0
- package/src/LeakDetector.ts +1 -0
- package/src/LockManager.ts +2 -2
- package/src/Lookup.test.ts +418 -0
- package/src/Lookup.ts +4 -3
- package/src/Microtask.test.ts +140 -0
- package/src/Number.test.ts +218 -0
- package/src/Number.ts +2 -2
- package/src/Object.test.ts +293 -0
- package/src/Object.ts +5 -4
- package/src/Option.test.ts +104 -0
- package/src/Option.ts +1 -2
- package/src/Order.test.ts +56 -0
- package/src/Platform.ts +2 -0
- package/src/Polyfills.ts +3 -4
- package/src/Random.test.ts +89 -0
- package/src/Random.ts +1 -1
- package/src/Ref.test.ts +139 -0
- package/src/RefCount.test.ts +184 -0
- package/src/Relation.test.ts +505 -0
- package/src/Resource.test.ts +4623 -0
- package/src/Result.test.ts +1829 -0
- package/src/Result.ts +49 -54
- package/src/Schedule.test.ts +1554 -0
- package/src/Set.test.ts +279 -0
- package/src/Set.ts +6 -6
- package/src/Sqlite.ts +1 -1
- package/src/Store.test.ts +288 -0
- package/src/String.test.ts +54 -0
- package/src/String.ts +3 -3
- package/src/Task.test.ts +10820 -0
- package/src/Task.ts +38 -43
- package/src/Test.test.ts +88 -0
- package/src/Test.ts +49 -3
- package/src/Time.test.ts +926 -0
- package/src/Time.ts +13 -21
- package/src/Type.base64.test.ts +88 -0
- package/src/Type.test.ts +20465 -0
- package/src/Type.ts +114 -111
- package/src/Types.test.ts +324 -0
- package/src/Types.ts +13 -13
- package/src/WebSocket.ts +4 -7
- package/src/Worker.test.ts +755 -0
- package/src/Worker.ts +13 -18
- package/src/index.ts +1 -1
- package/src/intl/_en.ts +1 -1
- package/src/intl/ar.ts +1 -1
- package/src/intl/bn.ts +1 -1
- package/src/intl/ca.ts +1 -1
- package/src/intl/cs.ts +1 -1
- package/src/intl/da.ts +1 -1
- package/src/intl/de.ts +1 -1
- package/src/intl/el.ts +1 -1
- package/src/intl/es.ts +1 -1
- package/src/intl/fa.ts +1 -1
- package/src/intl/fi.ts +1 -1
- package/src/intl/fil.ts +1 -1
- package/src/intl/fr.ts +1 -1
- package/src/intl/he.ts +1 -1
- package/src/intl/hi.ts +1 -1
- package/src/intl/hr.ts +1 -1
- package/src/intl/hu.ts +1 -1
- package/src/intl/id.ts +1 -1
- package/src/intl/intl.test.ts +3106 -0
- package/src/intl/it.ts +1 -1
- package/src/intl/ja.ts +1 -1
- package/src/intl/ko.ts +1 -1
- package/src/intl/ml.ts +1 -1
- package/src/intl/mr.ts +1 -1
- package/src/intl/ms.ts +1 -1
- package/src/intl/nb.ts +1 -1
- package/src/intl/nl.ts +1 -1
- package/src/intl/pa.ts +1 -1
- package/src/intl/pl.ts +1 -1
- package/src/intl/pt-BR.ts +1 -1
- package/src/intl/pt.ts +1 -1
- package/src/intl/ro.ts +1 -1
- package/src/intl/sk.ts +1 -1
- package/src/intl/sl.ts +1 -1
- package/src/intl/sv.ts +1 -1
- package/src/intl/sw.ts +1 -1
- package/src/intl/ta.ts +1 -1
- package/src/intl/te.ts +1 -1
- package/src/intl/th.ts +1 -1
- package/src/intl/tr.ts +1 -1
- package/src/intl/uk.ts +1 -1
- package/src/intl/ur.ts +1 -1
- package/src/intl/vi.ts +1 -1
- package/src/intl/zh-CN.ts +1 -1
- package/src/intl/zh-TW.ts +1 -1
- package/src/local-first/Evolu.test.ts +1921 -0
- package/src/local-first/Evolu.ts +18 -14
- package/src/local-first/Owner.test.ts +83 -0
- package/src/local-first/Protocol.test.ts +1037 -0
- package/src/local-first/Protocol.ts +75 -290
- package/src/local-first/Query.test.ts +349 -0
- package/src/local-first/Query.ts +11 -11
- package/src/local-first/Relay.ts +2 -2
- package/src/local-first/Schema.ts +3 -5
- package/src/local-first/Shared.test.ts +1828 -0
- package/src/local-first/Shared.ts +9 -8
- package/src/local-first/Storage.ts +4 -4
- package/src/local-first/Timestamp.test.ts +374 -0
- package/src/local-first/Timestamp.ts +22 -10
- package/dist/src/Buffer.d.ts +0 -122
- package/dist/src/Buffer.d.ts.map +0 -1
- package/dist/src/Buffer.js +0 -69
- package/src/Buffer.ts +0 -206
|
@@ -0,0 +1,958 @@
|
|
|
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
|
+
import { bytesToUtf8, utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
14
|
+
export { bytesToHex, concatBytes, hexToBytes } from "@noble/ciphers/utils.js";
|
|
15
|
+
export { bytesToUtf8, utf8ToBytes };
|
|
16
|
+
/**
|
|
17
|
+
* Custom error for {@link Buffer}-related failures like premature end of data.
|
|
18
|
+
* Provides better stack traces for debugging binary protocol issues.
|
|
19
|
+
*/
|
|
20
|
+
export class BufferError extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = this.constructor.name;
|
|
24
|
+
Error.captureStackTrace(this, this.constructor);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Creates a {@link Buffer} for efficient byte operations. */
|
|
28
|
+
export const createBuffer = (arrayLike) => {
|
|
29
|
+
const initialLength = arrayLike?.length ?? 0;
|
|
30
|
+
assertNonNegativeInt(initialLength, "arrayLike.length");
|
|
31
|
+
let value = arrayLike ? new Uint8Array(arrayLike) : new Uint8Array(512);
|
|
32
|
+
let length = initialLength;
|
|
33
|
+
const buffer = {
|
|
34
|
+
getCapacity: () => value.length,
|
|
35
|
+
getLength: () => length,
|
|
36
|
+
extend: (arg) => {
|
|
37
|
+
const argLength = arg.length;
|
|
38
|
+
assertNonNegativeInt(argLength, "arg.length");
|
|
39
|
+
const targetSize = length + argLength;
|
|
40
|
+
assertNonNegativeInt(targetSize, "Buffer length");
|
|
41
|
+
if (value.length < targetSize) {
|
|
42
|
+
const oldValue = value;
|
|
43
|
+
const newCapacity = Math.max(value.length * 2, targetSize);
|
|
44
|
+
value = new Uint8Array(newCapacity);
|
|
45
|
+
value.set(oldValue);
|
|
46
|
+
}
|
|
47
|
+
value.set(arg, length);
|
|
48
|
+
length = targetSize;
|
|
49
|
+
},
|
|
50
|
+
shift: () => {
|
|
51
|
+
assertBufferHasRemainingBytes(length, 1);
|
|
52
|
+
const first = value[0];
|
|
53
|
+
value = value.subarray(1);
|
|
54
|
+
length--;
|
|
55
|
+
return first;
|
|
56
|
+
},
|
|
57
|
+
shiftN: (n) => {
|
|
58
|
+
assertBufferHasRemainingBytes(length, n);
|
|
59
|
+
const subarray = value.subarray(0, n);
|
|
60
|
+
value = value.subarray(n);
|
|
61
|
+
length = (length - n);
|
|
62
|
+
return subarray;
|
|
63
|
+
},
|
|
64
|
+
truncate: (newLength) => {
|
|
65
|
+
if (newLength > length) {
|
|
66
|
+
throw new BufferError("Cannot truncate to a length greater than current");
|
|
67
|
+
}
|
|
68
|
+
length = newLength;
|
|
69
|
+
},
|
|
70
|
+
reset: () => {
|
|
71
|
+
length = 0;
|
|
72
|
+
},
|
|
73
|
+
unwrap: () => (value.length === length ? value : value.subarray(0, length)),
|
|
74
|
+
};
|
|
75
|
+
return buffer;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Evolu uses MessagePack to handle finite numbers except for NonNegativeInt.
|
|
79
|
+
* For NonNegativeInt, Evolu provides more efficient encoding.
|
|
80
|
+
*/
|
|
81
|
+
export const encodeNumber = (buffer, number) => {
|
|
82
|
+
encodeJsonValue(buffer, number);
|
|
83
|
+
};
|
|
84
|
+
export const decodeNumber = (buffer) => {
|
|
85
|
+
const value = decodeJsonValue(buffer);
|
|
86
|
+
if (typeof value !== "number") {
|
|
87
|
+
throw new BufferError("Expected an encoded number.");
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Encodes an array of boolean flags into a single byte.
|
|
93
|
+
*
|
|
94
|
+
* Each element in the array corresponds to a bit (0-7). Array can have 0-8
|
|
95
|
+
* elements.
|
|
96
|
+
*
|
|
97
|
+
* ### Example
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { assertEqual, createBuffer, encodeFlags } from "@evolu/common";
|
|
101
|
+
*
|
|
102
|
+
* const buffer = createBuffer();
|
|
103
|
+
* encodeFlags(buffer, [true, false, true]);
|
|
104
|
+
*
|
|
105
|
+
* assertEqual(buffer.unwrap(), new Uint8Array([0b101]));
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export const encodeFlags = (buffer, flags) => {
|
|
109
|
+
let byte = 0;
|
|
110
|
+
for (let i = 0; i < flags.length && i < 8; i++) {
|
|
111
|
+
if (flags[i]) {
|
|
112
|
+
byte |= 1 << i;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
buffer.extend([byte]);
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Decodes a byte into an array of boolean flags.
|
|
119
|
+
*
|
|
120
|
+
* ### Example
|
|
121
|
+
*
|
|
122
|
+
* ```ts
|
|
123
|
+
* import {
|
|
124
|
+
* assertEqual,
|
|
125
|
+
* createBuffer,
|
|
126
|
+
* decodeFlags,
|
|
127
|
+
* PositiveInt,
|
|
128
|
+
* } from "@evolu/common";
|
|
129
|
+
*
|
|
130
|
+
* const buffer = createBuffer([0b101]);
|
|
131
|
+
* const flags = decodeFlags(buffer, PositiveInt.orThrow(3));
|
|
132
|
+
*
|
|
133
|
+
* assertEqual(flags, [true, false, true]);
|
|
134
|
+
* assertEqual(buffer.getLength(), 0);
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export const decodeFlags = (buffer, count) => {
|
|
138
|
+
const byte = buffer.shift();
|
|
139
|
+
const length = Math.min(count, 8);
|
|
140
|
+
// oxlint-disable-next-line unicorn/no-new-array -- Preallocation is intentional in this decoding hot path.
|
|
141
|
+
const flags = new Array(length);
|
|
142
|
+
for (let i = 0; i < length; i++) {
|
|
143
|
+
flags[i] = (byte & (1 << i)) !== 0;
|
|
144
|
+
}
|
|
145
|
+
return flags;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Encodes a non-negative integer into a variable-length integer format. It's
|
|
149
|
+
* more efficient than encoding via {@link encodeNumber}.
|
|
150
|
+
*
|
|
151
|
+
* https://en.wikipedia.org/wiki/Variable-length_quantity
|
|
152
|
+
*/
|
|
153
|
+
export const encodeNonNegativeInt = (buffer, value) => {
|
|
154
|
+
if (value === 0) {
|
|
155
|
+
buffer.extend([0]);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
let remaining = BigInt(value);
|
|
159
|
+
const bytes = [];
|
|
160
|
+
while (remaining !== 0n) {
|
|
161
|
+
const byte = Number(remaining & 127n);
|
|
162
|
+
bytes.push(byte);
|
|
163
|
+
remaining >>= 7n;
|
|
164
|
+
}
|
|
165
|
+
for (let i = 0; i < bytes.length - 1; i++) {
|
|
166
|
+
bytes[i] |= 128;
|
|
167
|
+
}
|
|
168
|
+
buffer.extend(bytes);
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Decodes a non-negative integer from a variable-length integer format.
|
|
172
|
+
*
|
|
173
|
+
* https://en.wikipedia.org/wiki/Variable-length_quantity
|
|
174
|
+
*/
|
|
175
|
+
export const decodeNonNegativeInt = (buffer) => {
|
|
176
|
+
let result = 0n;
|
|
177
|
+
let shift = 0n;
|
|
178
|
+
let byte = 0;
|
|
179
|
+
// 8 is the smallest required count
|
|
180
|
+
for (let byteCount = 0; byteCount < 8; byteCount++) {
|
|
181
|
+
byte = buffer.shift();
|
|
182
|
+
result |= BigInt(byte & 127) << shift;
|
|
183
|
+
if ((byte & 128) === 0)
|
|
184
|
+
break;
|
|
185
|
+
shift += 7n;
|
|
186
|
+
}
|
|
187
|
+
if ((byte & 128) !== 0) {
|
|
188
|
+
throw new BufferError("Variable-length quantity must terminate within 8 bytes.");
|
|
189
|
+
}
|
|
190
|
+
const value = Number(result);
|
|
191
|
+
assertNonNegativeInt(value, "Decoded integer");
|
|
192
|
+
return value;
|
|
193
|
+
};
|
|
194
|
+
/** Encodes the length of an array-like value. */
|
|
195
|
+
export const encodeLength = (buffer, value) => {
|
|
196
|
+
assertNonNegativeInt(value.length, "Array-like length");
|
|
197
|
+
encodeNonNegativeInt(buffer, value.length);
|
|
198
|
+
};
|
|
199
|
+
/** Decodes an array-like value length. */
|
|
200
|
+
export const decodeLength = decodeNonNegativeInt;
|
|
201
|
+
/** Encodes a length-prefixed UTF-8 string. */
|
|
202
|
+
export const encodeString = (buffer, value) => {
|
|
203
|
+
const bytes = utf8ToBytes(value);
|
|
204
|
+
encodeLength(buffer, bytes);
|
|
205
|
+
buffer.extend(bytes);
|
|
206
|
+
};
|
|
207
|
+
/** Decodes a length-prefixed UTF-8 string. */
|
|
208
|
+
export const decodeString = (buffer) => {
|
|
209
|
+
const length = decodeLength(buffer);
|
|
210
|
+
const bytes = buffer.shiftN(length);
|
|
211
|
+
return bytesToUtf8(bytes);
|
|
212
|
+
};
|
|
213
|
+
/** Creates an incremental run-length encoder. */
|
|
214
|
+
export const createRunLengthEncoder = (encodeValue) => {
|
|
215
|
+
const buffer = createBuffer();
|
|
216
|
+
let previousLength = 0;
|
|
217
|
+
let previousValue = null;
|
|
218
|
+
let runLength = 0;
|
|
219
|
+
return {
|
|
220
|
+
add: (value) => {
|
|
221
|
+
if (Object.is(value, previousValue)) {
|
|
222
|
+
runLength++;
|
|
223
|
+
buffer.truncate(previousLength);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
previousValue = value;
|
|
227
|
+
runLength = 1;
|
|
228
|
+
}
|
|
229
|
+
previousLength = buffer.getLength();
|
|
230
|
+
encodeValue(buffer, value);
|
|
231
|
+
encodeNonNegativeInt(buffer, runLength);
|
|
232
|
+
},
|
|
233
|
+
getLength: () => buffer.getLength(),
|
|
234
|
+
unwrap: () => buffer.unwrap(),
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
/** Decodes a run-length encoded sequence. */
|
|
238
|
+
export const decodeRle = (buffer, length, decodeValue) => {
|
|
239
|
+
// oxlint-disable-next-line unicorn/no-new-array -- Preallocation is intentional in this decoding hot path.
|
|
240
|
+
const values = new Array(length);
|
|
241
|
+
let index = 0;
|
|
242
|
+
while (index < length) {
|
|
243
|
+
const value = decodeValue();
|
|
244
|
+
const runLength = decodeNonNegativeInt(buffer);
|
|
245
|
+
// Prevent infinite loop on malformed input.
|
|
246
|
+
if (runLength === 0) {
|
|
247
|
+
throw new BufferError("Invalid RLE encoding: runLength must be positive");
|
|
248
|
+
}
|
|
249
|
+
const remaining = length - index;
|
|
250
|
+
// Prevent CPU/memory amplification via oversized runLength.
|
|
251
|
+
if (runLength > remaining) {
|
|
252
|
+
throw new BufferError(`Invalid RLE encoding: runLength ${runLength} exceeds remaining ${remaining}`);
|
|
253
|
+
}
|
|
254
|
+
for (let i = 0; i < runLength; i++) {
|
|
255
|
+
values[index] = value;
|
|
256
|
+
index++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return values;
|
|
260
|
+
};
|
|
261
|
+
const jsonKeyCacheSize = 4096;
|
|
262
|
+
const maxCachedJsonKeyByteLength = 32;
|
|
263
|
+
const maxJsonNestingDepth = 1_000;
|
|
264
|
+
let jsonEncoderTarget = new Uint8Array(8192);
|
|
265
|
+
let jsonEncoderTargetView = new DataView(jsonEncoderTarget.buffer);
|
|
266
|
+
let jsonEncoderPosition = 0;
|
|
267
|
+
let jsonEncoderDepth = 0;
|
|
268
|
+
let jsonEncoderIsActive = false;
|
|
269
|
+
const emptyJsonDecoderSource = new Uint8Array(0);
|
|
270
|
+
const emptyJsonDecoderView = new DataView(emptyJsonDecoderSource.buffer);
|
|
271
|
+
let jsonDecoderSource = emptyJsonDecoderSource;
|
|
272
|
+
let jsonDecoderView = emptyJsonDecoderView;
|
|
273
|
+
let jsonDecoderPosition = 0;
|
|
274
|
+
let jsonDecoderDepth = 0;
|
|
275
|
+
const jsonStringFromCharCode = String.fromCharCode;
|
|
276
|
+
// Cache only short keys and use a fixed table to bound retained memory.
|
|
277
|
+
const jsonKeyCache = Array.from({ length: jsonKeyCacheSize }, () => undefined);
|
|
278
|
+
/**
|
|
279
|
+
* Encodes a {@link JsonValue} using the MessagePack format.
|
|
280
|
+
*
|
|
281
|
+
* ### Example
|
|
282
|
+
*
|
|
283
|
+
* ```ts
|
|
284
|
+
* import {
|
|
285
|
+
* assertEqual,
|
|
286
|
+
* createBuffer,
|
|
287
|
+
* encodeJsonValue,
|
|
288
|
+
* JsonValue,
|
|
289
|
+
* } from "@evolu/common";
|
|
290
|
+
*
|
|
291
|
+
* const buffer = createBuffer();
|
|
292
|
+
* const value = JsonValue.orThrow({ name: "Ada" });
|
|
293
|
+
*
|
|
294
|
+
* encodeJsonValue(buffer, value);
|
|
295
|
+
*
|
|
296
|
+
* assertEqual(
|
|
297
|
+
* buffer.unwrap(),
|
|
298
|
+
* new Uint8Array([
|
|
299
|
+
* 0x81, 0xa4, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x41, 0x64, 0x61,
|
|
300
|
+
* ]),
|
|
301
|
+
* );
|
|
302
|
+
* ```
|
|
303
|
+
*
|
|
304
|
+
* Encoding is artificially limited to 1,000 nested arrays or objects to keep
|
|
305
|
+
* recursive encoding and decoding safe and symmetric. JSON data should not
|
|
306
|
+
* require such depth; flatten or split deeply nested data, or use a
|
|
307
|
+
* purpose-built serialization format.
|
|
308
|
+
*/
|
|
309
|
+
export const encodeJsonValue = (buffer, value) => {
|
|
310
|
+
if (jsonEncoderIsActive) {
|
|
311
|
+
throw new BufferError("Reentrant JSON encoding is not supported.");
|
|
312
|
+
}
|
|
313
|
+
jsonEncoderIsActive = true;
|
|
314
|
+
jsonEncoderPosition = 0;
|
|
315
|
+
try {
|
|
316
|
+
encodeJsonValueToTarget(value);
|
|
317
|
+
buffer.extend(jsonEncoderTarget.subarray(0, jsonEncoderPosition));
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
jsonEncoderPosition = 0;
|
|
321
|
+
jsonEncoderDepth = 0;
|
|
322
|
+
jsonEncoderIsActive = false;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* Decodes a {@link JsonValue} using the MessagePack format.
|
|
327
|
+
*
|
|
328
|
+
* Throws a {@link BufferError} without modifying the Buffer if the encoded value
|
|
329
|
+
* is malformed, truncated, unsupported, outside the JsonValue domain, or
|
|
330
|
+
* exceeds 1,000 nested arrays or objects.
|
|
331
|
+
*
|
|
332
|
+
* ### Example
|
|
333
|
+
*
|
|
334
|
+
* ```ts
|
|
335
|
+
* import {
|
|
336
|
+
* assertEqual,
|
|
337
|
+
* createBuffer,
|
|
338
|
+
* decodeJsonValue,
|
|
339
|
+
* } from "@evolu/common";
|
|
340
|
+
*
|
|
341
|
+
* const buffer = createBuffer([
|
|
342
|
+
* 0x81, 0xa4, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x41, 0x64, 0x61,
|
|
343
|
+
* ]);
|
|
344
|
+
*
|
|
345
|
+
* assertEqual(decodeJsonValue(buffer), { name: "Ada" });
|
|
346
|
+
* assertEqual(buffer.unwrap(), new Uint8Array());
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
export const decodeJsonValue = (buffer) => {
|
|
350
|
+
const source = buffer.unwrap();
|
|
351
|
+
jsonDecoderSource = source;
|
|
352
|
+
jsonDecoderView = new DataView(source.buffer, source.byteOffset, source.byteLength);
|
|
353
|
+
jsonDecoderPosition = 0;
|
|
354
|
+
try {
|
|
355
|
+
const value = decodeJsonValueFromSource();
|
|
356
|
+
buffer.shiftN(jsonDecoderPosition);
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
if (error instanceof BufferError)
|
|
361
|
+
throw error;
|
|
362
|
+
throw new BufferError("Invalid MessagePack data");
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
jsonDecoderSource = emptyJsonDecoderSource;
|
|
366
|
+
jsonDecoderView = emptyJsonDecoderView;
|
|
367
|
+
jsonDecoderPosition = 0;
|
|
368
|
+
jsonDecoderDepth = 0;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
const encodeJsonValueToTarget = (value) => {
|
|
372
|
+
if (value === null) {
|
|
373
|
+
writeJsonEncoderByte(0xc0);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
|
|
377
|
+
switch (typeof value) {
|
|
378
|
+
case "string":
|
|
379
|
+
encodeJsonStringToTarget(value);
|
|
380
|
+
return;
|
|
381
|
+
case "number": {
|
|
382
|
+
if (Object.is(value, -0)) {
|
|
383
|
+
ensureJsonEncoderCapacity(9);
|
|
384
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcb;
|
|
385
|
+
jsonEncoderTargetView.setFloat64(jsonEncoderPosition, value);
|
|
386
|
+
jsonEncoderPosition += 8;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (value >>> 0 === value) {
|
|
390
|
+
if (value < 0x80) {
|
|
391
|
+
writeJsonEncoderByte(value);
|
|
392
|
+
}
|
|
393
|
+
else if (value < 0x100) {
|
|
394
|
+
ensureJsonEncoderCapacity(2);
|
|
395
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcc;
|
|
396
|
+
jsonEncoderTarget[jsonEncoderPosition++] = value;
|
|
397
|
+
}
|
|
398
|
+
else if (value < 0x10000) {
|
|
399
|
+
ensureJsonEncoderCapacity(3);
|
|
400
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcd;
|
|
401
|
+
jsonEncoderTargetView.setUint16(jsonEncoderPosition, value);
|
|
402
|
+
jsonEncoderPosition += 2;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
ensureJsonEncoderCapacity(5);
|
|
406
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xce;
|
|
407
|
+
jsonEncoderTargetView.setUint32(jsonEncoderPosition, value);
|
|
408
|
+
jsonEncoderPosition += 4;
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (Number.isInteger(value) && value >= -0x80000000 && value < 0) {
|
|
413
|
+
if (value >= -0x20) {
|
|
414
|
+
writeJsonEncoderByte(0x100 + value);
|
|
415
|
+
}
|
|
416
|
+
else if (value >= -0x80) {
|
|
417
|
+
ensureJsonEncoderCapacity(2);
|
|
418
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xd0;
|
|
419
|
+
jsonEncoderTargetView.setInt8(jsonEncoderPosition++, value);
|
|
420
|
+
}
|
|
421
|
+
else if (value >= -0x8000) {
|
|
422
|
+
ensureJsonEncoderCapacity(3);
|
|
423
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xd1;
|
|
424
|
+
jsonEncoderTargetView.setInt16(jsonEncoderPosition, value);
|
|
425
|
+
jsonEncoderPosition += 2;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
ensureJsonEncoderCapacity(5);
|
|
429
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xd2;
|
|
430
|
+
jsonEncoderTargetView.setInt32(jsonEncoderPosition, value);
|
|
431
|
+
jsonEncoderPosition += 4;
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
ensureJsonEncoderCapacity(9);
|
|
436
|
+
jsonEncoderTarget[jsonEncoderPosition++] = 0xcb;
|
|
437
|
+
jsonEncoderTargetView.setFloat64(jsonEncoderPosition, value);
|
|
438
|
+
jsonEncoderPosition += 8;
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
case "boolean":
|
|
442
|
+
writeJsonEncoderByte(value ? 0xc3 : 0xc2);
|
|
443
|
+
return;
|
|
444
|
+
case "object": {
|
|
445
|
+
if (jsonEncoderDepth >= maxJsonNestingDepth) {
|
|
446
|
+
throw new BufferError(`JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`);
|
|
447
|
+
}
|
|
448
|
+
jsonEncoderDepth++;
|
|
449
|
+
if (Array.isArray(value)) {
|
|
450
|
+
const array = value;
|
|
451
|
+
const length = array.length;
|
|
452
|
+
writeJsonCollectionHeader(length, 0x90, 0xdc, 0xdd);
|
|
453
|
+
for (const item of array)
|
|
454
|
+
encodeJsonValueToTarget(item);
|
|
455
|
+
jsonEncoderDepth--;
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const object = value;
|
|
459
|
+
const keys = Object.keys(object);
|
|
460
|
+
writeJsonCollectionHeader(keys.length, 0x80, 0xde, 0xdf);
|
|
461
|
+
for (const key of keys) {
|
|
462
|
+
encodeJsonStringToTarget(key);
|
|
463
|
+
encodeJsonValueToTarget(object[key]);
|
|
464
|
+
}
|
|
465
|
+
jsonEncoderDepth--;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
const encodeJsonStringToTarget = (value) => {
|
|
470
|
+
const valueLength = value.length;
|
|
471
|
+
const headerLength = valueLength < 0x20
|
|
472
|
+
? 1
|
|
473
|
+
: valueLength < 0x100
|
|
474
|
+
? 2
|
|
475
|
+
: valueLength < 0x10000
|
|
476
|
+
? 3
|
|
477
|
+
: 5;
|
|
478
|
+
ensureJsonEncoderCapacity(5 + valueLength * 3);
|
|
479
|
+
const headerPosition = jsonEncoderPosition;
|
|
480
|
+
jsonEncoderPosition += headerLength;
|
|
481
|
+
for (let index = 0; index < valueLength; index++) {
|
|
482
|
+
let first = value.charCodeAt(index);
|
|
483
|
+
if (first < 0x80) {
|
|
484
|
+
jsonEncoderTarget[jsonEncoderPosition++] = first;
|
|
485
|
+
}
|
|
486
|
+
else if (first < 0x800) {
|
|
487
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first >> 6) | 0xc0;
|
|
488
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
|
|
489
|
+
}
|
|
490
|
+
else if ((first & 0xfc00) === 0xd800 &&
|
|
491
|
+
(value.charCodeAt(index + 1) & 0xfc00) === 0xdc00) {
|
|
492
|
+
const second = value.charCodeAt(++index);
|
|
493
|
+
first = 0x10000 + ((first & 0x03ff) << 10) + (second & 0x03ff);
|
|
494
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first >> 18) | 0xf0;
|
|
495
|
+
jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 12) & 0x3f) | 0x80;
|
|
496
|
+
jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 6) & 0x3f) | 0x80;
|
|
497
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first >> 12) | 0xe0;
|
|
501
|
+
jsonEncoderTarget[jsonEncoderPosition++] = ((first >> 6) & 0x3f) | 0x80;
|
|
502
|
+
jsonEncoderTarget[jsonEncoderPosition++] = (first & 0x3f) | 0x80;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const byteLength = jsonEncoderPosition - headerPosition - headerLength;
|
|
506
|
+
assertMessagePackLength(byteLength, "String byte length");
|
|
507
|
+
if (byteLength < 0x20) {
|
|
508
|
+
jsonEncoderTarget[headerPosition] = 0xa0 | byteLength;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (byteLength < 0x100) {
|
|
512
|
+
if (headerLength === 1) {
|
|
513
|
+
jsonEncoderTarget.copyWithin(headerPosition + 2, headerPosition + 1, jsonEncoderPosition);
|
|
514
|
+
jsonEncoderPosition++;
|
|
515
|
+
}
|
|
516
|
+
jsonEncoderTarget[headerPosition] = 0xd9;
|
|
517
|
+
jsonEncoderTarget[headerPosition + 1] = byteLength;
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (byteLength < 0x10000) {
|
|
521
|
+
if (headerLength < 3) {
|
|
522
|
+
const additionalHeaderLength = 3 - headerLength;
|
|
523
|
+
jsonEncoderTarget.copyWithin(headerPosition + 3, headerPosition + headerLength, jsonEncoderPosition);
|
|
524
|
+
jsonEncoderPosition += additionalHeaderLength;
|
|
525
|
+
}
|
|
526
|
+
jsonEncoderTarget[headerPosition] = 0xda;
|
|
527
|
+
jsonEncoderTargetView.setUint16(headerPosition + 1, byteLength);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (headerLength < 5) {
|
|
531
|
+
const additionalHeaderLength = 5 - headerLength;
|
|
532
|
+
jsonEncoderTarget.copyWithin(headerPosition + 5, headerPosition + headerLength, jsonEncoderPosition);
|
|
533
|
+
jsonEncoderPosition += additionalHeaderLength;
|
|
534
|
+
}
|
|
535
|
+
jsonEncoderTarget[headerPosition] = 0xdb;
|
|
536
|
+
jsonEncoderTargetView.setUint32(headerPosition + 1, byteLength);
|
|
537
|
+
};
|
|
538
|
+
const writeJsonCollectionHeader = (length, fixedMarker, marker16, marker32) => {
|
|
539
|
+
assertMessagePackLength(length, "Collection length");
|
|
540
|
+
if (length < 0x10) {
|
|
541
|
+
writeJsonEncoderByte(fixedMarker | length);
|
|
542
|
+
}
|
|
543
|
+
else if (length < 0x10000) {
|
|
544
|
+
ensureJsonEncoderCapacity(3);
|
|
545
|
+
jsonEncoderTarget[jsonEncoderPosition++] = marker16;
|
|
546
|
+
jsonEncoderTargetView.setUint16(jsonEncoderPosition, length);
|
|
547
|
+
jsonEncoderPosition += 2;
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
ensureJsonEncoderCapacity(5);
|
|
551
|
+
jsonEncoderTarget[jsonEncoderPosition++] = marker32;
|
|
552
|
+
jsonEncoderTargetView.setUint32(jsonEncoderPosition, length);
|
|
553
|
+
jsonEncoderPosition += 4;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const writeJsonEncoderByte = (value) => {
|
|
557
|
+
ensureJsonEncoderCapacity(1);
|
|
558
|
+
jsonEncoderTarget[jsonEncoderPosition++] = value;
|
|
559
|
+
};
|
|
560
|
+
const ensureJsonEncoderCapacity = (additionalLength) => {
|
|
561
|
+
const requiredLength = jsonEncoderPosition + additionalLength;
|
|
562
|
+
assertNonNegativeInt(requiredLength, "Encoded JSON value length");
|
|
563
|
+
if (requiredLength <= jsonEncoderTarget.length)
|
|
564
|
+
return;
|
|
565
|
+
const newCapacity = Math.max(jsonEncoderTarget.length * 2, requiredLength);
|
|
566
|
+
assertNonNegativeInt(newCapacity, "JSON encoder capacity");
|
|
567
|
+
const oldTarget = jsonEncoderTarget;
|
|
568
|
+
jsonEncoderTarget = new Uint8Array(newCapacity);
|
|
569
|
+
jsonEncoderTarget.set(oldTarget.subarray(0, jsonEncoderPosition));
|
|
570
|
+
jsonEncoderTargetView = new DataView(jsonEncoderTarget.buffer);
|
|
571
|
+
};
|
|
572
|
+
const assertMessagePackLength = (length, name) => {
|
|
573
|
+
if (length > 0xffffffff) {
|
|
574
|
+
throw new BufferError(`${name} exceeds the MessagePack uint32 limit.`);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
const decodeJsonValueFromSource = () => {
|
|
578
|
+
const marker = readJsonDecoderByte();
|
|
579
|
+
if (marker <= 0x7f)
|
|
580
|
+
return marker;
|
|
581
|
+
if (marker <= 0x8f)
|
|
582
|
+
return decodeJsonMap(marker - 0x80);
|
|
583
|
+
if (marker <= 0x9f)
|
|
584
|
+
return decodeJsonArray(marker - 0x90);
|
|
585
|
+
if (marker <= 0xbf)
|
|
586
|
+
return decodeJsonString(marker - 0xa0);
|
|
587
|
+
if (marker >= 0xe0)
|
|
588
|
+
return (marker - 0x100);
|
|
589
|
+
switch (marker) {
|
|
590
|
+
case 0xc0:
|
|
591
|
+
return null;
|
|
592
|
+
case 0xc2:
|
|
593
|
+
return false;
|
|
594
|
+
case 0xc3:
|
|
595
|
+
return true;
|
|
596
|
+
case 0xca:
|
|
597
|
+
return decodeJsonFloat(4);
|
|
598
|
+
case 0xcb:
|
|
599
|
+
return decodeJsonFloat(8);
|
|
600
|
+
case 0xcc:
|
|
601
|
+
return readJsonDecoderByte();
|
|
602
|
+
case 0xcd:
|
|
603
|
+
return readJsonUint16();
|
|
604
|
+
case 0xce:
|
|
605
|
+
return readJsonUint32();
|
|
606
|
+
case 0xd0: {
|
|
607
|
+
assertJsonDecoderHasRemainingBytes(1);
|
|
608
|
+
return jsonDecoderView.getInt8(jsonDecoderPosition++);
|
|
609
|
+
}
|
|
610
|
+
case 0xd1: {
|
|
611
|
+
assertJsonDecoderHasRemainingBytes(2);
|
|
612
|
+
const value = jsonDecoderView.getInt16(jsonDecoderPosition);
|
|
613
|
+
jsonDecoderPosition += 2;
|
|
614
|
+
return value;
|
|
615
|
+
}
|
|
616
|
+
case 0xd2: {
|
|
617
|
+
assertJsonDecoderHasRemainingBytes(4);
|
|
618
|
+
const value = jsonDecoderView.getInt32(jsonDecoderPosition);
|
|
619
|
+
jsonDecoderPosition += 4;
|
|
620
|
+
return value;
|
|
621
|
+
}
|
|
622
|
+
case 0xd9:
|
|
623
|
+
return decodeJsonString(readJsonDecoderByte());
|
|
624
|
+
case 0xda:
|
|
625
|
+
return decodeJsonString(readJsonUint16());
|
|
626
|
+
case 0xdb:
|
|
627
|
+
return decodeJsonString(readJsonUint32());
|
|
628
|
+
case 0xdc:
|
|
629
|
+
return decodeJsonArray(readJsonUint16());
|
|
630
|
+
case 0xdd:
|
|
631
|
+
return decodeJsonArray(readJsonUint32());
|
|
632
|
+
case 0xde:
|
|
633
|
+
return decodeJsonMap(readJsonUint16());
|
|
634
|
+
case 0xdf:
|
|
635
|
+
return decodeJsonMap(readJsonUint32());
|
|
636
|
+
default:
|
|
637
|
+
throw new BufferError(`Unsupported MessagePack marker 0x${marker.toString(16).padStart(2, "0")}.`);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
const decodeJsonFloat = (byteLength) => {
|
|
641
|
+
assertJsonDecoderHasRemainingBytes(byteLength);
|
|
642
|
+
const value = byteLength === 4
|
|
643
|
+
? jsonDecoderView.getFloat32(jsonDecoderPosition)
|
|
644
|
+
: jsonDecoderView.getFloat64(jsonDecoderPosition);
|
|
645
|
+
jsonDecoderPosition += byteLength;
|
|
646
|
+
if (!Number.isFinite(value)) {
|
|
647
|
+
throw new BufferError("A decoded JSON number must be finite.");
|
|
648
|
+
}
|
|
649
|
+
return value;
|
|
650
|
+
};
|
|
651
|
+
const decodeJsonString = (byteLength) => {
|
|
652
|
+
assertJsonDecoderHasRemainingBytes(byteLength);
|
|
653
|
+
shortAscii: if (byteLength < 16) {
|
|
654
|
+
if (byteLength === 0)
|
|
655
|
+
return "";
|
|
656
|
+
const start = jsonDecoderPosition;
|
|
657
|
+
const first = jsonDecoderSource[jsonDecoderPosition++];
|
|
658
|
+
if ((first & 0x80) !== 0) {
|
|
659
|
+
jsonDecoderPosition = start;
|
|
660
|
+
break shortAscii;
|
|
661
|
+
}
|
|
662
|
+
if (byteLength === 1)
|
|
663
|
+
return jsonStringFromCharCode(first);
|
|
664
|
+
const second = jsonDecoderSource[jsonDecoderPosition++];
|
|
665
|
+
if ((second & 0x80) !== 0) {
|
|
666
|
+
jsonDecoderPosition = start;
|
|
667
|
+
break shortAscii;
|
|
668
|
+
}
|
|
669
|
+
if (byteLength === 2)
|
|
670
|
+
return jsonStringFromCharCode(first, second);
|
|
671
|
+
const third = jsonDecoderSource[jsonDecoderPosition++];
|
|
672
|
+
if ((third & 0x80) !== 0) {
|
|
673
|
+
jsonDecoderPosition = start;
|
|
674
|
+
break shortAscii;
|
|
675
|
+
}
|
|
676
|
+
if (byteLength === 3)
|
|
677
|
+
return jsonStringFromCharCode(first, second, third);
|
|
678
|
+
const fourth = jsonDecoderSource[jsonDecoderPosition++];
|
|
679
|
+
if ((fourth & 0x80) !== 0) {
|
|
680
|
+
jsonDecoderPosition = start;
|
|
681
|
+
break shortAscii;
|
|
682
|
+
}
|
|
683
|
+
if (byteLength === 4) {
|
|
684
|
+
return jsonStringFromCharCode(first, second, third, fourth);
|
|
685
|
+
}
|
|
686
|
+
const fifth = jsonDecoderSource[jsonDecoderPosition++];
|
|
687
|
+
if ((fifth & 0x80) !== 0) {
|
|
688
|
+
jsonDecoderPosition = start;
|
|
689
|
+
break shortAscii;
|
|
690
|
+
}
|
|
691
|
+
if (byteLength === 5) {
|
|
692
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth);
|
|
693
|
+
}
|
|
694
|
+
const sixth = jsonDecoderSource[jsonDecoderPosition++];
|
|
695
|
+
if ((sixth & 0x80) !== 0) {
|
|
696
|
+
jsonDecoderPosition = start;
|
|
697
|
+
break shortAscii;
|
|
698
|
+
}
|
|
699
|
+
if (byteLength === 6) {
|
|
700
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth);
|
|
701
|
+
}
|
|
702
|
+
const seventh = jsonDecoderSource[jsonDecoderPosition++];
|
|
703
|
+
if ((seventh & 0x80) !== 0) {
|
|
704
|
+
jsonDecoderPosition = start;
|
|
705
|
+
break shortAscii;
|
|
706
|
+
}
|
|
707
|
+
if (byteLength === 7) {
|
|
708
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh);
|
|
709
|
+
}
|
|
710
|
+
const eighth = jsonDecoderSource[jsonDecoderPosition++];
|
|
711
|
+
if ((eighth & 0x80) !== 0) {
|
|
712
|
+
jsonDecoderPosition = start;
|
|
713
|
+
break shortAscii;
|
|
714
|
+
}
|
|
715
|
+
if (byteLength === 8) {
|
|
716
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth);
|
|
717
|
+
}
|
|
718
|
+
const ninth = jsonDecoderSource[jsonDecoderPosition++];
|
|
719
|
+
if ((ninth & 0x80) !== 0) {
|
|
720
|
+
jsonDecoderPosition = start;
|
|
721
|
+
break shortAscii;
|
|
722
|
+
}
|
|
723
|
+
if (byteLength === 9) {
|
|
724
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth);
|
|
725
|
+
}
|
|
726
|
+
const tenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
727
|
+
if ((tenth & 0x80) !== 0) {
|
|
728
|
+
jsonDecoderPosition = start;
|
|
729
|
+
break shortAscii;
|
|
730
|
+
}
|
|
731
|
+
if (byteLength === 10) {
|
|
732
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth);
|
|
733
|
+
}
|
|
734
|
+
const eleventh = jsonDecoderSource[jsonDecoderPosition++];
|
|
735
|
+
if ((eleventh & 0x80) !== 0) {
|
|
736
|
+
jsonDecoderPosition = start;
|
|
737
|
+
break shortAscii;
|
|
738
|
+
}
|
|
739
|
+
if (byteLength === 11) {
|
|
740
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh);
|
|
741
|
+
}
|
|
742
|
+
const twelfth = jsonDecoderSource[jsonDecoderPosition++];
|
|
743
|
+
if ((twelfth & 0x80) !== 0) {
|
|
744
|
+
jsonDecoderPosition = start;
|
|
745
|
+
break shortAscii;
|
|
746
|
+
}
|
|
747
|
+
if (byteLength === 12) {
|
|
748
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth);
|
|
749
|
+
}
|
|
750
|
+
const thirteenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
751
|
+
if ((thirteenth & 0x80) !== 0) {
|
|
752
|
+
jsonDecoderPosition = start;
|
|
753
|
+
break shortAscii;
|
|
754
|
+
}
|
|
755
|
+
if (byteLength === 13) {
|
|
756
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth);
|
|
757
|
+
}
|
|
758
|
+
const fourteenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
759
|
+
if ((fourteenth & 0x80) !== 0) {
|
|
760
|
+
jsonDecoderPosition = start;
|
|
761
|
+
break shortAscii;
|
|
762
|
+
}
|
|
763
|
+
if (byteLength === 14) {
|
|
764
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth);
|
|
765
|
+
}
|
|
766
|
+
const fifteenth = jsonDecoderSource[jsonDecoderPosition++];
|
|
767
|
+
if ((fifteenth & 0x80) !== 0) {
|
|
768
|
+
jsonDecoderPosition = start;
|
|
769
|
+
break shortAscii;
|
|
770
|
+
}
|
|
771
|
+
return jsonStringFromCharCode(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth);
|
|
772
|
+
}
|
|
773
|
+
const end = jsonDecoderPosition + byteLength;
|
|
774
|
+
const units = [];
|
|
775
|
+
let result = "";
|
|
776
|
+
while (jsonDecoderPosition < end) {
|
|
777
|
+
const first = jsonDecoderSource[jsonDecoderPosition++];
|
|
778
|
+
if (first < 0x80) {
|
|
779
|
+
units.push(first);
|
|
780
|
+
}
|
|
781
|
+
else if (first >= 0xc2 && first <= 0xdf) {
|
|
782
|
+
assertJsonStringHasRemainingBytes(end, 1);
|
|
783
|
+
const second = readJsonContinuationByte();
|
|
784
|
+
units.push(((first & 0x1f) << 6) | second);
|
|
785
|
+
}
|
|
786
|
+
else if (first >= 0xe0 && first <= 0xef) {
|
|
787
|
+
assertJsonStringHasRemainingBytes(end, 2);
|
|
788
|
+
const secondByte = jsonDecoderSource[jsonDecoderPosition];
|
|
789
|
+
if (first === 0xe0 && secondByte < 0xa0) {
|
|
790
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
791
|
+
}
|
|
792
|
+
const second = readJsonContinuationByte();
|
|
793
|
+
const third = readJsonContinuationByte();
|
|
794
|
+
units.push(((first & 0x0f) << 12) | (second << 6) | third);
|
|
795
|
+
}
|
|
796
|
+
else if (first >= 0xf0 && first <= 0xf4) {
|
|
797
|
+
assertJsonStringHasRemainingBytes(end, 3);
|
|
798
|
+
const secondByte = jsonDecoderSource[jsonDecoderPosition];
|
|
799
|
+
if ((first === 0xf0 && secondByte < 0x90) ||
|
|
800
|
+
(first === 0xf4 && secondByte > 0x8f)) {
|
|
801
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
802
|
+
}
|
|
803
|
+
const second = readJsonContinuationByte();
|
|
804
|
+
const third = readJsonContinuationByte();
|
|
805
|
+
const fourth = readJsonContinuationByte();
|
|
806
|
+
const codePoint = ((first & 0x07) << 18) | (second << 12) | (third << 6) | fourth;
|
|
807
|
+
const pair = codePoint - 0x10000;
|
|
808
|
+
units.push(0xd800 | (pair >> 10), 0xdc00 | (pair & 0x3ff));
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
812
|
+
}
|
|
813
|
+
if (units.length >= 0x1000) {
|
|
814
|
+
result += jsonStringFromCharCode(...units);
|
|
815
|
+
units.length = 0;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (units.length > 0) {
|
|
819
|
+
result += jsonStringFromCharCode(...units);
|
|
820
|
+
}
|
|
821
|
+
return result;
|
|
822
|
+
};
|
|
823
|
+
const decodeJsonArray = (length) => {
|
|
824
|
+
if (jsonDecoderDepth >= maxJsonNestingDepth) {
|
|
825
|
+
throw new BufferError(`JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`);
|
|
826
|
+
}
|
|
827
|
+
if (length > jsonDecoderSource.length - jsonDecoderPosition) {
|
|
828
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
829
|
+
}
|
|
830
|
+
// oxlint-disable-next-line unicorn/no-new-array -- Preallocation is intentional in this decoding hot path.
|
|
831
|
+
const value = new Array(length);
|
|
832
|
+
jsonDecoderDepth++;
|
|
833
|
+
for (let index = 0; index < length; index++) {
|
|
834
|
+
value[index] = decodeJsonValueFromSource();
|
|
835
|
+
}
|
|
836
|
+
jsonDecoderDepth--;
|
|
837
|
+
return value;
|
|
838
|
+
};
|
|
839
|
+
const decodeJsonMap = (length) => {
|
|
840
|
+
if (jsonDecoderDepth >= maxJsonNestingDepth) {
|
|
841
|
+
throw new BufferError(`JSON nesting exceeds the maximum depth of ${maxJsonNestingDepth}.`);
|
|
842
|
+
}
|
|
843
|
+
if (length > (jsonDecoderSource.length - jsonDecoderPosition) / 2) {
|
|
844
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
845
|
+
}
|
|
846
|
+
const value = {};
|
|
847
|
+
jsonDecoderDepth++;
|
|
848
|
+
for (let index = 0; index < length; index++) {
|
|
849
|
+
const marker = readJsonDecoderByte();
|
|
850
|
+
let key;
|
|
851
|
+
if (marker >= 0xa0 && marker <= 0xbf) {
|
|
852
|
+
key = decodeCachedJsonKey(marker - 0xa0);
|
|
853
|
+
}
|
|
854
|
+
else if (marker === 0xd9) {
|
|
855
|
+
key = decodeCachedJsonKey(readJsonDecoderByte());
|
|
856
|
+
}
|
|
857
|
+
else if (marker === 0xda) {
|
|
858
|
+
key = decodeCachedJsonKey(readJsonUint16());
|
|
859
|
+
}
|
|
860
|
+
else if (marker === 0xdb) {
|
|
861
|
+
key = decodeCachedJsonKey(readJsonUint32());
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
jsonDecoderPosition--;
|
|
865
|
+
decodeJsonValueFromSource();
|
|
866
|
+
throw new BufferError("A decoded JSON object key must be a string.");
|
|
867
|
+
}
|
|
868
|
+
const entryValue = decodeJsonValueFromSource();
|
|
869
|
+
if (key === "__proto__") {
|
|
870
|
+
Object.defineProperty(value, key, {
|
|
871
|
+
value: entryValue,
|
|
872
|
+
configurable: true,
|
|
873
|
+
enumerable: true,
|
|
874
|
+
writable: true,
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
value[key] = entryValue;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
jsonDecoderDepth--;
|
|
882
|
+
return value;
|
|
883
|
+
};
|
|
884
|
+
const decodeCachedJsonKey = (byteLength) => {
|
|
885
|
+
if (byteLength > maxCachedJsonKeyByteLength) {
|
|
886
|
+
return decodeJsonString(byteLength);
|
|
887
|
+
}
|
|
888
|
+
assertJsonDecoderHasRemainingBytes(byteLength);
|
|
889
|
+
const start = jsonDecoderPosition;
|
|
890
|
+
const end = start + byteLength;
|
|
891
|
+
const firstBytes = byteLength > 1
|
|
892
|
+
? jsonDecoderView.getUint16(start)
|
|
893
|
+
: byteLength === 1
|
|
894
|
+
? jsonDecoderSource[start]
|
|
895
|
+
: 0;
|
|
896
|
+
const cacheIndex = ((byteLength << 5) ^ firstBytes) & (jsonKeyCacheSize - 1);
|
|
897
|
+
const entry = jsonKeyCache[cacheIndex];
|
|
898
|
+
if (entry?.bytes.length === byteLength) {
|
|
899
|
+
let index = 0;
|
|
900
|
+
while (index < byteLength &&
|
|
901
|
+
entry.bytes[index] === jsonDecoderSource[start + index]) {
|
|
902
|
+
index++;
|
|
903
|
+
}
|
|
904
|
+
if (index === byteLength) {
|
|
905
|
+
jsonDecoderPosition = end;
|
|
906
|
+
return entry.value;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const value = decodeJsonString(byteLength);
|
|
910
|
+
jsonKeyCache[cacheIndex] = {
|
|
911
|
+
bytes: jsonDecoderSource.slice(start, end),
|
|
912
|
+
value,
|
|
913
|
+
};
|
|
914
|
+
return value;
|
|
915
|
+
};
|
|
916
|
+
const readJsonContinuationByte = () => {
|
|
917
|
+
const byte = jsonDecoderSource[jsonDecoderPosition++];
|
|
918
|
+
if ((byte & 0xc0) !== 0x80) {
|
|
919
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
920
|
+
}
|
|
921
|
+
return byte & 0x3f;
|
|
922
|
+
};
|
|
923
|
+
const assertJsonStringHasRemainingBytes = (end, requiredBytes) => {
|
|
924
|
+
if (end - jsonDecoderPosition < requiredBytes) {
|
|
925
|
+
throw new BufferError("Invalid UTF-8 string encoding.");
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const readJsonDecoderByte = () => {
|
|
929
|
+
assertJsonDecoderHasRemainingBytes(1);
|
|
930
|
+
return jsonDecoderSource[jsonDecoderPosition++];
|
|
931
|
+
};
|
|
932
|
+
const readJsonUint16 = () => {
|
|
933
|
+
assertJsonDecoderHasRemainingBytes(2);
|
|
934
|
+
const value = jsonDecoderView.getUint16(jsonDecoderPosition);
|
|
935
|
+
jsonDecoderPosition += 2;
|
|
936
|
+
return value;
|
|
937
|
+
};
|
|
938
|
+
const readJsonUint32 = () => {
|
|
939
|
+
assertJsonDecoderHasRemainingBytes(4);
|
|
940
|
+
const value = jsonDecoderView.getUint32(jsonDecoderPosition);
|
|
941
|
+
jsonDecoderPosition += 4;
|
|
942
|
+
return value;
|
|
943
|
+
};
|
|
944
|
+
const assertJsonDecoderHasRemainingBytes = (requiredBytes) => {
|
|
945
|
+
if (jsonDecoderSource.length - jsonDecoderPosition < requiredBytes) {
|
|
946
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
const assertNonNegativeInt = (value, name) => {
|
|
950
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
951
|
+
throw new BufferError(`${name} must be a non-negative safe integer.`);
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
const assertBufferHasRemainingBytes = (remainingBytes, requiredBytes) => {
|
|
955
|
+
if (remainingBytes < requiredBytes) {
|
|
956
|
+
throw new BufferError("Buffer parse ended prematurely");
|
|
957
|
+
}
|
|
958
|
+
};
|