@evolu/common 8.7.0 → 8.9.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 +498 -141
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +648 -99
- package/dist/src/{Buffer.d.ts → Binary.d.ts} +85 -12
- package/dist/src/Binary.d.ts.map +1 -0
- package/dist/src/{Buffer.js → Binary.js} +206 -24
- 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.d.ts +6 -0
- package/dist/src/Polyfills.d.ts.map +1 -1
- package/dist/src/Polyfills.js +10 -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 +14 -76
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +29 -206
- 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 -4
- package/src/Array.test.ts +809 -0
- package/src/Array.ts +21 -21
- package/src/Assert.test.ts +1232 -0
- package/src/Assert.ts +1023 -227
- package/src/BigInt.test.ts +28 -0
- package/src/Binary.test.ts +1128 -0
- package/src/{Buffer.ts → Binary.ts} +273 -46
- 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 +9 -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 +58 -261
- 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.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 {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
*
|
|
88
|
-
*
|
|
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
|
|
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
|
|
248
|
-
let jsonEncoderTargetView = new
|
|
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
|
|
253
|
-
const emptyJsonDecoderView: DataView = new
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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
|
|
827
|
+
jsonEncoderTarget = new Uint8Array(newCapacity);
|
|
601
828
|
jsonEncoderTarget.set(oldTarget.subarray(0, jsonEncoderPosition));
|
|
602
|
-
jsonEncoderTargetView = new
|
|
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 (!
|
|
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
|
-
|
|
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 (!
|
|
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
|
+
});
|