diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c0e52bc..0cb9525 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,5 +1,10 @@ /** @type {import('eslint').Linter.Config} */ module.exports = { + globals: { + BigInt: false, + BigInt64Array: false, + BigUint64Array: false, + }, root: true, parserOptions: { ecmaVersion: 'latest', diff --git a/README.md b/README.md index fc87076..5822aad 100644 --- a/README.md +++ b/README.md @@ -223,22 +223,24 @@ console.log(schema.size()); // 1, because it's a bool. ## Primitive types -| Type Name | Bytes | Range of Values | -|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| -| bool (alias: boolean) | 1 (worst case, see [boolean coalescence](#boolean-coalescence)) | Truthy values are coerced to `true`; falsy values to `false` | -| int8 | 1 | -128 to 127 | -| uint8 | 1 | 0 to 255 | -| int16 | 2 | -32,768 to 32,767 | -| uint16 | 2 | 0 to 65,535 | -| int32 | 4 | -2,147,483,648 to 2,147,483,647 | -| uint32 | 4 | 0 to 4,294,967,295 | -| float32 | 4 | 3.4E +/- 38 (7 digits) | -| float64 | 8 | 1.7E +/- 308 (15 digits) | -| string | [Prefix](#varuint-prefixes) followed by the [encoded](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encodeInto) string bytes | Any string | -| buffer | [Prefix](#varuint-prefixes) followed by the bytes of the buffer | Any `ArrayBuffer`

Decodes to a `DataView`.

See: [buffers and arrays](#buffers-and-arrays). | -| varuint |
sizeminmax
10127
212816,383
316,3842,097,151
42,097,152268,435,455
5268,435,45634,359,738,367
634,359,738,3684,398,046,511,103
74,398,046,511,104562,949,953,421,311
| 0 to 562,949,953,421,311 | -| varint |
sizeminmax
1-6463
2-8,1928,191
3-1,048,5761,048,575
4-134,217,728134,217,727
5-17,179,869,18417,179,869,183
6-2,199,023,255,5522,199,023,255,551
7-281,474,976,710,656281,474,976,710,655
| -281,474,976,710,656 to 281,474,976,710,655 | -| date | Same as `string` above after calling [`toIsoString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) | Value is coerced to `Date` e.g. `new Date(value).toIsoString()` | +| Type Name | Bytes | Range of Values | +|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| bool (alias: boolean) | 1 (worst case, see [boolean coalescence](#boolean-coalescence)) | Truthy values are coerced to `true`; falsy values to `false` | +| int8 | 1 | -128 to 127 | +| uint8 | 1 | 0 to 255 | +| int16 | 2 | -32,768 to 32,767 | +| uint16 | 2 | 0 to 65,535 | +| int32 | 4 | -2,147,483,648 to 2,147,483,647 | +| uint32 | 4 | 0 to 4,294,967,295 | +| int64 | 8 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

**NOTE:** Only accepts and decodes to [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)s. | +| uint64 | 8 | 0 to 18,446,744,073,709,551,615

**NOTE:** Only accepts and decodes to [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)s. | +| float32 | 4 | 3.4E +/- 38 (7 digits) | +| float64 | 8 | 1.7E +/- 308 (15 digits) | +| string | [Prefix](#varuint-prefixes) followed by the [encoded](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encodeInto) string bytes | Any string | +| buffer | [Prefix](#varuint-prefixes) followed by the bytes of the buffer | Any `ArrayBuffer`

**NOTE:** Decodes to a `DataView`.

See: [buffers and arrays](#buffers-and-arrays). | +| varuint |
sizeminmax
10127
212816,383
316,3842,097,151
42,097,152268,435,455
5268,435,45634,359,738,367
634,359,738,3684,398,046,511,103
74,398,046,511,104562,949,953,421,311
| 0 to 562,949,953,421,311 | +| varint |
sizeminmax
1-6463
2-8,1928,191
3-1,048,5761,048,575
4-134,217,728134,217,727
5-17,179,869,18417,179,869,183
6-2,199,023,255,5522,199,023,255,551
7-281,474,976,710,656281,474,976,710,655
| -281,474,976,710,656 to 281,474,976,710,655 | +| date | Same as `string` above after calling [`toIsoString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) | Value is coerced to `Date` e.g. `new Date(value).toIsoString()` | ## Aggregate types @@ -385,16 +387,13 @@ A similar performance gain is also used for arrays. The fast path is used for ar - `uint32` - `float32` - `float64` +- `int64` +- `uint64` Instead of copying the data from the buffer, a [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays#typed_array_views) is created over the encoded binary and returned instead. The same optimization is applied for encoding. This is roughly **1.5x** faster for encoding and ***50x*** faster for decoding a 1024-byte array on the machine used to benchmark. The gains increase even more as the size of the array increases. **NOTE:** `float64` arrays are padded with an extra 4 bytes after the length prefix to satisfy the required 8-byte alignment. -# TODO - -- BigInts? -- Endianness? - # Q/A **Q**: Why did you call it `crunches`? diff --git a/src/codecs/array.js b/src/codecs/array.js index 5a2fa08..b0524c6 100644 --- a/src/codecs/array.js +++ b/src/codecs/array.js @@ -10,6 +10,8 @@ export function typeToElementClass(type) { case 'uint32': return Uint32Array; case 'float32': return Float32Array; case 'float64': return Float64Array; + case 'bigint64': return BigInt64Array; + case 'biguint64': return BigUint64Array; } return undefined; } diff --git a/src/codecs/array.test.js b/src/codecs/array.test.js index b43d316..2a9c93d 100644 --- a/src/codecs/array.test.js +++ b/src/codecs/array.test.js @@ -11,6 +11,8 @@ import Uint32Codec from './uint32.js'; import Float32Codec from './float32.js'; import Float64Codec from './float64.js'; import StringCodec from './string.js'; +import Int64Codec from './int64.js'; +import Uint64Codec from './uint64.js'; Codecs.uint8 = Uint8Codec; Codecs.int8 = Int8Codec; @@ -22,6 +24,8 @@ Codecs.uint32 = Uint32Codec; Codecs.float32 = Float32Codec; Codecs.float64 = Float64Codec; Codecs.string = StringCodec; +Codecs.int64 = Int64Codec; +Codecs.uint64 = Uint64Codec; for (const numberType of [ 'int8', @@ -47,6 +51,26 @@ for (const numberType of [ }); } +test('int64 array', async () => { + const codec = new Codec({ + element: {type: 'int64'}, + }); + const value = [1n, -2n, 3n, -4n]; + const view = new DataView(new ArrayBuffer(codec.size(value))); + expect(codec.encode(value, view, 0)).to.equal(36); + expect(codec.decode(view, {byteOffset: 0})).to.deep.equal(value); +}); + +test('uint64 array', async () => { + const codec = new Codec({ + element: {type: 'uint64'}, + }); + const value = [1n, 2n, 3n, 4n]; + const view = new DataView(new ArrayBuffer(codec.size(value))); + expect(codec.encode(value, view, 0)).to.equal(36); + expect(codec.decode(view, {byteOffset: 0})).to.deep.equal(value); +}); + test('string array', async () => { const codec = new Codec({ element: {type: 'string'}, diff --git a/src/codecs/int64.js b/src/codecs/int64.js new file mode 100644 index 0000000..ff8417f --- /dev/null +++ b/src/codecs/int64.js @@ -0,0 +1,16 @@ +class Int64Codec { + decode(view, target) { + const value = view.getBigInt64(target.byteOffset); + target.byteOffset += 8; + return value; + } + encode(value, view, byteOffset) { + view.setBigInt64(byteOffset, value); + return 8; + } + size() { + return 8; + } +} + +export default Int64Codec; diff --git a/src/codecs/int64.test.js b/src/codecs/int64.test.js new file mode 100644 index 0000000..cc04a39 --- /dev/null +++ b/src/codecs/int64.test.js @@ -0,0 +1,11 @@ +import {expect, test} from 'vitest'; + +import Codec from './int64.js'; + +test('int64', async () => { + const codec = new Codec(); + const value = -32n; + const view = new DataView(new ArrayBuffer(codec.size(value))); + expect(codec.encode(value, view, 0)).to.equal(8); + expect(codec.decode(view, {byteOffset: 0})).to.deep.equal(value); +}); diff --git a/src/codecs/uint64.js b/src/codecs/uint64.js new file mode 100644 index 0000000..8b4911f --- /dev/null +++ b/src/codecs/uint64.js @@ -0,0 +1,16 @@ +class Uint64Codec { + decode(view, target) { + const value = view.getBigUint64(target.byteOffset); + target.byteOffset += 8; + return value; + } + encode(value, view, byteOffset) { + view.setBigUint64(byteOffset, value); + return 8; + } + size() { + return 8; + } +} + +export default Uint64Codec; diff --git a/src/codecs/uint64.test.js b/src/codecs/uint64.test.js new file mode 100644 index 0000000..3ef068c --- /dev/null +++ b/src/codecs/uint64.test.js @@ -0,0 +1,11 @@ +import {expect, test} from 'vitest'; + +import Codec from './uint64.js'; + +test('uint64', async () => { + const codec = new Codec(); + const value = 32n; + const view = new DataView(new ArrayBuffer(codec.size(value))); + expect(codec.encode(value, view, 0)).to.equal(8); + expect(codec.decode(view, {byteOffset: 0})).to.deep.equal(value); +});