├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── bench ├── array_bench.ts ├── bigint_bench.ts ├── bool_bench.ts ├── float_bench.ts ├── integer_bench.ts ├── null_bench.ts ├── object_bench.ts ├── string_bench.ts └── undefined_bench.ts ├── readme.md ├── src ├── _deps.ts ├── _util.ts ├── array.ts ├── array_buffer.ts ├── bigint.ts ├── boolean.ts ├── float.ts ├── integer.ts ├── mod.ts ├── null.ts ├── object_reference.ts ├── objects.ts ├── string.ts ├── typed_array.ts └── undefined.ts └── tests ├── _core.ts ├── _deps.ts ├── _util.ts ├── array_buffer_test.ts ├── array_test.ts ├── bigint_test.ts ├── boolean_test.ts ├── float_test.ts ├── integer_test.ts ├── null_test.ts ├── object_test.ts ├── reference_test.ts ├── string_test.ts └── undefined_test.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Clone repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Deno 17 | uses: denoland/setup-deno@v1.0.0 18 | 19 | - name: Format 20 | run: deno fmt --check 21 | 22 | - name: Lint 23 | run: deno lint 24 | 25 | - name: Test 26 | run: deno test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | testing.js 2 | cov -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Skye 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bench/array_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Array, serializeJsArray } from "../src/array.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | import { associativeArray, denseArray, sparseArray } from "../tests/_util.ts"; 4 | 5 | Deno.bench("nop", () => {}); 6 | 7 | const DENSE_ARRAY = denseArray(); 8 | const SPARSE_ARRAY = sparseArray(); 9 | const ASSOCIATIVE_DENSE_ARRAY = associativeArray(denseArray()); 10 | const ASSOCIATIVE_SPARSE_ARRAY = associativeArray(sparseArray()); 11 | 12 | Deno.bench("nop", () => {}); 13 | 14 | Deno.bench({ 15 | name: "Serialize Dense Array (v8)", 16 | group: "Serialize Dense", 17 | baseline: true, 18 | fn: () => { 19 | DENO_CORE.serialize(DENSE_ARRAY); 20 | }, 21 | }); 22 | 23 | Deno.bench({ 24 | name: "Serialize Dense Array (js)", 25 | group: "Serialize Dense", 26 | fn: () => { 27 | serializeJsArray(DENSE_ARRAY); 28 | }, 29 | }); 30 | 31 | Deno.bench({ 32 | name: "Deserialize Dense Array (v8)", 33 | group: "Deserialize Dense", 34 | baseline: true, 35 | fn: () => { 36 | const buff = DENO_CORE.serialize(DENSE_ARRAY); 37 | DENO_CORE.deserialize(buff); 38 | }, 39 | }); 40 | 41 | Deno.bench({ 42 | name: "Deserialize Dense Array (js)", 43 | group: "Deserialize Dense", 44 | fn: () => { 45 | const buff = DENO_CORE.serialize(DENSE_ARRAY).subarray(2); 46 | deserializeV8Array(buff); 47 | }, 48 | }); 49 | 50 | Deno.bench({ 51 | name: "Serialize Sparse Array (v8)", 52 | group: "Serialize Sparse", 53 | baseline: true, 54 | fn: () => { 55 | DENO_CORE.serialize(SPARSE_ARRAY); 56 | }, 57 | }); 58 | 59 | Deno.bench({ 60 | name: "Serialize Sparse Array (js)", 61 | group: "Serialize Sparse", 62 | fn: () => { 63 | serializeJsArray(SPARSE_ARRAY); 64 | }, 65 | }); 66 | 67 | Deno.bench({ 68 | name: "Deserialize Sparse Array (v8)", 69 | group: "Deserialize Sparse", 70 | baseline: true, 71 | fn: () => { 72 | const buff = DENO_CORE.serialize(SPARSE_ARRAY); 73 | DENO_CORE.deserialize(buff); 74 | }, 75 | }); 76 | 77 | Deno.bench({ 78 | name: "Deserialize Sparse Array (js)", 79 | group: "Deserialize Sparse", 80 | fn: () => { 81 | const buff = DENO_CORE.serialize(SPARSE_ARRAY).subarray(2); 82 | deserializeV8Array(buff); 83 | }, 84 | }); 85 | 86 | Deno.bench({ 87 | name: "Serialize Associative Dense Array (v8)", 88 | group: "Serialize Associative Dense", 89 | baseline: true, 90 | fn: () => { 91 | DENO_CORE.serialize(ASSOCIATIVE_DENSE_ARRAY); 92 | }, 93 | }); 94 | 95 | Deno.bench({ 96 | name: "Serialize Associative Dense Array (js)", 97 | group: "Serialize Associative Dense", 98 | fn: () => { 99 | serializeJsArray(ASSOCIATIVE_DENSE_ARRAY); 100 | }, 101 | }); 102 | 103 | Deno.bench({ 104 | name: "Deserialize Associative Dense Array (v8)", 105 | group: "Deserialize Associative Dense", 106 | baseline: true, 107 | fn: () => { 108 | const buff = DENO_CORE.serialize(ASSOCIATIVE_DENSE_ARRAY); 109 | DENO_CORE.deserialize(buff); 110 | }, 111 | }); 112 | 113 | Deno.bench({ 114 | name: "Deserialize Associative Dense Array (js)", 115 | group: "Deserialize Associative Dense", 116 | fn: () => { 117 | const buff = DENO_CORE.serialize(ASSOCIATIVE_DENSE_ARRAY).subarray(2); 118 | deserializeV8Array(buff); 119 | }, 120 | }); 121 | 122 | Deno.bench({ 123 | name: "Serialize Associative Sparse Array (v8)", 124 | group: "Serialize Associative Sparse", 125 | baseline: true, 126 | fn: () => { 127 | DENO_CORE.serialize(ASSOCIATIVE_SPARSE_ARRAY); 128 | }, 129 | }); 130 | 131 | Deno.bench({ 132 | name: "Serialize Associative Sparse Array (js)", 133 | group: "Serialize Associative Sparse", 134 | fn: () => { 135 | serializeJsArray(ASSOCIATIVE_SPARSE_ARRAY); 136 | }, 137 | }); 138 | 139 | Deno.bench({ 140 | name: "Deserialize Associative Sparse Array (v8)", 141 | group: "Deserialize Associative Sparse", 142 | baseline: true, 143 | fn: () => { 144 | const buff = DENO_CORE.serialize(ASSOCIATIVE_SPARSE_ARRAY); 145 | DENO_CORE.deserialize(buff); 146 | }, 147 | }); 148 | 149 | Deno.bench({ 150 | name: "Deserialize Associative Sparse Array (js)", 151 | group: "Deserialize Associative Sparse", 152 | fn: () => { 153 | const buff = DENO_CORE.serialize(ASSOCIATIVE_SPARSE_ARRAY).subarray(2); 154 | deserializeV8Array(buff); 155 | }, 156 | }); 157 | -------------------------------------------------------------------------------- /bench/bigint_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8BigInt, serializeJsBigInt } from "../src/bigint.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | 4 | const INPUT = BigInt(Number.MAX_VALUE); 5 | 6 | Deno.bench("nop", () => {}); 7 | 8 | Deno.bench({ 9 | name: "Serialize BigInt (v8)", 10 | group: "Serialize", 11 | baseline: true, 12 | fn: () => { 13 | DENO_CORE.serialize(INPUT); 14 | }, 15 | }); 16 | 17 | Deno.bench({ 18 | name: "Serialize BigInt (js)", 19 | group: "Serialize", 20 | fn: () => { 21 | serializeJsBigInt(INPUT); 22 | }, 23 | }); 24 | 25 | Deno.bench({ 26 | name: "Deserialize BigInt (v8)", 27 | group: "Deserialize", 28 | baseline: true, 29 | fn: () => { 30 | const buff = DENO_CORE.serialize(INPUT); 31 | DENO_CORE.deserialize(buff); 32 | }, 33 | }); 34 | 35 | Deno.bench({ 36 | name: "Deserialize BigInt (js)", 37 | group: "Deserialize", 38 | fn: () => { 39 | const buff = DENO_CORE.serialize(INPUT).subarray(2); 40 | deserializeV8BigInt(buff); 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /bench/bool_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Boolean, serializeJsBoolean } from "../src/boolean.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | 4 | Deno.bench("nop", () => {}); 5 | 6 | Deno.bench({ 7 | name: "Serialize Boolean (v8)", 8 | group: "Serialize", 9 | baseline: true, 10 | fn: () => { 11 | DENO_CORE.serialize(true); 12 | DENO_CORE.serialize(false); 13 | }, 14 | }); 15 | 16 | Deno.bench({ 17 | name: "Serialize Boolean (js)", 18 | group: "Serialize", 19 | fn: () => { 20 | serializeJsBoolean(true); 21 | serializeJsBoolean(false); 22 | }, 23 | }); 24 | 25 | Deno.bench({ 26 | name: "Deserialize Boolean (v8)", 27 | group: "Deserialize", 28 | baseline: true, 29 | fn: () => { 30 | const buff = DENO_CORE.serialize(true); 31 | DENO_CORE.deserialize(buff); 32 | const buff2 = DENO_CORE.serialize(false); 33 | DENO_CORE.deserialize(buff2); 34 | }, 35 | }); 36 | 37 | Deno.bench({ 38 | name: "Deserialize Boolean (js)", 39 | group: "Deserialize", 40 | fn: () => { 41 | const buff = DENO_CORE.serialize(true).subarray(2); 42 | deserializeV8Boolean(buff); 43 | const buff2 = DENO_CORE.serialize(false).subarray(2); 44 | deserializeV8Boolean(buff2); 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /bench/float_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Float, serializeJsFloat } from "../src/float.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | 4 | Deno.bench("nop", () => {}); 5 | 6 | const INPUT = Math.random() * 100; 7 | 8 | Deno.bench("nop", () => {}); 9 | 10 | Deno.bench({ 11 | name: "Serialize Float (v8)", 12 | group: "Serialize", 13 | baseline: true, 14 | fn: () => { 15 | DENO_CORE.serialize(INPUT); 16 | }, 17 | }); 18 | 19 | Deno.bench({ 20 | name: "Serialize Float (js)", 21 | group: "Serialize", 22 | fn: () => { 23 | serializeJsFloat(INPUT); 24 | }, 25 | }); 26 | 27 | Deno.bench({ 28 | name: "Deserialize Float (v8)", 29 | group: "Deserialize", 30 | baseline: true, 31 | fn: () => { 32 | const buff = DENO_CORE.serialize(INPUT); 33 | DENO_CORE.deserialize(buff); 34 | }, 35 | }); 36 | 37 | Deno.bench({ 38 | name: "Deserialize Float (js)", 39 | group: "Deserialize", 40 | fn: () => { 41 | const buff = DENO_CORE.serialize(INPUT).subarray(2); 42 | deserializeV8Float(buff); 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /bench/integer_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Integer, serializeJsInteger } from "../src/integer.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | 4 | Deno.bench("nop", () => {}); 5 | 6 | const INPUT = Math.random() * 100 | 0; 7 | const MIN_INT_VALUE = -1_073_741_824; 8 | const MAX_INT_VALUE = 1_073_741_823; 9 | 10 | Deno.bench("nop", () => {}); 11 | 12 | Deno.bench({ 13 | name: "Serialize Integer (v8)", 14 | group: "Serialize", 15 | baseline: true, 16 | fn: () => { 17 | DENO_CORE.serialize(MIN_INT_VALUE); 18 | DENO_CORE.serialize(INPUT); 19 | DENO_CORE.serialize(MAX_INT_VALUE); 20 | }, 21 | }); 22 | 23 | Deno.bench({ 24 | name: "Serialize Integer (js)", 25 | group: "Serialize", 26 | fn: () => { 27 | serializeJsInteger(MIN_INT_VALUE); 28 | serializeJsInteger(INPUT); 29 | serializeJsInteger(MAX_INT_VALUE); 30 | }, 31 | }); 32 | 33 | Deno.bench({ 34 | name: "Deserialize Integer (v8)", 35 | group: "Deserialize", 36 | baseline: true, 37 | fn: () => { 38 | const buff0 = DENO_CORE.serialize(INPUT); 39 | const buff1 = DENO_CORE.serialize(MIN_INT_VALUE); 40 | const buff2 = DENO_CORE.serialize(MAX_INT_VALUE); 41 | DENO_CORE.deserialize(buff0); 42 | DENO_CORE.deserialize(buff1); 43 | DENO_CORE.deserialize(buff2); 44 | }, 45 | }); 46 | 47 | Deno.bench({ 48 | name: "Deserialize Integer (js)", 49 | group: "Deserialize", 50 | fn: () => { 51 | const buff0 = DENO_CORE.serialize(INPUT).subarray(2); 52 | const buff1 = DENO_CORE.serialize(MIN_INT_VALUE).subarray(2); 53 | const buff2 = DENO_CORE.serialize(MAX_INT_VALUE).subarray(2); 54 | deserializeV8Integer(buff0); 55 | deserializeV8Integer(buff1); 56 | deserializeV8Integer(buff2); 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /bench/null_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Null, serializeJsNull } from "../src/null.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | 4 | Deno.bench("nop", () => {}); 5 | 6 | Deno.bench({ 7 | name: "Serialize Null (v8)", 8 | group: "Serialize", 9 | baseline: true, 10 | fn: () => { 11 | DENO_CORE.serialize(null); 12 | }, 13 | }); 14 | 15 | Deno.bench({ 16 | name: "Serialize Null (js)", 17 | group: "Serialize", 18 | fn: () => { 19 | serializeJsNull(null); 20 | }, 21 | }); 22 | 23 | Deno.bench({ 24 | name: "Deserialize Null (v8)", 25 | group: "Deserialize", 26 | baseline: true, 27 | fn: () => { 28 | const SERIALIZED_NULL = DENO_CORE.serialize(null); 29 | DENO_CORE.deserialize(SERIALIZED_NULL); 30 | }, 31 | }); 32 | 33 | Deno.bench({ 34 | name: "Deserialize Null (js)", 35 | group: "Deserialize", 36 | fn: () => { 37 | const SERIALIZED_NULL = DENO_CORE.serialize(null).subarray(2); 38 | deserializeV8Null(SERIALIZED_NULL); 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /bench/object_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Object, serializeJsObject } from "../src/objects.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | import { fullObject } from "../tests/_util.ts"; 4 | 5 | Deno.bench("nop", () => {}); 6 | 7 | const INPUT = fullObject(); 8 | 9 | Deno.bench("nop", () => {}); 10 | 11 | Deno.bench({ 12 | name: "Serialize Object (v8)", 13 | group: "Serialize", 14 | baseline: true, 15 | fn: () => { 16 | DENO_CORE.serialize(INPUT); 17 | }, 18 | }); 19 | 20 | Deno.bench({ 21 | name: "Serialize Object (js)", 22 | group: "Serialize", 23 | fn: () => { 24 | serializeJsObject(INPUT, []); 25 | }, 26 | }); 27 | 28 | Deno.bench({ 29 | name: "Deserialize Object (v8)", 30 | group: "Deserialize", 31 | baseline: true, 32 | fn: () => { 33 | const buff = DENO_CORE.serialize(INPUT); 34 | DENO_CORE.deserialize(buff); 35 | }, 36 | }); 37 | 38 | Deno.bench({ 39 | name: "Deserialize Object (js)", 40 | group: "Deserialize", 41 | fn: () => { 42 | const buff = DENO_CORE.serialize(INPUT).subarray(2); 43 | deserializeV8Object(buff, []); 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /bench/string_bench.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8String, serializeJsString } from "../src/string.ts"; 2 | import { DENO_CORE } from "../tests/_core.ts"; 3 | 4 | Deno.bench("nop", () => {}); 5 | 6 | const INPUT = "Hello Beautiful World!"; 7 | const INPUT2 = "Hello Parking meter 😃"; 8 | 9 | Deno.bench("nop", () => {}); 10 | 11 | Deno.bench({ 12 | name: "Serialize String (v8)", 13 | group: "Serialize", 14 | baseline: true, 15 | fn: () => { 16 | DENO_CORE.serialize(INPUT); 17 | }, 18 | }); 19 | 20 | Deno.bench({ 21 | name: "Serialize String (js)", 22 | group: "Serialize", 23 | fn: () => { 24 | serializeJsString(INPUT); 25 | }, 26 | }); 27 | 28 | Deno.bench({ 29 | name: "Deserialize String (v8)", 30 | group: "Deserialize", 31 | baseline: true, 32 | fn: () => { 33 | const buff = DENO_CORE.serialize(INPUT); 34 | DENO_CORE.deserialize(buff); 35 | }, 36 | }); 37 | 38 | Deno.bench({ 39 | name: "Deserialize String (js)", 40 | group: "Deserialize", 41 | fn: () => { 42 | const buff = DENO_CORE.serialize(INPUT).subarray(2); 43 | deserializeV8String(buff); 44 | }, 45 | }); 46 | 47 | Deno.bench({ 48 | name: "Serialize Two-Byte String (v8)", 49 | group: "Serialize 2b", 50 | baseline: true, 51 | fn: () => { 52 | DENO_CORE.serialize(INPUT2); 53 | }, 54 | }); 55 | 56 | Deno.bench({ 57 | name: "Serialize Two-Byte String (js)", 58 | group: "Serialize 2b", 59 | fn: () => { 60 | serializeJsString(INPUT2); 61 | }, 62 | }); 63 | 64 | Deno.bench({ 65 | name: "Deserialize Two-Byte String (v8)", 66 | group: "Deserialize 2b", 67 | baseline: true, 68 | fn: () => { 69 | const buff = DENO_CORE.serialize(INPUT2); 70 | DENO_CORE.deserialize(buff); 71 | }, 72 | }); 73 | 74 | Deno.bench({ 75 | name: "Deserialize Two-Byte String (js)", 76 | group: "Deserialize 2b", 77 | fn: () => { 78 | const buff = DENO_CORE.serialize(INPUT2).subarray(2); 79 | deserializeV8String(buff); 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /bench/undefined_bench.ts: -------------------------------------------------------------------------------- 1 | import { 2 | deserializeV8Undefined, 3 | serializeJsUndefined, 4 | } from "../src/undefined.ts"; 5 | import { DENO_CORE } from "../tests/_core.ts"; 6 | 7 | Deno.bench("nop", () => {}); 8 | 9 | Deno.bench({ 10 | name: "Serialize Undefined (v8)", 11 | group: "Serialize", 12 | baseline: true, 13 | fn: () => { 14 | DENO_CORE.serialize(undefined); 15 | }, 16 | }); 17 | 18 | Deno.bench({ 19 | name: "Serialize Undefined (js)", 20 | group: "Serialize", 21 | fn: () => { 22 | serializeJsUndefined(undefined); 23 | }, 24 | }); 25 | 26 | Deno.bench({ 27 | name: "Deserialize Undefined (v8)", 28 | group: "Deserialize", 29 | baseline: true, 30 | fn: () => { 31 | const SERIALIZED_UNDEFINED = DENO_CORE.serialize(undefined); 32 | DENO_CORE.deserialize(SERIALIZED_UNDEFINED); 33 | }, 34 | }); 35 | 36 | Deno.bench({ 37 | name: "Deserialize Undefined (js)", 38 | group: "Deserialize", 39 | fn: () => { 40 | const SERIALIZED_UNDEFINED = DENO_CORE.serialize(undefined).subarray(2); 41 | deserializeV8Undefined(SERIALIZED_UNDEFINED); 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Explanation 2 | 3 | Idk why I am doing this. But this "guide" shows how to serialize js values into 4 | their v8 binary representation and how to deserialize the v8 binary 5 | representation back to a js value used by the [V8 Engine](https://v8.dev). 6 | 7 | ## General Format 8 | 9 | The format always starts with 2 header bytes `0xFF 0x0F` Then it will use a 10 | indicator byte to tell the deserializer on how to deserialize the next section 11 | of bytes. None of the format examples include the 2 header bytes but these are 12 | needed at the beginning of the serialized data. The reference serializers and 13 | deserializers don't include these bytes. But the `serialize` and `deserialize` 14 | api's in `references/mod.ts` do add these bytes 15 | 16 | ## Primitive Types 17 | 18 | Primitive values are the following values: 19 | 20 | - [string](#string-formats) 21 | - [integers](#integer-format) 22 | - [float](#float-format) 23 | - [bigint](#bigint-format) 24 | - [boolean](#boolean-format) 25 | - [null](#null-format) 26 | - [undefined](#undefined-format) 27 | 28 | Null is also included in this list eventho it's technically a object but works 29 | like a primitive. The difference between a primitive and a object is for example 30 | how they're passed as function argument. It is handy to know the difference 31 | because objects have a few quirks that primitives don't. But we'll get into that 32 | later in the [Object Types](#object-types) section. 33 | 34 | ### String Formats 35 | 36 | V8 has 3 types of strings. `Utf8String`, `OneByteString` and `TwoByteString`. 37 | The first one I have no idea what it is used for. 38 | 39 | The One byte string is the most common one and is used in places where every 40 | character in the string is part of the extended ascii table (0x00 up to 0xFF) 41 | 42 | The Two Byte string is used for characters that need 2 bytes to be represented. 43 | This would include character sets like arabic and emoji's 44 | 45 | All format's all start with a type indicator and then a varint encoded length 46 | and then the raw data 47 | 48 | #### Utf8 String Format 49 | 50 | Seems to only be used internally. (maybe found with JIT compiled functions. 51 | Needs triage) 52 | 53 | #### One Byte String Format 54 | 55 | One byte strings start off with a `"` (0x22) to indicate the string datatype. 56 | Then uses a LEB128 encoded varint to indicate the length of the raw data of the 57 | string and then the raw data. 58 | 59 | serializing a string like `HelloWorld` this will look like this. 60 | 61 | ``` 62 | 0x22 0x0A String indicator byte and varint encoded length 63 | 0x48 0x65 He 64 | 0x6C 0x6C ll 65 | 0x6F 0x57 oW 66 | 0x6F 0x72 or 67 | 0x6C 0x64 ld 68 | ``` 69 | 70 | #### Two Byte String Format 71 | 72 | Two byte strings start off with a `c` (0x63) to indicate the two byte string 73 | datatype. Then uses a LEB128 encoded varint to indicate the length of the raw 74 | data of the string and then the raw data. 75 | 76 | This format is used for when we have characters that need to be represented as 77 | multiple bytes. Like emoji's or non-latin languages like arabic 78 | 79 | serializing `Hi!😃` 80 | 81 | ``` 82 | 0x63 0x0A String indicator byte and varint encoded length 83 | 0x48 0x00 H (UTF-8 characters don't need a second byte. Therefore null byte) 84 | 0x69 0x00 i 85 | 0x21 0x00 ! 86 | 0x3D 0xD8 =Ø (these 4 bytes are the emoji) 87 | 0x03 0xDE 0x03 Þ 88 | ``` 89 | 90 | ### Integer Format 91 | 92 | V8 has 2 integer formats one is unsigned integer and the other one signed. It 93 | appears that usually signed integers are used even when unsigned integers can be 94 | used. 95 | 96 | I am not entirely sure when unsigned integers are used. Signed integers are 97 | stored as SMI's. These are 30 bit integers so where are the rest? The other 2 98 | bits are used as SMI flag internally and the other as sign bit. This does not 99 | mean that we are limited to 32 bits though. If the value is outside of the SMI 100 | range (-1_073_741_824 - 1_073_741_823) then it will use a float to store the int 101 | value 102 | 103 | #### Signed Integer Format 104 | 105 | The integer format of v8 is quite simple but confusing at first. It uses varint 106 | encoding for all signed integers. However it does not use the LEB128 signed 107 | varint. Instead it uses the zigzag algorithm used in protobufs. 108 | 109 | some examples 110 | 111 | Negative Integer -12 112 | 113 | ``` 114 | 0x49 0x17 Indicator byte then zigzag encoded + varint encoded value 115 | ``` 116 | 117 | Positive Integer 12 118 | 119 | ``` 120 | 0x49 0x18 Indicator byte then zigzag encoded + varint encoded value 121 | ``` 122 | 123 | #### Unsigned Integer Format 124 | 125 | Eventho I have not found out where v8 uses this. It is essentially the same as 126 | the signed int format but a different indicator byte `U` (0x) and the value does 127 | not get zig-zag encoded unlike the signed integers.\ 128 | Do note that this format is not compatible with the Signed integer format 129 | because positive integers in that format also get zigzag encoded. 130 | 131 | Selfnotes: 132 | 133 | - Might be found when using JIT compiled functions. 134 | - Might be used when SMI's are at hand. 135 | 136 | ### BigInt Format 137 | 138 | The bigint format does not have multiple variants and only has one. It has a 139 | indicator byte which is `Z` (0x5A) and after that a varint bitfield specifying 140 | how many u64 integers are used for the bigint and if the value is positive or 141 | negative. It is alot more complex than either a float or integer because it has 142 | a unknown size 143 | 144 | Negative BigInt -12 145 | 146 | ``` 147 | 0x5A 0x11 BigInt indicator byte and Varint bitfield. 148 | 0x0C 0x00 Bigint value... 149 | 0x00 0x00 150 | 0x00 0x00 151 | 0x00 152 | ``` 153 | 154 | Positive BigInt 12 155 | 156 | ``` 157 | 0x5A 0x10 BigInt indicator byte and Varint bitfield. 158 | 0x0C 0x00 Bigint value... 159 | 0x00 0x00 160 | 0x00 0x00 161 | 0x00 162 | ``` 163 | 164 | As you can see both are the same and the only difference is the bitfield. In the 165 | negative it changed the LSB (Least significant byte) from a 0 to a 1 making it a 166 | negative value 167 | 168 | ### Float Format 169 | 170 | The float format is like the integer format, quite easy to understand. It's a 171 | little bit simpler because we don't deal with a variable size of bytes. The 172 | float format is by far the easiest one to understand. You only have an indicator 173 | byte `N` (0x4E) and then the float value as 64 bit float (or double) 174 | 175 | ``` 176 | 0x4E 0x00 Indicator byte and first byte of the 64 bit float 177 | 0x00 0x00 bytes of the float(12.69) 178 | 0x00 0x00 179 | 0x00 0x29 180 | 0x40 181 | ``` 182 | 183 | ### Boolean Format 184 | 185 | Booleans are the easiest format to serialize. The indicator byte is both the 186 | value and type. `F` (0x46) and `T` (0x54) Are the boolean type indicators where 187 | `F` (0x46) is false and `T` (0x54) is true 188 | 189 | False 190 | 191 | ``` 192 | 0x46 Indicator byte False 193 | ``` 194 | 195 | True 196 | 197 | ``` 198 | 0x54 Indicator byte True 199 | ``` 200 | 201 | ### Null Format 202 | 203 | The null format is effectively the same as the boolean format. Just that the 204 | indicator byte changed to `0` (0x30). 205 | 206 | ``` 207 | 0x30 Indicator byte Null 208 | ``` 209 | 210 | ### Undefined Format 211 | 212 | Let's repeat the easiest format once again. For `undefined` the format is still 213 | the same as null and booleans but the byte changed once again to `_` (0x5F) 214 | 215 | ``` 216 | 0x5F Indicator byte Undefined 217 | ``` 218 | 219 | ## Referable Types 220 | 221 | Referable types are a lot more complex than meets the eye. It looks fairly 222 | simple until you realise that javascript is a quirky language that allows things 223 | like associative arrays and arrays where there are empty elements or referencing 224 | a object in itself creating recursive references. This makes (de)serializing 225 | referable types a lot more complex than a simple primitive. For external use of 226 | the v8 format the following referable types work with it. 227 | 228 | - [Object References](#object-references) 229 | - [Array](#array) 230 | - [Object literals](#classes--plain-objects) 231 | - [User created classes](#classes--plain-objects) 232 | - [ArrayBuffer](#arraybuffer) 233 | - Uint*Array (where * is 8, 16, 32 or 64) 234 | - Int*Array (where * is 8, 16, 32 or 64) 235 | - Map 236 | - Set 237 | - Date 238 | - Regex 239 | - Error 240 | 241 | These do not work outside of v8 or v8 bindings due to them serializing into a ID 242 | that v8 holds the value for. 243 | 244 | - SharedArrayBuffer (not usable outside v8) 245 | - WebAssembly.Module (not usable outside v8) 246 | - WebAssembly.Memory (not usable outside v8) (only serializable when 247 | `shared = true`) 248 | 249 | ### Object References 250 | 251 | Object's are used for complex data structures. But it would be a waste of space 252 | and time to serialize the exact same object multiple time. This is what a object 253 | reference is used for. It is indicated by `^` (0x5E)and the best way to explain 254 | how a reference works is with a example. So let's say we got a object with 2 255 | keys and their value is the same object literal like here below 256 | 257 | ```ts 258 | const innerObject = {}; 259 | 260 | const object = { 261 | key1: innerObject, 262 | key2: innerObject, 263 | }; 264 | ``` 265 | 266 | Then what happens is that `object` get's serialized as key value pairs. `key1` 267 | will be serialized as a string and the value here as a empty object. Then `key2` 268 | will also be serialized as a string but it's value will be serialized as a 269 | reference to a object serialized earlier. Do note that this is only for this 270 | specific example because both values reference the same object. If the object 271 | has 2 identical objects like 2x `{}` then it will not use a reference because 272 | they're not the same object but rather 2 individuals that have the same inner 273 | values and structure. 274 | 275 | ### Array 276 | 277 | Arrays are a lot more complex than meets the eye. They can do quite a few quirky 278 | things like indexing then with strings `myArray["HelloWorld"] = 12;`. But they 279 | also allow empty slots. An empty slot is unique. It's not filled with anything 280 | like `null` or `undefined` (it does map to undefined) but there is nothing. 281 | Which we need to keep in mind when (de)serializing an array. We effectively got 282 | 4 types of arrays to keep in mind (you could argue that there are 5. Which is 283 | only a associated array without any regular slots. But this would be the same as 284 | a dense associated array) 285 | 286 | 1. Dense Array (all allocated slots are occupied) 287 | 2. Sparse Array (some allocated slots are not used) 288 | 3. Dense Associated Array (all allocated slots are occupied & some values are 289 | indexed by strings) 290 | 4. Sparse Associated Array (some allocated slots are not used & some values are 291 | indexed by strings) 292 | 293 | Dense Array `[null, null]` 294 | 295 | ``` 296 | 0x41 0x02 Dense array indicator byte + varint encoded array length 297 | 0x30 0x30 null null 298 | 0x24 0x00 Ending byte + varint encoded kv pair length 299 | 0x02 Varint encoded slot count (array length) 300 | ``` 301 | 302 | Sparse Array `[null, ,null]` 303 | 304 | ``` 305 | 0x61 0x03 Sparse array indicator byte + varint encoded array length 306 | 0x49 0x00 Integer indicator byte + varint encoded index 307 | 0x30 0x49 null + Integer indicator byte 308 | 0x04 0x30 varint encoded index + null 309 | 0x40 0x02 Ending byte + varint encoded kv pairs length 310 | 0x03 Varint encoded slot count (array length) 311 | ``` 312 | 313 | Dense Associated Array `const arr = [null, null]; arr["k"] = null;` 314 | 315 | ``` 316 | 0x41 0x02 Dense array indicator byte + varint encoded array length 317 | 0x30 0x30 null null 318 | 0x22 0x01 string indicator byte + varint encoded string length 319 | 0x6B 0x30 key "k" + null 320 | 0x24 0x01 Ending byte + varint encoded kv pair length 321 | 0x02 Varint encoded slot count (array length) 322 | ``` 323 | 324 | Sparse Array `const arr = [null, ,null]; arr["k"] = null;` 325 | 326 | ``` 327 | 0x61 0x03 Sparse array indicator byte + varint encoded array length 328 | 0x49 0x00 Integer indicator byte + varint encoded index 329 | 0x30 0x49 null + Integer indicator byte 330 | 0x04 0x30 varint encoded index + null 331 | 0x22 0x01 string indicator byte + varint encoded string length 332 | 0x6B 0x30 key "k" + null 333 | 0x40 0x03 Ending byte + varint encoded kv pairs length 334 | 0x03 Varint encoded slot count (array length) 335 | ``` 336 | 337 | ### Classes & Plain Objects 338 | 339 | Classes and plain objects are the same to v8's binary format. They both have the 340 | indicator byte 0x6F `o` and ending byte 0x7B `{`. They're both just key value 341 | pairs that are kinda similar to associative arrays. The difference being that 342 | each value has a key. Which is not always the case with associative arrays (if 343 | they're dense for example). In all other ways they're pretty much the same. One 344 | thing that is special about objects is that string keys that can be integers 345 | will be stored as integers. so an object like this `{ "12": null }` will have 12 346 | as a integer rather than a string 347 | 348 | empty object `{}` 349 | 350 | ``` 351 | 0x6F 0x7B Object Indicator byte `o` & Ending byte `{` 352 | 0x00 Varint encoded kv pair count 353 | ``` 354 | 355 | object with string keys `{ k: null }` 356 | 357 | ``` 358 | 0x6F 0x22 Object indicator byte `o` & string indicator byte `"` 359 | 0x01 0x6B Varint encoded string length & byte `0x6B` key `k` 360 | 0x30 0x7B Value null & ending byte 361 | 0x01 Varint encoded kv pair count 362 | ``` 363 | 364 | object with integer keys `{ 12: null, "13": null }` 365 | 366 | ``` 367 | 0x6F 0x49 Object indicator byte `o` & signed int indicator byte `I` 368 | 0x18 0x30 Varint encoded integer as key and null as value 369 | 0x49 0x1A Signed int indicator byte `I` & integer as key 370 | 0x30 0x7B Null as value & ending byte 371 | 0x02 Varint encoded kv pair count 372 | ``` 373 | 374 | object with string & integer keys `{ k: null, 12: null, "13": null }` 375 | 376 | ``` 377 | 0x6F 0x49 Object indicator byte `o` & signed int indicator byte `I` 378 | 0x18 0x30 Varint encoded integer as key and null as value 379 | 0x49 0x1A Signed int indicator byte `I` & integer as key 380 | 0x30 0x22 Null as value & string indicator byte 381 | 0x01 0x6B Varint encoded string length & byte `k` 382 | 0x30 0x7B Null as value & ending byte 383 | 0x03 Varint encoded kv pair count 384 | ``` 385 | 386 | ### ArrayBuffer 387 | 388 | `ArrayBuffer` is the raw datastore for typed array's like `Uint8Array`. You 389 | cannot interact with it directly. But only via `DataView` or a typed array. It's 390 | indicator byte is 0x42 `B` 391 | 392 | empty ArrayBuffer(4) 393 | 394 | ``` 395 | 0x42 0x04 ArrayBuffer indicator byte `B` & varint encoded length 396 | 0x00 0x00 Bytes in the ArrayBuffer 397 | 0x00 0x00 Bytes in the ArrayBuffer 398 | ``` 399 | 400 | ArrayBuffer with content `[1,2,3,4]` 401 | 402 | ``` 403 | 0x42 0x04 ArrayBuffer indicator byte `B` & varint encoded length 404 | 0x01 0x02 Bytes in de ArrayBuffer 405 | 0x03 0x04 Bytes in de ArrayBuffer 406 | ``` 407 | 408 | ### Typed Arrays 409 | -------------------------------------------------------------------------------- /src/_deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | decode32 as varintDecode, 3 | encode as varintEncode, 4 | } from "https://deno.land/x/varint@v2.0.0/varint.ts"; 5 | -------------------------------------------------------------------------------- /src/_util.ts: -------------------------------------------------------------------------------- 1 | const MAX_SMI_VAL = (1 << 30) - 1; 2 | const MIN_SMI_VAL = -(1 << 30); 3 | 4 | export interface ArrayMetadata { 5 | isSparse: boolean; 6 | indexedLength: number; 7 | unindexedLength: number; 8 | } 9 | 10 | export function arrayMetadata(array: T[]): ArrayMetadata { 11 | const [indexedLength, unindexedLength] = Object.keys(array).reduce( 12 | (acc, cur) => { 13 | acc[strIsIntIndex(cur) ? 0 : 1]++; 14 | return acc; 15 | }, 16 | [0, 0], 17 | ); 18 | 19 | return { 20 | isSparse: indexedLength !== array.length, 21 | indexedLength: indexedLength, 22 | unindexedLength: unindexedLength, 23 | }; 24 | } 25 | 26 | /** 27 | * Object keys that are strings should sometimes be serialized into integers if they can be a valid v8 signed integer. 28 | */ 29 | export function strIsIntIndex(val: string): boolean { 30 | return isSMI(parseFloat(val)); 31 | } 32 | 33 | export function isSMI(val: number): boolean { 34 | const valAsInt = val | 0; 35 | return valAsInt === val && 36 | !Number.isNaN(valAsInt) && 37 | valAsInt >= MIN_SMI_VAL && 38 | valAsInt <= MAX_SMI_VAL; 39 | } 40 | 41 | /** 42 | * copy bytes to the front of the array and nullifying data behind it. 43 | * effectively consuming it 44 | */ 45 | export function consume(data: Uint8Array, length: number) { 46 | data.copyWithin(0, length); 47 | data.fill(0, data.length - length); 48 | } 49 | -------------------------------------------------------------------------------- /src/array.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | import { arrayMetadata, consume } from "./_util.ts"; 3 | import { deserializeV8Integer, serializeJsInteger } from "./integer.ts"; 4 | import { deserializeAny, serializeAny } from "./mod.ts"; 5 | import { serializeReference } from "./object_reference.ts"; 6 | import { deserializeV8String, serializeJsString } from "./string.ts"; 7 | 8 | export function serializeJsArray( 9 | array: T[], 10 | // deno-lint-ignore ban-types 11 | objRefs: {}[] = [], 12 | ): Uint8Array { 13 | if (!Array.isArray(array)) { 14 | throw new Error("Not a JS array"); 15 | } 16 | 17 | const refIdx = objRefs.indexOf(array); 18 | if (refIdx > -1) { 19 | return serializeReference(refIdx); 20 | } 21 | objRefs.push(array); 22 | 23 | // parse metadata from array 24 | const metadata = arrayMetadata(array); 25 | const indicatorByte = metadata.isSparse ? 0x61 : 0x41; 26 | const endingByte = metadata.isSparse ? 0x40 : 0x24; 27 | 28 | // varint encode length of array 29 | const indexedValuesVarint = varintEncode(array.length)[0]; 30 | const serializedValues = [ 31 | Uint8Array.of(indicatorByte), 32 | indexedValuesVarint, 33 | ]; 34 | 35 | const arrayKeys = Object.keys(array); 36 | const len = arrayKeys.length; 37 | for (let i = 0; i < len; i++) { 38 | const key = arrayKeys[i]; 39 | if (i >= metadata.indexedLength) { 40 | serializedValues.push(serializeJsString(key)); 41 | } else if (metadata.isSparse) { 42 | serializedValues.push(serializeJsInteger(parseInt(key))); 43 | } 44 | 45 | // Serialize value 46 | const value = array[key as unknown as number]; 47 | 48 | // If object already serialized. Just put in a reference 49 | const refIndex = objRefs.findIndex((x) => value === x); 50 | if (refIndex > -1) { 51 | serializedValues.push(serializeReference(refIndex)); 52 | continue; 53 | } 54 | 55 | serializedValues.push(serializeAny(value, objRefs)); 56 | } 57 | 58 | // varint encode amount of kvPairs 59 | const kvPairsVarint = varintEncode( 60 | // This is faster for sparsed associative array 61 | // (Number(metadata.isSparse) * metadata.indexedLength) + metadata.unindexedLength 62 | metadata.isSparse 63 | ? metadata.indexedLength + metadata.unindexedLength 64 | : metadata.unindexedLength, 65 | )[0]; 66 | 67 | // Push array ending byte and lengths into the Uint8Array[] 68 | serializedValues.push( 69 | Uint8Array.of(endingByte), 70 | kvPairsVarint, 71 | indexedValuesVarint, 72 | ); 73 | 74 | // Calculate the length for a new Uint8Array slice 75 | const length = serializedValues.reduce((x, y) => x + y.length, 0); 76 | // Create new slice 77 | const serializedArray = new Uint8Array(length); 78 | 79 | // Copy all Uint8Array's in `serializedValues` into `serializedArray` 80 | for (let i = 0, offset = 0; i < serializedValues.length; i++) { 81 | const current = serializedValues[i]; 82 | serializedArray.set(current, offset); 83 | offset += current.length; 84 | } 85 | 86 | // Return 87 | return serializedArray; 88 | } 89 | 90 | export function deserializeV8Array( 91 | data: Uint8Array, 92 | // deno-lint-ignore ban-types 93 | objRefs: {}[] = [], 94 | ): T[] { 95 | if (data[0] as number !== 0x61 && data[0] !== 0x41) { 96 | throw new Error("Not a V8 array"); 97 | } 98 | const startingByte = data[0]; 99 | const endingByte = startingByte === 0x61 ? 0x40 : 0x24; 100 | 101 | const [arrayLength, bytesUsed] = varintDecode(data, 1); 102 | const arr: T[] = []; 103 | objRefs.push(arr); 104 | 105 | let useKvPairs = data[0] === 0x61 || arr.length === arrayLength; 106 | 107 | consume(data, bytesUsed); 108 | while (data[0] !== endingByte) { 109 | let key: string | number | undefined = undefined; 110 | if (useKvPairs) { 111 | key = data[0] === 0x63 || data[0] === 0x22 112 | ? deserializeV8String(data) 113 | : deserializeV8Integer(data); 114 | } 115 | 116 | const value = deserializeAny(data, objRefs); 117 | if (key !== undefined) { 118 | arr[key as number] = value!; 119 | } else { 120 | arr.push(value!); 121 | } 122 | 123 | useKvPairs = useKvPairs || arr.length === arrayLength; 124 | } 125 | 126 | // TODO: assert that deserialized length is the one provided 127 | // Decode trailing varint's to know their byte length. Then return just that length. 128 | const tailBytesLength = varintDecode(data, varintDecode(data)[1])[1]; 129 | // Consume tail bytes length + 1 (ending array byte) 130 | consume(data, tailBytesLength + 1); 131 | return arr; 132 | } 133 | -------------------------------------------------------------------------------- /src/array_buffer.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | 3 | export function serializeArrayBuffer(buff: ArrayBuffer) { 4 | const view = new Uint8Array(buff); 5 | const [varint, bytes] = varintEncode(buff.byteLength); 6 | 7 | const data = new Uint8Array(buff.byteLength + 1 + bytes); 8 | data[0] = 0x42; 9 | data.set(varint, 1); 10 | data.set(view, bytes + 1); 11 | 12 | return data; 13 | } 14 | 15 | export function deserializeArrayBuffer(data: Uint8Array): ArrayBuffer { 16 | if (data[0] !== 0x42) throw new Error("Not a serialized ArrayBuffer"); 17 | const [_, offset] = varintDecode(data, 1); 18 | const ab = Uint8Array.from(data.subarray(offset)); 19 | return ab.buffer; 20 | } 21 | -------------------------------------------------------------------------------- /src/bigint.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | import { consume } from "./_util.ts"; 3 | 4 | /** 5 | * @param { BigInt } value - BigInt To Serialize 6 | * @returns { Uint8Array } Serialized JS BigInt without magic bytes 7 | */ 8 | export function serializeJsBigInt(value: bigint): Uint8Array { 9 | if (typeof value !== "bigint") throw new Error("Not a BigInt"); 10 | // Check if the value is negative 11 | const isNegative = value < 0n; 12 | // make the value positive if negative 13 | value = isNegative ? value - value * 2n : value; 14 | // Construct a new u64 array 15 | const bigintArray = []; 16 | 17 | // Bitshift by 64 bits until value is 0 18 | while (value !== 0n) { 19 | // Push u64's 20 | bigintArray.push(value); 21 | value >>= 64n; 22 | } 23 | 24 | // Calculate bitfield value 25 | const bitfield = bigintArray.length * 16 + (isNegative ? 1 : 0); 26 | // Get ArrayBuffer of array 27 | const { buffer: bigintArrayBuffer } = new BigUint64Array(bigintArray); 28 | 29 | // encode bitfield bytes into a varint 30 | const [bitfieldVarintBytes] = varintEncode(bitfield); 31 | const arrayLength = 1 + bitfieldVarintBytes.length + bigintArray.length * 8; 32 | // Create serialized data 33 | const serializedData = new Uint8Array(arrayLength); 34 | 35 | // Set indicator byte 36 | serializedData[0] = 0x5A; 37 | // Set varint bitfield 38 | serializedData.set(bitfieldVarintBytes, 1); 39 | // Set bigint data 40 | serializedData.set( 41 | new Uint8Array(bigintArrayBuffer), 42 | 1 + bitfieldVarintBytes.length, 43 | ); 44 | 45 | return serializedData; 46 | } 47 | 48 | /** 49 | * This function assumes that there is no magic bytes and the first element is the type indicator 50 | * @param { Uint8Array } data - Serialized BigInt data 51 | * @returns { BigInt } Deserialized BigInt 52 | */ 53 | export function deserializeV8BigInt(data: Uint8Array): bigint { 54 | if (data[0] !== 0x5A) throw new Error("Not a v8 bigint"); 55 | // Decode varint bitfield 56 | const [bitfield, bytesUsed] = varintDecode(data, 1); 57 | const bigintCount = bitfield / 16 | 0; 58 | // Create bigint dataview 59 | const dataview = new DataView(data.buffer, data.byteOffset + bytesUsed); 60 | 61 | let num = 0n; 62 | for (let i = 0; i < bigintCount; i++) { 63 | num = num << 64n | dataview.getBigUint64((bigintCount - i) * 8 - 8, true); 64 | } 65 | 66 | consume(data, bigintCount * 8 + bytesUsed); 67 | return (bitfield % 16 === 1) ? -num : num; 68 | } 69 | -------------------------------------------------------------------------------- /src/boolean.ts: -------------------------------------------------------------------------------- 1 | import { consume } from "./_util.ts"; 2 | 3 | /** 4 | * @param { boolean } bool - Boolean To Serialize 5 | * @returns { Uint8Array } Serialized JS boolean without magic bytes 6 | */ 7 | export function serializeJsBoolean(bool: boolean): Uint8Array { 8 | if (typeof bool !== "boolean") throw new Error("Not a boolean"); 9 | return Uint8Array.of(bool ? 0x54 : 0x46); 10 | } 11 | 12 | /** 13 | * This function assumes that there is no magic bytes and the first element is the type indicator 14 | * @param { Uint8Array } data - Serialized boolean data 15 | * @returns { boolean } Deserialized boolean 16 | */ 17 | export function deserializeV8Boolean(data: Uint8Array): boolean { 18 | const bool = data[0]; 19 | if (bool !== 0x54 && bool !== 0x46) throw new Error("Not a v8 boolean"); 20 | // Consume bytes (this is so that we don't need to pass a offset to the next deserialize function) 21 | consume(data, 1); 22 | return bool === 0x54; 23 | } 24 | -------------------------------------------------------------------------------- /src/float.ts: -------------------------------------------------------------------------------- 1 | import { consume } from "./_util.ts"; 2 | 3 | const MIN_INT_VALUE = -1_073_741_824; 4 | const MAX_INT_VALUE = 1_073_741_823; 5 | 6 | /** 7 | * @param { number } float - Float64 To Serialize 8 | * @returns { Uint8Array } Serialized Float64 without magic bytes 9 | */ 10 | export function serializeJsFloat(float: number): Uint8Array { 11 | if ( 12 | Number.isInteger(float) && 13 | float > MIN_INT_VALUE && 14 | float < MAX_INT_VALUE 15 | ) { 16 | throw new Error("Not a float"); 17 | } 18 | 19 | const binaryView = new Uint8Array(9); 20 | 21 | binaryView[0] = 0x4E; 22 | 23 | // Set float64 at offset=1 as little endian 24 | new DataView(binaryView.buffer).setFloat64(1, float, true); 25 | 26 | return binaryView; 27 | } 28 | 29 | /** 30 | * This function assumes that there is no magic bytes and the first element is the type indicator 31 | * @param { Uint8Array } data - Serialized float data 32 | * @returns { number } Deserialized Float64 33 | */ 34 | export function deserializeV8Float(data: Uint8Array): number { 35 | if (data[0] !== 0x4E) throw new Error("Not a v8 float"); 36 | // Create new slice 37 | const dt = new DataView(data.buffer, data.byteOffset + 1); 38 | const val = dt.getFloat64(0, true); 39 | // Consume bytes (this is so that we don't need to pass a offset to the next deserialize function) 40 | consume(data, 9); 41 | return val; 42 | } 43 | -------------------------------------------------------------------------------- /src/integer.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | import { consume } from "./_util.ts"; 3 | 4 | const MIN_INT_VALUE = -1_073_741_824; 5 | const MAX_INT_VALUE = 1_073_741_823; 6 | 7 | /** 8 | * @param { number } value - Integer To Serialize 9 | * @returns { Uint8Array } Serialized JS Integer without magic bytes 10 | */ 11 | export function serializeJsInteger(value: number): Uint8Array { 12 | if (!Number.isInteger(value)) { 13 | throw new Error("Not a integer"); 14 | } 15 | 16 | if (value < MIN_INT_VALUE || value > MAX_INT_VALUE) { 17 | throw new Error("Outside of the integer range"); 18 | } 19 | 20 | // ZigZag Encode 21 | const v = (value >> 31) ^ (value << 1); 22 | 23 | // Varint Encode 24 | const [varintBytes, length] = varintEncode(v); 25 | 26 | const buffer = new Uint8Array(length + 1); 27 | buffer[0] = 0x49; 28 | buffer.set(varintBytes, 1); 29 | 30 | return buffer; 31 | } 32 | 33 | /** 34 | * This function assumes that there is no magic bytes and the first element is the type indicator 35 | * @param { Uint8Array } data - Serialized integer data 36 | * @returns { number } Deserialized integer 37 | */ 38 | export function deserializeV8Integer(data: Uint8Array): number { 39 | if (data[0] !== 0x49) throw new Error("Not a v8 integer"); 40 | // Varint Decode 41 | const [rawValue, bytesUsed] = varintDecode(data, 1); 42 | // Consume bytes (this is so that we don't need to pass a offset to the next deserialize function) 43 | consume(data, bytesUsed); 44 | 45 | // ZigZag Decode 46 | return (rawValue >> 1) ^ -(rawValue & 1); 47 | } 48 | -------------------------------------------------------------------------------- /src/mod.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Array, serializeJsArray } from "./array.ts"; 2 | import { deserializeV8BigInt, serializeJsBigInt } from "./bigint.ts"; 3 | import { deserializeV8Boolean, serializeJsBoolean } from "./boolean.ts"; 4 | import { deserializeV8Float, serializeJsFloat } from "./float.ts"; 5 | import { deserializeReference } from "./object_reference.ts"; 6 | import { deserializeV8Integer, serializeJsInteger } from "./integer.ts"; 7 | import { deserializeV8Null, serializeJsNull } from "./null.ts"; 8 | import { deserializeV8String, serializeJsString } from "./string.ts"; 9 | import { deserializeV8Undefined, serializeJsUndefined } from "./undefined.ts"; 10 | import { deserializeV8Object, serializeJsObject } from "./objects.ts"; 11 | import { isSMI } from "./_util.ts"; 12 | // deno-lint-ignore no-explicit-any ban-types 13 | export function serializeAny(value: any, objRefs: {}[] = []): Uint8Array { 14 | switch (typeof value) { 15 | case "bigint": 16 | return serializeJsBigInt(value); 17 | case "boolean": 18 | return serializeJsBoolean(value); 19 | case "number": { 20 | return isSMI(value) ? serializeJsInteger(value) : serializeJsFloat(value); 21 | } 22 | 23 | case "string": 24 | return serializeJsString(value); 25 | case "undefined": 26 | return serializeJsUndefined(value); 27 | case "object": { 28 | if (value === null) { 29 | // Null 30 | return serializeJsNull(value); 31 | } 32 | 33 | if (Array.isArray(value)) { 34 | // Array (either dense or sparse) 35 | return serializeJsArray(value, objRefs); 36 | } 37 | 38 | if (value instanceof Object) { 39 | // Plain object 40 | return serializeJsObject(value, objRefs); 41 | } 42 | 43 | throw new Error("Object cannot be serialized"); 44 | } 45 | default: 46 | throw new Error("Type cannot be serialized"); 47 | } 48 | } 49 | 50 | // deno-lint-ignore no-explicit-any ban-types 51 | export function deserializeAny(data: Uint8Array, objRefs: {}[] = []): any { 52 | switch (data[0]) { 53 | // String 54 | case 0x63: 55 | case 0x22: 56 | return deserializeV8String(data); 57 | // Integer 58 | case 0x49: 59 | return deserializeV8Integer(data); 60 | // Bigint 61 | case 0x5A: 62 | return deserializeV8BigInt(data); 63 | // Float 64 | case 0x4E: 65 | return deserializeV8Float(data); 66 | // Boolean 67 | case 0x46: 68 | case 0x54: 69 | return deserializeV8Boolean(data); 70 | // Null 71 | case 0x30: 72 | return deserializeV8Null(data); 73 | // Undefined 74 | case 0x5F: 75 | return deserializeV8Undefined(data); 76 | // Array 77 | case 0x61: 78 | case 0x41: 79 | return deserializeV8Array(data, objRefs); 80 | // Object Reference 81 | case 0x5E: 82 | return deserializeReference(data, objRefs); 83 | // Object 84 | case 0x6F: 85 | return deserializeV8Object(data, objRefs); 86 | default: 87 | throw new Error("Could not deserialize value"); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/null.ts: -------------------------------------------------------------------------------- 1 | import { consume } from "./_util.ts"; 2 | 3 | /** 4 | * @param { null } val - null To Serialize 5 | * @returns { Uint8Array } Serialized null 6 | */ 7 | export function serializeJsNull(val: null): Uint8Array { 8 | if (val !== null) throw new Error("Not Null"); 9 | return Uint8Array.of(0x30); 10 | } 11 | 12 | /** 13 | * This function assumes that there is no magic bytes and the first element is the type indicator 14 | * @param { Uint8Array } data - Serialized null data 15 | * @returns { null } Deserialized null 16 | */ 17 | export function deserializeV8Null( 18 | data: Uint8Array, 19 | ): null { 20 | if (data[0] !== 0x30) throw new Error("Not a V8 null"); 21 | // Consume bytes (this is so that we don't need to pass a offset to the next deserialize function) 22 | consume(data, 1); 23 | return null; 24 | } 25 | -------------------------------------------------------------------------------- /src/object_reference.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | import { consume } from "./_util.ts"; 3 | 4 | export function serializeReference(idx: number): Uint8Array { 5 | const varint = varintEncode(idx)[0]; 6 | return Uint8Array.of(0x5E, ...varint); 7 | } 8 | 9 | export function deserializeReference( 10 | data: Uint8Array, 11 | // deno-lint-ignore ban-types 12 | objRefs: {}[], 13 | // deno-lint-ignore ban-types 14 | ): {} { 15 | if (data[0] !== 0x5E) throw new Error("Not a v8 object reference"); 16 | const [idx, bytesUsed] = varintDecode(data, 1); 17 | consume(data, bytesUsed); 18 | return objRefs[idx]; 19 | } 20 | -------------------------------------------------------------------------------- /src/objects.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | import { consume, strIsIntIndex } from "./_util.ts"; 3 | import { deserializeV8Integer, serializeJsInteger } from "./integer.ts"; 4 | import { deserializeAny, serializeAny } from "./mod.ts"; 5 | import { deserializeV8String, serializeJsString } from "./string.ts"; 6 | import { serializeReference } from "./object_reference.ts"; 7 | 8 | export function serializeJsObject( 9 | // deno-lint-ignore ban-types 10 | object: {}, 11 | // deno-lint-ignore ban-types 12 | objRefs: {}[] = [], 13 | ): Uint8Array { 14 | if (object.constructor.name !== "Object") { 15 | throw new Error("Not a JS object"); 16 | } 17 | 18 | const refIdx = objRefs.indexOf(object); 19 | if (refIdx > -1) { 20 | return serializeReference(refIdx); 21 | } else { 22 | objRefs.push(object); 23 | } 24 | 25 | const values: Uint8Array[] = [Uint8Array.of(0x6F)]; 26 | for (const [key, value] of Object.entries(object)) { 27 | const keySerialized = strIsIntIndex(key) 28 | ? serializeJsInteger(parseInt(key)) 29 | : serializeJsString(key); 30 | values.push(keySerialized, serializeAny(value, objRefs)); 31 | } 32 | 33 | // Push ending byte and length 34 | values.push(Uint8Array.of(0x7B), varintEncode(Object.keys(object).length)[0]); 35 | // Create new slice 36 | const serializedData = new Uint8Array( 37 | values.reduce((x, y) => x + y.length, 0), 38 | ); 39 | 40 | // Copy all Uint8Array's in `values` into `serializedData` 41 | values.reduce((ptr, current) => { 42 | // Copy current dataslice to ptr 43 | serializedData.set(current, ptr); 44 | return ptr + current.length; 45 | }, 0); 46 | 47 | return serializedData; 48 | } 49 | 50 | export function deserializeV8Object( 51 | data: Uint8Array, 52 | // deno-lint-ignore ban-types 53 | objRefs: {}[] = [], 54 | ): Record { 55 | if (data[0] as number !== 0x6F) throw new Error("Not a v8 object"); 56 | const obj: Record = {}; 57 | objRefs.push(obj); 58 | // Consume indicator byte 59 | consume(data, 1); 60 | 61 | while (data[0] !== 0x7B) { 62 | // Deserialize key as string or integer 63 | const key = data[0] === 0x63 || data[0] === 0x22 64 | ? deserializeV8String(data) 65 | : deserializeV8Integer(data); 66 | 67 | const value = deserializeAny(data, objRefs); 68 | 69 | obj[key] = value; 70 | } 71 | // TODO: assert that deserialized length is the one provided 72 | // Decode trailing varint's to know their byte length. Then return just that length. 73 | const tailBytesLength = varintDecode(data)[1]; 74 | // Consume tail bytes length + 1 (ending array byte) 75 | consume(data, tailBytesLength + 1); 76 | 77 | return obj; 78 | } 79 | -------------------------------------------------------------------------------- /src/string.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | import { consume } from "./_util.ts"; 3 | 4 | const TEXT_ENCODER = new TextEncoder(); 5 | const TWO_BYTE_STR_REGEX = /[^\x20-\x7E]+/g; 6 | const TEXT_DECODER_UTF16 = new TextDecoder("utf-16"); 7 | const TEXT_DECODER_UTF8 = new TextDecoder("utf-8"); 8 | 9 | /** 10 | * @param { string } data - String To Serialize 11 | * @returns { Uint8Array } Serialized string without magic bytes 12 | */ 13 | export function serializeJsString(data: string): Uint8Array { 14 | if (typeof data !== "string") throw new Error("Not a string"); 15 | const isTwoByteString = TWO_BYTE_STR_REGEX.test(data); 16 | const indicatorByte = isTwoByteString ? 0x63 : 0x22; 17 | 18 | let stringEncoded: Uint8Array; 19 | if (isTwoByteString) { 20 | const u16Array = Uint16Array.from( 21 | data.split("").map((c) => c.charCodeAt(0)), 22 | ); 23 | stringEncoded = new Uint8Array(u16Array.buffer); 24 | } else { 25 | stringEncoded = TEXT_ENCODER.encode(data); 26 | } 27 | 28 | const [varintBytes] = varintEncode(stringEncoded.length); 29 | const serializedData = new Uint8Array( 30 | varintBytes.length + stringEncoded.length + 1, 31 | ); 32 | serializedData[0] = indicatorByte; 33 | serializedData.set(varintBytes, 1); 34 | serializedData.set(stringEncoded, varintBytes.length + 1); 35 | 36 | return serializedData; 37 | } 38 | 39 | /** 40 | * This function assumes that there is no magic bytes and the first element is the type indicator 41 | * @param { Uint8Array } data - Serialized string data 42 | * @returns { string } Deserialized string 43 | */ 44 | export function deserializeV8String(data: Uint8Array): string { 45 | if (data[0] !== 0x22 && data[0] !== 0x63) throw new Error("Not a v8 string"); 46 | const decoder = data[0] === 0x63 ? TEXT_DECODER_UTF16 : TEXT_DECODER_UTF8; 47 | const [stringLength, bytesUsed] = varintDecode(data, 1); 48 | // Decode string 49 | const decoded = decoder.decode( 50 | data.subarray(bytesUsed, bytesUsed + stringLength), 51 | ); 52 | // Consume bytes (this is so that we don't need to pass a offset to the next deserialize function) 53 | consume(data, bytesUsed + stringLength); 54 | return decoded; 55 | } 56 | -------------------------------------------------------------------------------- /src/typed_array.ts: -------------------------------------------------------------------------------- 1 | import { varintDecode, varintEncode } from "./_deps.ts"; 2 | import { 3 | deserializeArrayBuffer, 4 | serializeArrayBuffer, 5 | } from "./array_buffer.ts"; 6 | import { consume as _ } from "./_util.ts"; 7 | 8 | type UnsignedTypedArray = 9 | | Uint8ClampedArray 10 | | Uint8Array 11 | | Uint16Array 12 | | Uint32Array 13 | | BigUint64Array; 14 | 15 | type SignedTypedArray = 16 | | Int8Array 17 | | Int16Array 18 | | Int32Array 19 | | BigInt64Array; 20 | 21 | type FloatTypedArray = Float32Array | Float64Array; 22 | 23 | type TypedArray = UnsignedTypedArray | SignedTypedArray | FloatTypedArray; 24 | 25 | const TYPED_ARRAY_TO_INDICATOR: Record = { 26 | Int8Array: "b".charCodeAt(0), 27 | Uint8Array: "B".charCodeAt(0), 28 | Uint8ClampedArray: "C".charCodeAt(0), 29 | Int16Array: "w".charCodeAt(0), 30 | Uint16Array: "W".charCodeAt(0), 31 | Int32Array: "d".charCodeAt(0), 32 | Uint32Array: "D".charCodeAt(0), 33 | Float32Array: "f".charCodeAt(0), 34 | Float64Array: "F".charCodeAt(0), 35 | BigInt64Array: "q".charCodeAt(0), 36 | BigUint64Array: "Q".charCodeAt(0), 37 | }; 38 | 39 | type TypedArrayConstructor = 40 | | Uint8ArrayConstructor 41 | | Int8ArrayConstructor 42 | | Uint8ClampedArrayConstructor 43 | | Int16ArrayConstructor 44 | | Uint16ArrayConstructor 45 | | Int32ArrayConstructor 46 | | Uint32ArrayConstructor 47 | | Float32ArrayConstructor 48 | | Float64ArrayConstructor 49 | | BigInt64ArrayConstructor 50 | | BigUint64ArrayConstructor; 51 | 52 | const INDICATOR_TO_TYPED_ARRAY_CONSTRUCTOR: Record< 53 | number, 54 | TypedArrayConstructor 55 | > = { 56 | 98: Int8Array, 57 | 66: Uint8Array, 58 | 67: Uint8ClampedArray, 59 | 119: Int16Array, 60 | 87: Uint16Array, 61 | 100: Int32Array, 62 | 68: Uint32Array, 63 | 102: Float32Array, 64 | 70: Float64Array, 65 | 113: BigInt64Array, 66 | 81: BigUint64Array, 67 | }; 68 | export function serializeTypedArray(typedArray: TypedArray) { 69 | const indicator = 70 | // deno-lint-ignore no-explicit-any 71 | TYPED_ARRAY_TO_INDICATOR[(typedArray as any).constructor.name]; 72 | if (indicator === undefined) throw new Error("Not a js TypedArray"); 73 | 74 | const serializedAB = serializeArrayBuffer(typedArray.buffer); 75 | const buffer = new Uint8Array(serializedAB.length + 12); 76 | 77 | buffer.set(serializedAB, 0); 78 | 79 | let offset = serializedAB.length; 80 | // TypedArray Indicator 81 | buffer[offset++] = 0x56; 82 | // Buffer View Indicator 83 | buffer[offset++] = indicator; 84 | 85 | const byteOffset = varintEncode(typedArray.byteOffset)[0]; 86 | buffer.set(byteOffset, offset--); 87 | offset += byteOffset.length; 88 | const byteLength = varintEncode(typedArray.byteLength)[0]; 89 | offset += byteLength.length; 90 | buffer.set(byteLength, offset); 91 | // buffer[sr]; 92 | buffer[offset + 1] = 232; 93 | buffer[offset + 2] = 71; 94 | return buffer.subarray(0, offset + 3); 95 | } 96 | 97 | export function deserializeTypedArray(data: Uint8Array): TypedArray { 98 | const ab = deserializeArrayBuffer(data); 99 | if (data[0] !== 0x56) throw new Error("Not a TypedArray"); 100 | const constructor = INDICATOR_TO_TYPED_ARRAY_CONSTRUCTOR[data[1]]; 101 | let byteLength, [byteOffset, offset] = varintDecode(data, 1); 102 | [byteLength, offset] = varintDecode(data, offset); 103 | return new constructor(ab, byteOffset, byteLength); 104 | } 105 | -------------------------------------------------------------------------------- /src/undefined.ts: -------------------------------------------------------------------------------- 1 | import { consume } from "./_util.ts"; 2 | 3 | /** 4 | * @param { undefined } val - undefined To Serialize 5 | * @returns { Uint8Array } Serialized undefined 6 | */ 7 | export function serializeJsUndefined(val?: undefined): Uint8Array { 8 | if (val !== undefined) throw new Error("Not Undefined"); 9 | return Uint8Array.of(0x5F); 10 | } 11 | 12 | /** 13 | * This function assumes that there is no magic bytes and the first element is the type indicator 14 | * @param { Uint8Array } data - Serialized undefined data 15 | * @returns { undefined } Deserialized undefined 16 | */ 17 | export function deserializeV8Undefined( 18 | data: Uint8Array, 19 | ): undefined { 20 | if (data[0] !== 0x5F) throw new Error("Not a V8 undefined"); 21 | // Consume bytes (this is so that we don't need to pass a offset to the next deserialize function) 22 | consume(data, 1); 23 | return undefined; 24 | } 25 | -------------------------------------------------------------------------------- /tests/_core.ts: -------------------------------------------------------------------------------- 1 | interface DenoCore { 2 | deserialize(data: Uint8Array): T; 3 | serialize(data: T): Uint8Array; 4 | decode(data: Uint8Array): string; 5 | encode(data: string): Uint8Array; 6 | } 7 | 8 | // @ts-ignore fuck off 9 | export const DENO_CORE: DenoCore = Deno[Deno.internal].core; 10 | -------------------------------------------------------------------------------- /tests/_deps.ts: -------------------------------------------------------------------------------- 1 | export * from "https://deno.land/std@0.138.0/testing/asserts.ts"; 2 | -------------------------------------------------------------------------------- /tests/_util.ts: -------------------------------------------------------------------------------- 1 | const VALUES = [ 2 | null, 3 | "value", 4 | 12, 5 | 12.58, 6 | true, 7 | false, 8 | null, 9 | undefined, 10 | 12n, 11 | {}, 12 | [], 13 | ]; 14 | VALUES.push(VALUES); 15 | 16 | const FULL_OBJECT: Record = { 17 | 1.1: null, 18 | key: "value", 19 | int: 12, 20 | float: 12.58, 21 | true: true, 22 | false: false, 23 | null: null, 24 | undefined: undefined, 25 | bigint: 12n, 26 | sparseArray: sparseArray(), 27 | denseArray: denseArray(), 28 | emptyObject: {}, 29 | }; 30 | 31 | FULL_OBJECT["denseAssociativeArray"] = associativeArray(denseArray()); 32 | FULL_OBJECT["sparseAssociativeArray"] = associativeArray(sparseArray()); 33 | 34 | VALUES.push(FULL_OBJECT); 35 | 36 | export function sparseArray() { 37 | const arr = new Array(3); 38 | arr[2] = null; 39 | arr.push(...VALUES); 40 | return arr; 41 | } 42 | 43 | export function denseArray() { 44 | return [...VALUES]; 45 | } 46 | 47 | export function associativeArray(baseArray: T[]): T[] { 48 | // deno-lint-ignore no-explicit-any 49 | const arr: any = baseArray; 50 | arr[1.1] = null; 51 | arr["key"] = "value"; 52 | arr["int"] = 12; 53 | arr["float"] = 12.58; 54 | arr["boolT"] = true; 55 | arr["boolF"] = false; 56 | arr["NULL"] = null; 57 | arr["undefined"] = undefined; 58 | arr["bigint"] = 12n; 59 | arr["ref"] = arr; 60 | arr["denseArray"] = denseArray(); 61 | arr["sparseArray"] = sparseArray(); 62 | arr["emptyObject"] = {}; 63 | arr["fullObject"] = FULL_OBJECT; 64 | return arr; 65 | } 66 | 67 | export function mixedArray(dense: boolean): T[] { 68 | const arr: T[] = dense ? denseArray() : sparseArray(); 69 | associativeArray(arr); 70 | 71 | return arr; 72 | } 73 | 74 | // deno-lint-ignore ban-types 75 | export function fullObject(): {} { 76 | return { 77 | 1.1: null, 78 | key: "value", 79 | int: 12, 80 | float: 12.58, 81 | true: true, 82 | false: false, 83 | null: null, 84 | undefined: undefined, 85 | bigint: 12n, 86 | sparseArray: sparseArray(), 87 | denseArray: denseArray(), 88 | denseAssociativeArray: associativeArray(denseArray()), 89 | sparseAssociativeArray: associativeArray(sparseArray()), 90 | emptyObject: {}, 91 | fullObject: FULL_OBJECT, 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /tests/array_buffer_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./_deps.ts"; 2 | import { 3 | deserializeArrayBuffer, 4 | serializeArrayBuffer, 5 | } from "../src/array_buffer.ts"; 6 | import { DENO_CORE } from "./_core.ts"; 7 | 8 | Deno.test({ 9 | name: "Serialize ArrayBuffer", 10 | fn: async function (t) { 11 | await t.step({ 12 | name: "Serialize Empty AB", 13 | fn: function () { 14 | const ab = new ArrayBuffer(4); 15 | const ref = DENO_CORE.serialize(ab).subarray(2); 16 | const res = serializeArrayBuffer(ab); 17 | assertEquals(res, ref); 18 | }, 19 | }); 20 | 21 | await t.step({ 22 | name: "Serialize Full AB", 23 | fn: function () { 24 | const view = Uint8Array.of(1, 2, 3, 4); 25 | const ref = DENO_CORE.serialize(view.buffer).subarray(2); 26 | const res = serializeArrayBuffer(view.buffer); 27 | assertEquals(res, ref); 28 | }, 29 | }); 30 | }, 31 | }); 32 | 33 | Deno.test({ 34 | name: "Deserialize ArrayBuffer", 35 | fn: async function (t) { 36 | await t.step({ 37 | name: "Deserialize Empty ArrayBuffer", 38 | fn: function () { 39 | const ab = new ArrayBuffer(4); 40 | const input = DENO_CORE.serialize(ab).subarray(2); 41 | const res = deserializeArrayBuffer(input); 42 | assertEquals(new Uint8Array(res), new Uint8Array(ab)); 43 | }, 44 | }); 45 | 46 | await t.step({ 47 | name: "Deserialize Full ArrayBuffer", 48 | fn: function () { 49 | const ab = Uint8Array.of(1, 2, 3, 4).buffer; 50 | const input = DENO_CORE.serialize(ab).subarray(2); 51 | const res = deserializeArrayBuffer(input); 52 | assertEquals(new Uint8Array(res), new Uint8Array(ab)); 53 | }, 54 | }); 55 | 56 | await t.step({ 57 | name: "Deserialize non ArrayBuffer", 58 | fn: function () { 59 | assertThrows(() => deserializeArrayBuffer(DENO_CORE.serialize(12))); 60 | }, 61 | }); 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /tests/array_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./_deps.ts"; 2 | import { deserializeV8Array, serializeJsArray } from "../src/array.ts"; 3 | import { DENO_CORE } from "./_core.ts"; 4 | import { 5 | associativeArray, 6 | denseArray, 7 | mixedArray, 8 | sparseArray, 9 | } from "./_util.ts"; 10 | 11 | Deno.test({ 12 | name: "Deserialize Dense Array", 13 | 14 | fn: async function (t) { 15 | await t.step({ 16 | name: "Deserialize Dense Array: Empty Array", 17 | fn: function () { 18 | const input = DENO_CORE 19 | .serialize([]) 20 | .subarray(2); 21 | 22 | assertEquals(input[0], 0x41); 23 | const res = deserializeV8Array(input); 24 | assertEquals(res, []); 25 | assertEquals(input, new Uint8Array(input.length).fill(0)); 26 | }, 27 | }); 28 | 29 | await t.step({ 30 | name: "Deserialize Dense Array: Regular Array", 31 | fn: function () { 32 | const data = denseArray(); 33 | 34 | const input = DENO_CORE 35 | .serialize(data) 36 | .subarray(2); 37 | 38 | assertEquals(input[0], 0x41); 39 | const res = deserializeV8Array(input); 40 | assertEquals(res, data); 41 | assertEquals(input, new Uint8Array(input.length).fill(0)); 42 | }, 43 | }); 44 | 45 | await t.step({ 46 | name: "Deserialize Dense Array: Associative Array", 47 | fn: function () { 48 | const data = associativeArray(denseArray()); 49 | 50 | const input = DENO_CORE 51 | .serialize(data) 52 | .subarray(2); 53 | 54 | assertEquals(input[0], 0x41); 55 | const res = deserializeV8Array(input); 56 | assertEquals(res, data); 57 | assertEquals(input, new Uint8Array(input.length).fill(0)); 58 | }, 59 | }); 60 | 61 | await t.step({ 62 | name: "Deserialize Dense Array: Mixed Array", 63 | fn: function () { 64 | const data = mixedArray(true); 65 | 66 | const input = DENO_CORE 67 | .serialize(data) 68 | .subarray(2); 69 | 70 | assertEquals(input[0], 0x41); 71 | const res = deserializeV8Array(input); 72 | assertEquals(res, data); 73 | assertEquals(input, new Uint8Array(input.length).fill(0)); 74 | }, 75 | }); 76 | 77 | await t.step({ 78 | name: "Deserialize Dense Array: self as ref", 79 | fn: function () { 80 | const data: unknown[] = []; 81 | data[0] = data; 82 | 83 | const input = DENO_CORE 84 | .serialize(data) 85 | .subarray(2); 86 | 87 | assertEquals(input[0], 0x41); 88 | const res = deserializeV8Array(input); 89 | assertEquals(res, data); 90 | assertEquals(input, new Uint8Array(input.length).fill(0)); 91 | }, 92 | }); 93 | }, 94 | }); 95 | 96 | Deno.test({ 97 | name: "Deserialize Sparse Array", 98 | fn: async function (t) { 99 | await t.step({ 100 | name: "Deserialize Sparse Array: Object As Array", 101 | fn: function () { 102 | const input = DENO_CORE 103 | .serialize({}) 104 | .subarray(2); 105 | assertThrows(() => deserializeV8Array(input)); 106 | }, 107 | }); 108 | 109 | await t.step({ 110 | name: "Deserialize Sparse Array: Regular Array", 111 | fn: function () { 112 | const data = sparseArray(); 113 | 114 | const input = DENO_CORE 115 | .serialize(data) 116 | .subarray(2); 117 | 118 | assertEquals(input[0], 0x61); 119 | const res = deserializeV8Array(input); 120 | assertEquals(res, data); 121 | assertEquals(input, new Uint8Array(input.length).fill(0)); 122 | }, 123 | }); 124 | 125 | await t.step({ 126 | name: "Deserialize Sparse Array: Mixed Array", 127 | fn: function () { 128 | const data = mixedArray(false); 129 | 130 | const input = DENO_CORE 131 | .serialize(data) 132 | .subarray(2); 133 | 134 | assertEquals(input[0], 0x61); 135 | const res = deserializeV8Array(input); 136 | assertEquals(res, data); 137 | assertEquals(input, new Uint8Array(input.length).fill(0)); 138 | }, 139 | }); 140 | 141 | await t.step({ 142 | name: "Deserialize Sparse Array: self as ref", 143 | fn: function () { 144 | const data: unknown[] = new Array(3); 145 | data[0] = data; 146 | 147 | const input = DENO_CORE 148 | .serialize(data) 149 | .subarray(2); 150 | 151 | assertEquals(input[0], 0x61); 152 | const res = deserializeV8Array(input); 153 | assertEquals(res, data); 154 | assertEquals(input, new Uint8Array(input.length).fill(0)); 155 | }, 156 | }); 157 | }, 158 | }); 159 | 160 | Deno.test({ 161 | name: "Serialize Dense Array", 162 | fn: async function (t) { 163 | await t.step({ 164 | name: "Serialize Dense Array: Empty Array", 165 | fn: function () { 166 | const data: string[] = []; 167 | const ref = DENO_CORE 168 | .serialize(data) 169 | .subarray(2); 170 | 171 | const res = serializeJsArray(data); 172 | 173 | assertEquals(res[0], 0x41); 174 | assertEquals(res, ref); 175 | }, 176 | }); 177 | 178 | await t.step({ 179 | name: "Serialize Dense Array: Regular Array", 180 | fn: function () { 181 | const data = denseArray(); 182 | 183 | const ref = DENO_CORE 184 | .serialize(data) 185 | .subarray(2); 186 | 187 | const res = serializeJsArray(data); 188 | assertEquals(res, ref); 189 | }, 190 | }); 191 | 192 | await t.step({ 193 | name: "Serialize Dense Array: Associative Array", 194 | fn: function () { 195 | const data = associativeArray(denseArray()); 196 | 197 | const ref = DENO_CORE 198 | .serialize(data) 199 | .subarray(2); 200 | 201 | const res = serializeJsArray(data); 202 | assertEquals(res[0], 0x41); 203 | assertEquals(ref, res); 204 | }, 205 | }); 206 | 207 | await t.step({ 208 | name: "Serialize Dense Array: Mixed Array", 209 | fn: function () { 210 | const data = mixedArray(true); 211 | 212 | const ref = DENO_CORE 213 | .serialize(data) 214 | .subarray(2); 215 | 216 | const res = serializeJsArray(data); 217 | 218 | assertEquals(res[0], 0x41); 219 | assertEquals(res, ref); 220 | }, 221 | }); 222 | 223 | await t.step({ 224 | name: "Serialize Dense Array: self as ref", 225 | fn: function () { 226 | const data: unknown[] = []; 227 | data[0] = data; 228 | 229 | const ref = DENO_CORE 230 | .serialize(data) 231 | .subarray(2); 232 | 233 | assertEquals(ref[0], 0x41); 234 | const res = serializeJsArray(data); 235 | assertEquals(res, ref); 236 | }, 237 | }); 238 | }, 239 | }); 240 | 241 | Deno.test({ 242 | name: "Serialize Sparse Array", 243 | fn: async function (t) { 244 | await t.step({ 245 | name: "Serialize Sparse Array: Object As Array", 246 | fn: function () { 247 | // deno-lint-ignore no-explicit-any 248 | assertThrows(() => serializeJsArray({} as any)); 249 | }, 250 | }); 251 | 252 | await t.step({ 253 | name: "Serialize Sparse Array: Regular Array", 254 | fn: function () { 255 | const data = sparseArray(); 256 | const ref = DENO_CORE 257 | .serialize(data) 258 | .subarray(2); 259 | 260 | const res = serializeJsArray(data); 261 | 262 | assertEquals(res[0], 0x61); 263 | assertEquals(res, ref); 264 | }, 265 | }); 266 | 267 | await t.step({ 268 | name: "Serialize Sparse Array: Mixed Array", 269 | fn: function () { 270 | const data = mixedArray(false); 271 | 272 | const ref = DENO_CORE 273 | .serialize(data) 274 | .subarray(2); 275 | 276 | const res = serializeJsArray(data); 277 | 278 | assertEquals(res, ref); 279 | }, 280 | }); 281 | await t.step({ 282 | name: "Serialize Sparse Array: self as ref", 283 | fn: function () { 284 | const data: unknown[] = new Array(3); 285 | data[0] = data; 286 | 287 | const ref = DENO_CORE 288 | .serialize(data) 289 | .subarray(2); 290 | 291 | assertEquals(ref[0], 0x61); 292 | const res = serializeJsArray(data); 293 | assertEquals(res, ref); 294 | }, 295 | }); 296 | }, 297 | }); 298 | -------------------------------------------------------------------------------- /tests/bigint_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./_deps.ts"; 2 | import { deserializeV8BigInt, serializeJsBigInt } from "../src/bigint.ts"; 3 | import { DENO_CORE } from "./_core.ts"; 4 | 5 | Deno.test({ 6 | name: "Deserialize BigInt", 7 | fn: async function (t) { 8 | await t.step({ 9 | name: "Deserialize BigInt: Positive BigInt", 10 | fn: function () { 11 | const input = DENO_CORE 12 | .serialize(12n) 13 | .subarray(2); 14 | 15 | const res = deserializeV8BigInt(input); 16 | assertEquals(res, 12n); 17 | assertEquals(input, new Uint8Array(input.length).fill(0)); 18 | }, 19 | }); 20 | 21 | await t.step({ 22 | name: "Deserialize BigInt: Negative BigInt", 23 | fn: function () { 24 | const input = DENO_CORE 25 | .serialize(-12n) 26 | .subarray(2); 27 | 28 | const res = deserializeV8BigInt(input); 29 | assertEquals(res, -12n); 30 | assertEquals(input, new Uint8Array(input.length).fill(0)); 31 | }, 32 | }); 33 | 34 | await t.step({ 35 | name: "Deserialize BigInt: Number.MAX_VALUE as BigInt", 36 | fn: function () { 37 | const input = DENO_CORE 38 | .serialize(BigInt(Number.MAX_VALUE)) 39 | .subarray(2); 40 | 41 | const res = deserializeV8BigInt(input); 42 | assertEquals(res, BigInt(Number.MAX_VALUE)); 43 | assertEquals(input, new Uint8Array(input.length).fill(0)); 44 | }, 45 | }); 46 | 47 | await t.step({ 48 | name: "Deserialize BigInt: Number.MIN_VALUE as BigInt", 49 | fn: function () { 50 | const input = DENO_CORE 51 | .serialize(BigInt(Number.MIN_VALUE | 0)) 52 | .subarray(2); 53 | 54 | const res = deserializeV8BigInt(input); 55 | assertEquals(res, BigInt(Number.MIN_VALUE | 0)); 56 | assertEquals(input, new Uint8Array(input.length).fill(0)); 57 | }, 58 | }); 59 | 60 | await t.step({ 61 | name: "Deserialize BigInt: Invalid BigInt", 62 | fn: function () { 63 | const serializedNumber = DENO_CORE 64 | .serialize(12) 65 | .subarray(2); 66 | 67 | assertThrows(() => deserializeV8BigInt(serializedNumber)); 68 | }, 69 | }); 70 | }, 71 | }); 72 | 73 | Deno.test({ 74 | name: "Serialize BigInt", 75 | fn: async function (t) { 76 | await t.step({ 77 | name: "Serialize BigInt: Positive BigInt", 78 | fn: function () { 79 | const ref = DENO_CORE 80 | .serialize(12n) 81 | .subarray(2); 82 | 83 | const res = serializeJsBigInt(12n); 84 | 85 | assertEquals(res, ref); 86 | }, 87 | }); 88 | 89 | await t.step({ 90 | name: "Serialize BigInt: Negative BigInt", 91 | fn: function () { 92 | const ref = DENO_CORE 93 | .serialize(-12n) 94 | .subarray(2); 95 | 96 | const res = serializeJsBigInt(-12n); 97 | 98 | assertEquals(res, ref); 99 | }, 100 | }); 101 | 102 | await t.step({ 103 | name: "Serialize BigInt: Number.MAX_VALUE as BigInt", 104 | fn: function () { 105 | const ref = DENO_CORE 106 | .serialize(BigInt(Number.MAX_VALUE)) 107 | .subarray(2); 108 | 109 | const res = serializeJsBigInt(BigInt(Number.MAX_VALUE)); 110 | 111 | assertEquals(res, ref); 112 | }, 113 | }); 114 | 115 | await t.step({ 116 | name: "Serialize BigInt: Number.MIN_VALUE as BigInt", 117 | fn: function () { 118 | const ref = DENO_CORE 119 | .serialize(BigInt(Number.MIN_VALUE | 0)) 120 | .subarray(2); 121 | 122 | const res = serializeJsBigInt(BigInt(Number.MIN_VALUE | 0)); 123 | 124 | assertEquals(res, ref); 125 | }, 126 | }); 127 | 128 | await t.step({ 129 | name: "Serialize BigInt: Bad BigInt", 130 | fn: function () { 131 | assertThrows(() => serializeJsBigInt(12 as unknown as bigint)); 132 | }, 133 | }); 134 | }, 135 | }); 136 | -------------------------------------------------------------------------------- /tests/boolean_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./_deps.ts"; 2 | import { deserializeV8Boolean, serializeJsBoolean } from "../src/boolean.ts"; 3 | import { DENO_CORE } from "./_core.ts"; 4 | 5 | Deno.test({ 6 | name: "Deserialize Boolean", 7 | fn: async function (t) { 8 | await t.step({ 9 | name: "Deserialize Boolean: True", 10 | fn: function () { 11 | const input = DENO_CORE 12 | .serialize(true) 13 | .subarray(2); 14 | 15 | const s = deserializeV8Boolean(input); 16 | 17 | assertEquals(s, true); 18 | assertEquals(input, new Uint8Array(input.length).fill(0)); 19 | }, 20 | }); 21 | 22 | await t.step({ 23 | name: "Deserialize Boolean: False", 24 | fn: function () { 25 | const input = DENO_CORE 26 | .serialize(false) 27 | .subarray(2); 28 | 29 | const s = deserializeV8Boolean(input); 30 | 31 | assertEquals(s, false); 32 | assertEquals(input, new Uint8Array(input.length).fill(0)); 33 | }, 34 | }); 35 | 36 | await t.step({ 37 | name: "Deserialize Boolean: Not A Boolean", 38 | fn: function () { 39 | const serializedBoolean = DENO_CORE 40 | .serialize(0) 41 | .subarray(2); 42 | 43 | assertThrows(() => deserializeV8Boolean(serializedBoolean)); 44 | }, 45 | }); 46 | }, 47 | }); 48 | 49 | Deno.test({ 50 | name: "Serialize Boolean", 51 | fn: async function (t) { 52 | await t.step({ 53 | name: "Serialize Boolean: True", 54 | fn: function () { 55 | const ref = DENO_CORE 56 | .serialize(true) 57 | .subarray(2); 58 | 59 | const res = serializeJsBoolean(true); 60 | 61 | assertEquals(res, ref); 62 | }, 63 | }); 64 | 65 | await t.step({ 66 | name: "Serialize Boolean: False", 67 | fn: function () { 68 | const ref = DENO_CORE 69 | .serialize(false) 70 | .subarray(2); 71 | 72 | const res = serializeJsBoolean(false); 73 | 74 | assertEquals(res, ref); 75 | }, 76 | }); 77 | 78 | await t.step({ 79 | name: "Serialize Boolean: Not A Boolean", 80 | fn: function () { 81 | assertThrows(() => serializeJsBoolean(0 as unknown as boolean)); 82 | }, 83 | }); 84 | }, 85 | }); 86 | -------------------------------------------------------------------------------- /tests/float_test.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Float, serializeJsFloat } from "../src/float.ts"; 2 | import { DENO_CORE } from "./_core.ts"; 3 | import { assertEquals, assertThrows } from "./_deps.ts"; 4 | 5 | Deno.test({ 6 | name: "Deserialize Float", 7 | fn: async function (t) { 8 | await t.step({ 9 | name: "Deserialize Float: Positive Float", 10 | fn: function () { 11 | const input = DENO_CORE 12 | .serialize(12.69) 13 | .subarray(2); 14 | 15 | const res = deserializeV8Float(input); 16 | 17 | assertEquals(res, 12.69); 18 | assertEquals(input, new Uint8Array(input.length).fill(0)); 19 | }, 20 | }); 21 | 22 | await t.step({ 23 | name: "Deserialize Float: Negative Float", 24 | fn: function () { 25 | const input = DENO_CORE 26 | .serialize(-12.69) 27 | .subarray(2); 28 | 29 | const res = deserializeV8Float(input); 30 | 31 | assertEquals(res, -12.69); 32 | assertEquals(input, new Uint8Array(input.length).fill(0)); 33 | }, 34 | }); 35 | 36 | await t.step({ 37 | name: "Deserialize Float: Number.MAX_SAFE_INTEGER + 0.69", 38 | fn: function () { 39 | const input = DENO_CORE 40 | .serialize(Number.MAX_SAFE_INTEGER + 0.69) 41 | .subarray(2); 42 | const res = deserializeV8Float(input); 43 | 44 | assertEquals(res, Number.MAX_SAFE_INTEGER + 0.69); 45 | assertEquals(input, new Uint8Array(input.length).fill(0)); 46 | }, 47 | }); 48 | 49 | await t.step({ 50 | name: "Deserialize Float: Number.MIN_VALUE", 51 | fn: function () { 52 | const input = DENO_CORE 53 | .serialize(Number.MIN_VALUE) 54 | .subarray(2); 55 | 56 | const res = deserializeV8Float(input); 57 | 58 | assertEquals(res, Number.MIN_VALUE); 59 | assertEquals(input, new Uint8Array(input.length).fill(0)); 60 | }, 61 | }); 62 | 63 | await t.step({ 64 | name: "Deserialize Float: Number.EPSILON", 65 | fn: function () { 66 | const input = DENO_CORE 67 | .serialize(Number.EPSILON) 68 | .subarray(2); 69 | const res = deserializeV8Float(input); 70 | 71 | assertEquals(res, Number.EPSILON); 72 | assertEquals(input, new Uint8Array(input.length).fill(0)); 73 | }, 74 | }); 75 | 76 | await t.step({ 77 | name: "Deserialize Float: Non-Float", 78 | fn: function () { 79 | const input = DENO_CORE 80 | .serialize(10) 81 | .subarray(2); 82 | assertThrows(() => deserializeV8Float(input)); 83 | }, 84 | }); 85 | }, 86 | }); 87 | 88 | Deno.test({ 89 | name: "Serialize Float", 90 | fn: async function (t) { 91 | await t.step({ 92 | name: "Serialize Float: Positive Float", 93 | fn: function () { 94 | const ref = DENO_CORE 95 | .serialize(12.69) 96 | .subarray(2); 97 | 98 | const res = serializeJsFloat(12.69); 99 | 100 | assertEquals(res, ref); 101 | }, 102 | }); 103 | 104 | await t.step({ 105 | name: "Serialize Float: Negative Float", 106 | fn: function () { 107 | const ref = DENO_CORE 108 | .serialize(-12.69) 109 | .subarray(2); 110 | 111 | const res = serializeJsFloat(-12.69); 112 | 113 | assertEquals(res, ref); 114 | }, 115 | }); 116 | 117 | await t.step({ 118 | name: "Serialize Float: Number.MAX_SAFE_INTEGER + 0.69", 119 | fn: function () { 120 | const ref = DENO_CORE 121 | .serialize(Number.MAX_SAFE_INTEGER + 0.69) 122 | .subarray(2); 123 | 124 | const res = serializeJsFloat(Number.MAX_SAFE_INTEGER + 0.69); 125 | 126 | assertEquals(res, ref); 127 | }, 128 | }); 129 | 130 | await t.step({ 131 | name: "Serialize Float: Number.MIN_VALUE", 132 | fn: function () { 133 | const ref = DENO_CORE 134 | .serialize(Number.MIN_VALUE) 135 | .subarray(2); 136 | 137 | const res = serializeJsFloat(Number.MIN_VALUE); 138 | 139 | assertEquals(res, ref); 140 | }, 141 | }); 142 | 143 | await t.step({ 144 | name: "Serialize Float: Number.EPSILON", 145 | fn: function () { 146 | const ref = DENO_CORE 147 | .serialize(Number.EPSILON) 148 | .subarray(2); 149 | 150 | const res = serializeJsFloat(Number.EPSILON); 151 | 152 | assertEquals(res, ref); 153 | }, 154 | }); 155 | 156 | await t.step({ 157 | name: "Serialize Float: Non-Float", 158 | fn: function () { 159 | assertThrows(() => serializeJsFloat(10)); 160 | }, 161 | }); 162 | }, 163 | }); 164 | -------------------------------------------------------------------------------- /tests/integer_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./_deps.ts"; 2 | import { deserializeV8Integer, serializeJsInteger } from "../src/integer.ts"; 3 | import { DENO_CORE } from "./_core.ts"; 4 | 5 | const MIN_INT_VALUE = -1_073_741_824; 6 | const MAX_INT_VALUE = 1_073_741_823; 7 | 8 | Deno.test({ 9 | name: "Deserialize Integer", 10 | fn: async function (t) { 11 | await t.step({ 12 | name: "Deserialize Integer: Positive Integer", 13 | fn: function () { 14 | const input = DENO_CORE 15 | .serialize(12) 16 | .subarray(2); 17 | 18 | const res = deserializeV8Integer(input); 19 | assertEquals(res, 12); 20 | assertEquals(input, new Uint8Array(input.length).fill(0)); 21 | }, 22 | }); 23 | 24 | await t.step({ 25 | name: "Deserialize Integer: Negative Integer", 26 | fn: function () { 27 | const input = DENO_CORE 28 | .serialize(-12) 29 | .subarray(2); 30 | 31 | const res = deserializeV8Integer(input); 32 | assertEquals(res, -12); 33 | assertEquals(input, new Uint8Array(input.length).fill(0)); 34 | }, 35 | }); 36 | 37 | await t.step({ 38 | name: "Deserialize Integer: Max Integer", 39 | fn: function () { 40 | const input = DENO_CORE 41 | .serialize(MAX_INT_VALUE) 42 | .subarray(2); 43 | 44 | const res = deserializeV8Integer(input); 45 | assertEquals(res, MAX_INT_VALUE); 46 | assertEquals(input, new Uint8Array(input.length).fill(0)); 47 | }, 48 | }); 49 | 50 | await t.step({ 51 | name: "Deserialize Integer: Min Integer", 52 | fn: function () { 53 | const input = DENO_CORE 54 | .serialize(MIN_INT_VALUE) 55 | .subarray(2); 56 | 57 | const res = deserializeV8Integer(input); 58 | assertEquals(res, MIN_INT_VALUE); 59 | assertEquals(input, new Uint8Array(input.length).fill(0)); 60 | }, 61 | }); 62 | 63 | await t.step({ 64 | name: "Deserialize Integer: Non-Integer", 65 | fn: function () { 66 | const serializedBool = DENO_CORE 67 | .serialize(true) 68 | .subarray(2); 69 | 70 | assertThrows(() => deserializeV8Integer(serializedBool)); 71 | }, 72 | }); 73 | }, 74 | }); 75 | 76 | Deno.test({ 77 | name: "Serialize Integer", 78 | fn: async function (t) { 79 | await t.step({ 80 | name: "Serialize Integer: Positive Integer", 81 | fn: function () { 82 | const ref = DENO_CORE 83 | .serialize(12) 84 | .subarray(2); 85 | 86 | const res = serializeJsInteger(12); 87 | assertEquals(res, ref); 88 | }, 89 | }); 90 | 91 | await t.step({ 92 | name: "Serialize Integer: Negative Integer", 93 | fn: function () { 94 | const ref = DENO_CORE 95 | .serialize(-12) 96 | .subarray(2); 97 | 98 | const res = serializeJsInteger(-12); 99 | assertEquals(res, ref); 100 | }, 101 | }); 102 | 103 | await t.step({ 104 | name: "Serialize Integer: Max Integer", 105 | fn: function () { 106 | const ref = DENO_CORE 107 | .serialize(MAX_INT_VALUE) 108 | .subarray(2); 109 | 110 | const res = serializeJsInteger(MAX_INT_VALUE); 111 | assertEquals(res, ref); 112 | }, 113 | }); 114 | 115 | await t.step({ 116 | name: "Serialize Integer: Min Integer", 117 | fn: function () { 118 | const ref = DENO_CORE 119 | .serialize(MIN_INT_VALUE) 120 | .subarray(2); 121 | 122 | const res = serializeJsInteger(MIN_INT_VALUE); 123 | assertEquals(res, ref); 124 | }, 125 | }); 126 | 127 | await t.step({ 128 | name: "Serialize Bad Integer", 129 | fn: function () { 130 | assertThrows(() => serializeJsInteger(MAX_INT_VALUE + 2)); 131 | }, 132 | }); 133 | 134 | await t.step({ 135 | name: "Serialize Integer: Non-Integer", 136 | fn: function () { 137 | assertThrows( 138 | () => serializeJsInteger(null as unknown as number), 139 | undefined, 140 | "Not a integer", 141 | ); 142 | }, 143 | }); 144 | }, 145 | }); 146 | -------------------------------------------------------------------------------- /tests/null_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./_deps.ts"; 2 | import { deserializeV8Null, serializeJsNull } from "../src/null.ts"; 3 | import { DENO_CORE } from "./_core.ts"; 4 | 5 | Deno.test({ 6 | name: "Deserialize Null", 7 | fn: async function (t) { 8 | await t.step({ 9 | name: "Deserialize Null: Null", 10 | fn: function () { 11 | const input = DENO_CORE 12 | .serialize(null) 13 | .subarray(2); 14 | 15 | const res = deserializeV8Null(input); 16 | assertEquals(res, null); 17 | assertEquals(input, new Uint8Array(input.length).fill(0)); 18 | }, 19 | }); 20 | 21 | await t.step({ 22 | name: "Deserialize Null: Non-Null", 23 | fn: function () { 24 | const serializedUndefined = DENO_CORE 25 | .serialize(undefined) 26 | .subarray(2); 27 | 28 | assertThrows(() => deserializeV8Null(serializedUndefined)); 29 | }, 30 | }); 31 | }, 32 | }); 33 | 34 | Deno.test({ 35 | name: "Serialize Null", 36 | fn: async function (t) { 37 | await t.step({ 38 | name: "Serialize Null: Null", 39 | fn: function () { 40 | const ref = DENO_CORE 41 | .serialize(null) 42 | .subarray(2); 43 | 44 | const res = serializeJsNull(null); 45 | 46 | assertEquals(res, ref); 47 | }, 48 | }); 49 | 50 | await t.step({ 51 | name: "Serialize Null: Non-Null", 52 | fn: function () { 53 | assertThrows(() => serializeJsNull(undefined as unknown as null)); 54 | }, 55 | }); 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /tests/object_test.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8Object, serializeJsObject } from "../src/objects.ts"; 2 | import { DENO_CORE } from "./_core.ts"; 3 | import { fullObject } from "./_util.ts"; 4 | import { assertEquals, assertThrows } from "./_deps.ts"; 5 | 6 | Deno.test({ 7 | name: "Deserialize V8 object", 8 | fn: async function (t) { 9 | await t.step({ 10 | name: "Deserialize non-object", 11 | fn: function () { 12 | const data = DENO_CORE.serialize([]).subarray(2); 13 | assertThrows(() => deserializeV8Object(data)); 14 | }, 15 | }); 16 | 17 | await t.step({ 18 | name: "Deserialize Empty Object", 19 | fn: function () { 20 | const input = DENO_CORE 21 | .serialize({}) 22 | .subarray(2); 23 | 24 | const res = deserializeV8Object(input); 25 | assertEquals(res, {}); 26 | assertEquals(input, new Uint8Array(input.length).fill(0)); 27 | }, 28 | }); 29 | 30 | await t.step({ 31 | name: "Deserialize self as ref", 32 | fn: function () { 33 | const data: Record = {}; 34 | data["self"] = data; 35 | const input = DENO_CORE 36 | .serialize(data) 37 | .subarray(2); 38 | 39 | const res = deserializeV8Object(input); 40 | assertEquals(res, data); 41 | assertEquals(input, new Uint8Array(input.length).fill(0)); 42 | }, 43 | }); 44 | 45 | await t.step({ 46 | name: "Deserialize string key", 47 | fn: function () { 48 | const data = { "key": null }; 49 | 50 | const input = DENO_CORE 51 | .serialize(data) 52 | .subarray(2); 53 | 54 | const res = deserializeV8Object(input); 55 | assertEquals(res, data); 56 | assertEquals(input, new Uint8Array(input.length).fill(0)); 57 | }, 58 | }); 59 | 60 | await t.step({ 61 | name: "Deserialize string key as int", 62 | fn: function () { 63 | const data = { "12": null }; 64 | 65 | const input = DENO_CORE 66 | .serialize(data) 67 | .subarray(2); 68 | 69 | const res = deserializeV8Object(input); 70 | assertEquals(res, data); 71 | assertEquals(input, new Uint8Array(input.length).fill(0)); 72 | }, 73 | }); 74 | 75 | await t.step({ 76 | name: "Deserialize string & int keys", 77 | fn: function () { 78 | const data = { "key": null, "12": null, 13: null }; 79 | 80 | const input = DENO_CORE 81 | .serialize(data) 82 | .subarray(2); 83 | 84 | const res = deserializeV8Object(input); 85 | assertEquals(res, data); 86 | assertEquals(input, new Uint8Array(input.length).fill(0)); 87 | }, 88 | }); 89 | 90 | await t.step({ 91 | name: "Deserialize full object", 92 | fn: function () { 93 | const data = fullObject(); 94 | 95 | const input = DENO_CORE 96 | .serialize(data) 97 | .subarray(2); 98 | 99 | const res = deserializeV8Object(input); 100 | assertEquals(res, data); 101 | assertEquals(input, new Uint8Array(input.length).fill(0)); 102 | }, 103 | }); 104 | }, 105 | }); 106 | 107 | Deno.test({ 108 | name: "Serialize JS Object", 109 | fn: async function (t) { 110 | await t.step({ 111 | name: "Serialize non-object", 112 | fn: function () { 113 | assertThrows(() => serializeJsObject([])); 114 | }, 115 | }); 116 | 117 | await t.step({ 118 | name: "Serialize Empty Object", 119 | fn: function () { 120 | const data = {}; 121 | const ref = DENO_CORE 122 | .serialize(data) 123 | .subarray(2); 124 | 125 | const res = serializeJsObject(data); 126 | 127 | assertEquals(res, ref); 128 | }, 129 | }); 130 | 131 | await t.step({ 132 | name: "Serialize self as ref", 133 | fn: function () { 134 | const data: Record = {}; 135 | data["self"] = data; 136 | 137 | const ref = DENO_CORE 138 | .serialize(data) 139 | .subarray(2); 140 | 141 | const res = serializeJsObject(data); 142 | 143 | assertEquals(res, ref); 144 | }, 145 | }); 146 | 147 | await t.step({ 148 | name: "Serialize string key", 149 | fn: function () { 150 | const data = { "key": null }; 151 | const ref = DENO_CORE 152 | .serialize(data) 153 | .subarray(2); 154 | 155 | const res = serializeJsObject(data); 156 | 157 | assertEquals(res, ref); 158 | }, 159 | }); 160 | 161 | await t.step({ 162 | name: "Serialize string key as int", 163 | fn: function () { 164 | const data = { "12": null }; 165 | const ref = DENO_CORE 166 | .serialize(data) 167 | .subarray(2); 168 | 169 | const res = serializeJsObject(data); 170 | 171 | assertEquals(res, ref); 172 | }, 173 | }); 174 | 175 | await t.step({ 176 | name: "Serialize string & int keys", 177 | fn: function () { 178 | const data = { "key": null, "12": null, 13: null }; 179 | const ref = DENO_CORE 180 | .serialize(data) 181 | .subarray(2); 182 | 183 | const res = serializeJsObject(data); 184 | 185 | assertEquals(res, ref); 186 | }, 187 | }); 188 | 189 | await t.step({ 190 | name: "Serialize full object", 191 | fn: function () { 192 | const data = fullObject(); 193 | const ref = DENO_CORE 194 | .serialize(data) 195 | .subarray(2); 196 | 197 | const res = serializeJsObject(data); 198 | 199 | assertEquals(res, ref); 200 | }, 201 | }); 202 | }, 203 | }); 204 | -------------------------------------------------------------------------------- /tests/reference_test.ts: -------------------------------------------------------------------------------- 1 | import { assertThrows } from "./_deps.ts"; 2 | import { deserializeReference } from "../src/object_reference.ts"; 3 | import { DENO_CORE } from "./_core.ts"; 4 | 5 | Deno.test({ 6 | name: "Deserialize reference", 7 | fn: function () { 8 | const data = DENO_CORE.serialize(true).subarray(2); 9 | assertThrows(() => deserializeReference(data, [])); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /tests/string_test.ts: -------------------------------------------------------------------------------- 1 | import { deserializeV8String, serializeJsString } from "../src/string.ts"; 2 | import { assertEquals, assertThrows } from "./_deps.ts"; 3 | import { DENO_CORE } from "./_core.ts"; 4 | 5 | Deno.test({ 6 | name: "Deserialize String", 7 | fn: async function (t) { 8 | await t.step({ 9 | name: "Deserialize String: One-Byte String", 10 | fn: function () { 11 | const input = DENO_CORE 12 | .serialize("Hello World!") 13 | .subarray(2); 14 | 15 | const res = deserializeV8String(input); 16 | assertEquals(res, "Hello World!"); 17 | assertEquals(input, new Uint8Array(input.length).fill(0)); 18 | }, 19 | }); 20 | 21 | await t.step({ 22 | name: "Deserialize String: Two-Byte String", 23 | fn: function () { 24 | const input = DENO_CORE 25 | .serialize("Hello World! 😃") 26 | .subarray(2); 27 | 28 | const res = deserializeV8String(input); 29 | assertEquals(res, "Hello World! 😃"); 30 | assertEquals(input, new Uint8Array(input.length).fill(0)); 31 | }, 32 | }); 33 | 34 | await t.step({ 35 | name: "Deserialize String: Non-String", 36 | fn: function () { 37 | const input = DENO_CORE 38 | .serialize(null) 39 | .subarray(2); 40 | 41 | assertThrows(() => deserializeV8String(input)); 42 | }, 43 | }); 44 | }, 45 | }); 46 | 47 | Deno.test({ 48 | name: "Serialize String", 49 | fn: async function (t) { 50 | await t.step({ 51 | name: "Serialize String: One-Byte String", 52 | fn: function () { 53 | const ref = DENO_CORE 54 | .serialize("Hello World!") 55 | .subarray(2); 56 | 57 | const res = serializeJsString("Hello World!"); 58 | 59 | assertEquals(res, ref); 60 | }, 61 | }); 62 | 63 | await t.step({ 64 | name: "Serialize String: Two-Byte String", 65 | fn: function () { 66 | const ref = DENO_CORE 67 | .serialize("Hello World! 😃") 68 | .subarray(2); 69 | 70 | const res = serializeJsString("Hello World! 😃"); 71 | 72 | assertEquals(res, ref); 73 | }, 74 | }); 75 | 76 | await t.step({ 77 | name: "Serialize String: Non-String", 78 | fn: function () { 79 | assertThrows(() => serializeJsString(null as unknown as string)); 80 | }, 81 | }); 82 | }, 83 | }); 84 | -------------------------------------------------------------------------------- /tests/undefined_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from "./_deps.ts"; 2 | import { 3 | deserializeV8Undefined, 4 | serializeJsUndefined, 5 | } from "../src/undefined.ts"; 6 | import { DENO_CORE } from "./_core.ts"; 7 | 8 | Deno.test({ 9 | name: "Deserialize Undefined", 10 | fn: async function (t) { 11 | await t.step({ 12 | name: "Deserialize Undefined: Undefined", 13 | fn: function () { 14 | const serializedUndefined = DENO_CORE 15 | .serialize(undefined) 16 | .subarray(2); 17 | 18 | const res = deserializeV8Undefined(serializedUndefined); 19 | assertEquals(res, undefined); 20 | }, 21 | }); 22 | 23 | await t.step({ 24 | name: "Deserialize Undefined: Not Undefined", 25 | fn: function () { 26 | const serializedUndefined = DENO_CORE 27 | .serialize(null) 28 | .subarray(2); 29 | 30 | assertThrows(() => deserializeV8Undefined(serializedUndefined)); 31 | }, 32 | }); 33 | }, 34 | }); 35 | 36 | Deno.test({ 37 | name: "Serialize Undefined", 38 | fn: async function (t) { 39 | await t.step({ 40 | name: "Serialize Undefined: Undefined", 41 | fn: function () { 42 | const ref = DENO_CORE 43 | .serialize(undefined) 44 | .subarray(2); 45 | 46 | const res = serializeJsUndefined(undefined); 47 | 48 | assertEquals(res, ref); 49 | }, 50 | }); 51 | 52 | await t.step({ 53 | name: "Serialize Undefined: Not Undefined", 54 | fn: function () { 55 | assertThrows(() => serializeJsUndefined(null as unknown as undefined)); 56 | }, 57 | }); 58 | }, 59 | }); 60 | --------------------------------------------------------------------------------