├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.MD ├── index-async.d.ts ├── index-async.js ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json └── test └── index.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": false, 5 | "browser": false 6 | }, 7 | "globals": { 8 | "console": true 9 | }, 10 | "extends": "eslint:recommended", 11 | "parserOptions": { 12 | "ecmaVersion": 2020, 13 | "sourceType": "module" 14 | }, 15 | "ignorePatterns": [], 16 | "rules": { 17 | "indent": [ 18 | "error", 19 | "tab", 20 | { 21 | "SwitchCase": 1 22 | } 23 | ], 24 | "linebreak-style": [ 25 | "error", 26 | "unix" 27 | ], 28 | "quotes": [ 29 | "error", 30 | "double" 31 | ], 32 | "semi": [ 33 | "error", 34 | "always" 35 | ], 36 | "no-console": [ 37 | "warn" 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gildas Lormeau 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 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ## YaBSON 2 | 3 | YaBSON is a library allowing schemaless binary-encoded parsing/serialization of 4 | JavaScript data with a generator-based parser/serializer. 5 | 6 | This library is designed to transfer large arbitrary amounts of data into 7 | chunks. The main goal is to provide a very simple and easily extensible API and 8 | implementation. This also illustrates pedagogically the interest of 9 | [iterators and generators](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Iterators_and_Generators) 10 | in JavaScript. Note that the binary encoding is determined by the platform 11 | endianness. 12 | 13 | ## Example 14 | 15 | ```js 16 | import { getParser, getSerializer } from "yabson"; 17 | // Deno: import { getParser, getSerializer } from "https://deno.land/x/yabson"; 18 | // Browser: import { getParser, getSerializer } from "https://unpkg.com/yabson"; 19 | 20 | // `object` is the data to serialize 21 | const object = { 22 | array: [ 23 | 1, 24 | 2, 25 | 3.1415927, 26 | true, 27 | undefined, 28 | null, 29 | NaN, 30 | 42n, 31 | "string", 32 | ], 33 | [Symbol("symbol")]: "symbol", 34 | typedArray: new Uint8Array([1, 2, 3]), 35 | misc: { 36 | date: new Date(), 37 | error: new Error("error"), 38 | regExp: /test/gi, 39 | }, 40 | map: new Map([["key", "value"], [42, { value: "result" }]]), 41 | set: new Set([1, 2, 3]), 42 | stringObject: new String("abc"), 43 | numberObject: new Number(123), 44 | booleanObject: new Boolean(true), 45 | arrayBuffer: new Uint16Array([1, 2, 3]).buffer, 46 | }; 47 | // Create empty slots in `object.array` 48 | object.array[12] = 12; 49 | // Add a circular reference 50 | object.map.set(object.array, object); 51 | // Add a property to a native object 52 | object.numberObject.myProperty = "propertyValue"; 53 | 54 | // `chunkSize` (optional) is the max. size in bytes of `chunk` in the for-of loop below 55 | const serializer = getSerializer(object, { chunkSize: 16 }); 56 | const parser = getParser(); 57 | 58 | let result; 59 | // `chunk` is a Uint8Array of binary encoded data 60 | for (const chunk of serializer) { 61 | // Parse immediately binary data 62 | result = parser.next(chunk); 63 | } 64 | // Display a deep clone of `object` 65 | console.log(result.value); 66 | ``` 67 | 68 | Test it on JSFiddle: https://jsfiddle.net/np4581x2 69 | 70 | ## Example with a custom type 71 | 72 | ```js 73 | import { 74 | getParser, 75 | getSerializer, 76 | parseString, 77 | registerType, 78 | serializeString, 79 | } from "yabson"; 80 | 81 | // Custom type class 82 | class CustomType { 83 | constructor(name) { 84 | this.name = name; 85 | } 86 | } 87 | 88 | // Register the custom type 89 | registerType(serializeCustomType, parseCustomType, testCustomType); 90 | 91 | function* serializeCustomType(data, customType) { 92 | // Delegate serialization to `serializeString` from yabson 93 | yield* serializeString(data, customType.name); 94 | } 95 | 96 | function* parseCustomType(data) { 97 | // Delegate parsing to `parseString` from yabson 98 | const name = yield* parseString(data); 99 | return new CustomType(name); 100 | } 101 | 102 | function testCustomType(value) { 103 | return value instanceof CustomType; 104 | } 105 | 106 | // Run test 107 | const array = [ 108 | new CustomType("first"), 109 | new CustomType("second"), 110 | ]; 111 | 112 | const serializer = getSerializer(array); 113 | const parser = getParser(); 114 | 115 | let result; 116 | for (const chunk of serializer) { 117 | result = parser.next(chunk); 118 | } 119 | // Display a deep clone of `array` 120 | console.log(result.value); 121 | ``` 122 | 123 | ## Install 124 | 125 | ```sh 126 | npm install https://www.npmjs.com/package/yabson 127 | ``` 128 | -------------------------------------------------------------------------------- /index-async.d.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any ban-types 2 | 3 | interface TypedArray { 4 | length: number; 5 | buffer: ArrayBuffer; 6 | } 7 | 8 | interface SerializerOptions { 9 | chunkSize?: number; 10 | } 11 | 12 | interface SerializerData { 13 | append(array: Uint8Array): Promise; 14 | flush(): Promise; 15 | addObject(value: any): void; 16 | } 17 | 18 | interface ParserData { 19 | consume(size: number): Promise; 20 | createObjectWrapper(): Object; 21 | addObjectSetter(functionArguments: Array, setterFunction: Function): void; 22 | executeSetters(): void; 23 | } 24 | 25 | export function getSerializer(value: any, options?: SerializerOptions): AsyncGenerator; 26 | export function getParser(): AsyncGenerator; 27 | export function registerType(serialize: Function, parse: Function, test: Function, type?: number): void; 28 | export function clone(object: any, options?: SerializerOptions): Promise; 29 | export function serialize(object: any, options?: SerializerOptions): Promise; 30 | export function parse(array: Uint8Array): Promise; 31 | 32 | export function serializeValue(data: SerializerData, value: any): Promise; 33 | export function serializeObject(data: SerializerData, object: object): Promise; 34 | export function serializeArray(data: SerializerData, array: Array): Promise; 35 | export function serializeString(data: SerializerData, string: string): Promise; 36 | export function serializeTypedArray(data: SerializerData, array: TypedArray): Promise; 37 | export function serializeArrayBuffer(data: SerializerData, array: ArrayBuffer): Promise; 38 | export function serializeNumber(data: SerializerData, number: number): Promise; 39 | export function serializeBigInt(data: SerializerData, number: bigint): Promise; 40 | export function serializeUint32(data: SerializerData, number: number): Promise; 41 | export function serializeInt32(data: SerializerData, number: number): Promise; 42 | export function serializeUint16(data: SerializerData, number: number): Promise; 43 | export function serializeInt16(data: SerializerData, number: number): Promise; 44 | export function serializeUint8(data: SerializerData, number: number): Promise; 45 | export function serializeInt8(data: SerializerData, number: number): Promise; 46 | export function serializeBoolean(data: SerializerData, boolean: boolean): Promise; 47 | export function serializeMap(data: SerializerData, map: Map): Promise; 48 | export function serializeSet(data: SerializerData, set: Set): Promise; 49 | export function serializeDate(data: SerializerData, date: Date): Promise; 50 | export function serializeError(data: SerializerData, error: Error): Promise; 51 | export function serializeRegExp(data: SerializerData, regExp: RegExp): Promise; 52 | export function serializeStringObject(data: SerializerData, string: String): Promise; 53 | export function serializeNumberObject(data: SerializerData, number: Number): Promise; 54 | export function serializeBooleanObject(data: SerializerData, boolean: Boolean): Promise; 55 | export function serializeSymbol(data: SerializerData, symbol: Symbol): Promise; 56 | 57 | export function parseValue(data: ParserData): Promise; 58 | export function parseObject(data: ParserData): Promise; 59 | export function parseArray(data: ParserData): Promise; 60 | export function parseString(data: ParserData): Promise; 61 | export function parseBigUint64Array(data: ParserData): Promise; 62 | export function parseBigInt64Array(data: ParserData): Promise; 63 | export function parseFloat64Array(data: ParserData): Promise; 64 | export function parseFloat32Array(data: ParserData): Promise; 65 | export function parseUint32Array(data: ParserData): Promise; 66 | export function parseInt32Array(data: ParserData): Promise; 67 | export function parseUint16Array(data: ParserData): Promise; 68 | export function parseInt16Array(data: ParserData): Promise; 69 | export function parseUint8ClampedArray(data: ParserData): Promise; 70 | export function parseUint8Array(data: ParserData): Promise; 71 | export function parseInt8Array(data: ParserData): Promise; 72 | export function parseNumber(data: ParserData): Promise; 73 | export function parseBigInt(data: ParserData): Promise; 74 | export function parseUint32(data: ParserData): Promise; 75 | export function parseInt32(data: ParserData): Promise; 76 | export function parseUint16(data: ParserData): Promise; 77 | export function parseInt16(data: ParserData): Promise; 78 | export function parseUint8(data: ParserData): Promise; 79 | export function parseInt8(data: ParserData): Promise; 80 | export function parseArrayBuffer(data: ParserData): Promise; 81 | export function parseUndefined(): Promise; 82 | export function parseNull(): Promise; 83 | export function parseNaN(): Promise; 84 | export function parseBoolean(data: ParserData): Promise; 85 | export function parseMap(data: ParserData): Promise; 86 | export function parseSet(data: ParserData): Promise; 87 | export function parseDate(data: ParserData): Promise; 88 | export function parseError(data: ParserData): Promise; 89 | export function parseRegExp(data: ParserData): Promise; 90 | export function parseStringObject(data: ParserData): Promise; 91 | export function parseNumberObject(data: ParserData): Promise; 92 | export function parseBooleanObject(data: ParserData): Promise; 93 | export function parseSymbol(data: ParserData): Promise; 94 | 95 | export function testObject(value: any): boolean; 96 | export function testArray(value: any): boolean; 97 | export function testString(value: any): boolean; 98 | export function testBigUint64Array(value: any): boolean; 99 | export function testBigInt64Array(value: any): boolean; 100 | export function testFloat64Array(value: any): boolean; 101 | export function testUint32Array(value: any): boolean; 102 | export function testInt32Array(value: any): boolean; 103 | export function testUint16Array(value: any): boolean; 104 | export function testFloat32Array(value: any): boolean; 105 | export function testInt16Array(value: any): boolean; 106 | export function testUint8ClampedArray(value: any): boolean; 107 | export function testUint8Array(value: any): boolean; 108 | export function testInt8Array(value: any): boolean; 109 | export function testNumber(value: any): boolean; 110 | export function testBigInt(value: any): boolean; 111 | export function testUint32(value: any): boolean; 112 | export function testInt32(value: any): boolean; 113 | export function testUint16(value: any): boolean; 114 | export function testInt16(value: any): boolean; 115 | export function testUint8(value: any): boolean; 116 | export function testInt8(value: any): boolean; 117 | export function testArrayBuffer(value: any): boolean; 118 | export function testInteger(value: any): boolean; 119 | export function testUndefined(value: any): boolean; 120 | export function testNull(value: any): boolean; 121 | export function testNaN(value: any): boolean; 122 | export function testBoolean(value: any): boolean; 123 | export function testMap(value: any): boolean; 124 | export function testSet(value: any): boolean; 125 | export function testDate(value: any): boolean; 126 | export function testError(value: any): boolean; 127 | export function testRegExp(value: any): boolean; 128 | export function testStringObject(value: any): boolean; 129 | export function testNumberObject(value: any): boolean; 130 | export function testBooleanObject(value: any): boolean; 131 | export function testSymbol(value: any): boolean; -------------------------------------------------------------------------------- /index-async.js: -------------------------------------------------------------------------------- 1 | /* global TextEncoder, TextDecoder, BigInt64Array, BigUint64Array */ 2 | 3 | const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; 4 | const TYPE_REFERENCE = 0; 5 | const SPECIAL_TYPES = [TYPE_REFERENCE]; 6 | const EMPTY_SLOT_VALUE = Symbol(); 7 | 8 | const textEncoder = new TextEncoder(); 9 | const textDecoder = new TextDecoder(); 10 | const types = new Array(256); 11 | let typeIndex = 0; 12 | 13 | registerType(serializeCircularReference, parseCircularReference, testCircularReference, TYPE_REFERENCE); 14 | registerType(null, parseObject, testObject); 15 | registerType(serializeArray, parseArray, testArray); 16 | registerType(serializeString, parseString, testString); 17 | registerType(serializeTypedArray, parseBigUint64Array, testBigUint64Array); 18 | registerType(serializeTypedArray, parseBigInt64Array, testBigInt64Array); 19 | registerType(serializeTypedArray, parseFloat64Array, testFloat64Array); 20 | registerType(serializeTypedArray, parseFloat32Array, testFloat32Array); 21 | registerType(serializeTypedArray, parseUint32Array, testUint32Array); 22 | registerType(serializeTypedArray, parseInt32Array, testInt32Array); 23 | registerType(serializeTypedArray, parseUint16Array, testUint16Array); 24 | registerType(serializeTypedArray, parseInt16Array, testInt16Array); 25 | registerType(serializeTypedArray, parseUint8ClampedArray, testUint8ClampedArray); 26 | registerType(serializeTypedArray, parseUint8Array, testUint8Array); 27 | registerType(serializeTypedArray, parseInt8Array, testInt8Array); 28 | registerType(serializeArrayBuffer, parseArrayBuffer, testArrayBuffer); 29 | registerType(serializeNumber, parseNumber, testNumber); 30 | registerType(serializeBigInt, parseBigInt, testBigInt); 31 | registerType(serializeUint32, parseUint32, testUint32); 32 | registerType(serializeInt32, parseInt32, testInt32); 33 | registerType(serializeUint16, parseUint16, testUint16); 34 | registerType(serializeInt16, parseInt16, testInt16); 35 | registerType(serializeUint8, parseUint8, testUint8); 36 | registerType(serializeInt8, parseInt8, testInt8); 37 | registerType(null, parseUndefined, testUndefined); 38 | registerType(null, parseNull, testNull); 39 | registerType(null, parseNaN, testNaN); 40 | registerType(serializeBoolean, parseBoolean, testBoolean); 41 | registerType(serializeSymbol, parseSymbol, testSymbol); 42 | registerType(null, parseEmptySlot, testEmptySlot); 43 | registerType(serializeMap, parseMap, testMap); 44 | registerType(serializeSet, parseSet, testSet); 45 | registerType(serializeDate, parseDate, testDate); 46 | registerType(serializeError, parseError, testError); 47 | registerType(serializeRegExp, parseRegExp, testRegExp); 48 | registerType(serializeStringObject, parseStringObject, testStringObject); 49 | registerType(serializeNumberObject, parseNumberObject, testNumberObject); 50 | registerType(serializeBooleanObject, parseBooleanObject, testBooleanObject); 51 | 52 | export { 53 | getSerializer, 54 | getParser, 55 | registerType, 56 | clone, 57 | serialize, 58 | parse, 59 | serializeValue, 60 | serializeArray, 61 | serializeString, 62 | serializeTypedArray, 63 | serializeArrayBuffer, 64 | serializeNumber, 65 | serializeBigInt, 66 | serializeUint32, 67 | serializeInt32, 68 | serializeUint16, 69 | serializeInt16, 70 | serializeUint8, 71 | serializeInt8, 72 | serializeBoolean, 73 | serializeMap, 74 | serializeSet, 75 | serializeDate, 76 | serializeError, 77 | serializeRegExp, 78 | serializeStringObject, 79 | serializeNumberObject, 80 | serializeBooleanObject, 81 | serializeSymbol, 82 | parseValue, 83 | parseObject, 84 | parseArray, 85 | parseString, 86 | parseBigUint64Array, 87 | parseBigInt64Array, 88 | parseFloat64Array, 89 | parseFloat32Array, 90 | parseUint32Array, 91 | parseInt32Array, 92 | parseUint16Array, 93 | parseInt16Array, 94 | parseUint8ClampedArray, 95 | parseUint8Array, 96 | parseInt8Array, 97 | parseArrayBuffer, 98 | parseNumber, 99 | parseBigInt, 100 | parseUint32, 101 | parseInt32, 102 | parseUint16, 103 | parseInt16, 104 | parseUint8, 105 | parseInt8, 106 | parseUndefined, 107 | parseNull, 108 | parseNaN, 109 | parseBoolean, 110 | parseMap, 111 | parseSet, 112 | parseDate, 113 | parseError, 114 | parseRegExp, 115 | parseStringObject, 116 | parseNumberObject, 117 | parseBooleanObject, 118 | parseSymbol, 119 | testObject, 120 | testArray, 121 | testString, 122 | testBigUint64Array, 123 | testBigInt64Array, 124 | testFloat64Array, 125 | testFloat32Array, 126 | testUint32Array, 127 | testInt32Array, 128 | testUint16Array, 129 | testInt16Array, 130 | testUint8ClampedArray, 131 | testUint8Array, 132 | testInt8Array, 133 | testArrayBuffer, 134 | testNumber, 135 | testBigInt, 136 | testUint32, 137 | testInt32, 138 | testUint16, 139 | testInt16, 140 | testUint8, 141 | testInt8, 142 | testInteger, 143 | testUndefined, 144 | testNull, 145 | testNaN, 146 | testBoolean, 147 | testMap, 148 | testSet, 149 | testDate, 150 | testError, 151 | testRegExp, 152 | testStringObject, 153 | testNumberObject, 154 | testBooleanObject, 155 | testSymbol 156 | }; 157 | 158 | function registerType(serialize, parse, test, type) { 159 | if (type === undefined) { 160 | typeIndex++; 161 | if (types.length - typeIndex >= SPECIAL_TYPES.length) { 162 | types[types.length - typeIndex] = { serialize, parse, test }; 163 | } else { 164 | throw new Error("Reached maximum number of custom types"); 165 | } 166 | } else { 167 | types[type] = { serialize, parse, test }; 168 | } 169 | } 170 | 171 | async function clone(object, options) { 172 | const serializer = getSerializer(object, options); 173 | const parser = getParser(); 174 | let result; 175 | for await (const chunk of serializer) { 176 | result = await parser.next(chunk); 177 | } 178 | result = await parser.next(); 179 | return result.value; 180 | } 181 | 182 | async function serialize(object, options) { 183 | const serializer = getSerializer(object, options); 184 | let result = new Uint8Array([]); 185 | for await (const chunk of serializer) { 186 | const previousResult = result; 187 | result = new Uint8Array(previousResult.length + chunk.length); 188 | result.set(previousResult, 0); 189 | result.set(chunk, previousResult.length); 190 | } 191 | return result; 192 | } 193 | 194 | async function parse(array) { 195 | const parser = getParser(); 196 | await parser.next(array); 197 | const result = await parser.next(); 198 | return result.value; 199 | } 200 | 201 | class SerializerData { 202 | constructor(appendData, chunkSize) { 203 | this.stream = new WriteStream(appendData, chunkSize); 204 | this.objects = []; 205 | } 206 | 207 | append(array) { 208 | return this.stream.append(array); 209 | } 210 | 211 | flush() { 212 | return this.stream.flush(); 213 | } 214 | 215 | addObject(value) { 216 | this.objects.push(testReferenceable(value) && !testCircularReference(value, this) ? value : undefined); 217 | } 218 | } 219 | 220 | class WriteStream { 221 | constructor(appendData, chunkSize) { 222 | this.offset = 0; 223 | this.appendData = appendData; 224 | this.value = new Uint8Array(chunkSize); 225 | } 226 | 227 | async append(array) { 228 | if (this.offset + array.length > this.value.length) { 229 | const offset = this.value.length - this.offset; 230 | await this.append(array.subarray(0, offset)); 231 | await this.appendData({ value: this.value }); 232 | this.offset = 0; 233 | await this.append(array.subarray(offset)); 234 | } else { 235 | this.value.set(array, this.offset); 236 | this.offset += array.length; 237 | } 238 | } 239 | 240 | async flush() { 241 | if (this.offset) { 242 | await this.appendData({ value: this.value.subarray(0, this.offset), done: true }); 243 | } 244 | } 245 | } 246 | 247 | function getSerializer(value, { chunkSize = DEFAULT_CHUNK_SIZE } = {}) { 248 | let serializerData, result, setResult, iterationDone, previousResult, resolvePreviousResult; 249 | return { 250 | [Symbol.asyncIterator]() { 251 | return { 252 | next() { 253 | return iterationDone ? { done: iterationDone } : getResult(); 254 | }, 255 | return() { 256 | return { done: true }; 257 | } 258 | }; 259 | } 260 | }; 261 | 262 | async function getResult() { 263 | if (resolvePreviousResult) { 264 | resolvePreviousResult(); 265 | } else { 266 | initSerializerData().catch(() => { /* ignored */ }); 267 | } 268 | initPreviousData(); 269 | const value = await getValue(); 270 | return { value }; 271 | } 272 | 273 | async function initSerializerData() { 274 | initResult(); 275 | serializerData = new SerializerData(appendData, chunkSize); 276 | await serializeValue(serializerData, value); 277 | await serializerData.flush(); 278 | } 279 | 280 | function initResult() { 281 | result = new Promise(resolve => setResult = resolve); 282 | } 283 | 284 | function initPreviousData() { 285 | previousResult = new Promise(resolve => resolvePreviousResult = resolve); 286 | } 287 | 288 | async function appendData(result) { 289 | setResult(result); 290 | await previousResult; 291 | } 292 | 293 | async function getValue() { 294 | const { value, done } = await result; 295 | iterationDone = done; 296 | if (!done) { 297 | initResult(); 298 | } 299 | return value; 300 | } 301 | } 302 | 303 | async function serializeValue(data, value) { 304 | const type = types.findIndex(({ test } = {}) => test && test(value, data)); 305 | data.addObject(value); 306 | await data.append(new Uint8Array([type])); 307 | const serialize = types[type].serialize; 308 | if (serialize) { 309 | await serialize(data, value); 310 | } 311 | if (type != TYPE_REFERENCE && testObject(value)) { 312 | await serializeSymbols(data, value); 313 | await serializeOwnProperties(data, value); 314 | } 315 | } 316 | 317 | async function serializeSymbols(data, value) { 318 | const ownPropertySymbols = Object.getOwnPropertySymbols(value); 319 | const symbols = ownPropertySymbols.map(propertySymbol => [propertySymbol, value[propertySymbol]]); 320 | await serializeArray(data, symbols); 321 | } 322 | 323 | async function serializeOwnProperties(data, value) { 324 | if (ArrayBuffer.isView(value)) { 325 | await serializeValue(data, 0); 326 | } else { 327 | let entries = Object.entries(value); 328 | if (testArray(value)) { 329 | entries = entries.filter(([key]) => !testInteger(Number(key))); 330 | } 331 | await serializeValue(data, entries.length); 332 | for (const [key, value] of entries) { 333 | await serializeString(data, key); 334 | await serializeValue(data, value); 335 | } 336 | } 337 | } 338 | 339 | async function serializeCircularReference(data, value) { 340 | const index = data.objects.indexOf(value); 341 | await serializeValue(data, index); 342 | } 343 | 344 | async function serializeArray(data, array) { 345 | await serializeValue(data, array.length); 346 | const notEmptyIndexes = Object.keys(array).filter(key => testInteger(Number(key))).map(key => Number(key)); 347 | let indexNotEmptyIndexes = 0, currentNotEmptyIndex = notEmptyIndexes[indexNotEmptyIndexes]; 348 | for (const [indexArray, value] of array.entries()) { 349 | if (currentNotEmptyIndex == indexArray) { 350 | currentNotEmptyIndex = notEmptyIndexes[++indexNotEmptyIndexes]; 351 | await serializeValue(data, value); 352 | } else { 353 | await serializeValue(data, EMPTY_SLOT_VALUE); 354 | } 355 | } 356 | } 357 | 358 | async function serializeString(data, string) { 359 | const encodedString = textEncoder.encode(string); 360 | await serializeValue(data, encodedString.length); 361 | await data.append(encodedString); 362 | } 363 | 364 | async function serializeTypedArray(data, array) { 365 | await serializeValue(data, array.length); 366 | await data.append(new Uint8Array(array.buffer)); 367 | } 368 | 369 | async function serializeArrayBuffer(data, arrayBuffer) { 370 | await serializeValue(data, arrayBuffer.byteLength); 371 | await data.append(new Uint8Array(arrayBuffer)); 372 | } 373 | 374 | async function serializeNumber(data, number) { 375 | const serializedNumber = new Uint8Array(new Float64Array([number]).buffer); 376 | await data.append(serializedNumber); 377 | } 378 | 379 | async function serializeBigInt(data, number) { 380 | const serializedNumber = new Uint8Array(new BigInt64Array([number]).buffer); 381 | await data.append(serializedNumber); 382 | } 383 | 384 | async function serializeUint32(data, number) { 385 | const serializedNumber = new Uint8Array(new Uint32Array([number]).buffer); 386 | await data.append(serializedNumber); 387 | } 388 | 389 | async function serializeInt32(data, number) { 390 | const serializedNumber = new Uint8Array(new Int32Array([number]).buffer); 391 | await data.append(serializedNumber); 392 | } 393 | 394 | async function serializeUint16(data, number) { 395 | const serializedNumber = new Uint8Array(new Uint16Array([number]).buffer); 396 | await data.append(serializedNumber); 397 | } 398 | 399 | async function serializeInt16(data, number) { 400 | const serializedNumber = new Uint8Array(new Int16Array([number]).buffer); 401 | await data.append(serializedNumber); 402 | } 403 | 404 | async function serializeUint8(data, number) { 405 | const serializedNumber = new Uint8Array([number]); 406 | await data.append(serializedNumber); 407 | } 408 | 409 | async function serializeInt8(data, number) { 410 | const serializedNumber = new Uint8Array(new Int8Array([number]).buffer); 411 | await data.append(serializedNumber); 412 | } 413 | 414 | async function serializeBoolean(data, boolean) { 415 | const serializedBoolean = new Uint8Array([Number(boolean)]); 416 | await data.append(serializedBoolean); 417 | } 418 | 419 | async function serializeMap(data, map) { 420 | const entries = map.entries(); 421 | await serializeValue(data, map.size); 422 | for (const [key, value] of entries) { 423 | await serializeValue(data, key); 424 | await serializeValue(data, value); 425 | } 426 | } 427 | 428 | async function serializeSet(data, set) { 429 | await serializeValue(data, set.size); 430 | for (const value of set) { 431 | await serializeValue(data, value); 432 | } 433 | } 434 | 435 | async function serializeDate(data, date) { 436 | await serializeNumber(data, date.getTime()); 437 | } 438 | 439 | async function serializeError(data, error) { 440 | await serializeString(data, error.message); 441 | await serializeString(data, error.stack); 442 | } 443 | 444 | async function serializeRegExp(data, regExp) { 445 | await serializeString(data, regExp.source); 446 | await serializeString(data, regExp.flags); 447 | } 448 | 449 | async function serializeStringObject(data, string) { 450 | await serializeString(data, string.valueOf()); 451 | } 452 | 453 | async function serializeNumberObject(data, number) { 454 | await serializeNumber(data, number.valueOf()); 455 | } 456 | 457 | async function serializeBooleanObject(data, boolean) { 458 | await serializeBoolean(data, boolean.valueOf()); 459 | } 460 | 461 | async function serializeSymbol(data, symbol) { 462 | await serializeString(data, symbol.description); 463 | } 464 | 465 | class Reference { 466 | constructor(index, data) { 467 | this.index = index; 468 | this.data = data; 469 | } 470 | 471 | getObject() { 472 | return this.data.objects[this.index]; 473 | } 474 | } 475 | 476 | class ParserData { 477 | constructor(consumeData) { 478 | this.stream = new ReadStream(consumeData); 479 | this.objects = []; 480 | this.setters = []; 481 | } 482 | 483 | consume(size) { 484 | return this.stream.consume(size); 485 | } 486 | 487 | getObjectId() { 488 | const objectIndex = this.objects.length; 489 | this.objects.push(undefined); 490 | return objectIndex; 491 | } 492 | 493 | resolveObject(objectId, value) { 494 | if (testReferenceable(value) && !testReference(value)) { 495 | this.objects[objectId] = value; 496 | } 497 | } 498 | 499 | setObject(functionArguments, setterFunction) { 500 | this.setters.push({ functionArguments, setterFunction }); 501 | } 502 | 503 | executeSetters() { 504 | this.setters.forEach(({ functionArguments, setterFunction }) => { 505 | const resolvedArguments = functionArguments.map(argument => testReference(argument) ? argument.getObject() : argument); 506 | setterFunction(...resolvedArguments); 507 | }); 508 | } 509 | } 510 | 511 | class ReadStream { 512 | constructor(consumeData) { 513 | this.offset = 0; 514 | this.value = new Uint8Array(0); 515 | this.consumeData = consumeData; 516 | } 517 | 518 | async consume(size) { 519 | if (this.offset + size > this.value.length) { 520 | const pending = this.value.subarray(this.offset, this.value.length); 521 | const value = await this.consumeData(); 522 | if (pending.length + value.length != this.value.length) { 523 | this.value = new Uint8Array(pending.length + value.length); 524 | } 525 | this.value.set(pending); 526 | this.value.set(value, pending.length); 527 | this.offset = 0; 528 | return this.consume(size); 529 | } else { 530 | const result = this.value.slice(this.offset, this.offset + size); 531 | this.offset += result.length; 532 | return result; 533 | } 534 | } 535 | } 536 | 537 | function getParser() { 538 | let parserData, input, setInput, value, previousData, resolvePreviousData; 539 | return { 540 | async next(input) { 541 | return input ? getResult(input) : { value: await value, done: true }; 542 | }, 543 | return() { 544 | return { done: true }; 545 | } 546 | }; 547 | 548 | async function getResult(input) { 549 | if (previousData) { 550 | await previousData; 551 | } else { 552 | initParserData().catch(() => { /* ignored */ }); 553 | } 554 | initPreviousData(); 555 | setInput(input); 556 | return { done: false }; 557 | } 558 | 559 | async function initParserData() { 560 | let setValue; 561 | value = new Promise(resolve => setValue = resolve); 562 | parserData = new ParserData(consumeData); 563 | initChunk(); 564 | const data = await parseValue(parserData); 565 | parserData.executeSetters(); 566 | setValue(data); 567 | } 568 | 569 | function initChunk() { 570 | input = new Promise(resolve => setInput = resolve); 571 | } 572 | 573 | function initPreviousData() { 574 | previousData = new Promise(resolve => resolvePreviousData = resolve); 575 | } 576 | 577 | async function consumeData() { 578 | const data = await input; 579 | initChunk(); 580 | if (resolvePreviousData) { 581 | resolvePreviousData(); 582 | } 583 | return data; 584 | } 585 | } 586 | 587 | async function parseValue(data) { 588 | const array = await data.consume(1); 589 | const parserType = array[0]; 590 | const parse = types[parserType].parse; 591 | const valueId = data.getObjectId(); 592 | const result = await parse(data); 593 | if (parserType != TYPE_REFERENCE && testObject(result)) { 594 | await parseSymbols(data, result); 595 | await parseOwnProperties(data, result); 596 | } 597 | data.resolveObject(valueId, result); 598 | return result; 599 | } 600 | 601 | async function parseSymbols(data, value) { 602 | const symbols = await parseArray(data); 603 | data.setObject([symbols], symbols => symbols.forEach(([symbol, propertyValue]) => value[symbol] = propertyValue)); 604 | } 605 | 606 | async function parseOwnProperties(data, object) { 607 | const size = await parseValue(data); 608 | if (size) { 609 | await parseNextProperty(); 610 | } 611 | 612 | async function parseNextProperty(indexKey = 0) { 613 | const key = await parseString(data); 614 | const value = await parseValue(data); 615 | data.setObject([value], value => object[key] = value); 616 | if (indexKey < size - 1) { 617 | await parseNextProperty(indexKey + 1); 618 | } 619 | } 620 | } 621 | 622 | async function parseCircularReference(data) { 623 | const index = await parseValue(data); 624 | const result = new Reference(index, data); 625 | return result; 626 | } 627 | 628 | function parseObject() { 629 | return {}; 630 | } 631 | 632 | async function parseArray(data) { 633 | const length = await parseValue(data); 634 | const array = new Array(length); 635 | if (length) { 636 | await parseNextSlot(); 637 | } 638 | return array; 639 | 640 | async function parseNextSlot(indexArray = 0) { 641 | const value = await parseValue(data); 642 | if (!testEmptySlot(value)) { 643 | data.setObject([value], value => array[indexArray] = value); 644 | } 645 | if (indexArray < length - 1) { 646 | await parseNextSlot(indexArray + 1); 647 | } 648 | } 649 | } 650 | 651 | function parseEmptySlot() { 652 | return EMPTY_SLOT_VALUE; 653 | } 654 | 655 | async function parseString(data) { 656 | const size = await parseValue(data); 657 | const array = await data.consume(size); 658 | return textDecoder.decode(array); 659 | } 660 | 661 | async function parseBigUint64Array(data) { 662 | const length = await parseValue(data); 663 | const array = await data.consume(length * 8); 664 | return new BigUint64Array(array.buffer); 665 | } 666 | 667 | async function parseFloat64Array(data) { 668 | const length = await parseValue(data); 669 | const array = await data.consume(length * 8); 670 | return new Float64Array(array.buffer); 671 | } 672 | 673 | async function parseBigInt64Array(data) { 674 | const length = await parseValue(data); 675 | const array = await data.consume(length * 8); 676 | return new BigInt64Array(array.buffer); 677 | } 678 | 679 | async function parseFloat32Array(data) { 680 | const length = await parseValue(data); 681 | const array = await data.consume(length * 4); 682 | return new Float32Array(array.buffer); 683 | } 684 | 685 | async function parseUint32Array(data) { 686 | const length = await parseValue(data); 687 | const array = await data.consume(length * 4); 688 | return new Uint32Array(array.buffer); 689 | } 690 | 691 | async function parseInt32Array(data) { 692 | const length = await parseValue(data); 693 | const array = await data.consume(length * 4); 694 | return new Int32Array(array.buffer); 695 | } 696 | 697 | async function parseUint16Array(data) { 698 | const length = await parseValue(data); 699 | const array = await data.consume(length * 2); 700 | return new Uint16Array(array.buffer); 701 | } 702 | 703 | async function parseInt16Array(data) { 704 | const length = await parseValue(data); 705 | const array = await data.consume(length * 2); 706 | return new Int16Array(array.buffer); 707 | } 708 | 709 | async function parseUint8ClampedArray(data) { 710 | const length = await parseValue(data); 711 | const array = await data.consume(length); 712 | return new Uint8ClampedArray(array.buffer); 713 | } 714 | 715 | async function parseUint8Array(data) { 716 | const length = await parseValue(data); 717 | const array = await data.consume(length); 718 | return array; 719 | } 720 | 721 | async function parseInt8Array(data) { 722 | const length = await parseValue(data); 723 | const array = await data.consume(length); 724 | return new Int8Array(array.buffer); 725 | } 726 | 727 | async function parseArrayBuffer(data) { 728 | const length = await parseValue(data); 729 | const array = await data.consume(length); 730 | return array.buffer; 731 | } 732 | 733 | async function parseNumber(data) { 734 | const array = await data.consume(8); 735 | return new Float64Array(array.buffer)[0]; 736 | } 737 | 738 | async function parseBigInt(data) { 739 | const array = await data.consume(8); 740 | return new BigInt64Array(array.buffer)[0]; 741 | } 742 | 743 | async function parseUint32(data) { 744 | const array = await data.consume(4); 745 | return new Uint32Array(array.buffer)[0]; 746 | } 747 | 748 | async function parseInt32(data) { 749 | const array = await data.consume(4); 750 | return new Int32Array(array.buffer)[0]; 751 | } 752 | 753 | async function parseUint16(data) { 754 | const array = await data.consume(2); 755 | return new Uint16Array(array.buffer)[0]; 756 | } 757 | 758 | async function parseInt16(data) { 759 | const array = await data.consume(2); 760 | return new Int16Array(array.buffer)[0]; 761 | } 762 | 763 | async function parseUint8(data) { 764 | const array = await data.consume(1); 765 | return new Uint8Array(array.buffer)[0]; 766 | } 767 | 768 | async function parseInt8(data) { 769 | const array = await data.consume(1); 770 | return new Int8Array(array.buffer)[0]; 771 | } 772 | 773 | function parseUndefined() { 774 | return undefined; 775 | } 776 | 777 | function parseNull() { 778 | return null; 779 | } 780 | 781 | function parseNaN() { 782 | return NaN; 783 | } 784 | 785 | async function parseBoolean(data) { 786 | const array = await data.consume(1); 787 | return Boolean(array[0]); 788 | } 789 | 790 | async function parseMap(data) { 791 | const size = await parseValue(data); 792 | const map = new Map(); 793 | if (size) { 794 | await parseNextEntry(); 795 | } 796 | return map; 797 | 798 | async function parseNextEntry(indexKey = 0) { 799 | const key = await parseValue(data); 800 | const value = await parseValue(data); 801 | data.setObject([key, value], (key, value) => map.set(key, value)); 802 | if (indexKey < size - 1) { 803 | await parseNextEntry(indexKey + 1); 804 | } 805 | } 806 | } 807 | 808 | async function parseSet(data) { 809 | const size = await parseValue(data); 810 | const set = new Set(); 811 | if (size) { 812 | await parseNextEntry(); 813 | } 814 | return set; 815 | 816 | async function parseNextEntry(indexKey = 0) { 817 | const value = await parseValue(data); 818 | data.setObject([value], value => set.add(value)); 819 | if (indexKey < size - 1) { 820 | await parseNextEntry(indexKey + 1); 821 | } 822 | } 823 | } 824 | 825 | async function parseDate(data) { 826 | const milliseconds = await parseNumber(data); 827 | return new Date(milliseconds); 828 | } 829 | 830 | async function parseError(data) { 831 | const message = await parseString(data); 832 | const stack = await parseString(data); 833 | const error = new Error(message); 834 | error.stack = stack; 835 | return error; 836 | } 837 | 838 | async function parseRegExp(data) { 839 | const source = await parseString(data); 840 | const flags = await parseString(data); 841 | return new RegExp(source, flags); 842 | } 843 | 844 | async function parseStringObject(data) { 845 | return new String(await parseString(data)); 846 | } 847 | 848 | async function parseNumberObject(data) { 849 | return new Number(await parseNumber(data)); 850 | } 851 | 852 | async function parseBooleanObject(data) { 853 | return new Boolean(await parseBoolean(data)); 854 | } 855 | 856 | async function parseSymbol(data) { 857 | const description = await parseString(data); 858 | return Symbol(description); 859 | } 860 | 861 | function testCircularReference(value, data) { 862 | return testObject(value) && data.objects.includes(value); 863 | } 864 | 865 | function testReference(value) { 866 | return value instanceof Reference; 867 | } 868 | 869 | function testObject(value) { 870 | return value === Object(value); 871 | } 872 | 873 | function testArray(value) { 874 | return typeof value.length == "number"; 875 | } 876 | 877 | function testEmptySlot(value) { 878 | return value === EMPTY_SLOT_VALUE; 879 | } 880 | 881 | function testString(value) { 882 | return typeof value == "string"; 883 | } 884 | 885 | function testBigUint64Array(value) { 886 | return value instanceof BigUint64Array; 887 | } 888 | 889 | function testBigInt64Array(value) { 890 | return value instanceof BigInt64Array; 891 | } 892 | 893 | function testFloat64Array(value) { 894 | return value instanceof Float64Array; 895 | } 896 | 897 | function testUint32Array(value) { 898 | return value instanceof Uint32Array; 899 | } 900 | 901 | function testInt32Array(value) { 902 | return value instanceof Int32Array; 903 | } 904 | 905 | function testUint16Array(value) { 906 | return value instanceof Uint16Array; 907 | } 908 | 909 | function testFloat32Array(value) { 910 | return value instanceof Float32Array; 911 | } 912 | 913 | function testInt16Array(value) { 914 | return value instanceof Int16Array; 915 | } 916 | 917 | function testUint8ClampedArray(value) { 918 | return value instanceof Uint8ClampedArray; 919 | } 920 | 921 | function testUint8Array(value) { 922 | return value instanceof Uint8Array; 923 | } 924 | 925 | function testInt8Array(value) { 926 | return value instanceof Int8Array; 927 | } 928 | 929 | function testArrayBuffer(value) { 930 | return value instanceof ArrayBuffer; 931 | } 932 | 933 | function testNumber(value) { 934 | return typeof value == "number"; 935 | } 936 | 937 | function testBigInt(value) { 938 | return typeof value == "bigint"; 939 | } 940 | 941 | function testUint32(value) { 942 | return testInteger(value) && value >= 0 && value <= 4294967295; 943 | } 944 | 945 | function testInt32(value) { 946 | return testInteger(value) && value >= -2147483648 && value <= 2147483647; 947 | } 948 | 949 | function testUint16(value) { 950 | return testInteger(value) && value >= 0 && value <= 65535; 951 | } 952 | 953 | function testInt16(value) { 954 | return testInteger(value) && value >= -32768 && value <= 32767; 955 | } 956 | 957 | function testUint8(value) { 958 | return testInteger(value) && value >= 0 && value <= 255; 959 | } 960 | 961 | function testInt8(value) { 962 | return testInteger(value) && value >= -128 && value <= 127; 963 | } 964 | 965 | function testInteger(value) { 966 | return testNumber(value) && Number.isInteger(value); 967 | } 968 | 969 | function testUndefined(value) { 970 | return value === undefined; 971 | } 972 | 973 | function testNull(value) { 974 | return value === null; 975 | } 976 | 977 | function testNaN(value) { 978 | return Number.isNaN(value); 979 | } 980 | 981 | function testBoolean(value) { 982 | return typeof value == "boolean"; 983 | } 984 | 985 | function testMap(value) { 986 | return value instanceof Map; 987 | } 988 | 989 | function testSet(value) { 990 | return value instanceof Set; 991 | } 992 | 993 | function testDate(value) { 994 | return value instanceof Date; 995 | } 996 | 997 | function testError(value) { 998 | return value instanceof Error; 999 | } 1000 | 1001 | function testRegExp(value) { 1002 | return value instanceof RegExp; 1003 | } 1004 | 1005 | function testStringObject(value) { 1006 | return value instanceof String; 1007 | } 1008 | 1009 | function testNumberObject(value) { 1010 | return value instanceof Number; 1011 | } 1012 | 1013 | function testBooleanObject(value) { 1014 | return value instanceof Boolean; 1015 | } 1016 | 1017 | function testSymbol(value) { 1018 | return typeof value == "symbol"; 1019 | } 1020 | 1021 | function testReferenceable(value) { 1022 | return testObject(value) || testSymbol(value); 1023 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file ban-types no-explicit-any 2 | 3 | interface TypedArray { 4 | length: number; 5 | buffer: ArrayBuffer; 6 | } 7 | 8 | interface SerializerOptions { 9 | chunkSize?: number; 10 | } 11 | 12 | interface SerializerData { 13 | append(array: Uint8Array): Generator; 14 | flush(): Generator; 15 | addObject(value: any): void; 16 | } 17 | 18 | interface ParserData { 19 | consume(size: number): Generator; 20 | createObjectWrapper(): Object; 21 | addObjectSetter(functionArguments: Array, setterFunction: Function): void; 22 | executeSetters(): void; 23 | } 24 | 25 | export function getSerializer(value: any, options?: SerializerOptions): Generator; 26 | export function getParser(): Generator; 27 | export function registerType(serialize: Function, parse: Function, test: Function, type?: number): void; 28 | export function clone(object: any, options?: SerializerOptions): Object; 29 | export function serialize(object: any, options?: SerializerOptions): Uint8Array; 30 | export function parse(array: Uint8Array): any; 31 | 32 | export function serializeValue(data: SerializerData, value: any): Generator; 33 | export function serializeObject(data: SerializerData, object: object): Generator; 34 | export function serializeArray(data: SerializerData, array: Array): Generator; 35 | export function serializeString(data: SerializerData, string: string): Generator; 36 | export function serializeTypedArray(data: SerializerData, array: TypedArray): Generator; 37 | export function serializeArrayBuffer(data: SerializerData, array: ArrayBuffer): Generator; 38 | export function serializeNumber(data: SerializerData, number: number): Generator; 39 | export function serializeBigInt(data: SerializerData, number: bigint): Generator; 40 | export function serializeUint32(data: SerializerData, number: number): Generator; 41 | export function serializeInt32(data: SerializerData, number: number): Generator; 42 | export function serializeUint16(data: SerializerData, number: number): Generator; 43 | export function serializeInt16(data: SerializerData, number: number): Generator; 44 | export function serializeUint8(data: SerializerData, number: number): Generator; 45 | export function serializeInt8(data: SerializerData, number: number): Generator; 46 | export function serializeBoolean(data: SerializerData, boolean: boolean): Generator; 47 | export function serializeMap(data: SerializerData, map: Map): Generator; 48 | export function serializeSet(data: SerializerData, set: Set): Generator; 49 | export function serializeDate(data: SerializerData, date: Date): Generator; 50 | export function serializeError(data: SerializerData, error: Error): Generator; 51 | export function serializeRegExp(data: SerializerData, regExp: RegExp): Generator; 52 | export function serializeStringObject(data: SerializerData, string: String): Generator; 53 | export function serializeNumberObject(data: SerializerData, number: Number): Generator; 54 | export function serializeBooleanObject(data: SerializerData, boolean: Boolean): Generator; 55 | export function serializeSymbol(data: SerializerData, symbol: Symbol): Generator; 56 | 57 | export function parseValue(data: ParserData): Generator; 58 | export function parseObject(data: ParserData): Generator; 59 | export function parseArray(data: ParserData): Generator, Uint8Array>; 60 | export function parseString(data: ParserData): Generator; 61 | export function parseBigUint64Array(data: ParserData): Generator; 62 | export function parseBigInt64Array(data: ParserData): Generator; 63 | export function parseFloat64Array(data: ParserData): Generator; 64 | export function parseFloat32Array(data: ParserData): Generator; 65 | export function parseUint32Array(data: ParserData): Generator; 66 | export function parseInt32Array(data: ParserData): Generator; 67 | export function parseUint16Array(data: ParserData): Generator; 68 | export function parseInt16Array(data: ParserData): Generator; 69 | export function parseUint8ClampedArray(data: ParserData): Generator; 70 | export function parseUint8Array(data: ParserData): Generator; 71 | export function parseInt8Array(data: ParserData): Generator; 72 | export function parseNumber(data: ParserData): Generator; 73 | export function parseBigInt(data: ParserData): Generator; 74 | export function parseUint32(data: ParserData): Generator; 75 | export function parseInt32(data: ParserData): Generator; 76 | export function parseUint16(data: ParserData): Generator; 77 | export function parseInt16(data: ParserData): Generator; 78 | export function parseUint8(data: ParserData): Generator; 79 | export function parseInt8(data: ParserData): Generator; 80 | export function parseArrayBuffer(data: ParserData): Generator; 81 | export function parseUndefined(): Generator; 82 | export function parseNull(): Generator; 83 | export function parseNaN(): Generator; 84 | export function parseBoolean(data: ParserData): Generator; 85 | export function parseMap(data: ParserData): Generator, Uint8Array>; 86 | export function parseSet(data: ParserData): Generator, Uint8Array>; 87 | export function parseDate(data: ParserData): Generator; 88 | export function parseError(data: ParserData): Generator; 89 | export function parseRegExp(data: ParserData): Generator; 90 | export function parseStringObject(data: ParserData): Generator; 91 | export function parseNumberObject(data: ParserData): Generator; 92 | export function parseBooleanObject(data: ParserData): Generator; 93 | export function parseSymbol(data: ParserData): Generator; 94 | 95 | export function testObject(value: any): boolean; 96 | export function testArray(value: any): boolean; 97 | export function testString(value: any): boolean; 98 | export function testBigUint64Array(value: any): boolean; 99 | export function testBigInt64Array(value: any): boolean; 100 | export function testFloat64Array(value: any): boolean; 101 | export function testUint32Array(value: any): boolean; 102 | export function testInt32Array(value: any): boolean; 103 | export function testUint16Array(value: any): boolean; 104 | export function testFloat32Array(value: any): boolean; 105 | export function testInt16Array(value: any): boolean; 106 | export function testUint8ClampedArray(value: any): boolean; 107 | export function testUint8Array(value: any): boolean; 108 | export function testInt8Array(value: any): boolean; 109 | export function testNumber(value: any): boolean; 110 | export function testBigInt(value: any): boolean; 111 | export function testUint32(value: any): boolean; 112 | export function testInt32(value: any): boolean; 113 | export function testUint16(value: any): boolean; 114 | export function testInt16(value: any): boolean; 115 | export function testUint8(value: any): boolean; 116 | export function testInt8(value: any): boolean; 117 | export function testArrayBuffer(value: any): boolean; 118 | export function testInteger(value: any): boolean; 119 | export function testUndefined(value: any): boolean; 120 | export function testNull(value: any): boolean; 121 | export function testNaN(value: any): boolean; 122 | export function testBoolean(value: any): boolean; 123 | export function testMap(value: any): boolean; 124 | export function testSet(value: any): boolean; 125 | export function testDate(value: any): boolean; 126 | export function testError(value: any): boolean; 127 | export function testRegExp(value: any): boolean; 128 | export function testStringObject(value: any): boolean; 129 | export function testNumberObject(value: any): boolean; 130 | export function testBooleanObject(value: any): boolean; 131 | export function testSymbol(value: any): boolean; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global TextEncoder, TextDecoder, BigInt64Array, BigUint64Array */ 2 | 3 | const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; 4 | const TYPE_REFERENCE = 0; 5 | const SPECIAL_TYPES = [TYPE_REFERENCE]; 6 | const EMPTY_SLOT_VALUE = Symbol(); 7 | 8 | const textEncoder = new TextEncoder(); 9 | const textDecoder = new TextDecoder(); 10 | const types = new Array(256); 11 | let typeIndex = 0; 12 | 13 | registerType(serializeCircularReference, parseCircularReference, testCircularReference, TYPE_REFERENCE); 14 | registerType(null, parseObject, testObject); 15 | registerType(serializeArray, parseArray, testArray); 16 | registerType(serializeString, parseString, testString); 17 | registerType(serializeTypedArray, parseBigUint64Array, testBigUint64Array); 18 | registerType(serializeTypedArray, parseBigInt64Array, testBigInt64Array); 19 | registerType(serializeTypedArray, parseFloat64Array, testFloat64Array); 20 | registerType(serializeTypedArray, parseFloat32Array, testFloat32Array); 21 | registerType(serializeTypedArray, parseUint32Array, testUint32Array); 22 | registerType(serializeTypedArray, parseInt32Array, testInt32Array); 23 | registerType(serializeTypedArray, parseUint16Array, testUint16Array); 24 | registerType(serializeTypedArray, parseInt16Array, testInt16Array); 25 | registerType(serializeTypedArray, parseUint8ClampedArray, testUint8ClampedArray); 26 | registerType(serializeTypedArray, parseUint8Array, testUint8Array); 27 | registerType(serializeTypedArray, parseInt8Array, testInt8Array); 28 | registerType(serializeArrayBuffer, parseArrayBuffer, testArrayBuffer); 29 | registerType(serializeNumber, parseNumber, testNumber); 30 | registerType(serializeBigInt, parseBigInt, testBigInt); 31 | registerType(serializeUint32, parseUint32, testUint32); 32 | registerType(serializeInt32, parseInt32, testInt32); 33 | registerType(serializeUint16, parseUint16, testUint16); 34 | registerType(serializeInt16, parseInt16, testInt16); 35 | registerType(serializeUint8, parseUint8, testUint8); 36 | registerType(serializeInt8, parseInt8, testInt8); 37 | registerType(null, parseUndefined, testUndefined); 38 | registerType(null, parseNull, testNull); 39 | registerType(null, parseNaN, testNaN); 40 | registerType(serializeBoolean, parseBoolean, testBoolean); 41 | registerType(serializeSymbol, parseSymbol, testSymbol); 42 | registerType(null, parseEmptySlot, testEmptySlot); 43 | registerType(serializeMap, parseMap, testMap); 44 | registerType(serializeSet, parseSet, testSet); 45 | registerType(serializeDate, parseDate, testDate); 46 | registerType(serializeError, parseError, testError); 47 | registerType(serializeRegExp, parseRegExp, testRegExp); 48 | registerType(serializeStringObject, parseStringObject, testStringObject); 49 | registerType(serializeNumberObject, parseNumberObject, testNumberObject); 50 | registerType(serializeBooleanObject, parseBooleanObject, testBooleanObject); 51 | 52 | export { 53 | getSerializer, 54 | getParser, 55 | registerType, 56 | clone, 57 | serialize, 58 | parse, 59 | serializeValue, 60 | serializeArray, 61 | serializeString, 62 | serializeTypedArray, 63 | serializeArrayBuffer, 64 | serializeNumber, 65 | serializeBigInt, 66 | serializeUint32, 67 | serializeInt32, 68 | serializeUint16, 69 | serializeInt16, 70 | serializeUint8, 71 | serializeInt8, 72 | serializeBoolean, 73 | serializeMap, 74 | serializeSet, 75 | serializeDate, 76 | serializeError, 77 | serializeRegExp, 78 | serializeStringObject, 79 | serializeNumberObject, 80 | serializeBooleanObject, 81 | serializeSymbol, 82 | parseValue, 83 | parseObject, 84 | parseArray, 85 | parseString, 86 | parseBigUint64Array, 87 | parseBigInt64Array, 88 | parseFloat64Array, 89 | parseFloat32Array, 90 | parseUint32Array, 91 | parseInt32Array, 92 | parseUint16Array, 93 | parseInt16Array, 94 | parseUint8ClampedArray, 95 | parseUint8Array, 96 | parseInt8Array, 97 | parseArrayBuffer, 98 | parseNumber, 99 | parseBigInt, 100 | parseUint32, 101 | parseInt32, 102 | parseUint16, 103 | parseInt16, 104 | parseUint8, 105 | parseInt8, 106 | parseUndefined, 107 | parseNull, 108 | parseNaN, 109 | parseBoolean, 110 | parseMap, 111 | parseSet, 112 | parseDate, 113 | parseError, 114 | parseRegExp, 115 | parseStringObject, 116 | parseNumberObject, 117 | parseBooleanObject, 118 | parseSymbol, 119 | testObject, 120 | testArray, 121 | testString, 122 | testBigUint64Array, 123 | testBigInt64Array, 124 | testFloat64Array, 125 | testFloat32Array, 126 | testUint32Array, 127 | testInt32Array, 128 | testUint16Array, 129 | testInt16Array, 130 | testUint8ClampedArray, 131 | testUint8Array, 132 | testInt8Array, 133 | testArrayBuffer, 134 | testNumber, 135 | testBigInt, 136 | testUint32, 137 | testInt32, 138 | testUint16, 139 | testInt16, 140 | testUint8, 141 | testInt8, 142 | testInteger, 143 | testUndefined, 144 | testNull, 145 | testNaN, 146 | testBoolean, 147 | testMap, 148 | testSet, 149 | testDate, 150 | testError, 151 | testRegExp, 152 | testStringObject, 153 | testNumberObject, 154 | testBooleanObject, 155 | testSymbol 156 | }; 157 | 158 | function registerType(serialize, parse, test, type) { 159 | if (type === undefined) { 160 | typeIndex++; 161 | if (types.length - typeIndex >= SPECIAL_TYPES.length) { 162 | types[types.length - typeIndex] = { serialize, parse, test }; 163 | } else { 164 | throw new Error("Reached maximum number of custom types"); 165 | } 166 | } else { 167 | types[type] = { serialize, parse, test }; 168 | } 169 | } 170 | 171 | function clone(object, options) { 172 | const serializer = getSerializer(object, options); 173 | const parser = getParser(); 174 | let result; 175 | for (const chunk of serializer) { 176 | result = parser.next(chunk); 177 | } 178 | return result.value; 179 | } 180 | 181 | function serialize(object, options) { 182 | const serializer = getSerializer(object, options); 183 | let result = new Uint8Array([]); 184 | for (const chunk of serializer) { 185 | const previousResult = result; 186 | result = new Uint8Array(previousResult.length + chunk.length); 187 | result.set(previousResult, 0); 188 | result.set(chunk, previousResult.length); 189 | } 190 | return result; 191 | } 192 | 193 | function parse(array) { 194 | const parser = getParser(); 195 | const result = parser.next(array); 196 | return result.value; 197 | } 198 | 199 | class SerializerData { 200 | constructor(chunkSize) { 201 | this.stream = new WriteStream(chunkSize); 202 | this.objects = []; 203 | } 204 | 205 | append(array) { 206 | return this.stream.append(array); 207 | } 208 | 209 | flush() { 210 | return this.stream.flush(); 211 | } 212 | 213 | addObject(value) { 214 | this.objects.push(testReferenceable(value) && !testCircularReference(value, this) ? value : undefined); 215 | } 216 | } 217 | 218 | class WriteStream { 219 | constructor(chunkSize) { 220 | this.offset = 0; 221 | this.value = new Uint8Array(chunkSize); 222 | } 223 | 224 | *append(array) { 225 | if (this.offset + array.length > this.value.length) { 226 | const offset = this.value.length - this.offset; 227 | yield* this.append(array.subarray(0, offset)); 228 | yield this.value; 229 | this.offset = 0; 230 | yield* this.append(array.subarray(offset)); 231 | } else { 232 | this.value.set(array, this.offset); 233 | this.offset += array.length; 234 | } 235 | } 236 | 237 | *flush() { 238 | if (this.offset) { 239 | yield this.value.subarray(0, this.offset); 240 | } 241 | } 242 | } 243 | 244 | function* getSerializer(value, { chunkSize = DEFAULT_CHUNK_SIZE } = {}) { 245 | const data = new SerializerData(chunkSize); 246 | yield* serializeValue(data, value); 247 | yield* data.flush(); 248 | } 249 | 250 | function* serializeValue(data, value) { 251 | const type = types.findIndex(({ test } = {}) => test && test(value, data)); 252 | data.addObject(value); 253 | yield* data.append(new Uint8Array([type])); 254 | const serialize = types[type].serialize; 255 | if (serialize) { 256 | yield* serialize(data, value); 257 | } 258 | if (type != TYPE_REFERENCE && testObject(value)) { 259 | yield* serializeSymbols(data, value); 260 | yield* serializeOwnProperties(data, value); 261 | } 262 | } 263 | 264 | function* serializeSymbols(data, value) { 265 | const ownPropertySymbols = Object.getOwnPropertySymbols(value); 266 | const symbols = ownPropertySymbols.map(propertySymbol => [propertySymbol, value[propertySymbol]]); 267 | yield* serializeArray(data, symbols); 268 | } 269 | 270 | function* serializeOwnProperties(data, value) { 271 | if (ArrayBuffer.isView(value)) { 272 | yield* serializeValue(data, 0); 273 | } else { 274 | let entries = Object.entries(value); 275 | if (testArray(value)) { 276 | entries = entries.filter(([key]) => !testInteger(Number(key))); 277 | } 278 | yield* serializeValue(data, entries.length); 279 | for (const [key, value] of entries) { 280 | yield* serializeString(data, key); 281 | yield* serializeValue(data, value); 282 | } 283 | } 284 | } 285 | 286 | function* serializeCircularReference(data, value) { 287 | const index = data.objects.indexOf(value); 288 | yield* serializeValue(data, index); 289 | } 290 | 291 | function* serializeArray(data, array) { 292 | yield* serializeValue(data, array.length); 293 | const notEmptyIndexes = Object.keys(array).filter(key => testInteger(Number(key))).map(key => Number(key)); 294 | let indexNotEmptyIndexes = 0, currentNotEmptyIndex = notEmptyIndexes[indexNotEmptyIndexes]; 295 | for (const [indexArray, value] of array.entries()) { 296 | if (currentNotEmptyIndex == indexArray) { 297 | currentNotEmptyIndex = notEmptyIndexes[++indexNotEmptyIndexes]; 298 | yield* serializeValue(data, value); 299 | } else { 300 | yield* serializeValue(data, EMPTY_SLOT_VALUE); 301 | } 302 | } 303 | } 304 | 305 | function* serializeString(data, string) { 306 | const encodedString = textEncoder.encode(string); 307 | yield* serializeValue(data, encodedString.length); 308 | yield* data.append(encodedString); 309 | } 310 | 311 | function* serializeTypedArray(data, array) { 312 | yield* serializeValue(data, array.length); 313 | yield* data.append(new Uint8Array(array.buffer)); 314 | } 315 | 316 | function* serializeArrayBuffer(data, arrayBuffer) { 317 | yield* serializeValue(data, arrayBuffer.byteLength); 318 | yield* data.append(new Uint8Array(arrayBuffer)); 319 | } 320 | 321 | function* serializeNumber(data, number) { 322 | const serializedNumber = new Uint8Array(new Float64Array([number]).buffer); 323 | yield* data.append(serializedNumber); 324 | } 325 | 326 | function* serializeBigInt(data, number) { 327 | const serializedNumber = new Uint8Array(new BigInt64Array([number]).buffer); 328 | yield* data.append(serializedNumber); 329 | } 330 | 331 | function* serializeUint32(data, number) { 332 | const serializedNumber = new Uint8Array(new Uint32Array([number]).buffer); 333 | yield* data.append(serializedNumber); 334 | } 335 | 336 | function* serializeInt32(data, number) { 337 | const serializedNumber = new Uint8Array(new Int32Array([number]).buffer); 338 | yield* data.append(serializedNumber); 339 | } 340 | 341 | function* serializeUint16(data, number) { 342 | const serializedNumber = new Uint8Array(new Uint16Array([number]).buffer); 343 | yield* data.append(serializedNumber); 344 | } 345 | 346 | function* serializeInt16(data, number) { 347 | const serializedNumber = new Uint8Array(new Int16Array([number]).buffer); 348 | yield* data.append(serializedNumber); 349 | } 350 | 351 | function* serializeUint8(data, number) { 352 | const serializedNumber = new Uint8Array([number]); 353 | yield* data.append(serializedNumber); 354 | } 355 | 356 | function* serializeInt8(data, number) { 357 | const serializedNumber = new Uint8Array(new Int8Array([number]).buffer); 358 | yield* data.append(serializedNumber); 359 | } 360 | 361 | function* serializeBoolean(data, boolean) { 362 | const serializedBoolean = new Uint8Array([Number(boolean)]); 363 | yield* data.append(serializedBoolean); 364 | } 365 | 366 | function* serializeMap(data, map) { 367 | const entries = map.entries(); 368 | yield* serializeValue(data, map.size); 369 | for (const [key, value] of entries) { 370 | yield* serializeValue(data, key); 371 | yield* serializeValue(data, value); 372 | } 373 | } 374 | 375 | function* serializeSet(data, set) { 376 | yield* serializeValue(data, set.size); 377 | for (const value of set) { 378 | yield* serializeValue(data, value); 379 | } 380 | } 381 | 382 | function* serializeDate(data, date) { 383 | yield* serializeNumber(data, date.getTime()); 384 | } 385 | 386 | function* serializeError(data, error) { 387 | yield* serializeString(data, error.message); 388 | yield* serializeString(data, error.stack); 389 | } 390 | 391 | function* serializeRegExp(data, regExp) { 392 | yield* serializeString(data, regExp.source); 393 | yield* serializeString(data, regExp.flags); 394 | } 395 | 396 | function* serializeStringObject(data, string) { 397 | yield* serializeString(data, string.valueOf()); 398 | } 399 | 400 | function* serializeNumberObject(data, number) { 401 | yield* serializeNumber(data, number.valueOf()); 402 | } 403 | 404 | function* serializeBooleanObject(data, boolean) { 405 | yield* serializeBoolean(data, boolean.valueOf()); 406 | } 407 | 408 | function* serializeSymbol(data, symbol) { 409 | yield* serializeString(data, symbol.description); 410 | } 411 | 412 | class Reference { 413 | constructor(index, data) { 414 | this.index = index; 415 | this.data = data; 416 | } 417 | 418 | getObject() { 419 | return this.data.objects[this.index]; 420 | } 421 | } 422 | 423 | class ParserData { 424 | constructor() { 425 | this.stream = new ReadStream(); 426 | this.objects = []; 427 | this.setters = []; 428 | } 429 | 430 | consume(size) { 431 | return this.stream.consume(size); 432 | } 433 | 434 | getObjectId() { 435 | const objectIndex = this.objects.length; 436 | this.objects.push(undefined); 437 | return objectIndex; 438 | } 439 | 440 | resolveObject(objectId, value) { 441 | if (testReferenceable(value) && !testReference(value)) { 442 | this.objects[objectId] = value; 443 | } 444 | } 445 | 446 | setObject(functionArguments, setterFunction) { 447 | this.setters.push({ functionArguments, setterFunction }); 448 | } 449 | 450 | executeSetters() { 451 | this.setters.forEach(({ functionArguments, setterFunction }) => { 452 | const resolvedArguments = functionArguments.map(argument => testReference(argument) ? argument.getObject() : argument); 453 | setterFunction(...resolvedArguments); 454 | }); 455 | } 456 | } 457 | 458 | class ReadStream { 459 | constructor() { 460 | this.offset = 0; 461 | this.value = new Uint8Array(0); 462 | } 463 | 464 | *consume(size) { 465 | if (this.offset + size > this.value.length) { 466 | const pending = this.value.subarray(this.offset, this.value.length); 467 | const value = yield; 468 | if (pending.length + value.length != this.value.length) { 469 | this.value = new Uint8Array(pending.length + value.length); 470 | } 471 | this.value.set(pending); 472 | this.value.set(value, pending.length); 473 | this.offset = 0; 474 | return yield* this.consume(size); 475 | } else { 476 | const result = this.value.slice(this.offset, this.offset + size); 477 | this.offset += result.length; 478 | return result; 479 | } 480 | } 481 | } 482 | 483 | function getParser() { 484 | const parser = getParseGenerator(); 485 | parser.next(); 486 | return parser; 487 | } 488 | 489 | function* getParseGenerator() { 490 | const data = new ParserData(); 491 | const result = yield* parseValue(data); 492 | data.executeSetters(); 493 | return result; 494 | } 495 | 496 | function* parseValue(data) { 497 | const array = yield* data.consume(1); 498 | const parserType = array[0]; 499 | const parse = types[parserType].parse; 500 | const valueId = data.getObjectId(); 501 | const result = yield* parse(data); 502 | if (parserType != TYPE_REFERENCE && testObject(result)) { 503 | yield* parseSymbols(data, result); 504 | yield* parseOwnProperties(data, result); 505 | } 506 | data.resolveObject(valueId, result); 507 | return result; 508 | } 509 | 510 | function* parseSymbols(data, value) { 511 | const symbols = yield* parseArray(data); 512 | data.setObject([symbols], symbols => symbols.forEach(([symbol, propertyValue]) => value[symbol] = propertyValue)); 513 | } 514 | 515 | function* parseOwnProperties(data, object) { 516 | const size = yield* parseValue(data); 517 | for (let indexKey = 0; indexKey < size; indexKey++) { 518 | const key = yield* parseString(data); 519 | const value = yield* parseValue(data); 520 | data.setObject([value], value => object[key] = value); 521 | } 522 | } 523 | 524 | function* parseCircularReference(data) { 525 | const index = yield* parseValue(data); 526 | const result = new Reference(index, data); 527 | return result; 528 | } 529 | 530 | // eslint-disable-next-line require-yield 531 | function* parseObject() { 532 | return {}; 533 | } 534 | 535 | function* parseArray(data) { 536 | const length = yield* parseValue(data); 537 | const array = new Array(length); 538 | for (let indexArray = 0; indexArray < length; indexArray++) { 539 | const value = yield* parseValue(data); 540 | if (!testEmptySlot(value)) { 541 | data.setObject([value], value => array[indexArray] = value); 542 | } 543 | } 544 | return array; 545 | } 546 | 547 | // eslint-disable-next-line require-yield 548 | function* parseEmptySlot() { 549 | return EMPTY_SLOT_VALUE; 550 | } 551 | 552 | function* parseString(data) { 553 | const size = yield* parseValue(data); 554 | const array = yield* data.consume(size); 555 | return textDecoder.decode(array); 556 | } 557 | 558 | function* parseBigUint64Array(data) { 559 | const length = yield* parseValue(data); 560 | const array = yield* data.consume(length * 8); 561 | return new BigUint64Array(array.buffer); 562 | } 563 | 564 | function* parseBigInt64Array(data) { 565 | const length = yield* parseValue(data); 566 | const array = yield* data.consume(length * 8); 567 | return new BigInt64Array(array.buffer); 568 | } 569 | 570 | function* parseFloat64Array(data) { 571 | const length = yield* parseValue(data); 572 | const array = yield* data.consume(length * 8); 573 | return new Float64Array(array.buffer); 574 | } 575 | 576 | function* parseFloat32Array(data) { 577 | const length = yield* parseValue(data); 578 | const array = yield* data.consume(length * 4); 579 | return new Float32Array(array.buffer); 580 | } 581 | 582 | function* parseUint32Array(data) { 583 | const length = yield* parseValue(data); 584 | const array = yield* data.consume(length * 4); 585 | return new Uint32Array(array.buffer); 586 | } 587 | 588 | function* parseInt32Array(data) { 589 | const length = yield* parseValue(data); 590 | const array = yield* data.consume(length * 4); 591 | return new Int32Array(array.buffer); 592 | } 593 | 594 | function* parseUint16Array(data) { 595 | const length = yield* parseValue(data); 596 | const array = yield* data.consume(length * 2); 597 | return new Uint16Array(array.buffer); 598 | } 599 | 600 | function* parseInt16Array(data) { 601 | const length = yield* parseValue(data); 602 | const array = yield* data.consume(length * 2); 603 | return new Int16Array(array.buffer); 604 | } 605 | 606 | function* parseUint8ClampedArray(data) { 607 | const length = yield* parseValue(data); 608 | const array = yield* data.consume(length); 609 | return new Uint8ClampedArray(array.buffer); 610 | } 611 | 612 | function* parseUint8Array(data) { 613 | const length = yield* parseValue(data); 614 | const array = yield* data.consume(length); 615 | return array; 616 | } 617 | 618 | function* parseInt8Array(data) { 619 | const length = yield* parseValue(data); 620 | const array = yield* data.consume(length); 621 | return new Int8Array(array.buffer); 622 | } 623 | 624 | function* parseArrayBuffer(data) { 625 | const length = yield* parseValue(data); 626 | const array = yield* data.consume(length); 627 | return array.buffer; 628 | } 629 | 630 | function* parseNumber(data) { 631 | const array = yield* data.consume(8); 632 | return new Float64Array(array.buffer)[0]; 633 | } 634 | 635 | function* parseBigInt(data) { 636 | const array = yield* data.consume(8); 637 | return new BigInt64Array(array.buffer)[0]; 638 | } 639 | 640 | function* parseUint32(data) { 641 | const array = yield* data.consume(4); 642 | return new Uint32Array(array.buffer)[0]; 643 | } 644 | 645 | function* parseInt32(data) { 646 | const array = yield* data.consume(4); 647 | return new Int32Array(array.buffer)[0]; 648 | } 649 | 650 | function* parseUint16(data) { 651 | const array = yield* data.consume(2); 652 | return new Uint16Array(array.buffer)[0]; 653 | } 654 | 655 | function* parseInt16(data) { 656 | const array = yield* data.consume(2); 657 | return new Int16Array(array.buffer)[0]; 658 | } 659 | 660 | function* parseUint8(data) { 661 | const array = yield* data.consume(1); 662 | return new Uint8Array(array.buffer)[0]; 663 | } 664 | 665 | function* parseInt8(data) { 666 | const array = yield* data.consume(1); 667 | return new Int8Array(array.buffer)[0]; 668 | } 669 | 670 | // eslint-disable-next-line require-yield 671 | function* parseUndefined() { 672 | return undefined; 673 | } 674 | 675 | // eslint-disable-next-line require-yield 676 | function* parseNull() { 677 | return null; 678 | } 679 | 680 | // eslint-disable-next-line require-yield 681 | function* parseNaN() { 682 | return NaN; 683 | } 684 | 685 | function* parseBoolean(data) { 686 | const array = yield* data.consume(1); 687 | return Boolean(array[0]); 688 | } 689 | 690 | function* parseMap(data) { 691 | const size = yield* parseValue(data); 692 | const map = new Map(); 693 | for (let indexKey = 0; indexKey < size; indexKey++) { 694 | const key = yield* parseValue(data); 695 | const value = yield* parseValue(data); 696 | data.setObject([key, value], (key, value) => map.set(key, value)); 697 | } 698 | return map; 699 | } 700 | 701 | function* parseSet(data) { 702 | const size = yield* parseValue(data); 703 | const set = new Set(); 704 | for (let indexKey = 0; indexKey < size; indexKey++) { 705 | const value = yield* parseValue(data); 706 | data.setObject([value], value => set.add(value)); 707 | } 708 | return set; 709 | } 710 | 711 | function* parseDate(data) { 712 | const milliseconds = yield* parseNumber(data); 713 | return new Date(milliseconds); 714 | } 715 | 716 | function* parseError(data) { 717 | const message = yield* parseString(data); 718 | const stack = yield* parseString(data); 719 | const error = new Error(message); 720 | error.stack = stack; 721 | return error; 722 | } 723 | 724 | function* parseRegExp(data) { 725 | const source = yield* parseString(data); 726 | const flags = yield* parseString(data); 727 | return new RegExp(source, flags); 728 | } 729 | 730 | function* parseStringObject(data) { 731 | return new String(yield* parseString(data)); 732 | } 733 | 734 | function* parseNumberObject(data) { 735 | return new Number(yield* parseNumber(data)); 736 | } 737 | 738 | function* parseBooleanObject(data) { 739 | return new Boolean(yield* parseBoolean(data)); 740 | } 741 | 742 | function* parseSymbol(data) { 743 | const description = yield* parseString(data); 744 | return Symbol(description); 745 | } 746 | 747 | function testCircularReference(value, data) { 748 | return testObject(value) && data.objects.includes(value); 749 | } 750 | 751 | function testReference(value) { 752 | return value instanceof Reference; 753 | } 754 | 755 | function testObject(value) { 756 | return value === Object(value); 757 | } 758 | 759 | function testArray(value) { 760 | return typeof value.length == "number"; 761 | } 762 | 763 | function testEmptySlot(value) { 764 | return value === EMPTY_SLOT_VALUE; 765 | } 766 | 767 | function testString(value) { 768 | return typeof value == "string"; 769 | } 770 | 771 | function testBigUint64Array(value) { 772 | return value instanceof BigUint64Array; 773 | } 774 | 775 | function testBigInt64Array(value) { 776 | return value instanceof BigInt64Array; 777 | } 778 | 779 | function testFloat64Array(value) { 780 | return value instanceof Float64Array; 781 | } 782 | 783 | function testUint32Array(value) { 784 | return value instanceof Uint32Array; 785 | } 786 | 787 | function testInt32Array(value) { 788 | return value instanceof Int32Array; 789 | } 790 | 791 | function testUint16Array(value) { 792 | return value instanceof Uint16Array; 793 | } 794 | 795 | function testFloat32Array(value) { 796 | return value instanceof Float32Array; 797 | } 798 | 799 | function testInt16Array(value) { 800 | return value instanceof Int16Array; 801 | } 802 | 803 | function testUint8ClampedArray(value) { 804 | return value instanceof Uint8ClampedArray; 805 | } 806 | 807 | function testUint8Array(value) { 808 | return value instanceof Uint8Array; 809 | } 810 | 811 | function testInt8Array(value) { 812 | return value instanceof Int8Array; 813 | } 814 | 815 | function testArrayBuffer(value) { 816 | return value instanceof ArrayBuffer; 817 | } 818 | 819 | function testNumber(value) { 820 | return typeof value == "number"; 821 | } 822 | 823 | function testBigInt(value) { 824 | return typeof value == "bigint"; 825 | } 826 | 827 | function testUint32(value) { 828 | return testInteger(value) && value >= 0 && value <= 4294967295; 829 | } 830 | 831 | function testInt32(value) { 832 | return testInteger(value) && value >= -2147483648 && value <= 2147483647; 833 | } 834 | 835 | function testUint16(value) { 836 | return testInteger(value) && value >= 0 && value <= 65535; 837 | } 838 | 839 | function testInt16(value) { 840 | return testInteger(value) && value >= -32768 && value <= 32767; 841 | } 842 | 843 | function testUint8(value) { 844 | return testInteger(value) && value >= 0 && value <= 255; 845 | } 846 | 847 | function testInt8(value) { 848 | return testInteger(value) && value >= -128 && value <= 127; 849 | } 850 | 851 | function testInteger(value) { 852 | return testNumber(value) && Number.isInteger(value); 853 | } 854 | 855 | function testUndefined(value) { 856 | return value === undefined; 857 | } 858 | 859 | function testNull(value) { 860 | return value === null; 861 | } 862 | 863 | function testNaN(value) { 864 | return Number.isNaN(value); 865 | } 866 | 867 | function testBoolean(value) { 868 | return typeof value == "boolean"; 869 | } 870 | 871 | function testMap(value) { 872 | return value instanceof Map; 873 | } 874 | 875 | function testSet(value) { 876 | return value instanceof Set; 877 | } 878 | 879 | function testDate(value) { 880 | return value instanceof Date; 881 | } 882 | 883 | function testError(value) { 884 | return value instanceof Error; 885 | } 886 | 887 | function testRegExp(value) { 888 | return value instanceof RegExp; 889 | } 890 | 891 | function testStringObject(value) { 892 | return value instanceof String; 893 | } 894 | 895 | function testNumberObject(value) { 896 | return value instanceof Number; 897 | } 898 | 899 | function testBooleanObject(value) { 900 | return value instanceof Boolean; 901 | } 902 | 903 | function testSymbol(value) { 904 | return typeof value == "symbol"; 905 | } 906 | 907 | function testReferenceable(value) { 908 | return testObject(value) || testSymbol(value); 909 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yabson", 3 | "version": "1.3.2", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "yabson", 9 | "version": "1.3.2", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yabson", 3 | "description": "Binary-encoded serialization of JavaScript objects with generator-based parser and serializer", 4 | "author": "Gildas Lormeau", 5 | "license": "MIT", 6 | "version": "1.3.2", 7 | "type": "module", 8 | "keywords": [ 9 | "serialization", 10 | "parse", 11 | "serialize", 12 | "generator" 13 | ], 14 | "main": "index.js", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/gildas-lormeau/YaBSON.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/gildas-lormeau/YaBSON/issues" 21 | }, 22 | "homepage": "https://github.com/gildas-lormeau/YaBSON", 23 | "scripts": { 24 | "test": "deno test ./test/index.js" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* global Deno, BigUint64Array, BigInt64Array */ 2 | 3 | import { getSerializer, clone } from "./../index.js"; 4 | import { getSerializer as getSerializerAsync, clone as cloneAsync } from "./../index-async.js"; 5 | 6 | const MAX_CHUNK_SIZE = 16 * 1024; 7 | const MAX_TESTS = 32; 8 | const MAX_DEPTH = 4; 9 | 10 | const objects = new Set(); 11 | const createFunctions = [ 12 | createNumber, 13 | createBoolean, 14 | createUndefined, 15 | createNull, 16 | createNaN, 17 | createString, 18 | createTypedArray, 19 | createArrayBuffer, 20 | createBigInt, 21 | createRegExp, 22 | createDate, 23 | createError, 24 | createMap, 25 | createSet, 26 | createStringObject, 27 | createNumberObject, 28 | createBooleanObject, 29 | createArray, 30 | createObject, 31 | createReference, 32 | createSymbol 33 | ]; 34 | 35 | Deno.test("Fuzzing tests should run without errors", () => { 36 | for (let indexTest = 0; indexTest < MAX_TESTS; indexTest++) { 37 | if (!test()) { 38 | return false; 39 | } 40 | } 41 | return true; 42 | }); 43 | Deno.test("Fuzzing tests should run without errors (Async API)", () => { 44 | return test(); 45 | 46 | async function test(indexTest = 0) { 47 | if (await testAsync()) { 48 | if (indexTest < MAX_TESTS) { 49 | await test(indexTest + 1); 50 | } else { 51 | return true; 52 | } 53 | } else { 54 | return false; 55 | } 56 | } 57 | }); 58 | 59 | function test() { 60 | const object = createObject(); 61 | const copy = clone(object); 62 | const serializerObject = getSerializer(object, { chunkSize: MAX_CHUNK_SIZE }); 63 | const serializerCopy = getSerializer(copy, { chunkSize: MAX_CHUNK_SIZE }); 64 | let serializedObject, serializedCopy; 65 | do { 66 | serializedObject = serializerObject.next(); 67 | serializedCopy = serializerCopy.next(); 68 | if (!serializedObject.done && 69 | !serializedCopy.done && 70 | JSON.stringify(Array.from(serializedObject.value)) != JSON.stringify(Array.from(serializedCopy.value))) { 71 | return false; 72 | } 73 | } while (!serializedObject.done && !serializedCopy.done); 74 | return true; 75 | } 76 | 77 | async function testAsync() { 78 | const object = createObject(); 79 | const copy = await cloneAsync(object); 80 | const serializerObject = getSerializerAsync(object, { chunkSize: MAX_CHUNK_SIZE })[Symbol.asyncIterator](); 81 | const serializerCopy = getSerializerAsync(copy, { chunkSize: MAX_CHUNK_SIZE })[Symbol.asyncIterator](); 82 | return test(); 83 | 84 | async function test() { 85 | const [serializedObject, serializedCopy] = await Promise.all([serializerObject.next(), serializerCopy.next()]); 86 | if (!serializedObject.done && 87 | !serializedCopy.done && 88 | JSON.stringify(Array.from(serializedObject.value)) != JSON.stringify(Array.from(serializedCopy.value))) { 89 | return false; 90 | } 91 | if (!serializedObject.done && !serializedCopy.done) { 92 | return test(); 93 | } else { 94 | return true; 95 | } 96 | } 97 | } 98 | 99 | function createValue(depth = 0) { 100 | if (depth < MAX_DEPTH) { 101 | const indexCreateFunction = Math.floor(Math.random() * createFunctions.length); 102 | const value = createFunctions[indexCreateFunction](depth); 103 | return value; 104 | } else { 105 | return undefined; 106 | } 107 | } 108 | 109 | function createNumber() { 110 | if (Math.random() < .5) { 111 | return (Math.random() - .5) * (Math.pow(10, Math.floor(Math.random() * 64)) * 2); 112 | } else { 113 | const maximums = [ 114 | 256, 115 | 65536, 116 | 2147483647, 117 | Number.MAX_SAFE_INTEGER * 2 118 | ]; 119 | return Math.floor((Math.random() - .5) * maximums[Math.floor(Math.random() * maximums.length)]); 120 | } 121 | } 122 | 123 | function createBoolean() { 124 | return Math.random() < .5; 125 | } 126 | 127 | function createUndefined() { 128 | return undefined; 129 | } 130 | 131 | function createNull() { 132 | return null; 133 | } 134 | 135 | function createNaN() { 136 | return NaN; 137 | } 138 | 139 | function createBigInt() { 140 | const array = new Uint8Array(8); 141 | for (let indexArray = 0; indexArray < array.length; indexArray++) { 142 | array[indexArray] = Math.floor(Math.random() * 256); 143 | } 144 | return new BigInt64Array(array.buffer)[0]; 145 | } 146 | 147 | function createArray(depth = 0) { 148 | const array = []; 149 | objects.add(array); 150 | for (let index = 0; index < 16 + Math.floor(Math.random() * 16); index++) { 151 | if (Math.random() > .1) { 152 | array[index] = createValue(depth + 1); 153 | } 154 | } 155 | for (let index = 0; index < Math.floor(Math.random() * 4); index++) { 156 | array[createSymbol()] = createValue(depth + 1); 157 | } 158 | return array; 159 | } 160 | 161 | function createString() { 162 | let string = ""; 163 | for (let index = 0; index < 16 + Math.floor(Math.random() * 16); index++) { 164 | string += String.fromCharCode(Math.floor(Math.random() * 58) + 64); 165 | } 166 | return string; 167 | } 168 | 169 | function createStringObject() { 170 | return new String(createString()); 171 | } 172 | 173 | function createNumberObject() { 174 | return new Number(createNumber()); 175 | } 176 | 177 | function createBooleanObject() { 178 | return new Boolean(createBoolean()); 179 | } 180 | 181 | function createMap(depth = 0) { 182 | const map = new Map(); 183 | objects.add(map); 184 | for (let index = 0; index < 16 + Math.floor(Math.random() * 16); index++) { 185 | map.set(createValue(depth + 1), createValue(depth + 1)); 186 | } 187 | return map; 188 | } 189 | 190 | function createSet(depth = 0) { 191 | const set = new Set(); 192 | objects.add(set); 193 | for (let index = 0; index < 16 + Math.floor(Math.random() * 16); index++) { 194 | set.add(createValue(depth + 1)); 195 | } 196 | return set; 197 | } 198 | 199 | function createObject(depth = 0) { 200 | const object = {}; 201 | objects.add(object); 202 | for (let index = 0; index < 16 + Math.floor(Math.random() * 16); index++) { 203 | object[createString()] = createValue(depth + 1); 204 | } 205 | for (let index = 0; index < Math.floor(Math.random() * 4); index++) { 206 | object[createSymbol()] = createValue(depth + 1); 207 | } 208 | return object; 209 | } 210 | 211 | function createReference() { 212 | return Array.from(objects)[Math.floor(Math.random() * objects.size)]; 213 | } 214 | 215 | function createTypedArray() { 216 | const array = new Uint8Array(Math.floor(Math.random() * 4) * 8); 217 | for (let indexArray = 0; indexArray < array.length; indexArray++) { 218 | array[indexArray] = Math.floor(Math.random() * 256); 219 | } 220 | switch (Math.floor(Math.random() * 12)) { 221 | case 0: return new BigUint64Array(array.buffer); 222 | case 2: return new BigInt64Array(array.buffer); 223 | case 3: return new Float64Array(array.buffer); 224 | case 4: return new Uint32Array(array.buffer); 225 | case 5: return new Int32Array(array.buffer); 226 | case 6: return new Uint16Array(array.buffer); 227 | case 7: return new Float32Array(array.buffer); 228 | case 8: return new Int16Array(array.buffer); 229 | case 9: return new Uint8ClampedArray(array.buffer); 230 | case 10: return new Uint8Array(array.buffer); 231 | case 11: return new Int8Array(array.buffer); 232 | } 233 | } 234 | 235 | function createArrayBuffer() { 236 | const array = new Uint8Array(Math.floor(Math.random() * 4) * 8); 237 | for (let indexArray = 0; indexArray < array.length; indexArray++) { 238 | array[indexArray] = Math.floor(Math.random() * 256); 239 | } 240 | return array.buffer; 241 | } 242 | 243 | function createRegExp() { 244 | return new RegExp(createString().replace(/\[|\]|\\/g, "")); 245 | } 246 | 247 | function createDate() { 248 | return new Date(Math.floor(Math.random() * 80 * 360 * 24 * 60 * 60 * 1000)); 249 | } 250 | 251 | function createError() { 252 | return new Error(createString()); 253 | } 254 | 255 | function createSymbol() { 256 | return Symbol(createString()); 257 | } --------------------------------------------------------------------------------