├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmarks ├── e2e │ ├── package.json │ ├── public │ │ ├── avsc.js │ │ └── index.html │ └── src │ │ ├── data │ │ ├── todos.json │ │ └── todos.proto │ │ └── index.ts └── isolated │ ├── data │ ├── todos.json │ └── todos.proto │ ├── des.benchmark.ts │ ├── package.json │ ├── ser.benchmark.ts │ └── serdes.benchmark.ts ├── examples ├── complex_object.ts ├── iterable.ts ├── object.ts ├── simple.ts └── todos.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── src └── index.ts ├── tests └── index.test.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.cjs 2 | *.css 3 | *.csv 4 | *.dic 5 | *.dpack 6 | *.html 7 | *.md 8 | *.png 9 | *.sh 10 | *.snap 11 | *.svg 12 | *.vue 13 | *.yaml 14 | *.yml 15 | *.gif 16 | **/datasets/*.json 17 | **/snapshots/*.json 18 | stemmer 19 | **/dist/** 20 | **/dist 21 | Dockerfile 22 | **/get-imdb-dataset.mjs 23 | node_modules -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | browser: true, 5 | }, 6 | parserOptions: { 7 | sourceType: "module", 8 | }, 9 | globals: { 10 | Deno: "readonly", 11 | }, 12 | root: true, 13 | parser: "@typescript-eslint/parser", 14 | plugins: ["@typescript-eslint", "import"], 15 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 16 | rules: { 17 | "import/extensions": [2, "always", { ignorePackages: true }], // This is required for proper ESM use 18 | "@typescript-eslint/no-non-null-assertion": 0, 19 | "no-async-promise-executor": 0, 20 | "@typescript-eslint/no-explicit-any": 0, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Nodejs 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | name: Build and Test 9 | timeout-minutes: 15 10 | 11 | strategy: 12 | matrix: 13 | node-version: [20.x] 14 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: pnpm/action-setup@v2 19 | with: 20 | version: 8 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'pnpm' 26 | - name: Install dependencies 27 | run: pnpm install 28 | - name: Run lint 29 | run: pnpm run lint 30 | - name: Run test 31 | run: pnpm run test 32 | 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node,osx 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,osx 3 | 4 | .wrangler 5 | .env.production 6 | .env.development 7 | 8 | ### Node ### 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | .pnpm-debug.log* 17 | 18 | # Diagnostic reports (https://nodejs.org/api/report.html) 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | *.lcov 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # Bower dependency directory (https://bower.io/) 41 | bower_components 42 | 43 | # node-waf configuration 44 | .lock-wscript 45 | 46 | # Compiled binary addons (https://nodejs.org/api/addons.html) 47 | build/Release 48 | 49 | # Dependency directories 50 | node_modules/ 51 | jspm_packages/ 52 | 53 | # Snowpack dependency directory (https://snowpack.dev/) 54 | web_modules/ 55 | 56 | # TypeScript cache 57 | *.tsbuildinfo 58 | 59 | # Optional npm cache directory 60 | .npm 61 | 62 | # Optional eslint cache 63 | .eslintcache 64 | 65 | # Optional stylelint cache 66 | .stylelintcache 67 | 68 | # Microbundle cache 69 | .rpt2_cache/ 70 | .rts2_cache_cjs/ 71 | .rts2_cache_es/ 72 | .rts2_cache_umd/ 73 | 74 | # Optional REPL history 75 | .node_repl_history 76 | 77 | # Output of 'npm pack' 78 | *.tgz 79 | 80 | # Yarn Integrity file 81 | .yarn-integrity 82 | 83 | # dotenv environment variable files 84 | .env 85 | .env.development.local 86 | .env.test.local 87 | .env.production.local 88 | .env.local 89 | 90 | # parcel-bundler cache (https://parceljs.org/) 91 | .cache 92 | .parcel-cache 93 | 94 | # Next.js build output 95 | .next 96 | out 97 | 98 | # Nuxt.js build / generate output 99 | .nuxt 100 | dist 101 | 102 | # Gatsby files 103 | .cache/ 104 | # Comment in the public line in if your project uses Gatsby and not Next.js 105 | # https://nextjs.org/blog/next-9-1#public-directory-support 106 | # public 107 | 108 | # vuepress build output 109 | .vuepress/dist 110 | 111 | # vuepress v2.x temp and cache directory 112 | .temp 113 | 114 | # Docusaurus cache and generated files 115 | .docusaurus 116 | 117 | # Serverless directories 118 | .serverless/ 119 | 120 | # FuseBox cache 121 | .fusebox/ 122 | 123 | # DynamoDB Local files 124 | .dynamodb/ 125 | 126 | # TernJS port file 127 | .tern-port 128 | 129 | # Stores VSCode versions used for testing VSCode extensions 130 | .vscode-test 131 | 132 | # yarn v2 133 | .yarn/cache 134 | .yarn/unplugged 135 | .yarn/build-state.yml 136 | .yarn/install-state.gz 137 | .pnp.* 138 | 139 | ### Node Patch ### 140 | # Serverless Webpack directories 141 | .webpack/ 142 | 143 | # Optional stylelint cache 144 | 145 | # SvelteKit build / generate output 146 | .svelte-kit 147 | 148 | ### OSX ### 149 | # General 150 | .DS_Store 151 | .AppleDouble 152 | .LSOverride 153 | 154 | # Icon must end with two \r 155 | Icon 156 | 157 | 158 | # Thumbnails 159 | ._* 160 | 161 | # Files that might appear in the root of a volume 162 | .DocumentRevisions-V100 163 | .fseventsd 164 | .Spotlight-V100 165 | .TemporaryItems 166 | .Trashes 167 | .VolumeIcon.icns 168 | .com.apple.timemachine.donotpresent 169 | 170 | # Directories potentially created on remote AFP share 171 | .AppleDB 172 | .AppleDesktop 173 | Network Trash Folder 174 | Temporary Items 175 | .apdisk 176 | 177 | # End of https://www.toptal.com/developers/gitignore/api/node,osx 178 | 179 | dist 180 | node_modules 181 | !templates/v1do/node_modules 182 | tmp 183 | .env.local 184 | .env.staging 185 | 186 | *.pem 187 | !tests/data/*.pem 188 | 189 | .idea 190 | 191 | bun.lockb 192 | benchmark 193 | 194 | *.0x/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SeqProto 2 | 3 | [![Nodejs](https://github.com/oramasearch/seqproto/actions/workflows/nodejs.yml/badge.svg)](https://github.com/oramasearch/seqproto/actions/workflows/nodejs.yml) 4 | ![npm package minimized gzipped size (select exports)](https://img.shields.io/bundlejs/size/seqproto) 5 | 6 | This library provides a simple way to serialize and deserialize objects in binary format. 7 | 8 | ## Why another serialization library? 9 | 10 | While I have been writing this library, I have in mind the following main goals: 11 | - **Runtime independent** - I want to have a library that runs in every javascript runtime, from Node.JS, through browsers, to CloudFlare. 12 | - **Performance** - I want to have a library that is fast and easy to use. 13 | - **Small size** - I want to have a library that is small and easy to use. 14 | - **Customizable** - Due to the JavaScript nature, the data structures are limited. 15 | - **TypeScript support** - I want to have a library that is easy to use in TypeScript. 16 | 17 | ## Installation 18 | 19 | Seqproto works in any JavaScript environment. You can install it via npm: 20 | 21 | ```sh 22 | npm install seqproto 23 | ``` 24 | 25 | Or via CDN: 26 | 27 | ```js 28 | import { createSer, createDes } from 'https://unpkg.com/seqproto@latest/dist/esm/index.js' 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### Examples 34 | 35 | For more examples, see the [examples](https://github.com/oramasearch/seqproto/tree/main/examples) directory. 36 | 37 | ```typescript 38 | import { createSer, createDes } from 'seqproto' 39 | 40 | // Create a serializer 41 | const ser = createSer() 42 | 43 | // Serialize some data 44 | ser.serializeBoolean(true) 45 | ser.serializeUInt32(42) 46 | ser.serializeFloat32(-0.5) 47 | ser.serializeString('hello world') 48 | ser.serializeArray([1, 2, 3], (ser, n) => ser.serializeUInt32(n)) 49 | 50 | // Get ArrayBuffer with serialized data 51 | const buffer = ser.getBuffer() 52 | 53 | // Create a deserializer 54 | const des = createDes(buffer) 55 | 56 | // Deserialize data 57 | const b = des.deserializeBoolean() 58 | const i = des.deserializeUInt32() 59 | const f = des.deserializeFloat32() 60 | const s = des.deserializeString() 61 | const a = des.deserializeArray((des) => des.deserializeUInt32()) 62 | 63 | console.log({ b, i, f, s, a }) 64 | ``` 65 | 66 | ### Object 67 | 68 | ```typescript 69 | import type { Ser, Des } from 'seqproto' 70 | import { createSer, createDes } from 'seqproto' 71 | 72 | interface Todo { 73 | id: number 74 | userId: number 75 | title: string 76 | completed: boolean 77 | } 78 | 79 | function serializeTodo (ser: Ser, todo: Todo) { 80 | ser.serializeUInt32(todo.id) 81 | ser.serializeUInt32(todo.userId) 82 | ser.serializeString(todo.title) 83 | ser.serializeBoolean(todo.completed) 84 | } 85 | 86 | function deserializeTodo (des: Des): Todo { 87 | const id = des.deserializeUInt32() 88 | const userId = des.deserializeUInt32() 89 | const title = des.deserializeString() 90 | const completed = des.deserializeBoolean() 91 | return { id, userId, title, completed } 92 | } 93 | 94 | const ser: Ser = createSer() 95 | 96 | serializeTodo(ser, { 97 | id: 1, 98 | userId: 1, 99 | title: 'hello', 100 | completed: false, 101 | }) 102 | 103 | const buffer = ser.getBuffer() 104 | 105 | const des: Des = createDes(buffer) 106 | const todo = deserializeTodo(des) 107 | 108 | console.log(JSON.stringify(todo, null, 2)) 109 | ``` 110 | 111 | ### Array of object 112 | 113 | ```typescript 114 | import type { Ser, Des } from 'seqproto' 115 | import { createSer, createDes } from 'seqproto' 116 | 117 | let buffer 118 | 119 | // Serialize 120 | const todos = [ 121 | { userId: 1, id: 1, completed: false, title: "delectus aut autem" }, 122 | { userId: 1, id: 2, completed: true, title: "quis ut nam facilis et officia qui" } 123 | ] 124 | 125 | const ser: Ser = createSer() 126 | 127 | ser.serializeArray(todos, (ser, todo) => { 128 | ser.serializeUInt32(todo.id) 129 | ser.serializeUInt32(todo.userId) 130 | ser.serializeString(todo.title) 131 | ser.serializeBoolean(todo.completed) 132 | }) 133 | 134 | buffer = ser.getBuffer() 135 | 136 | // Deserialize 137 | const des: Des = createDes(buffer) 138 | 139 | const deserializedTodos = des.deserializeArray((des) => { 140 | const id = des.deserializeUInt32() 141 | const userId = des.deserializeUInt32() 142 | const title = des.deserializeString() 143 | const completed = des.deserializeBoolean() 144 | return { id, userId, title, completed } 145 | }) 146 | 147 | console.log(deserializedTodos) 148 | ``` 149 | 150 | ## API 151 | 152 | This library exports the following functions: 153 | 154 | - `createSer()`/`createSer({ bufferSize: number })`: creates a new serializer. 155 | - `createDes(buffer)`: creates a new deserializer. 156 | - `ser.reset()`: reset the serializer. 157 | - `ser.serializeBoolean(b)`: serializes a boolean value. 158 | - `des.deserializeBoolean()`: deserializes a boolean value. 159 | - `ser.serializeUInt32(uint32)`: serializes a 32-bit unsigned integer. 160 | - `des.deserializeUInt32(uint32)`: deserializes a 32-bit unsigned integer. 161 | - `ser.serializeNumber(n)`: serializes a 32-bit unsigned integer or signed integer or float. 162 | - `des.deserializeNumber(n)`: deserializes a 32-bit unsigned integer or signed integer or float. 163 | - `ser.serializeString(string)`: serializes a string. 164 | - `des.deserializeString()`: deserializes a string. 165 | - `ser.serializeArray(array, (ser, item) => { ... })`: serializes an array. 166 | - `des.deserializeArray((des) => { ... })`: deserializes an array. 167 | - `ser.serializeIterable(iterable, (ser, item) => { ... })`: serializes an iterable. 168 | - `des.deserializeIterable((des) => { ... })`: deserializes an iterable. 169 | - `ser.serializeFloat32(float32)`: serializes float 32bit. 170 | - `des.deserializeFloat32()`: deserializes float 32bit. 171 | - `ser.getBuffer()`: returns the serialized buffer. 172 | 173 | ## Benchmarks 174 | 175 | We created 3 different benchmarks to compare the performance of SeqProto with JSON and Avro. 176 | 177 | ### Isolated benchmark 178 | 179 | You can run the benchmarks with the following command: 180 | ```sh 181 | npm run benchmark:serdes 182 | ``` 183 | 184 | Serialization / Deserialization: 185 | | name | ops | margin | percentSlower | 186 | | -------- | ------- | -------- | ------- | 187 | | seqproto | 29764 | 0.72 | 0 | 188 | | protobuf | 13698 | 0.19 | 53.98 | 189 | | avro | 24204 | 0.14 | 18.68 | 190 | | cbor | 803 | 0.22 | 97.3 | 191 | | cborx | 9707 | 0.32 | 67.39 | 192 | | msgpack | 6857 | 0.06 | 76.96 | 193 | | msgpackr | 10449 | 0.27 | 64.89 | 194 | | JSON | 14434 | 0.07 | 51.51 | 195 | 196 | ### Http benchmark 197 | 198 | You can run the benchmarks using 2 shells. 199 | 200 | 1. 201 | ```sh 202 | cd bechmarks/e2e 203 | pnpm install 204 | pnpm start 205 | ``` 206 | 2. 207 | ```sh 208 | cd bechmarks/e2e 209 | pnpm run autocannon:json 210 | pnpm run autocannon:seqproto 211 | pnpm run autocannon:avro 212 | ``` 213 | 214 | | type | req (in 10s) | Avg req/sec | Avg Bytes/Sec | Avg Latency (ms) | 215 | | -------- | ---- | --------- | ---- | ---- | 216 | | JSON | 164k | 14892 | 275 | 0.11 | 217 | | SeqProto | 269k | 26865.6 | 321 | 0.01 | 218 | | Avro | 197k | 17926.55 | 169 | 0.04 | 219 | 220 | ### e2e benchmark 221 | 222 | You can run the benchmarks with the following command: 223 | ```sh 224 | cd bechmarks/e2e 225 | pnpm install 226 | pnpm start 227 | ``` 228 | And go to http://localhost:3000/public/index.html. 229 | 230 | | iteration | parallelism | type | ms | 231 | | -------- | ------- | -------- | ------- | 232 | | 10 | 1 | JSON | 30.69999998807907 | 233 | | 10 | 1 | SeqProto | 25.600000023841858 | 234 | | 10 | 1 | Avro | 30.399999976158142 | 235 | | 100 | 1 | JSON | 108.80000001192093 | 236 | | 100 | 1 | SeqProto | 96.80000001192093 | 237 | | 100 | 1 | Avro | 96 | 238 | | 100 | 3 | JSON | 162.10000002384186 | 239 | | 100 | 3 | SeqProto | 152.4000000357628 | 240 | | 100 | 3 | Avro | 167.5 | 241 | | 100 | 6 | JSON | 277.19999998807907 | 242 | | 100 | 6 | SeqProto | 263.30000001192093 | 243 | | 100 | 6 | Avro | 308.19999998807907 | 244 | 245 | ## Contributing 246 | 247 | Contributions are welcome! Please open an issue if you have any ideas for improvement or found a bug. 248 | 249 | ## License 250 | 251 | Apache-2.0 252 | -------------------------------------------------------------------------------- /benchmarks/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seqproto-benchmarks-e2e", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "description": "", 6 | "scripts": { 7 | "start": "node --import tsx src/index.ts", 8 | "autocannon:json": "npx autocannon 'http://localhost:3000/todos'", 9 | "autocannon:seqproto": "npx autocannon -H 'accept=application/seqproto' 'http://localhost:3000/todos'", 10 | "autocannon:avro": "autocannon -H 'accept=application/avro' 'http://localhost:3000/todos'" 11 | }, 12 | "author": { 13 | "name": "Tommaso Allevi", 14 | "email": "tomallevi@gmail.com", 15 | "url": "https://github.com/allevo", 16 | "author": true 17 | }, 18 | "engines": { 19 | "node": ">= 20.0.0" 20 | }, 21 | "devDependencies": { 22 | "@msgpack/msgpack": "3.0.0-beta2", 23 | "@types/node": "=20.8.0", 24 | "autocannon": "^7.12.0", 25 | "avro-js": "^1.11.3", 26 | "avsc": "^5.7.7", 27 | "benny": "^3.7.1", 28 | "browserify": "^17.0.0", 29 | "cbor": "^9.0.1", 30 | "cbor-x": "^1.5.4", 31 | "fast-json-stringify": "^5.9.1", 32 | "lodash": "^4.17.21", 33 | "msgpackr": "^1.9.9", 34 | "protobufjs": "^7.2.5", 35 | "ts-standard": "^12.0.2", 36 | "tsx": "^3.14.0", 37 | "typescript": "^5.2.2", 38 | "undici-types": "^5.27.2" 39 | }, 40 | "dependencies": { 41 | "@fastify/static": "^6.12.0", 42 | "fastify": "^4.24.3", 43 | "seqproto": "workspace:^" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /benchmarks/e2e/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 |

SeqProto e2e benchmarks

17 | 18 |
19 |

Configure & Run

20 | 21 | 22 | 23 | 24 |

NB: the maximum parallel requests in Chrome is 6

25 | 26 | 27 | 28 | 29 | 30 |

31 |
32 | 33 |
34 |

Results

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
iterationparallelismtypems
46 |
47 | 48 | 49 | 150 | 151 | -------------------------------------------------------------------------------- /benchmarks/e2e/src/data/todos.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": 1, 4 | "id": 1, 5 | "title": "delectus aut autem", 6 | "completed": false 7 | }, 8 | { 9 | "userId": 1, 10 | "id": 2, 11 | "title": "quis ut nam facilis et officia qui", 12 | "completed": false 13 | }, 14 | { 15 | "userId": 1, 16 | "id": 3, 17 | "title": "fugiat veniam minus", 18 | "completed": false 19 | }, 20 | { 21 | "userId": 1, 22 | "id": 4, 23 | "title": "et porro tempora", 24 | "completed": true 25 | }, 26 | { 27 | "userId": 1, 28 | "id": 5, 29 | "title": "laboriosam mollitia et enim quasi adipisci quia provident illum", 30 | "completed": false 31 | }, 32 | { 33 | "userId": 1, 34 | "id": 6, 35 | "title": "qui ullam ratione quibusdam voluptatem quia omnis", 36 | "completed": false 37 | }, 38 | { 39 | "userId": 1, 40 | "id": 7, 41 | "title": "illo expedita consequatur quia in", 42 | "completed": false 43 | }, 44 | { 45 | "userId": 1, 46 | "id": 8, 47 | "title": "quo adipisci enim quam ut ab", 48 | "completed": true 49 | }, 50 | { 51 | "userId": 1, 52 | "id": 9, 53 | "title": "molestiae perspiciatis ipsa", 54 | "completed": false 55 | }, 56 | { 57 | "userId": 1, 58 | "id": 10, 59 | "title": "illo est ratione doloremque quia maiores aut", 60 | "completed": true 61 | }, 62 | { 63 | "userId": 1, 64 | "id": 11, 65 | "title": "vero rerum temporibus dolor", 66 | "completed": true 67 | }, 68 | { 69 | "userId": 1, 70 | "id": 12, 71 | "title": "ipsa repellendus fugit nisi", 72 | "completed": true 73 | }, 74 | { 75 | "userId": 1, 76 | "id": 13, 77 | "title": "et doloremque nulla", 78 | "completed": false 79 | }, 80 | { 81 | "userId": 1, 82 | "id": 14, 83 | "title": "repellendus sunt dolores architecto voluptatum", 84 | "completed": true 85 | }, 86 | { 87 | "userId": 1, 88 | "id": 15, 89 | "title": "ab voluptatum amet voluptas", 90 | "completed": true 91 | }, 92 | { 93 | "userId": 1, 94 | "id": 16, 95 | "title": "accusamus eos facilis sint et aut voluptatem", 96 | "completed": true 97 | }, 98 | { 99 | "userId": 1, 100 | "id": 17, 101 | "title": "quo laboriosam deleniti aut qui", 102 | "completed": true 103 | }, 104 | { 105 | "userId": 1, 106 | "id": 18, 107 | "title": "dolorum est consequatur ea mollitia in culpa", 108 | "completed": false 109 | }, 110 | { 111 | "userId": 1, 112 | "id": 19, 113 | "title": "molestiae ipsa aut voluptatibus pariatur dolor nihil", 114 | "completed": true 115 | }, 116 | { 117 | "userId": 1, 118 | "id": 20, 119 | "title": "ullam nobis libero sapiente ad optio sint", 120 | "completed": true 121 | }, 122 | { 123 | "userId": 2, 124 | "id": 21, 125 | "title": "suscipit repellat esse quibusdam voluptatem incidunt", 126 | "completed": false 127 | }, 128 | { 129 | "userId": 2, 130 | "id": 22, 131 | "title": "distinctio vitae autem nihil ut molestias quo", 132 | "completed": true 133 | }, 134 | { 135 | "userId": 2, 136 | "id": 23, 137 | "title": "et itaque necessitatibus maxime molestiae qui quas velit", 138 | "completed": false 139 | }, 140 | { 141 | "userId": 2, 142 | "id": 24, 143 | "title": "adipisci non ad dicta qui amet quaerat doloribus ea", 144 | "completed": false 145 | }, 146 | { 147 | "userId": 2, 148 | "id": 25, 149 | "title": "voluptas quo tenetur perspiciatis explicabo natus", 150 | "completed": true 151 | }, 152 | { 153 | "userId": 2, 154 | "id": 26, 155 | "title": "aliquam aut quasi", 156 | "completed": true 157 | }, 158 | { 159 | "userId": 2, 160 | "id": 27, 161 | "title": "veritatis pariatur delectus", 162 | "completed": true 163 | }, 164 | { 165 | "userId": 2, 166 | "id": 28, 167 | "title": "nesciunt totam sit blanditiis sit", 168 | "completed": false 169 | }, 170 | { 171 | "userId": 2, 172 | "id": 29, 173 | "title": "laborum aut in quam", 174 | "completed": false 175 | }, 176 | { 177 | "userId": 2, 178 | "id": 30, 179 | "title": "nemo perspiciatis repellat ut dolor libero commodi blanditiis omnis", 180 | "completed": true 181 | }, 182 | { 183 | "userId": 2, 184 | "id": 31, 185 | "title": "repudiandae totam in est sint facere fuga", 186 | "completed": false 187 | }, 188 | { 189 | "userId": 2, 190 | "id": 32, 191 | "title": "earum doloribus ea doloremque quis", 192 | "completed": false 193 | }, 194 | { 195 | "userId": 2, 196 | "id": 33, 197 | "title": "sint sit aut vero", 198 | "completed": false 199 | }, 200 | { 201 | "userId": 2, 202 | "id": 34, 203 | "title": "porro aut necessitatibus eaque distinctio", 204 | "completed": false 205 | }, 206 | { 207 | "userId": 2, 208 | "id": 35, 209 | "title": "repellendus veritatis molestias dicta incidunt", 210 | "completed": true 211 | }, 212 | { 213 | "userId": 2, 214 | "id": 36, 215 | "title": "excepturi deleniti adipisci voluptatem et neque optio illum ad", 216 | "completed": true 217 | }, 218 | { 219 | "userId": 2, 220 | "id": 37, 221 | "title": "sunt cum tempora", 222 | "completed": false 223 | }, 224 | { 225 | "userId": 2, 226 | "id": 38, 227 | "title": "totam quia non", 228 | "completed": false 229 | }, 230 | { 231 | "userId": 2, 232 | "id": 39, 233 | "title": "doloremque quibusdam asperiores libero corrupti illum qui omnis", 234 | "completed": false 235 | }, 236 | { 237 | "userId": 2, 238 | "id": 40, 239 | "title": "totam atque quo nesciunt", 240 | "completed": true 241 | }, 242 | { 243 | "userId": 3, 244 | "id": 41, 245 | "title": "aliquid amet impedit consequatur aspernatur placeat eaque fugiat suscipit", 246 | "completed": false 247 | }, 248 | { 249 | "userId": 3, 250 | "id": 42, 251 | "title": "rerum perferendis error quia ut eveniet", 252 | "completed": false 253 | }, 254 | { 255 | "userId": 3, 256 | "id": 43, 257 | "title": "tempore ut sint quis recusandae", 258 | "completed": true 259 | }, 260 | { 261 | "userId": 3, 262 | "id": 44, 263 | "title": "cum debitis quis accusamus doloremque ipsa natus sapiente omnis", 264 | "completed": true 265 | }, 266 | { 267 | "userId": 3, 268 | "id": 45, 269 | "title": "velit soluta adipisci molestias reiciendis harum", 270 | "completed": false 271 | }, 272 | { 273 | "userId": 3, 274 | "id": 46, 275 | "title": "vel voluptatem repellat nihil placeat corporis", 276 | "completed": false 277 | }, 278 | { 279 | "userId": 3, 280 | "id": 47, 281 | "title": "nam qui rerum fugiat accusamus", 282 | "completed": false 283 | }, 284 | { 285 | "userId": 3, 286 | "id": 48, 287 | "title": "sit reprehenderit omnis quia", 288 | "completed": false 289 | }, 290 | { 291 | "userId": 3, 292 | "id": 49, 293 | "title": "ut necessitatibus aut maiores debitis officia blanditiis velit et", 294 | "completed": false 295 | }, 296 | { 297 | "userId": 3, 298 | "id": 50, 299 | "title": "cupiditate necessitatibus ullam aut quis dolor voluptate", 300 | "completed": true 301 | }, 302 | { 303 | "userId": 3, 304 | "id": 51, 305 | "title": "distinctio exercitationem ab doloribus", 306 | "completed": false 307 | }, 308 | { 309 | "userId": 3, 310 | "id": 52, 311 | "title": "nesciunt dolorum quis recusandae ad pariatur ratione", 312 | "completed": false 313 | }, 314 | { 315 | "userId": 3, 316 | "id": 53, 317 | "title": "qui labore est occaecati recusandae aliquid quam", 318 | "completed": false 319 | }, 320 | { 321 | "userId": 3, 322 | "id": 54, 323 | "title": "quis et est ut voluptate quam dolor", 324 | "completed": true 325 | }, 326 | { 327 | "userId": 3, 328 | "id": 55, 329 | "title": "voluptatum omnis minima qui occaecati provident nulla voluptatem ratione", 330 | "completed": true 331 | }, 332 | { 333 | "userId": 3, 334 | "id": 56, 335 | "title": "deleniti ea temporibus enim", 336 | "completed": true 337 | }, 338 | { 339 | "userId": 3, 340 | "id": 57, 341 | "title": "pariatur et magnam ea doloribus similique voluptatem rerum quia", 342 | "completed": false 343 | }, 344 | { 345 | "userId": 3, 346 | "id": 58, 347 | "title": "est dicta totam qui explicabo doloribus qui dignissimos", 348 | "completed": false 349 | }, 350 | { 351 | "userId": 3, 352 | "id": 59, 353 | "title": "perspiciatis velit id laborum placeat iusto et aliquam odio", 354 | "completed": false 355 | }, 356 | { 357 | "userId": 3, 358 | "id": 60, 359 | "title": "et sequi qui architecto ut adipisci", 360 | "completed": true 361 | }, 362 | { 363 | "userId": 4, 364 | "id": 61, 365 | "title": "odit optio omnis qui sunt", 366 | "completed": true 367 | }, 368 | { 369 | "userId": 4, 370 | "id": 62, 371 | "title": "et placeat et tempore aspernatur sint numquam", 372 | "completed": false 373 | }, 374 | { 375 | "userId": 4, 376 | "id": 63, 377 | "title": "doloremque aut dolores quidem fuga qui nulla", 378 | "completed": true 379 | }, 380 | { 381 | "userId": 4, 382 | "id": 64, 383 | "title": "voluptas consequatur qui ut quia magnam nemo esse", 384 | "completed": false 385 | }, 386 | { 387 | "userId": 4, 388 | "id": 65, 389 | "title": "fugiat pariatur ratione ut asperiores necessitatibus magni", 390 | "completed": false 391 | }, 392 | { 393 | "userId": 4, 394 | "id": 66, 395 | "title": "rerum eum molestias autem voluptatum sit optio", 396 | "completed": false 397 | }, 398 | { 399 | "userId": 4, 400 | "id": 67, 401 | "title": "quia voluptatibus voluptatem quos similique maiores repellat", 402 | "completed": false 403 | }, 404 | { 405 | "userId": 4, 406 | "id": 68, 407 | "title": "aut id perspiciatis voluptatem iusto", 408 | "completed": false 409 | }, 410 | { 411 | "userId": 4, 412 | "id": 69, 413 | "title": "doloribus sint dolorum ab adipisci itaque dignissimos aliquam suscipit", 414 | "completed": false 415 | }, 416 | { 417 | "userId": 4, 418 | "id": 70, 419 | "title": "ut sequi accusantium et mollitia delectus sunt", 420 | "completed": false 421 | }, 422 | { 423 | "userId": 4, 424 | "id": 71, 425 | "title": "aut velit saepe ullam", 426 | "completed": false 427 | }, 428 | { 429 | "userId": 4, 430 | "id": 72, 431 | "title": "praesentium facilis facere quis harum voluptatibus voluptatem eum", 432 | "completed": false 433 | }, 434 | { 435 | "userId": 4, 436 | "id": 73, 437 | "title": "sint amet quia totam corporis qui exercitationem commodi", 438 | "completed": true 439 | }, 440 | { 441 | "userId": 4, 442 | "id": 74, 443 | "title": "expedita tempore nobis eveniet laborum maiores", 444 | "completed": false 445 | }, 446 | { 447 | "userId": 4, 448 | "id": 75, 449 | "title": "occaecati adipisci est possimus totam", 450 | "completed": false 451 | }, 452 | { 453 | "userId": 4, 454 | "id": 76, 455 | "title": "sequi dolorem sed", 456 | "completed": true 457 | }, 458 | { 459 | "userId": 4, 460 | "id": 77, 461 | "title": "maiores aut nesciunt delectus exercitationem vel assumenda eligendi at", 462 | "completed": false 463 | }, 464 | { 465 | "userId": 4, 466 | "id": 78, 467 | "title": "reiciendis est magnam amet nemo iste recusandae impedit quaerat", 468 | "completed": false 469 | }, 470 | { 471 | "userId": 4, 472 | "id": 79, 473 | "title": "eum ipsa maxime ut", 474 | "completed": true 475 | }, 476 | { 477 | "userId": 4, 478 | "id": 80, 479 | "title": "tempore molestias dolores rerum sequi voluptates ipsum consequatur", 480 | "completed": true 481 | }, 482 | { 483 | "userId": 5, 484 | "id": 81, 485 | "title": "suscipit qui totam", 486 | "completed": true 487 | }, 488 | { 489 | "userId": 5, 490 | "id": 82, 491 | "title": "voluptates eum voluptas et dicta", 492 | "completed": false 493 | }, 494 | { 495 | "userId": 5, 496 | "id": 83, 497 | "title": "quidem at rerum quis ex aut sit quam", 498 | "completed": true 499 | }, 500 | { 501 | "userId": 5, 502 | "id": 84, 503 | "title": "sunt veritatis ut voluptate", 504 | "completed": false 505 | }, 506 | { 507 | "userId": 5, 508 | "id": 85, 509 | "title": "et quia ad iste a", 510 | "completed": true 511 | }, 512 | { 513 | "userId": 5, 514 | "id": 86, 515 | "title": "incidunt ut saepe autem", 516 | "completed": true 517 | }, 518 | { 519 | "userId": 5, 520 | "id": 87, 521 | "title": "laudantium quae eligendi consequatur quia et vero autem", 522 | "completed": true 523 | }, 524 | { 525 | "userId": 5, 526 | "id": 88, 527 | "title": "vitae aut excepturi laboriosam sint aliquam et et accusantium", 528 | "completed": false 529 | }, 530 | { 531 | "userId": 5, 532 | "id": 89, 533 | "title": "sequi ut omnis et", 534 | "completed": true 535 | }, 536 | { 537 | "userId": 5, 538 | "id": 90, 539 | "title": "molestiae nisi accusantium tenetur dolorem et", 540 | "completed": true 541 | }, 542 | { 543 | "userId": 5, 544 | "id": 91, 545 | "title": "nulla quis consequatur saepe qui id expedita", 546 | "completed": true 547 | }, 548 | { 549 | "userId": 5, 550 | "id": 92, 551 | "title": "in omnis laboriosam", 552 | "completed": true 553 | }, 554 | { 555 | "userId": 5, 556 | "id": 93, 557 | "title": "odio iure consequatur molestiae quibusdam necessitatibus quia sint", 558 | "completed": true 559 | }, 560 | { 561 | "userId": 5, 562 | "id": 94, 563 | "title": "facilis modi saepe mollitia", 564 | "completed": false 565 | }, 566 | { 567 | "userId": 5, 568 | "id": 95, 569 | "title": "vel nihil et molestiae iusto assumenda nemo quo ut", 570 | "completed": true 571 | }, 572 | { 573 | "userId": 5, 574 | "id": 96, 575 | "title": "nobis suscipit ducimus enim asperiores voluptas", 576 | "completed": false 577 | }, 578 | { 579 | "userId": 5, 580 | "id": 97, 581 | "title": "dolorum laboriosam eos qui iure aliquam", 582 | "completed": false 583 | }, 584 | { 585 | "userId": 5, 586 | "id": 98, 587 | "title": "debitis accusantium ut quo facilis nihil quis sapiente necessitatibus", 588 | "completed": true 589 | }, 590 | { 591 | "userId": 5, 592 | "id": 99, 593 | "title": "neque voluptates ratione", 594 | "completed": false 595 | }, 596 | { 597 | "userId": 5, 598 | "id": 100, 599 | "title": "excepturi a et neque qui expedita vel voluptate", 600 | "completed": false 601 | }, 602 | { 603 | "userId": 6, 604 | "id": 101, 605 | "title": "explicabo enim cumque porro aperiam occaecati minima", 606 | "completed": false 607 | }, 608 | { 609 | "userId": 6, 610 | "id": 102, 611 | "title": "sed ab consequatur", 612 | "completed": false 613 | }, 614 | { 615 | "userId": 6, 616 | "id": 103, 617 | "title": "non sunt delectus illo nulla tenetur enim omnis", 618 | "completed": false 619 | }, 620 | { 621 | "userId": 6, 622 | "id": 104, 623 | "title": "excepturi non laudantium quo", 624 | "completed": false 625 | }, 626 | { 627 | "userId": 6, 628 | "id": 105, 629 | "title": "totam quia dolorem et illum repellat voluptas optio", 630 | "completed": true 631 | }, 632 | { 633 | "userId": 6, 634 | "id": 106, 635 | "title": "ad illo quis voluptatem temporibus", 636 | "completed": true 637 | }, 638 | { 639 | "userId": 6, 640 | "id": 107, 641 | "title": "praesentium facilis omnis laudantium fugit ad iusto nihil nesciunt", 642 | "completed": false 643 | }, 644 | { 645 | "userId": 6, 646 | "id": 108, 647 | "title": "a eos eaque nihil et exercitationem incidunt delectus", 648 | "completed": true 649 | }, 650 | { 651 | "userId": 6, 652 | "id": 109, 653 | "title": "autem temporibus harum quisquam in culpa", 654 | "completed": true 655 | }, 656 | { 657 | "userId": 6, 658 | "id": 110, 659 | "title": "aut aut ea corporis", 660 | "completed": true 661 | }, 662 | { 663 | "userId": 6, 664 | "id": 111, 665 | "title": "magni accusantium labore et id quis provident", 666 | "completed": false 667 | }, 668 | { 669 | "userId": 6, 670 | "id": 112, 671 | "title": "consectetur impedit quisquam qui deserunt non rerum consequuntur eius", 672 | "completed": false 673 | }, 674 | { 675 | "userId": 6, 676 | "id": 113, 677 | "title": "quia atque aliquam sunt impedit voluptatum rerum assumenda nisi", 678 | "completed": false 679 | }, 680 | { 681 | "userId": 6, 682 | "id": 114, 683 | "title": "cupiditate quos possimus corporis quisquam exercitationem beatae", 684 | "completed": false 685 | }, 686 | { 687 | "userId": 6, 688 | "id": 115, 689 | "title": "sed et ea eum", 690 | "completed": false 691 | }, 692 | { 693 | "userId": 6, 694 | "id": 116, 695 | "title": "ipsa dolores vel facilis ut", 696 | "completed": true 697 | }, 698 | { 699 | "userId": 6, 700 | "id": 117, 701 | "title": "sequi quae est et qui qui eveniet asperiores", 702 | "completed": false 703 | }, 704 | { 705 | "userId": 6, 706 | "id": 118, 707 | "title": "quia modi consequatur vero fugiat", 708 | "completed": false 709 | }, 710 | { 711 | "userId": 6, 712 | "id": 119, 713 | "title": "corporis ducimus ea perspiciatis iste", 714 | "completed": false 715 | }, 716 | { 717 | "userId": 6, 718 | "id": 120, 719 | "title": "dolorem laboriosam vel voluptas et aliquam quasi", 720 | "completed": false 721 | }, 722 | { 723 | "userId": 7, 724 | "id": 121, 725 | "title": "inventore aut nihil minima laudantium hic qui omnis", 726 | "completed": true 727 | }, 728 | { 729 | "userId": 7, 730 | "id": 122, 731 | "title": "provident aut nobis culpa", 732 | "completed": true 733 | }, 734 | { 735 | "userId": 7, 736 | "id": 123, 737 | "title": "esse et quis iste est earum aut impedit", 738 | "completed": false 739 | }, 740 | { 741 | "userId": 7, 742 | "id": 124, 743 | "title": "qui consectetur id", 744 | "completed": false 745 | }, 746 | { 747 | "userId": 7, 748 | "id": 125, 749 | "title": "aut quasi autem iste tempore illum possimus", 750 | "completed": false 751 | }, 752 | { 753 | "userId": 7, 754 | "id": 126, 755 | "title": "ut asperiores perspiciatis veniam ipsum rerum saepe", 756 | "completed": true 757 | }, 758 | { 759 | "userId": 7, 760 | "id": 127, 761 | "title": "voluptatem libero consectetur rerum ut", 762 | "completed": true 763 | }, 764 | { 765 | "userId": 7, 766 | "id": 128, 767 | "title": "eius omnis est qui voluptatem autem", 768 | "completed": false 769 | }, 770 | { 771 | "userId": 7, 772 | "id": 129, 773 | "title": "rerum culpa quis harum", 774 | "completed": false 775 | }, 776 | { 777 | "userId": 7, 778 | "id": 130, 779 | "title": "nulla aliquid eveniet harum laborum libero alias ut unde", 780 | "completed": true 781 | }, 782 | { 783 | "userId": 7, 784 | "id": 131, 785 | "title": "qui ea incidunt quis", 786 | "completed": false 787 | }, 788 | { 789 | "userId": 7, 790 | "id": 132, 791 | "title": "qui molestiae voluptatibus velit iure harum quisquam", 792 | "completed": true 793 | }, 794 | { 795 | "userId": 7, 796 | "id": 133, 797 | "title": "et labore eos enim rerum consequatur sunt", 798 | "completed": true 799 | }, 800 | { 801 | "userId": 7, 802 | "id": 134, 803 | "title": "molestiae doloribus et laborum quod ea", 804 | "completed": false 805 | }, 806 | { 807 | "userId": 7, 808 | "id": 135, 809 | "title": "facere ipsa nam eum voluptates reiciendis vero qui", 810 | "completed": false 811 | }, 812 | { 813 | "userId": 7, 814 | "id": 136, 815 | "title": "asperiores illo tempora fuga sed ut quasi adipisci", 816 | "completed": false 817 | }, 818 | { 819 | "userId": 7, 820 | "id": 137, 821 | "title": "qui sit non", 822 | "completed": false 823 | }, 824 | { 825 | "userId": 7, 826 | "id": 138, 827 | "title": "placeat minima consequatur rem qui ut", 828 | "completed": true 829 | }, 830 | { 831 | "userId": 7, 832 | "id": 139, 833 | "title": "consequatur doloribus id possimus voluptas a voluptatem", 834 | "completed": false 835 | }, 836 | { 837 | "userId": 7, 838 | "id": 140, 839 | "title": "aut consectetur in blanditiis deserunt quia sed laboriosam", 840 | "completed": true 841 | }, 842 | { 843 | "userId": 8, 844 | "id": 141, 845 | "title": "explicabo consectetur debitis voluptates quas quae culpa rerum non", 846 | "completed": true 847 | }, 848 | { 849 | "userId": 8, 850 | "id": 142, 851 | "title": "maiores accusantium architecto necessitatibus reiciendis ea aut", 852 | "completed": true 853 | }, 854 | { 855 | "userId": 8, 856 | "id": 143, 857 | "title": "eum non recusandae cupiditate animi", 858 | "completed": false 859 | }, 860 | { 861 | "userId": 8, 862 | "id": 144, 863 | "title": "ut eum exercitationem sint", 864 | "completed": false 865 | }, 866 | { 867 | "userId": 8, 868 | "id": 145, 869 | "title": "beatae qui ullam incidunt voluptatem non nisi aliquam", 870 | "completed": false 871 | }, 872 | { 873 | "userId": 8, 874 | "id": 146, 875 | "title": "molestiae suscipit ratione nihil odio libero impedit vero totam", 876 | "completed": true 877 | }, 878 | { 879 | "userId": 8, 880 | "id": 147, 881 | "title": "eum itaque quod reprehenderit et facilis dolor autem ut", 882 | "completed": true 883 | }, 884 | { 885 | "userId": 8, 886 | "id": 148, 887 | "title": "esse quas et quo quasi exercitationem", 888 | "completed": false 889 | }, 890 | { 891 | "userId": 8, 892 | "id": 149, 893 | "title": "animi voluptas quod perferendis est", 894 | "completed": false 895 | }, 896 | { 897 | "userId": 8, 898 | "id": 150, 899 | "title": "eos amet tempore laudantium fugit a", 900 | "completed": false 901 | }, 902 | { 903 | "userId": 8, 904 | "id": 151, 905 | "title": "accusamus adipisci dicta qui quo ea explicabo sed vero", 906 | "completed": true 907 | }, 908 | { 909 | "userId": 8, 910 | "id": 152, 911 | "title": "odit eligendi recusandae doloremque cumque non", 912 | "completed": false 913 | }, 914 | { 915 | "userId": 8, 916 | "id": 153, 917 | "title": "ea aperiam consequatur qui repellat eos", 918 | "completed": false 919 | }, 920 | { 921 | "userId": 8, 922 | "id": 154, 923 | "title": "rerum non ex sapiente", 924 | "completed": true 925 | }, 926 | { 927 | "userId": 8, 928 | "id": 155, 929 | "title": "voluptatem nobis consequatur et assumenda magnam", 930 | "completed": true 931 | }, 932 | { 933 | "userId": 8, 934 | "id": 156, 935 | "title": "nam quia quia nulla repellat assumenda quibusdam sit nobis", 936 | "completed": true 937 | }, 938 | { 939 | "userId": 8, 940 | "id": 157, 941 | "title": "dolorem veniam quisquam deserunt repellendus", 942 | "completed": true 943 | }, 944 | { 945 | "userId": 8, 946 | "id": 158, 947 | "title": "debitis vitae delectus et harum accusamus aut deleniti a", 948 | "completed": true 949 | }, 950 | { 951 | "userId": 8, 952 | "id": 159, 953 | "title": "debitis adipisci quibusdam aliquam sed dolore ea praesentium nobis", 954 | "completed": true 955 | }, 956 | { 957 | "userId": 8, 958 | "id": 160, 959 | "title": "et praesentium aliquam est", 960 | "completed": false 961 | }, 962 | { 963 | "userId": 9, 964 | "id": 161, 965 | "title": "ex hic consequuntur earum omnis alias ut occaecati culpa", 966 | "completed": true 967 | }, 968 | { 969 | "userId": 9, 970 | "id": 162, 971 | "title": "omnis laboriosam molestias animi sunt dolore", 972 | "completed": true 973 | }, 974 | { 975 | "userId": 9, 976 | "id": 163, 977 | "title": "natus corrupti maxime laudantium et voluptatem laboriosam odit", 978 | "completed": false 979 | }, 980 | { 981 | "userId": 9, 982 | "id": 164, 983 | "title": "reprehenderit quos aut aut consequatur est sed", 984 | "completed": false 985 | }, 986 | { 987 | "userId": 9, 988 | "id": 165, 989 | "title": "fugiat perferendis sed aut quidem", 990 | "completed": false 991 | }, 992 | { 993 | "userId": 9, 994 | "id": 166, 995 | "title": "quos quo possimus suscipit minima ut", 996 | "completed": false 997 | }, 998 | { 999 | "userId": 9, 1000 | "id": 167, 1001 | "title": "et quis minus quo a asperiores molestiae", 1002 | "completed": false 1003 | }, 1004 | { 1005 | "userId": 9, 1006 | "id": 168, 1007 | "title": "recusandae quia qui sunt libero", 1008 | "completed": false 1009 | }, 1010 | { 1011 | "userId": 9, 1012 | "id": 169, 1013 | "title": "ea odio perferendis officiis", 1014 | "completed": true 1015 | }, 1016 | { 1017 | "userId": 9, 1018 | "id": 170, 1019 | "title": "quisquam aliquam quia doloribus aut", 1020 | "completed": false 1021 | }, 1022 | { 1023 | "userId": 9, 1024 | "id": 171, 1025 | "title": "fugiat aut voluptatibus corrupti deleniti velit iste odio", 1026 | "completed": true 1027 | }, 1028 | { 1029 | "userId": 9, 1030 | "id": 172, 1031 | "title": "et provident amet rerum consectetur et voluptatum", 1032 | "completed": false 1033 | }, 1034 | { 1035 | "userId": 9, 1036 | "id": 173, 1037 | "title": "harum ad aperiam quis", 1038 | "completed": false 1039 | }, 1040 | { 1041 | "userId": 9, 1042 | "id": 174, 1043 | "title": "similique aut quo", 1044 | "completed": false 1045 | }, 1046 | { 1047 | "userId": 9, 1048 | "id": 175, 1049 | "title": "laudantium eius officia perferendis provident perspiciatis asperiores", 1050 | "completed": true 1051 | }, 1052 | { 1053 | "userId": 9, 1054 | "id": 176, 1055 | "title": "magni soluta corrupti ut maiores rem quidem", 1056 | "completed": false 1057 | }, 1058 | { 1059 | "userId": 9, 1060 | "id": 177, 1061 | "title": "et placeat temporibus voluptas est tempora quos quibusdam", 1062 | "completed": false 1063 | }, 1064 | { 1065 | "userId": 9, 1066 | "id": 178, 1067 | "title": "nesciunt itaque commodi tempore", 1068 | "completed": true 1069 | }, 1070 | { 1071 | "userId": 9, 1072 | "id": 179, 1073 | "title": "omnis consequuntur cupiditate impedit itaque ipsam quo", 1074 | "completed": true 1075 | }, 1076 | { 1077 | "userId": 9, 1078 | "id": 180, 1079 | "title": "debitis nisi et dolorem repellat et", 1080 | "completed": true 1081 | }, 1082 | { 1083 | "userId": 10, 1084 | "id": 181, 1085 | "title": "ut cupiditate sequi aliquam fuga maiores", 1086 | "completed": false 1087 | }, 1088 | { 1089 | "userId": 10, 1090 | "id": 182, 1091 | "title": "inventore saepe cumque et aut illum enim", 1092 | "completed": true 1093 | }, 1094 | { 1095 | "userId": 10, 1096 | "id": 183, 1097 | "title": "omnis nulla eum aliquam distinctio", 1098 | "completed": true 1099 | }, 1100 | { 1101 | "userId": 10, 1102 | "id": 184, 1103 | "title": "molestias modi perferendis perspiciatis", 1104 | "completed": false 1105 | }, 1106 | { 1107 | "userId": 10, 1108 | "id": 185, 1109 | "title": "voluptates dignissimos sed doloribus animi quaerat aut", 1110 | "completed": false 1111 | }, 1112 | { 1113 | "userId": 10, 1114 | "id": 186, 1115 | "title": "explicabo odio est et", 1116 | "completed": false 1117 | }, 1118 | { 1119 | "userId": 10, 1120 | "id": 187, 1121 | "title": "consequuntur animi possimus", 1122 | "completed": false 1123 | }, 1124 | { 1125 | "userId": 10, 1126 | "id": 188, 1127 | "title": "vel non beatae est", 1128 | "completed": true 1129 | }, 1130 | { 1131 | "userId": 10, 1132 | "id": 189, 1133 | "title": "culpa eius et voluptatem et", 1134 | "completed": true 1135 | }, 1136 | { 1137 | "userId": 10, 1138 | "id": 190, 1139 | "title": "accusamus sint iusto et voluptatem exercitationem", 1140 | "completed": true 1141 | }, 1142 | { 1143 | "userId": 10, 1144 | "id": 191, 1145 | "title": "temporibus atque distinctio omnis eius impedit tempore molestias pariatur", 1146 | "completed": true 1147 | }, 1148 | { 1149 | "userId": 10, 1150 | "id": 192, 1151 | "title": "ut quas possimus exercitationem sint voluptates", 1152 | "completed": false 1153 | }, 1154 | { 1155 | "userId": 10, 1156 | "id": 193, 1157 | "title": "rerum debitis voluptatem qui eveniet tempora distinctio a", 1158 | "completed": true 1159 | }, 1160 | { 1161 | "userId": 10, 1162 | "id": 194, 1163 | "title": "sed ut vero sit molestiae", 1164 | "completed": false 1165 | }, 1166 | { 1167 | "userId": 10, 1168 | "id": 195, 1169 | "title": "rerum ex veniam mollitia voluptatibus pariatur", 1170 | "completed": true 1171 | }, 1172 | { 1173 | "userId": 10, 1174 | "id": 196, 1175 | "title": "consequuntur aut ut fugit similique", 1176 | "completed": true 1177 | }, 1178 | { 1179 | "userId": 10, 1180 | "id": 197, 1181 | "title": "dignissimos quo nobis earum saepe", 1182 | "completed": true 1183 | }, 1184 | { 1185 | "userId": 10, 1186 | "id": 198, 1187 | "title": "quis eius est sint explicabo", 1188 | "completed": true 1189 | }, 1190 | { 1191 | "userId": 10, 1192 | "id": 199, 1193 | "title": "numquam repellendus a magnam", 1194 | "completed": true 1195 | }, 1196 | { 1197 | "userId": 10, 1198 | "id": 200, 1199 | "title": "ipsam aperiam voluptates qui", 1200 | "completed": false 1201 | } 1202 | ] -------------------------------------------------------------------------------- /benchmarks/e2e/src/data/todos.proto: -------------------------------------------------------------------------------- 1 | // todos.proto 2 | package todos; 3 | syntax = "proto3"; 4 | 5 | message TodoMessage { 6 | int32 id = 3; 7 | int32 userId = 2; 8 | string title = 1; 9 | bool completed = 4; 10 | } 11 | 12 | message TodosMessage { 13 | repeated TodoMessage objects = 1; 14 | } 15 | -------------------------------------------------------------------------------- /benchmarks/e2e/src/index.ts: -------------------------------------------------------------------------------- 1 | import fastify from 'fastify' 2 | import { readFile } from 'fs/promises' 3 | import { createSer } from 'seqproto' 4 | import avro from 'avsc' 5 | import fStatic from '@fastify/static' 6 | import path from 'node:path' 7 | 8 | // Load data 9 | 10 | interface Todo { 11 | userId: number 12 | id: number 13 | title: string 14 | completed: boolean 15 | } 16 | const todos: Todo[] = JSON.parse(await readFile('./src/data/todos.json', 'utf8')) 17 | 18 | // Avro schema 19 | const type = avro.Type.forSchema({ 20 | name: 'Todos', 21 | type: 'array', 22 | items: { 23 | name: 'Todo', 24 | type: 'record', 25 | fields: [ 26 | { name: 'userId', type: 'int' }, 27 | { name: 'id', type: 'int' }, 28 | { name: 'title', type: 'string' }, 29 | { name: 'completed', type: 'boolean' } 30 | ] 31 | } 32 | }) 33 | 34 | // seqproto serializer 35 | const ser = createSer() 36 | 37 | const server = fastify() 38 | 39 | server.register(fStatic, { 40 | root: path.resolve('public'), 41 | prefix: '/public/', // optional: default '/' 42 | cacheControl: true, // optional: default true 43 | }) 44 | 45 | server.get('/seqproto.js', async (request, reply) => { 46 | reply.type('application/javascript') 47 | return readFile('./node_modules/seqproto/dist/esm/index.js', 'utf8') 48 | }) 49 | 50 | server.get('/todos', { 51 | schema: { 52 | response: { 53 | 200: { 54 | type: 'array', 55 | items: { 56 | type: 'object', 57 | properties: { 58 | userId: { type: 'number' }, 59 | id: { type: 'number' }, 60 | title: { type: 'string' }, 61 | completed: { type: 'boolean' } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | }, async (request) => { 68 | if (request.headers.accept === 'application/seqproto') { 69 | ser.reset() 70 | ser.serializeArray(todos, (ser, todo) => { 71 | ser.serializeUInt32(todo.id) 72 | ser.serializeUInt32(todo.userId) 73 | ser.serializeString(todo.title) 74 | ser.serializeBoolean(todo.completed) 75 | }) 76 | const arrBuffer = ser.getBuffer() 77 | return new Uint8Array(arrBuffer) 78 | } 79 | if (request.headers.accept === 'application/avro') { 80 | return type.toBuffer(todos) 81 | } 82 | 83 | return todos 84 | }) 85 | 86 | const d = await server.listen({ port: 3000 }) 87 | console.log(d) 88 | 89 | -------------------------------------------------------------------------------- /benchmarks/isolated/data/todos.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": 1, 4 | "id": 1, 5 | "title": "delectus aut autem", 6 | "completed": false 7 | }, 8 | { 9 | "userId": 1, 10 | "id": 2, 11 | "title": "quis ut nam facilis et officia qui", 12 | "completed": false 13 | }, 14 | { 15 | "userId": 1, 16 | "id": 3, 17 | "title": "fugiat veniam minus", 18 | "completed": false 19 | }, 20 | { 21 | "userId": 1, 22 | "id": 4, 23 | "title": "et porro tempora", 24 | "completed": true 25 | }, 26 | { 27 | "userId": 1, 28 | "id": 5, 29 | "title": "laboriosam mollitia et enim quasi adipisci quia provident illum", 30 | "completed": false 31 | }, 32 | { 33 | "userId": 1, 34 | "id": 6, 35 | "title": "qui ullam ratione quibusdam voluptatem quia omnis", 36 | "completed": false 37 | }, 38 | { 39 | "userId": 1, 40 | "id": 7, 41 | "title": "illo expedita consequatur quia in", 42 | "completed": false 43 | }, 44 | { 45 | "userId": 1, 46 | "id": 8, 47 | "title": "quo adipisci enim quam ut ab", 48 | "completed": true 49 | }, 50 | { 51 | "userId": 1, 52 | "id": 9, 53 | "title": "molestiae perspiciatis ipsa", 54 | "completed": false 55 | }, 56 | { 57 | "userId": 1, 58 | "id": 10, 59 | "title": "illo est ratione doloremque quia maiores aut", 60 | "completed": true 61 | }, 62 | { 63 | "userId": 1, 64 | "id": 11, 65 | "title": "vero rerum temporibus dolor", 66 | "completed": true 67 | }, 68 | { 69 | "userId": 1, 70 | "id": 12, 71 | "title": "ipsa repellendus fugit nisi", 72 | "completed": true 73 | }, 74 | { 75 | "userId": 1, 76 | "id": 13, 77 | "title": "et doloremque nulla", 78 | "completed": false 79 | }, 80 | { 81 | "userId": 1, 82 | "id": 14, 83 | "title": "repellendus sunt dolores architecto voluptatum", 84 | "completed": true 85 | }, 86 | { 87 | "userId": 1, 88 | "id": 15, 89 | "title": "ab voluptatum amet voluptas", 90 | "completed": true 91 | }, 92 | { 93 | "userId": 1, 94 | "id": 16, 95 | "title": "accusamus eos facilis sint et aut voluptatem", 96 | "completed": true 97 | }, 98 | { 99 | "userId": 1, 100 | "id": 17, 101 | "title": "quo laboriosam deleniti aut qui", 102 | "completed": true 103 | }, 104 | { 105 | "userId": 1, 106 | "id": 18, 107 | "title": "dolorum est consequatur ea mollitia in culpa", 108 | "completed": false 109 | }, 110 | { 111 | "userId": 1, 112 | "id": 19, 113 | "title": "molestiae ipsa aut voluptatibus pariatur dolor nihil", 114 | "completed": true 115 | }, 116 | { 117 | "userId": 1, 118 | "id": 20, 119 | "title": "ullam nobis libero sapiente ad optio sint", 120 | "completed": true 121 | }, 122 | { 123 | "userId": 2, 124 | "id": 21, 125 | "title": "suscipit repellat esse quibusdam voluptatem incidunt", 126 | "completed": false 127 | }, 128 | { 129 | "userId": 2, 130 | "id": 22, 131 | "title": "distinctio vitae autem nihil ut molestias quo", 132 | "completed": true 133 | }, 134 | { 135 | "userId": 2, 136 | "id": 23, 137 | "title": "et itaque necessitatibus maxime molestiae qui quas velit", 138 | "completed": false 139 | }, 140 | { 141 | "userId": 2, 142 | "id": 24, 143 | "title": "adipisci non ad dicta qui amet quaerat doloribus ea", 144 | "completed": false 145 | }, 146 | { 147 | "userId": 2, 148 | "id": 25, 149 | "title": "voluptas quo tenetur perspiciatis explicabo natus", 150 | "completed": true 151 | }, 152 | { 153 | "userId": 2, 154 | "id": 26, 155 | "title": "aliquam aut quasi", 156 | "completed": true 157 | }, 158 | { 159 | "userId": 2, 160 | "id": 27, 161 | "title": "veritatis pariatur delectus", 162 | "completed": true 163 | }, 164 | { 165 | "userId": 2, 166 | "id": 28, 167 | "title": "nesciunt totam sit blanditiis sit", 168 | "completed": false 169 | }, 170 | { 171 | "userId": 2, 172 | "id": 29, 173 | "title": "laborum aut in quam", 174 | "completed": false 175 | }, 176 | { 177 | "userId": 2, 178 | "id": 30, 179 | "title": "nemo perspiciatis repellat ut dolor libero commodi blanditiis omnis", 180 | "completed": true 181 | }, 182 | { 183 | "userId": 2, 184 | "id": 31, 185 | "title": "repudiandae totam in est sint facere fuga", 186 | "completed": false 187 | }, 188 | { 189 | "userId": 2, 190 | "id": 32, 191 | "title": "earum doloribus ea doloremque quis", 192 | "completed": false 193 | }, 194 | { 195 | "userId": 2, 196 | "id": 33, 197 | "title": "sint sit aut vero", 198 | "completed": false 199 | }, 200 | { 201 | "userId": 2, 202 | "id": 34, 203 | "title": "porro aut necessitatibus eaque distinctio", 204 | "completed": false 205 | }, 206 | { 207 | "userId": 2, 208 | "id": 35, 209 | "title": "repellendus veritatis molestias dicta incidunt", 210 | "completed": true 211 | }, 212 | { 213 | "userId": 2, 214 | "id": 36, 215 | "title": "excepturi deleniti adipisci voluptatem et neque optio illum ad", 216 | "completed": true 217 | }, 218 | { 219 | "userId": 2, 220 | "id": 37, 221 | "title": "sunt cum tempora", 222 | "completed": false 223 | }, 224 | { 225 | "userId": 2, 226 | "id": 38, 227 | "title": "totam quia non", 228 | "completed": false 229 | }, 230 | { 231 | "userId": 2, 232 | "id": 39, 233 | "title": "doloremque quibusdam asperiores libero corrupti illum qui omnis", 234 | "completed": false 235 | }, 236 | { 237 | "userId": 2, 238 | "id": 40, 239 | "title": "totam atque quo nesciunt", 240 | "completed": true 241 | }, 242 | { 243 | "userId": 3, 244 | "id": 41, 245 | "title": "aliquid amet impedit consequatur aspernatur placeat eaque fugiat suscipit", 246 | "completed": false 247 | }, 248 | { 249 | "userId": 3, 250 | "id": 42, 251 | "title": "rerum perferendis error quia ut eveniet", 252 | "completed": false 253 | }, 254 | { 255 | "userId": 3, 256 | "id": 43, 257 | "title": "tempore ut sint quis recusandae", 258 | "completed": true 259 | }, 260 | { 261 | "userId": 3, 262 | "id": 44, 263 | "title": "cum debitis quis accusamus doloremque ipsa natus sapiente omnis", 264 | "completed": true 265 | }, 266 | { 267 | "userId": 3, 268 | "id": 45, 269 | "title": "velit soluta adipisci molestias reiciendis harum", 270 | "completed": false 271 | }, 272 | { 273 | "userId": 3, 274 | "id": 46, 275 | "title": "vel voluptatem repellat nihil placeat corporis", 276 | "completed": false 277 | }, 278 | { 279 | "userId": 3, 280 | "id": 47, 281 | "title": "nam qui rerum fugiat accusamus", 282 | "completed": false 283 | }, 284 | { 285 | "userId": 3, 286 | "id": 48, 287 | "title": "sit reprehenderit omnis quia", 288 | "completed": false 289 | }, 290 | { 291 | "userId": 3, 292 | "id": 49, 293 | "title": "ut necessitatibus aut maiores debitis officia blanditiis velit et", 294 | "completed": false 295 | }, 296 | { 297 | "userId": 3, 298 | "id": 50, 299 | "title": "cupiditate necessitatibus ullam aut quis dolor voluptate", 300 | "completed": true 301 | }, 302 | { 303 | "userId": 3, 304 | "id": 51, 305 | "title": "distinctio exercitationem ab doloribus", 306 | "completed": false 307 | }, 308 | { 309 | "userId": 3, 310 | "id": 52, 311 | "title": "nesciunt dolorum quis recusandae ad pariatur ratione", 312 | "completed": false 313 | }, 314 | { 315 | "userId": 3, 316 | "id": 53, 317 | "title": "qui labore est occaecati recusandae aliquid quam", 318 | "completed": false 319 | }, 320 | { 321 | "userId": 3, 322 | "id": 54, 323 | "title": "quis et est ut voluptate quam dolor", 324 | "completed": true 325 | }, 326 | { 327 | "userId": 3, 328 | "id": 55, 329 | "title": "voluptatum omnis minima qui occaecati provident nulla voluptatem ratione", 330 | "completed": true 331 | }, 332 | { 333 | "userId": 3, 334 | "id": 56, 335 | "title": "deleniti ea temporibus enim", 336 | "completed": true 337 | }, 338 | { 339 | "userId": 3, 340 | "id": 57, 341 | "title": "pariatur et magnam ea doloribus similique voluptatem rerum quia", 342 | "completed": false 343 | }, 344 | { 345 | "userId": 3, 346 | "id": 58, 347 | "title": "est dicta totam qui explicabo doloribus qui dignissimos", 348 | "completed": false 349 | }, 350 | { 351 | "userId": 3, 352 | "id": 59, 353 | "title": "perspiciatis velit id laborum placeat iusto et aliquam odio", 354 | "completed": false 355 | }, 356 | { 357 | "userId": 3, 358 | "id": 60, 359 | "title": "et sequi qui architecto ut adipisci", 360 | "completed": true 361 | }, 362 | { 363 | "userId": 4, 364 | "id": 61, 365 | "title": "odit optio omnis qui sunt", 366 | "completed": true 367 | }, 368 | { 369 | "userId": 4, 370 | "id": 62, 371 | "title": "et placeat et tempore aspernatur sint numquam", 372 | "completed": false 373 | }, 374 | { 375 | "userId": 4, 376 | "id": 63, 377 | "title": "doloremque aut dolores quidem fuga qui nulla", 378 | "completed": true 379 | }, 380 | { 381 | "userId": 4, 382 | "id": 64, 383 | "title": "voluptas consequatur qui ut quia magnam nemo esse", 384 | "completed": false 385 | }, 386 | { 387 | "userId": 4, 388 | "id": 65, 389 | "title": "fugiat pariatur ratione ut asperiores necessitatibus magni", 390 | "completed": false 391 | }, 392 | { 393 | "userId": 4, 394 | "id": 66, 395 | "title": "rerum eum molestias autem voluptatum sit optio", 396 | "completed": false 397 | }, 398 | { 399 | "userId": 4, 400 | "id": 67, 401 | "title": "quia voluptatibus voluptatem quos similique maiores repellat", 402 | "completed": false 403 | }, 404 | { 405 | "userId": 4, 406 | "id": 68, 407 | "title": "aut id perspiciatis voluptatem iusto", 408 | "completed": false 409 | }, 410 | { 411 | "userId": 4, 412 | "id": 69, 413 | "title": "doloribus sint dolorum ab adipisci itaque dignissimos aliquam suscipit", 414 | "completed": false 415 | }, 416 | { 417 | "userId": 4, 418 | "id": 70, 419 | "title": "ut sequi accusantium et mollitia delectus sunt", 420 | "completed": false 421 | }, 422 | { 423 | "userId": 4, 424 | "id": 71, 425 | "title": "aut velit saepe ullam", 426 | "completed": false 427 | }, 428 | { 429 | "userId": 4, 430 | "id": 72, 431 | "title": "praesentium facilis facere quis harum voluptatibus voluptatem eum", 432 | "completed": false 433 | }, 434 | { 435 | "userId": 4, 436 | "id": 73, 437 | "title": "sint amet quia totam corporis qui exercitationem commodi", 438 | "completed": true 439 | }, 440 | { 441 | "userId": 4, 442 | "id": 74, 443 | "title": "expedita tempore nobis eveniet laborum maiores", 444 | "completed": false 445 | }, 446 | { 447 | "userId": 4, 448 | "id": 75, 449 | "title": "occaecati adipisci est possimus totam", 450 | "completed": false 451 | }, 452 | { 453 | "userId": 4, 454 | "id": 76, 455 | "title": "sequi dolorem sed", 456 | "completed": true 457 | }, 458 | { 459 | "userId": 4, 460 | "id": 77, 461 | "title": "maiores aut nesciunt delectus exercitationem vel assumenda eligendi at", 462 | "completed": false 463 | }, 464 | { 465 | "userId": 4, 466 | "id": 78, 467 | "title": "reiciendis est magnam amet nemo iste recusandae impedit quaerat", 468 | "completed": false 469 | }, 470 | { 471 | "userId": 4, 472 | "id": 79, 473 | "title": "eum ipsa maxime ut", 474 | "completed": true 475 | }, 476 | { 477 | "userId": 4, 478 | "id": 80, 479 | "title": "tempore molestias dolores rerum sequi voluptates ipsum consequatur", 480 | "completed": true 481 | }, 482 | { 483 | "userId": 5, 484 | "id": 81, 485 | "title": "suscipit qui totam", 486 | "completed": true 487 | }, 488 | { 489 | "userId": 5, 490 | "id": 82, 491 | "title": "voluptates eum voluptas et dicta", 492 | "completed": false 493 | }, 494 | { 495 | "userId": 5, 496 | "id": 83, 497 | "title": "quidem at rerum quis ex aut sit quam", 498 | "completed": true 499 | }, 500 | { 501 | "userId": 5, 502 | "id": 84, 503 | "title": "sunt veritatis ut voluptate", 504 | "completed": false 505 | }, 506 | { 507 | "userId": 5, 508 | "id": 85, 509 | "title": "et quia ad iste a", 510 | "completed": true 511 | }, 512 | { 513 | "userId": 5, 514 | "id": 86, 515 | "title": "incidunt ut saepe autem", 516 | "completed": true 517 | }, 518 | { 519 | "userId": 5, 520 | "id": 87, 521 | "title": "laudantium quae eligendi consequatur quia et vero autem", 522 | "completed": true 523 | }, 524 | { 525 | "userId": 5, 526 | "id": 88, 527 | "title": "vitae aut excepturi laboriosam sint aliquam et et accusantium", 528 | "completed": false 529 | }, 530 | { 531 | "userId": 5, 532 | "id": 89, 533 | "title": "sequi ut omnis et", 534 | "completed": true 535 | }, 536 | { 537 | "userId": 5, 538 | "id": 90, 539 | "title": "molestiae nisi accusantium tenetur dolorem et", 540 | "completed": true 541 | }, 542 | { 543 | "userId": 5, 544 | "id": 91, 545 | "title": "nulla quis consequatur saepe qui id expedita", 546 | "completed": true 547 | }, 548 | { 549 | "userId": 5, 550 | "id": 92, 551 | "title": "in omnis laboriosam", 552 | "completed": true 553 | }, 554 | { 555 | "userId": 5, 556 | "id": 93, 557 | "title": "odio iure consequatur molestiae quibusdam necessitatibus quia sint", 558 | "completed": true 559 | }, 560 | { 561 | "userId": 5, 562 | "id": 94, 563 | "title": "facilis modi saepe mollitia", 564 | "completed": false 565 | }, 566 | { 567 | "userId": 5, 568 | "id": 95, 569 | "title": "vel nihil et molestiae iusto assumenda nemo quo ut", 570 | "completed": true 571 | }, 572 | { 573 | "userId": 5, 574 | "id": 96, 575 | "title": "nobis suscipit ducimus enim asperiores voluptas", 576 | "completed": false 577 | }, 578 | { 579 | "userId": 5, 580 | "id": 97, 581 | "title": "dolorum laboriosam eos qui iure aliquam", 582 | "completed": false 583 | }, 584 | { 585 | "userId": 5, 586 | "id": 98, 587 | "title": "debitis accusantium ut quo facilis nihil quis sapiente necessitatibus", 588 | "completed": true 589 | }, 590 | { 591 | "userId": 5, 592 | "id": 99, 593 | "title": "neque voluptates ratione", 594 | "completed": false 595 | }, 596 | { 597 | "userId": 5, 598 | "id": 100, 599 | "title": "excepturi a et neque qui expedita vel voluptate", 600 | "completed": false 601 | }, 602 | { 603 | "userId": 6, 604 | "id": 101, 605 | "title": "explicabo enim cumque porro aperiam occaecati minima", 606 | "completed": false 607 | }, 608 | { 609 | "userId": 6, 610 | "id": 102, 611 | "title": "sed ab consequatur", 612 | "completed": false 613 | }, 614 | { 615 | "userId": 6, 616 | "id": 103, 617 | "title": "non sunt delectus illo nulla tenetur enim omnis", 618 | "completed": false 619 | }, 620 | { 621 | "userId": 6, 622 | "id": 104, 623 | "title": "excepturi non laudantium quo", 624 | "completed": false 625 | }, 626 | { 627 | "userId": 6, 628 | "id": 105, 629 | "title": "totam quia dolorem et illum repellat voluptas optio", 630 | "completed": true 631 | }, 632 | { 633 | "userId": 6, 634 | "id": 106, 635 | "title": "ad illo quis voluptatem temporibus", 636 | "completed": true 637 | }, 638 | { 639 | "userId": 6, 640 | "id": 107, 641 | "title": "praesentium facilis omnis laudantium fugit ad iusto nihil nesciunt", 642 | "completed": false 643 | }, 644 | { 645 | "userId": 6, 646 | "id": 108, 647 | "title": "a eos eaque nihil et exercitationem incidunt delectus", 648 | "completed": true 649 | }, 650 | { 651 | "userId": 6, 652 | "id": 109, 653 | "title": "autem temporibus harum quisquam in culpa", 654 | "completed": true 655 | }, 656 | { 657 | "userId": 6, 658 | "id": 110, 659 | "title": "aut aut ea corporis", 660 | "completed": true 661 | }, 662 | { 663 | "userId": 6, 664 | "id": 111, 665 | "title": "magni accusantium labore et id quis provident", 666 | "completed": false 667 | }, 668 | { 669 | "userId": 6, 670 | "id": 112, 671 | "title": "consectetur impedit quisquam qui deserunt non rerum consequuntur eius", 672 | "completed": false 673 | }, 674 | { 675 | "userId": 6, 676 | "id": 113, 677 | "title": "quia atque aliquam sunt impedit voluptatum rerum assumenda nisi", 678 | "completed": false 679 | }, 680 | { 681 | "userId": 6, 682 | "id": 114, 683 | "title": "cupiditate quos possimus corporis quisquam exercitationem beatae", 684 | "completed": false 685 | }, 686 | { 687 | "userId": 6, 688 | "id": 115, 689 | "title": "sed et ea eum", 690 | "completed": false 691 | }, 692 | { 693 | "userId": 6, 694 | "id": 116, 695 | "title": "ipsa dolores vel facilis ut", 696 | "completed": true 697 | }, 698 | { 699 | "userId": 6, 700 | "id": 117, 701 | "title": "sequi quae est et qui qui eveniet asperiores", 702 | "completed": false 703 | }, 704 | { 705 | "userId": 6, 706 | "id": 118, 707 | "title": "quia modi consequatur vero fugiat", 708 | "completed": false 709 | }, 710 | { 711 | "userId": 6, 712 | "id": 119, 713 | "title": "corporis ducimus ea perspiciatis iste", 714 | "completed": false 715 | }, 716 | { 717 | "userId": 6, 718 | "id": 120, 719 | "title": "dolorem laboriosam vel voluptas et aliquam quasi", 720 | "completed": false 721 | }, 722 | { 723 | "userId": 7, 724 | "id": 121, 725 | "title": "inventore aut nihil minima laudantium hic qui omnis", 726 | "completed": true 727 | }, 728 | { 729 | "userId": 7, 730 | "id": 122, 731 | "title": "provident aut nobis culpa", 732 | "completed": true 733 | }, 734 | { 735 | "userId": 7, 736 | "id": 123, 737 | "title": "esse et quis iste est earum aut impedit", 738 | "completed": false 739 | }, 740 | { 741 | "userId": 7, 742 | "id": 124, 743 | "title": "qui consectetur id", 744 | "completed": false 745 | }, 746 | { 747 | "userId": 7, 748 | "id": 125, 749 | "title": "aut quasi autem iste tempore illum possimus", 750 | "completed": false 751 | }, 752 | { 753 | "userId": 7, 754 | "id": 126, 755 | "title": "ut asperiores perspiciatis veniam ipsum rerum saepe", 756 | "completed": true 757 | }, 758 | { 759 | "userId": 7, 760 | "id": 127, 761 | "title": "voluptatem libero consectetur rerum ut", 762 | "completed": true 763 | }, 764 | { 765 | "userId": 7, 766 | "id": 128, 767 | "title": "eius omnis est qui voluptatem autem", 768 | "completed": false 769 | }, 770 | { 771 | "userId": 7, 772 | "id": 129, 773 | "title": "rerum culpa quis harum", 774 | "completed": false 775 | }, 776 | { 777 | "userId": 7, 778 | "id": 130, 779 | "title": "nulla aliquid eveniet harum laborum libero alias ut unde", 780 | "completed": true 781 | }, 782 | { 783 | "userId": 7, 784 | "id": 131, 785 | "title": "qui ea incidunt quis", 786 | "completed": false 787 | }, 788 | { 789 | "userId": 7, 790 | "id": 132, 791 | "title": "qui molestiae voluptatibus velit iure harum quisquam", 792 | "completed": true 793 | }, 794 | { 795 | "userId": 7, 796 | "id": 133, 797 | "title": "et labore eos enim rerum consequatur sunt", 798 | "completed": true 799 | }, 800 | { 801 | "userId": 7, 802 | "id": 134, 803 | "title": "molestiae doloribus et laborum quod ea", 804 | "completed": false 805 | }, 806 | { 807 | "userId": 7, 808 | "id": 135, 809 | "title": "facere ipsa nam eum voluptates reiciendis vero qui", 810 | "completed": false 811 | }, 812 | { 813 | "userId": 7, 814 | "id": 136, 815 | "title": "asperiores illo tempora fuga sed ut quasi adipisci", 816 | "completed": false 817 | }, 818 | { 819 | "userId": 7, 820 | "id": 137, 821 | "title": "qui sit non", 822 | "completed": false 823 | }, 824 | { 825 | "userId": 7, 826 | "id": 138, 827 | "title": "placeat minima consequatur rem qui ut", 828 | "completed": true 829 | }, 830 | { 831 | "userId": 7, 832 | "id": 139, 833 | "title": "consequatur doloribus id possimus voluptas a voluptatem", 834 | "completed": false 835 | }, 836 | { 837 | "userId": 7, 838 | "id": 140, 839 | "title": "aut consectetur in blanditiis deserunt quia sed laboriosam", 840 | "completed": true 841 | }, 842 | { 843 | "userId": 8, 844 | "id": 141, 845 | "title": "explicabo consectetur debitis voluptates quas quae culpa rerum non", 846 | "completed": true 847 | }, 848 | { 849 | "userId": 8, 850 | "id": 142, 851 | "title": "maiores accusantium architecto necessitatibus reiciendis ea aut", 852 | "completed": true 853 | }, 854 | { 855 | "userId": 8, 856 | "id": 143, 857 | "title": "eum non recusandae cupiditate animi", 858 | "completed": false 859 | }, 860 | { 861 | "userId": 8, 862 | "id": 144, 863 | "title": "ut eum exercitationem sint", 864 | "completed": false 865 | }, 866 | { 867 | "userId": 8, 868 | "id": 145, 869 | "title": "beatae qui ullam incidunt voluptatem non nisi aliquam", 870 | "completed": false 871 | }, 872 | { 873 | "userId": 8, 874 | "id": 146, 875 | "title": "molestiae suscipit ratione nihil odio libero impedit vero totam", 876 | "completed": true 877 | }, 878 | { 879 | "userId": 8, 880 | "id": 147, 881 | "title": "eum itaque quod reprehenderit et facilis dolor autem ut", 882 | "completed": true 883 | }, 884 | { 885 | "userId": 8, 886 | "id": 148, 887 | "title": "esse quas et quo quasi exercitationem", 888 | "completed": false 889 | }, 890 | { 891 | "userId": 8, 892 | "id": 149, 893 | "title": "animi voluptas quod perferendis est", 894 | "completed": false 895 | }, 896 | { 897 | "userId": 8, 898 | "id": 150, 899 | "title": "eos amet tempore laudantium fugit a", 900 | "completed": false 901 | }, 902 | { 903 | "userId": 8, 904 | "id": 151, 905 | "title": "accusamus adipisci dicta qui quo ea explicabo sed vero", 906 | "completed": true 907 | }, 908 | { 909 | "userId": 8, 910 | "id": 152, 911 | "title": "odit eligendi recusandae doloremque cumque non", 912 | "completed": false 913 | }, 914 | { 915 | "userId": 8, 916 | "id": 153, 917 | "title": "ea aperiam consequatur qui repellat eos", 918 | "completed": false 919 | }, 920 | { 921 | "userId": 8, 922 | "id": 154, 923 | "title": "rerum non ex sapiente", 924 | "completed": true 925 | }, 926 | { 927 | "userId": 8, 928 | "id": 155, 929 | "title": "voluptatem nobis consequatur et assumenda magnam", 930 | "completed": true 931 | }, 932 | { 933 | "userId": 8, 934 | "id": 156, 935 | "title": "nam quia quia nulla repellat assumenda quibusdam sit nobis", 936 | "completed": true 937 | }, 938 | { 939 | "userId": 8, 940 | "id": 157, 941 | "title": "dolorem veniam quisquam deserunt repellendus", 942 | "completed": true 943 | }, 944 | { 945 | "userId": 8, 946 | "id": 158, 947 | "title": "debitis vitae delectus et harum accusamus aut deleniti a", 948 | "completed": true 949 | }, 950 | { 951 | "userId": 8, 952 | "id": 159, 953 | "title": "debitis adipisci quibusdam aliquam sed dolore ea praesentium nobis", 954 | "completed": true 955 | }, 956 | { 957 | "userId": 8, 958 | "id": 160, 959 | "title": "et praesentium aliquam est", 960 | "completed": false 961 | }, 962 | { 963 | "userId": 9, 964 | "id": 161, 965 | "title": "ex hic consequuntur earum omnis alias ut occaecati culpa", 966 | "completed": true 967 | }, 968 | { 969 | "userId": 9, 970 | "id": 162, 971 | "title": "omnis laboriosam molestias animi sunt dolore", 972 | "completed": true 973 | }, 974 | { 975 | "userId": 9, 976 | "id": 163, 977 | "title": "natus corrupti maxime laudantium et voluptatem laboriosam odit", 978 | "completed": false 979 | }, 980 | { 981 | "userId": 9, 982 | "id": 164, 983 | "title": "reprehenderit quos aut aut consequatur est sed", 984 | "completed": false 985 | }, 986 | { 987 | "userId": 9, 988 | "id": 165, 989 | "title": "fugiat perferendis sed aut quidem", 990 | "completed": false 991 | }, 992 | { 993 | "userId": 9, 994 | "id": 166, 995 | "title": "quos quo possimus suscipit minima ut", 996 | "completed": false 997 | }, 998 | { 999 | "userId": 9, 1000 | "id": 167, 1001 | "title": "et quis minus quo a asperiores molestiae", 1002 | "completed": false 1003 | }, 1004 | { 1005 | "userId": 9, 1006 | "id": 168, 1007 | "title": "recusandae quia qui sunt libero", 1008 | "completed": false 1009 | }, 1010 | { 1011 | "userId": 9, 1012 | "id": 169, 1013 | "title": "ea odio perferendis officiis", 1014 | "completed": true 1015 | }, 1016 | { 1017 | "userId": 9, 1018 | "id": 170, 1019 | "title": "quisquam aliquam quia doloribus aut", 1020 | "completed": false 1021 | }, 1022 | { 1023 | "userId": 9, 1024 | "id": 171, 1025 | "title": "fugiat aut voluptatibus corrupti deleniti velit iste odio", 1026 | "completed": true 1027 | }, 1028 | { 1029 | "userId": 9, 1030 | "id": 172, 1031 | "title": "et provident amet rerum consectetur et voluptatum", 1032 | "completed": false 1033 | }, 1034 | { 1035 | "userId": 9, 1036 | "id": 173, 1037 | "title": "harum ad aperiam quis", 1038 | "completed": false 1039 | }, 1040 | { 1041 | "userId": 9, 1042 | "id": 174, 1043 | "title": "similique aut quo", 1044 | "completed": false 1045 | }, 1046 | { 1047 | "userId": 9, 1048 | "id": 175, 1049 | "title": "laudantium eius officia perferendis provident perspiciatis asperiores", 1050 | "completed": true 1051 | }, 1052 | { 1053 | "userId": 9, 1054 | "id": 176, 1055 | "title": "magni soluta corrupti ut maiores rem quidem", 1056 | "completed": false 1057 | }, 1058 | { 1059 | "userId": 9, 1060 | "id": 177, 1061 | "title": "et placeat temporibus voluptas est tempora quos quibusdam", 1062 | "completed": false 1063 | }, 1064 | { 1065 | "userId": 9, 1066 | "id": 178, 1067 | "title": "nesciunt itaque commodi tempore", 1068 | "completed": true 1069 | }, 1070 | { 1071 | "userId": 9, 1072 | "id": 179, 1073 | "title": "omnis consequuntur cupiditate impedit itaque ipsam quo", 1074 | "completed": true 1075 | }, 1076 | { 1077 | "userId": 9, 1078 | "id": 180, 1079 | "title": "debitis nisi et dolorem repellat et", 1080 | "completed": true 1081 | }, 1082 | { 1083 | "userId": 10, 1084 | "id": 181, 1085 | "title": "ut cupiditate sequi aliquam fuga maiores", 1086 | "completed": false 1087 | }, 1088 | { 1089 | "userId": 10, 1090 | "id": 182, 1091 | "title": "inventore saepe cumque et aut illum enim", 1092 | "completed": true 1093 | }, 1094 | { 1095 | "userId": 10, 1096 | "id": 183, 1097 | "title": "omnis nulla eum aliquam distinctio", 1098 | "completed": true 1099 | }, 1100 | { 1101 | "userId": 10, 1102 | "id": 184, 1103 | "title": "molestias modi perferendis perspiciatis", 1104 | "completed": false 1105 | }, 1106 | { 1107 | "userId": 10, 1108 | "id": 185, 1109 | "title": "voluptates dignissimos sed doloribus animi quaerat aut", 1110 | "completed": false 1111 | }, 1112 | { 1113 | "userId": 10, 1114 | "id": 186, 1115 | "title": "explicabo odio est et", 1116 | "completed": false 1117 | }, 1118 | { 1119 | "userId": 10, 1120 | "id": 187, 1121 | "title": "consequuntur animi possimus", 1122 | "completed": false 1123 | }, 1124 | { 1125 | "userId": 10, 1126 | "id": 188, 1127 | "title": "vel non beatae est", 1128 | "completed": true 1129 | }, 1130 | { 1131 | "userId": 10, 1132 | "id": 189, 1133 | "title": "culpa eius et voluptatem et", 1134 | "completed": true 1135 | }, 1136 | { 1137 | "userId": 10, 1138 | "id": 190, 1139 | "title": "accusamus sint iusto et voluptatem exercitationem", 1140 | "completed": true 1141 | }, 1142 | { 1143 | "userId": 10, 1144 | "id": 191, 1145 | "title": "temporibus atque distinctio omnis eius impedit tempore molestias pariatur", 1146 | "completed": true 1147 | }, 1148 | { 1149 | "userId": 10, 1150 | "id": 192, 1151 | "title": "ut quas possimus exercitationem sint voluptates", 1152 | "completed": false 1153 | }, 1154 | { 1155 | "userId": 10, 1156 | "id": 193, 1157 | "title": "rerum debitis voluptatem qui eveniet tempora distinctio a", 1158 | "completed": true 1159 | }, 1160 | { 1161 | "userId": 10, 1162 | "id": 194, 1163 | "title": "sed ut vero sit molestiae", 1164 | "completed": false 1165 | }, 1166 | { 1167 | "userId": 10, 1168 | "id": 195, 1169 | "title": "rerum ex veniam mollitia voluptatibus pariatur", 1170 | "completed": true 1171 | }, 1172 | { 1173 | "userId": 10, 1174 | "id": 196, 1175 | "title": "consequuntur aut ut fugit similique", 1176 | "completed": true 1177 | }, 1178 | { 1179 | "userId": 10, 1180 | "id": 197, 1181 | "title": "dignissimos quo nobis earum saepe", 1182 | "completed": true 1183 | }, 1184 | { 1185 | "userId": 10, 1186 | "id": 198, 1187 | "title": "quis eius est sint explicabo", 1188 | "completed": true 1189 | }, 1190 | { 1191 | "userId": 10, 1192 | "id": 199, 1193 | "title": "numquam repellendus a magnam", 1194 | "completed": true 1195 | }, 1196 | { 1197 | "userId": 10, 1198 | "id": 200, 1199 | "title": "ipsam aperiam voluptates qui", 1200 | "completed": false 1201 | } 1202 | ] -------------------------------------------------------------------------------- /benchmarks/isolated/data/todos.proto: -------------------------------------------------------------------------------- 1 | // todos.proto 2 | package todos; 3 | syntax = "proto3"; 4 | 5 | message TodoMessage { 6 | int32 id = 3; 7 | int32 userId = 2; 8 | string title = 1; 9 | bool completed = 4; 10 | } 11 | 12 | message TodosMessage { 13 | repeated TodoMessage objects = 1; 14 | } 15 | -------------------------------------------------------------------------------- /benchmarks/isolated/des.benchmark.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | import { 3 | createDes, 4 | createSer 5 | } from '../../src/index.js' 6 | import fs from 'node:fs' 7 | 8 | import { encode as msgpackEncode, decode as msgpackDecode } from '@msgpack/msgpack' 9 | import cbor from 'cbor' 10 | import * as cborx from 'cbor-x' 11 | import * as msgpackr from 'msgpackr' 12 | import protobuf from 'protobufjs' 13 | import avro from 'avsc' 14 | 15 | interface Todo { 16 | userId: number 17 | id: number 18 | title: string 19 | completed: boolean 20 | } 21 | 22 | const todos: Todo[] = JSON.parse(fs.readFileSync('./benchmarks/isolated/data/todos.json', 'utf8')) 23 | 24 | const root = await protobuf.load('./benchmarks/isolated/data/todos.proto') 25 | const prtobufType = root.lookupType('todos.TodosMessage') 26 | 27 | const type = avro.Type.forSchema({ 28 | name: 'Todos', 29 | type: 'array', 30 | items: { 31 | name: 'Todo', 32 | type: 'record', 33 | fields: [ 34 | { name: 'userId', type: 'int' }, 35 | { name: 'id', type: 'int' }, 36 | { name: 'title', type: 'string' }, 37 | { name: 'completed', type: 'boolean' } 38 | ] 39 | } 40 | }) 41 | const ser = createSer() 42 | ser.reset() 43 | ser.serializeArray(todos, (ser, todo) => { 44 | ser.serializeUInt32(todo.id) 45 | ser.serializeUInt32(todo.userId) 46 | ser.serializeString(todo.title) 47 | ser.serializeBoolean(todo.completed) 48 | }) 49 | const bbb = ser.getBuffer() 50 | 51 | const des = createDes(new ArrayBuffer(1)) 52 | 53 | const protobufBuff = prtobufType.encode({ objects: todos }).finish() 54 | const avroBuff = type.toBuffer(todos) 55 | const cborBuff = cbor.encode(todos) 56 | const cborxBuff = cborx.encode(todos) 57 | const msgpackBuff = msgpackEncode(todos) 58 | const msgpackrBuff = msgpackr.pack(todos) 59 | const jsonStr = JSON.stringify(todos) 60 | 61 | await b.suite( 62 | 'Deserialize', 63 | 64 | b.add('seqproto', () => { 65 | des.setBuffer(bbb) 66 | des.deserializeArray(des => { 67 | const id = des.deserializeUInt32() 68 | const userId = des.deserializeUInt32() 69 | const title = des.deserializeString() 70 | const completed = des.deserializeBoolean() 71 | return { id, userId, title, completed } 72 | }) 73 | }), 74 | 75 | b.add('avro', () => { 76 | type.fromBuffer(avroBuff) 77 | }), 78 | 79 | b.add('protobuf', () => { 80 | prtobufType.decode(protobufBuff) 81 | }), 82 | 83 | b.add('cbor', () => { 84 | cbor.decode(cborBuff) 85 | }), 86 | 87 | b.add('cborx', () => { 88 | cborx.decode(cborxBuff) 89 | }), 90 | 91 | b.add('msgpack', () => { 92 | msgpackDecode(msgpackBuff) 93 | }), 94 | 95 | b.add('msgpackr', () => { 96 | msgpackr.unpack(msgpackrBuff) 97 | }), 98 | 99 | b.add('JSON', () => { 100 | JSON.parse(jsonStr) 101 | }), 102 | 103 | b.cycle(), 104 | b.complete(), 105 | b.save({ file: 'deserialize', version: '1.0.0' }), 106 | b.save({ file: 'deserialize', format: 'table.html' }) 107 | ) 108 | -------------------------------------------------------------------------------- /benchmarks/isolated/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seqproto-benchmarks-isolated", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "description": "", 6 | "scripts": {}, 7 | "author": { 8 | "name": "Matteo Pietro Dazzi", 9 | "url": "https://github.com/ilteoood", 10 | "author": true 11 | }, 12 | "engines": { 13 | "node": ">= 20.0.0" 14 | }, 15 | "devDependencies": { 16 | "@msgpack/msgpack": "3.0.0-beta2", 17 | "@types/node": "=20.8.0", 18 | "autocannon": "^7.12.0", 19 | "avro-js": "^1.11.3", 20 | "avsc": "^5.7.7", 21 | "benny": "^3.7.1", 22 | "browserify": "^17.0.0", 23 | "cbor": "^9.0.1", 24 | "cbor-x": "^1.5.4", 25 | "fast-json-stringify": "^5.9.1", 26 | "lodash": "^4.17.21", 27 | "msgpackr": "^1.9.9", 28 | "protobufjs": "^7.2.5", 29 | "ts-standard": "^12.0.2", 30 | "tsx": "^3.14.0", 31 | "typescript": "^5.2.2", 32 | "undici-types": "^5.27.2" 33 | }, 34 | "dependencies": { 35 | "@fastify/static": "^6.12.0", 36 | "fastify": "^4.24.3", 37 | "seqproto": "workspace:^" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /benchmarks/isolated/ser.benchmark.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | import fs from 'node:fs' 3 | import { 4 | createSer 5 | } from '../../src/index.js' 6 | 7 | import { encode as msgpackEncode } from '@msgpack/msgpack' 8 | import cbor from 'cbor' 9 | import * as cborx from 'cbor-x' 10 | import * as msgpackr from 'msgpackr' 11 | import protobuf from 'protobufjs' 12 | import avro from 'avsc' 13 | import fastJson from 'fast-json-stringify' 14 | 15 | const stringify = fastJson({ 16 | title: 'Example Schema', 17 | type: 'array', 18 | items: { 19 | type: 'object', 20 | properties: { 21 | userId: { type: 'integer' }, 22 | id: { type: 'integer' }, 23 | title: { type: 'string' }, 24 | completed: { type: 'boolean' } 25 | } 26 | } 27 | }) 28 | 29 | interface Todo { 30 | userId: number 31 | id: number 32 | title: string 33 | completed: boolean 34 | } 35 | 36 | const todos: Todo[] = JSON.parse(fs.readFileSync('./benchmarks/isolated/data/todos.json', 'utf8')) 37 | 38 | const root = await protobuf.load('./benchmarks/isolated/data/todos.proto') 39 | const prtobufType = root.lookupType('todos.TodosMessage') 40 | 41 | const type = avro.Type.forSchema({ 42 | name: 'Todos', 43 | type: 'array', 44 | items: { 45 | name: 'Todo', 46 | type: 'record', 47 | fields: [ 48 | { name: 'userId', type: 'int' }, 49 | { name: 'id', type: 'int' }, 50 | { name: 'title', type: 'string' }, 51 | { name: 'completed', type: 'boolean' } 52 | ] 53 | } 54 | }) 55 | const ser = createSer() 56 | 57 | await b.suite( 58 | 'Serialize', 59 | 60 | b.add('seqproto', () => { 61 | ser.reset() 62 | ser.serializeArray(todos, (ser, todo) => { 63 | ser.serializeUInt32(todo.id) 64 | ser.serializeUInt32(todo.userId) 65 | ser.serializeString(todo.title) 66 | ser.serializeBoolean(todo.completed) 67 | }) 68 | ser.getBuffer() 69 | }), 70 | 71 | b.add('protobuf', () => { 72 | prtobufType.encode({ objects: todos }).finish() 73 | }), 74 | 75 | b.add('avro', () => { 76 | type.toBuffer(todos) 77 | }), 78 | 79 | b.add('cbor', () => { 80 | cbor.encode(todos) 81 | }), 82 | 83 | b.add('cborx', () => { 84 | cborx.encode(todos) 85 | }), 86 | 87 | b.add('msgpack', () => { 88 | msgpackEncode(todos) 89 | }), 90 | 91 | b.add('msgpackr', () => { 92 | msgpackr.pack(todos) 93 | }), 94 | 95 | b.add('JSON', () => { 96 | JSON.stringify(todos) 97 | }), 98 | 99 | b.add('fast-json-stringify', () => { 100 | stringify(todos) 101 | }), 102 | 103 | b.cycle(), 104 | b.complete(), 105 | b.save({ file: 'serialize', version: '1.0.0' }), 106 | b.save({ file: 'serialize', format: 'table.html' }) 107 | ) 108 | -------------------------------------------------------------------------------- /benchmarks/isolated/serdes.benchmark.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | import fs from 'node:fs' 3 | import { 4 | createDes, 5 | createSer 6 | } from '../../src/index.js' 7 | 8 | import { encode as msgpackEncode, decode as msgpackDecode } from '@msgpack/msgpack' 9 | import cbor from 'cbor' 10 | import * as cborx from 'cbor-x' 11 | import * as msgpackr from 'msgpackr' 12 | import protobuf from 'protobufjs' 13 | import avro from 'avsc' 14 | 15 | interface Todo { 16 | userId: number 17 | id: number 18 | title: string 19 | completed: boolean 20 | } 21 | 22 | const todos: Todo[] = JSON.parse(fs.readFileSync('./benchmarks/isolated/data/todos.json', 'utf8')) 23 | 24 | const root = await protobuf.load('./benchmarks/isolated/data/todos.proto') 25 | const protobufType = root.lookupType('todos.TodosMessage') 26 | 27 | const type = avro.Type.forSchema({ 28 | name: 'Todos', 29 | type: 'array', 30 | items: { 31 | name: 'Todo', 32 | type: 'record', 33 | fields: [ 34 | { name: 'userId', type: 'int' }, 35 | { name: 'id', type: 'int' }, 36 | { name: 'title', type: 'string' }, 37 | { name: 'completed', type: 'boolean' } 38 | ] 39 | } 40 | }) 41 | const ser = createSer() 42 | 43 | await b.suite( 44 | 'Serialize / Deserialize', 45 | 46 | b.add('seqproto', () => { 47 | ser.reset() 48 | ser.serializeArray(todos, (ser, todo) => { 49 | ser.serializeUInt32(todo.id) 50 | ser.serializeUInt32(todo.userId) 51 | ser.serializeString(todo.title) 52 | ser.serializeBoolean(todo.completed) 53 | }) 54 | const bbb = ser.getBuffer() 55 | const des = createDes(bbb) 56 | des.deserializeArray(des => { 57 | const id = des.deserializeUInt32() 58 | const userId = des.deserializeUInt32() 59 | const title = des.deserializeString() 60 | const completed = des.deserializeBoolean() 61 | return { id, userId, title, completed } 62 | }) 63 | }), 64 | 65 | b.add('protobuf', () => { 66 | protobufType.decode(protobufType.encode({ objects: todos }).finish()) 67 | }), 68 | 69 | b.add('avro', () => { 70 | type.fromBuffer(type.toBuffer(todos)) 71 | }), 72 | 73 | b.add('cbor', () => { 74 | cbor.decode(cbor.encode(todos)) 75 | }), 76 | 77 | b.add('cborx', () => { 78 | cborx.decode(cborx.encode(todos)) 79 | }), 80 | 81 | b.add('msgpack', () => { 82 | msgpackDecode(msgpackEncode(todos)) 83 | }), 84 | 85 | b.add('msgpackr', () => { 86 | msgpackr.unpack(msgpackr.pack(todos)) 87 | }), 88 | 89 | b.add('JSON', () => { 90 | JSON.parse(JSON.stringify(todos)) 91 | }), 92 | 93 | b.cycle(), 94 | b.complete(), 95 | b.save({ file: 'des-ser-ialize', version: '1.0.0' }), 96 | b.save({ file: 'des-ser-serialize', format: 'table.html' }) 97 | ) 98 | -------------------------------------------------------------------------------- /examples/complex_object.ts: -------------------------------------------------------------------------------- 1 | import { createSer, createDes } from '../src/index.js' 2 | import type { Ser, Des } from '../src/index.js' 3 | 4 | interface User { 5 | id: number 6 | username: string 7 | } 8 | 9 | interface Comment { 10 | id: number 11 | body: string 12 | user: User 13 | } 14 | 15 | interface Post { 16 | id: number 17 | title: string 18 | creator: User 19 | comments: Comment[] 20 | } 21 | 22 | function serializeUser (ser: Ser, user: User): void { 23 | ser.serializeUInt32(user.id) 24 | ser.serializeString(user.username) 25 | } 26 | function deserializeUser (des: Des): User { 27 | const id = des.deserializeUInt32() 28 | const username = des.deserializeString() 29 | return { id, username } 30 | } 31 | function serializeComment (ser: Ser, comment: Comment): void { 32 | ser.serializeUInt32(comment.id) 33 | ser.serializeString(comment.body) 34 | serializeUser(ser, comment.user) 35 | } 36 | function deserializeComment (des: Des): Comment { 37 | const id = des.deserializeUInt32() 38 | const body = des.deserializeString() 39 | const user = deserializeUser(des) 40 | return { id, body, user } 41 | } 42 | function serializePost (ser: Ser, post: Post): void { 43 | ser.serializeUInt32(post.id) 44 | ser.serializeString(post.title) 45 | serializeUser(ser, post.creator) 46 | ser.serializeArray(post.comments, serializeComment) 47 | } 48 | function deserializePost (des: Des): Post { 49 | const id = des.deserializeUInt32() 50 | const title = des.deserializeString() 51 | const creator = deserializeUser(des) 52 | const comments = des.deserializeArray(deserializeComment) 53 | return { id, title, creator, comments } 54 | } 55 | 56 | const ser: Ser = createSer() 57 | serializePost(ser, { 58 | id: 1, 59 | title: 'hello', 60 | creator: { id: 1, username: 'bob' }, 61 | comments: [ 62 | { id: 1, body: 'hello', user: { id: 1, username: 'bob' } }, 63 | { id: 2, body: 'world', user: { id: 2, username: 'alice' } } 64 | ] 65 | }) 66 | const buffer = ser.getBuffer() 67 | 68 | const des: Des = createDes(buffer) 69 | const post = deserializePost(des) 70 | 71 | console.log(JSON.stringify(post, null, 2)) 72 | -------------------------------------------------------------------------------- /examples/iterable.ts: -------------------------------------------------------------------------------- 1 | import { createSer, createDes } from '../src/index.js' 2 | import type { Ser, Des } from '../src/index.js' 3 | 4 | let buffer 5 | 6 | { // Serialize 7 | const map = new Map([ 8 | [1, 'one'], 9 | [2, 'two'], 10 | [3, 'three'] 11 | ]) 12 | const ser: Ser = createSer() 13 | ser.serializeIterable(map, (ser, [k, v]) => { 14 | ser.serializeUInt32(k) 15 | ser.serializeString(v) 16 | }) 17 | buffer = ser.getBuffer() 18 | } 19 | 20 | { // Deserialize 21 | const des: Des = createDes(buffer) 22 | const map = new Map(des.deserializeIterable((des) => { 23 | return [des.deserializeUInt32(), des.deserializeString()] 24 | })) 25 | console.log(map) 26 | } 27 | -------------------------------------------------------------------------------- /examples/object.ts: -------------------------------------------------------------------------------- 1 | import { createSer, createDes } from '../src/index.js' 2 | import type { Ser, Des } from '../src/index.js' 3 | 4 | interface Todo { 5 | id: number 6 | userId: number 7 | title: string 8 | completed: boolean 9 | } 10 | 11 | function serializeTodo (ser: Ser, todo: Todo): void { 12 | ser.serializeUInt32(todo.id) 13 | ser.serializeUInt32(todo.userId) 14 | ser.serializeString(todo.title) 15 | ser.serializeBoolean(todo.completed) 16 | } 17 | function deserializeTodo (des: Des): Todo { 18 | const id = des.deserializeUInt32() 19 | const userId = des.deserializeUInt32() 20 | const title = des.deserializeString() 21 | const completed = des.deserializeBoolean() 22 | return { id, userId, title, completed } 23 | } 24 | 25 | const ser: Ser = createSer() 26 | serializeTodo(ser, { 27 | id: 1, 28 | userId: 1, 29 | title: 'hello', 30 | completed: false 31 | }) 32 | const buffer = ser.getBuffer() 33 | 34 | const des: Des = createDes(buffer) 35 | const todo = deserializeTodo(des) 36 | 37 | console.log(JSON.stringify(todo, null, 2)) 38 | -------------------------------------------------------------------------------- /examples/simple.ts: -------------------------------------------------------------------------------- 1 | import { createSer, createDes } from '../src/index.js' 2 | import type { Ser, Des } from '../src/index.js' 3 | 4 | const ser: Ser = createSer() 5 | 6 | ser.serializeBoolean(true) 7 | ser.serializeUInt32(42) 8 | ser.serializeFloat32(-0.5) 9 | ser.serializeString('hello world') 10 | ser.serializeArray([1, 2, 3], (ser, n) => ser.serializeUInt32(n)) 11 | 12 | const buffer = ser.getBuffer() 13 | 14 | const des: Des = createDes(buffer) 15 | 16 | const b = des.deserializeBoolean() 17 | const i = des.deserializeUInt32() 18 | const f = des.deserializeFloat32() 19 | const s = des.deserializeString() 20 | const a = des.deserializeArray((des) => des.deserializeUInt32()) 21 | 22 | console.log({ b, i, f, s, a }) 23 | -------------------------------------------------------------------------------- /examples/todos.ts: -------------------------------------------------------------------------------- 1 | import { createSer, createDes } from '../src/index.js' 2 | import type { Ser, Des } from '../src/index.js' 3 | 4 | let buffer 5 | 6 | { // Serialize 7 | const todos = [ 8 | { userId: 1, id: 1, completed: false, title: 'delectus aut autem' }, 9 | { userId: 1, id: 2, completed: true, title: 'quis ut nam facilis et officia qui' } 10 | ] 11 | const ser: Ser = createSer() 12 | ser.serializeArray(todos, (ser, todo) => { 13 | ser.serializeUInt32(todo.id) 14 | ser.serializeUInt32(todo.userId) 15 | ser.serializeString(todo.title) 16 | ser.serializeBoolean(todo.completed) 17 | }) 18 | buffer = ser.getBuffer() 19 | } 20 | 21 | { // Deserialize 22 | const des: Des = createDes(buffer) 23 | const todos = des.deserializeArray((des) => { 24 | const id = des.deserializeUInt32() 25 | const userId = des.deserializeUInt32() 26 | const title = des.deserializeString() 27 | const completed = des.deserializeBoolean() 28 | return { id, userId, title, completed } 29 | }) 30 | console.log(todos) 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seqproto", 3 | "version": "0.2.3", 4 | "type": "module", 5 | "description": "More than fast serialization / deserialization utility for your structured data", 6 | "main": "./dist/cjs/index.js", 7 | "types": "./dist/cjs/index.d.ts", 8 | "license": "Apache-2.0", 9 | "exports": { 10 | "import": "./dist/esm/index.js", 11 | "require": "./dist/cjs/index.cjs" 12 | }, 13 | "files": [ 14 | "dist" 15 | ], 16 | "scripts": { 17 | "build": "npm run build:cjs && npm run build:esm && npm run rename:cjs", 18 | "build:cjs": "tsc -p tsconfig.cjs.json", 19 | "build:esm": "tsc -p tsconfig.esm.json", 20 | "rename:cjs": "mv ./dist/cjs/index.js ./dist/cjs/index.cjs", 21 | "lint": "ts-standard", 22 | "benchmark:ser": "node --import tsx benchmarks/isolated/ser.benchmark.ts", 23 | "benchmark:des": "node --import tsx benchmarks/isolated/des.benchmark.ts", 24 | "benchmark:serdes": "node --import tsx benchmarks/isolated/serdes.benchmark.ts", 25 | "test": "node --import tsx tests/*.test.ts" 26 | }, 27 | "keywords": [ 28 | "serialization", 29 | "deserialization", 30 | "performance", 31 | "structured", 32 | "protocol", 33 | "json" 34 | ], 35 | "author": { 36 | "name": "Tommaso Allevi", 37 | "email": "tomallevi@gmail.com", 38 | "url": "https://github.com/allevo", 39 | "author": true 40 | }, 41 | "engines": { 42 | "node": ">= 20.0.0" 43 | }, 44 | "devDependencies": { 45 | "ts-standard": "^12.0.2", 46 | "tsx": "^3.14.0", 47 | "typescript": "^5.2.2" 48 | }, 49 | "ts-standard": { 50 | "ignore": [ 51 | "benchmarks/e2e" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'benchmarks/*' -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export type StrictArrayBuffer = ArrayBuffer & { buffer?: undefined } 3 | 4 | const TYPE_FLOAT = 0 5 | const TYPE_UINT32 = 1 6 | const TYPE_INT32 = 2 7 | const POW_2_32 = 2 ** 32 8 | 9 | export interface Ser { 10 | index: number 11 | buffer: ArrayBuffer 12 | uint32Array: Uint32Array 13 | float32Array: Float32Array 14 | reset: () => void 15 | getBuffer: () => StrictArrayBuffer 16 | serializeBoolean: (b: boolean) => void 17 | serializeUInt32: (n: number) => void 18 | serializeFloat32: (n: number) => void 19 | serializeNumber: (n: number) => void 20 | serializeString: (str: string) => void 21 | serializeArray: (arr: T[], serialize: (ser: Ser, t: T) => void) => void 22 | serializeIterable: (iterable: Iterable, serialize: (ser: Ser, t: T) => void) => void 23 | serializeIndexableArray: (arr: T[], serialize: (ser: Ser, t: T) => void) => void 24 | unsafeSerializeUint32Array: (buffer: Uint32Array) => void 25 | } 26 | export interface Des { 27 | index: number 28 | buffer: StrictArrayBuffer 29 | uint32Array: Uint32Array 30 | float32Array: Float32Array 31 | setBuffer: (buffer: StrictArrayBuffer, byteOffset?: number, byteLength?: number) => void 32 | deserializeBoolean: () => boolean 33 | deserializeUInt32: () => number 34 | deserializeFloat32: () => number 35 | deserializeNumber: () => number 36 | deserializeString: () => string 37 | deserializeArray: (deserialize: (des: Des) => T) => T[] 38 | deserializeIterable: (deserialize: (des: Des) => T) => Iterable 39 | unsafeDeserializeUint32Array: () => Uint32Array 40 | getArrayElements: (indexes: number[], deserialize: (des: Des, start: number, end: number) => T) => T[] 41 | } 42 | 43 | interface CreateSerOption { 44 | bufferSize?: number 45 | } 46 | export function createSer ({ bufferSize }: CreateSerOption = {}): Ser { 47 | const size = bufferSize ?? 2 ** 24 48 | if (size >= POW_2_32) { 49 | throw new Error('bufferSize option must be strictly less than 2 ** 32') 50 | } 51 | 52 | const buffer = new ArrayBuffer(size) 53 | return { 54 | index: 0, 55 | buffer, 56 | uint32Array: new Uint32Array(buffer), 57 | float32Array: new Float32Array(buffer), 58 | reset: function () { this.index = 0 }, 59 | serializeBoolean, 60 | serializeUInt32, 61 | serializeFloat32, 62 | serializeNumber, 63 | serializeString, 64 | serializeArray, 65 | serializeIterable, 66 | serializeIndexableArray, 67 | unsafeSerializeUint32Array, 68 | getBuffer: function () { return this.buffer.slice(0, this.index * 4) } 69 | } 70 | } 71 | 72 | export function createDes (buffer: StrictArrayBuffer): Des { 73 | const n32 = Math.floor(buffer.byteLength / 4) 74 | 75 | return { 76 | index: 0, 77 | buffer, 78 | uint32Array: new Uint32Array(buffer, 0, n32), 79 | float32Array: new Float32Array(buffer, 0, n32), 80 | setBuffer: function (buffer: StrictArrayBuffer, byteOffset?: number, byteLength?: number) { 81 | if (typeof byteOffset === 'number' && typeof byteLength === 'number') { 82 | this.index = Math.floor(byteOffset / 4) 83 | const n32 = this.index + Math.ceil(byteLength / 4) 84 | 85 | this.buffer = buffer 86 | this.uint32Array = new Uint32Array(buffer, 0, n32) 87 | this.float32Array = new Float32Array(buffer, 0, n32) 88 | 89 | return 90 | } 91 | 92 | const n32 = Math.floor(buffer.byteLength / 4) 93 | this.buffer = buffer 94 | this.index = 0 95 | this.uint32Array = new Uint32Array(buffer, 0, n32) 96 | this.float32Array = new Float32Array(buffer, 0, n32) 97 | }, 98 | deserializeBoolean, 99 | deserializeUInt32, 100 | deserializeFloat32, 101 | deserializeNumber, 102 | deserializeString, 103 | deserializeArray, 104 | deserializeIterable, 105 | getArrayElements, 106 | unsafeDeserializeUint32Array 107 | } 108 | } 109 | 110 | function serializeBoolean (this: Ser, b: boolean): void { 111 | this.uint32Array[this.index++] = b ? 1 : 0 112 | } 113 | function deserializeBoolean (this: Ser): boolean { 114 | return this.uint32Array[this.index++] === 1 115 | } 116 | 117 | function serializeUInt32 (this: Ser, n: number): void { 118 | this.uint32Array[this.index++] = n 119 | } 120 | function deserializeUInt32 (this: Des): number { 121 | return this.uint32Array[this.index++] 122 | } 123 | function serializeFloat32 (this: Ser, n: number): void { 124 | this.float32Array[this.index++] = n 125 | } 126 | function deserializeFloat32 (this: Des): number { 127 | return this.float32Array[this.index++] 128 | } 129 | function serializeNumber (this: Ser, n: number): void { 130 | // If it's not an integer 131 | if (n % 1 !== 0) { 132 | this.uint32Array[this.index++] = TYPE_FLOAT 133 | this.serializeFloat32(n) 134 | } else if (n >= 0) { 135 | this.uint32Array[this.index++] = TYPE_UINT32 136 | this.serializeUInt32(n) 137 | } else { 138 | this.uint32Array[this.index++] = TYPE_INT32 139 | this.uint32Array[this.index++] = POW_2_32 + n 140 | } 141 | } 142 | function deserializeNumber (this: Des): number { 143 | const type = this.uint32Array[this.index++] 144 | if (type === TYPE_FLOAT) { 145 | return this.deserializeFloat32() 146 | } else if (type === TYPE_UINT32) { 147 | return this.deserializeUInt32() 148 | } else if (type === TYPE_INT32) { 149 | return this.uint32Array[this.index++] - POW_2_32 150 | } else { 151 | throw new Error('Unknown type') 152 | } 153 | } 154 | 155 | const textEncoder = new TextEncoder() 156 | function serializeString (this: Ser, str: string): void { 157 | const r = textEncoder.encodeInto(str, new Uint8Array(this.buffer, (this.index + 1) * 4)) 158 | this.uint32Array[this.index] = r.written 159 | this.index += Math.ceil(r.written / 4) + 1 160 | } 161 | 162 | const textDecoder = new TextDecoder() 163 | function deserializeString (this: Des): string { 164 | const len = this.uint32Array[this.index++] 165 | const decoded = textDecoder.decode(new Uint8Array(this.buffer, this.index * 4, len)) 166 | this.index += Math.ceil(len / 4) 167 | return decoded 168 | } 169 | 170 | function serializeArray (this: Ser, arr: T[], serialize: (ser: Ser, t: T) => void): void { 171 | const len = arr.length 172 | this.serializeUInt32(len) 173 | for (let i = 0; i < len; i++) { 174 | serialize(this, arr[i]) 175 | } 176 | } 177 | function deserializeArray (this: Des, deserialize: (ser: Des) => T): T[] { 178 | const len = this.deserializeUInt32() 179 | const arr = new Array(len) 180 | for (let i = 0; i < len; i++) { 181 | arr[i] = deserialize(this) 182 | } 183 | return arr 184 | } 185 | 186 | function serializeIterable (this: Ser, iterable: Iterable, serialize: (ser: Ser, t: T) => void): void { 187 | // Keep space for the length 188 | const currentIndex = this.index++ 189 | let n = 0 190 | for (const t of iterable) { 191 | n++ 192 | serialize(this, t) 193 | } 194 | this.uint32Array[currentIndex] = n 195 | } 196 | function deserializeIterable (this: Des, deserialize: (des: Des) => T): Iterable { 197 | const len = this.deserializeUInt32() 198 | const aGeneratorObject = (function * (des) { 199 | for (let i = 0; i < len; i++) { 200 | yield deserialize(des) 201 | } 202 | })(this) 203 | 204 | return { 205 | [Symbol.iterator] () { 206 | return aGeneratorObject 207 | } 208 | } 209 | } 210 | 211 | function unsafeSerializeUint32Array (this: Ser, arr: Uint32Array): void { 212 | const length = Math.ceil(arr.byteLength / 4) 213 | this.uint32Array[this.index++] = length 214 | this.uint32Array.set(arr, this.index) 215 | this.index += length 216 | } 217 | function unsafeDeserializeUint32Array (this: Des): Uint32Array { 218 | const byteLength = this.uint32Array[this.index++] 219 | const d = new Uint32Array(this.buffer, this.index * 4, byteLength) 220 | this.index += byteLength 221 | return d 222 | } 223 | 224 | function serializeIndexableArray (this: Ser, arr: T[], serialize: (ser: Ser, t: T) => void): void { 225 | const l = arr.length 226 | this.uint32Array[this.index++] = l 227 | let indexOffsets = this.index 228 | // Skip the length of the array twice 229 | // to store the offset + length of the array element 230 | this.index += l * 2 231 | for (let i = 0; i < l; i++) { 232 | const offsetStart = this.index 233 | serialize(this, arr[i]) 234 | const offsetEnd = this.index 235 | this.uint32Array[indexOffsets++] = offsetStart 236 | this.uint32Array[indexOffsets++] = offsetEnd - offsetStart 237 | } 238 | } 239 | function getArrayElements (this: Des, indexes: number[], deserialize: (des: Des, start: number, end: number) => T): T[] { 240 | const currentIndex = this.index + 1 241 | const l = indexes.length 242 | const arr = new Array(l) 243 | for (let i = 0; i < l; i++) { 244 | const indexOffset = currentIndex + indexes[i] * 2 245 | const start = this.uint32Array[indexOffset] 246 | const end = this.uint32Array[indexOffset + 1] 247 | arr[i] = deserialize(this, start * 4, end) 248 | } 249 | return arr 250 | } 251 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import t from 'node:test' 2 | import assert from 'node:assert' 3 | import { createSer, createDes, Ser, Des } from '../src/index.js' 4 | 5 | await t.test('boolean', async t => { 6 | const bools = [ 7 | true, false 8 | ] 9 | 10 | for (const expected of bools) { 11 | await t.test(`serialize ${JSON.stringify(expected)}`, () => { 12 | const ser = createSer() 13 | ser.serializeBoolean(expected) 14 | 15 | const des = createDes(ser.getBuffer()) 16 | 17 | const actual = des.deserializeBoolean() 18 | assert.equal(actual, expected) 19 | }) 20 | } 21 | 22 | await t.test('serialize multiple boolean', () => { 23 | const ser = createSer() 24 | ser.serializeBoolean(true) 25 | ser.serializeBoolean(true) 26 | ser.serializeBoolean(false) 27 | ser.serializeBoolean(true) 28 | 29 | const des = createDes(ser.getBuffer()) 30 | 31 | const n1 = des.deserializeBoolean() 32 | const n2 = des.deserializeBoolean() 33 | const n3 = des.deserializeBoolean() 34 | const n4 = des.deserializeBoolean() 35 | 36 | assert.equal(n1, true) 37 | assert.equal(n2, true) 38 | assert.equal(n3, false) 39 | assert.equal(n4, true) 40 | }) 41 | }) 42 | 43 | await t.test('uint32', async t => { 44 | const numbers = [ 45 | 0, 1, 5, 123, 42, 10000 46 | ] 47 | 48 | for (const expected of numbers) { 49 | await t.test(`serialize ${expected}`, () => { 50 | const ser = createSer() 51 | ser.serializeUInt32(expected) 52 | 53 | const des = createDes(ser.getBuffer()) 54 | 55 | const actual = des.deserializeUInt32() 56 | assert.equal(actual, expected) 57 | }) 58 | } 59 | 60 | await t.test('serialize multiple numbers', () => { 61 | const ser = createSer() 62 | for (const n of numbers) { 63 | ser.serializeUInt32(n) 64 | } 65 | 66 | const des = createDes(ser.getBuffer()) 67 | 68 | for (let i = 0; i < numbers.length; i++) { 69 | assert.equal(des.deserializeUInt32(), numbers[i]) 70 | } 71 | }) 72 | }) 73 | 74 | await t.test('number', async t => { 75 | const numbers = [ 76 | 0, 0.0, -0, -0.0, 42, -42, 42.42, -42.42 77 | ] 78 | 79 | for (const expected of numbers) { 80 | await t.test(`serialize ${expected}`, () => { 81 | const ser = createSer() 82 | ser.serializeNumber(expected) 83 | 84 | const des = createDes(ser.getBuffer()) 85 | 86 | const actual = des.deserializeNumber() 87 | 88 | if (isFloat(expected)) { 89 | assertFloat32Equals(actual, expected) 90 | } else { 91 | assert.equal(actual, expected) 92 | } 93 | }) 94 | } 95 | 96 | await t.test('serialize multiple numbers', () => { 97 | const ser = createSer() 98 | for (const n of numbers) { 99 | ser.serializeNumber(n) 100 | } 101 | 102 | const des = createDes(ser.getBuffer()) 103 | 104 | for (let i = 0; i < numbers.length; i++) { 105 | if (isFloat(numbers[i])) { 106 | assertFloat32Equals(des.deserializeNumber(), numbers[i]) 107 | } else { 108 | assert.equal(des.deserializeNumber(), numbers[i]) 109 | } 110 | } 111 | }) 112 | }) 113 | 114 | await t.test('float32', async t => { 115 | const numbers = [ 116 | 0.01, -0.1, 99.8 117 | ] 118 | 119 | for (const expected of numbers) { 120 | await t.test(`serialize ${expected}`, () => { 121 | const ser = createSer() 122 | ser.serializeFloat32(expected) 123 | 124 | const des = createDes(ser.getBuffer()) 125 | 126 | const actual = des.deserializeFloat32() 127 | assertFloat32Equals(actual, expected) 128 | }) 129 | } 130 | 131 | await t.test('serialize multiple numbers', () => { 132 | const ser = createSer() 133 | ser.serializeFloat32(-3) 134 | ser.serializeFloat32(55.5) 135 | ser.serializeFloat32(42.42) 136 | ser.serializeFloat32(33.3) 137 | 138 | const des = createDes(ser.getBuffer()) 139 | 140 | const n1 = des.deserializeFloat32() 141 | const n2 = des.deserializeFloat32() 142 | const n3 = des.deserializeFloat32() 143 | const n4 = des.deserializeFloat32() 144 | 145 | assertFloat32Equals(n1, -3) 146 | assertFloat32Equals(n2, 55.5) 147 | assertFloat32Equals(n3, 42.42) 148 | assertFloat32Equals(n4, 33.3) 149 | }) 150 | }) 151 | 152 | await t.test('string', async t => { 153 | const strings = [ 154 | 'f', 155 | 'fo', 156 | 'foo', 157 | 'fooo', 158 | 'fooof', 159 | 'my name is Tommaso!', 160 | 'my name is\nTommaso!', 161 | 'my name is Tommaso!'.repeat(100), 162 | '', 163 | '👍', 164 | '👍👍', 165 | '👍👍👍', 166 | '👍👍👍👍', 167 | '👍👍👍👍👍', 168 | '☃', 169 | '☔', 170 | '🌈', 171 | '🏳️‍🌈', 172 | '⭕', 173 | 'السلام عليكم', 174 | '我的名字是Tommaso', 175 | 'विकिपीडिया', 176 | 'Привет' 177 | ] 178 | 179 | for (const expected of strings) { 180 | await t.test(`serialize ${expected}`, () => { 181 | const ser = createSer() 182 | ser.serializeString(expected) 183 | 184 | const des = createDes(ser.getBuffer()) 185 | 186 | const actual = des.deserializeString() 187 | assert.equal(actual, expected) 188 | }) 189 | } 190 | 191 | await t.test('serialize multiple numbers', () => { 192 | const ser = createSer() 193 | ser.serializeString('a') 194 | ser.serializeString('b') 195 | ser.serializeString('c') 196 | 197 | const des = createDes(ser.getBuffer()) 198 | 199 | const s1 = des.deserializeString() 200 | const s2 = des.deserializeString() 201 | const s3 = des.deserializeString() 202 | 203 | assert.equal(s1, 'a') 204 | assert.equal(s2, 'b') 205 | assert.equal(s3, 'c') 206 | }) 207 | }) 208 | 209 | await t.test('array', async t => { 210 | await t.test('array of uint32', async t => { 211 | const numbers = [ 212 | [0, 1, 10000], 213 | [] 214 | ] 215 | 216 | for (const expected of numbers) { 217 | await t.test(`serialize ${JSON.stringify(expected)}`, () => { 218 | const ser = createSer() 219 | ser.serializeArray(expected, (ser, n) => ser.serializeUInt32(n)) 220 | 221 | const des = createDes(ser.getBuffer()) 222 | 223 | const actual = des.deserializeArray(() => des.deserializeUInt32()) 224 | assert.deepStrictEqual(actual, expected) 225 | }) 226 | } 227 | }) 228 | 229 | await t.test('array of string', async t => { 230 | const numbers = [ 231 | ['f', 'foo', 'foof'], 232 | ['f', 'foo', '👍', '🏳️‍🌈'], 233 | [] 234 | ] 235 | 236 | for (const expected of numbers) { 237 | await t.test(`serialize ${JSON.stringify(expected)}`, () => { 238 | const ser = createSer() 239 | ser.serializeArray(expected, (ser, n) => ser.serializeString(n)) 240 | 241 | const des = createDes(ser.getBuffer()) 242 | 243 | const actual = des.deserializeArray(() => des.deserializeString()) 244 | assert.deepStrictEqual(actual, expected) 245 | }) 246 | } 247 | }) 248 | }) 249 | 250 | await t.test('iterable', async t => { 251 | await t.test('map', async () => { 252 | const expected: Map = new Map([[0, 1], [2, 3], [4, 5]]) 253 | 254 | const ser = createSer() 255 | ser.serializeIterable(expected.entries(), (ser, n) => { 256 | ser.serializeUInt32(n[0]) 257 | ser.serializeUInt32(n[1]) 258 | }) 259 | 260 | const des = createDes(ser.getBuffer()) 261 | const m = new Map(des.deserializeIterable(() => [des.deserializeUInt32(), des.deserializeUInt32()])) 262 | assert.deepStrictEqual(m, expected) 263 | }) 264 | 265 | await t.test('map', async () => { 266 | const expected: Map = new Map([ 267 | ['foo', 1], 268 | ['bar', 3], 269 | ['baz', 5] 270 | ]) 271 | 272 | const ser = createSer() 273 | ser.serializeIterable(expected.entries(), (ser, n) => { 274 | ser.serializeString(n[0]) 275 | ser.serializeUInt32(n[1]) 276 | }) 277 | 278 | const des = createDes(ser.getBuffer()) 279 | const m = new Map(des.deserializeIterable(() => [des.deserializeString(), des.deserializeUInt32()])) 280 | assert.deepStrictEqual(m, expected) 281 | }) 282 | 283 | await t.test('set', async () => { 284 | const expected: Set = new Set([1, 3, 5]) 285 | 286 | const ser = createSer() 287 | ser.serializeIterable(expected.values(), (ser, n) => { 288 | ser.serializeUInt32(n) 289 | }) 290 | 291 | const des = createDes(ser.getBuffer()) 292 | const m = new Set(des.deserializeIterable(() => des.deserializeUInt32())) 293 | assert.deepStrictEqual(m, expected) 294 | }) 295 | }) 296 | 297 | await t.test('setBuffer with options', async t => { 298 | await t.test('uint32', async () => { 299 | const ser = createSer() 300 | ser.serializeUInt32(1) 301 | ser.serializeUInt32(2) 302 | ser.serializeUInt32(3) 303 | ser.serializeUInt32(4) 304 | const buff = ser.getBuffer() 305 | 306 | const des = createDes(new ArrayBuffer(0)) 307 | des.setBuffer(buff, 0, 4) 308 | assert.equal(des.deserializeUInt32(), 1) 309 | 310 | des.setBuffer(buff, 4, 4) 311 | assert.equal(des.deserializeUInt32(), 2) 312 | 313 | des.setBuffer(buff, 8, 4) 314 | assert.equal(des.deserializeUInt32(), 3) 315 | 316 | des.setBuffer(buff, 12, 4) 317 | assert.equal(des.deserializeUInt32(), 4) 318 | 319 | des.setBuffer(buff, 4, 12) 320 | assert.equal(des.deserializeUInt32(), 2) 321 | assert.equal(des.deserializeUInt32(), 3) 322 | assert.equal(des.deserializeUInt32(), 4) 323 | }) 324 | 325 | await t.test('uint32 & string', async () => { 326 | const ser = createSer() 327 | ser.serializeUInt32(1) 328 | ser.serializeString('v1') 329 | const buff = ser.getBuffer() 330 | 331 | const des = createDes(new ArrayBuffer(0)) 332 | des.setBuffer(buff, 0, 12) 333 | assert.equal(des.deserializeUInt32(), 1) 334 | assert.equal(des.deserializeString(), 'v1') 335 | 336 | des.setBuffer(buff, 4, 8) 337 | assert.equal(des.deserializeString(), 'v1') 338 | }) 339 | }) 340 | 341 | await t.test('reset', async () => { 342 | const ser = createSer() 343 | ser.serializeUInt32(42) 344 | 345 | const des = createDes(ser.getBuffer()) 346 | assert.deepStrictEqual(des.deserializeUInt32(), 42) 347 | 348 | ser.serializeUInt32(33) 349 | des.setBuffer(ser.getBuffer()) 350 | assert.deepStrictEqual(des.deserializeUInt32(), 42) 351 | assert.deepStrictEqual(des.deserializeUInt32(), 33) 352 | 353 | ser.reset() 354 | ser.serializeUInt32(11) 355 | des.setBuffer(ser.getBuffer()) 356 | assert.deepStrictEqual(des.deserializeUInt32(), 11) 357 | }) 358 | 359 | function serializeItem (ser: Ser, t: { foo: string, bar: number }): void { 360 | ser.serializeString(t.foo) 361 | ser.serializeUInt32(t.bar) 362 | } 363 | 364 | function deserializeItem (des: Des): { foo: string, bar: number } { 365 | const foo = des.deserializeString() 366 | const bar = des.deserializeUInt32() 367 | return { 368 | foo, 369 | bar 370 | } 371 | } 372 | 373 | await t.test('serialize + getArrayelements + serialize unsafe + deserialize with deserialize unsafe', async () => { 374 | const arr = [ 375 | { foo: 'v1', bar: 42 }, 376 | { foo: 'v2', bar: 2 }, 377 | { foo: 'v3', bar: 99 } 378 | ] 379 | const elementIndexes = [0, 2] 380 | 381 | let docsStorageBuffer: ArrayBuffer 382 | { 383 | const ser = createSer() 384 | ser.serializeIndexableArray(arr, serializeItem) 385 | docsStorageBuffer = ser.getBuffer() 386 | } 387 | 388 | let foo: ArrayBuffer 389 | { 390 | const ser = createSer() 391 | 392 | const des = createDes(docsStorageBuffer) 393 | const elements = des.getArrayElements(elementIndexes, function (_des, offset, length) { 394 | return new Uint32Array(docsStorageBuffer, offset, length) 395 | }) 396 | 397 | ser.serializeArray(elements, (ser, uint32Array) => { 398 | ser.unsafeSerializeUint32Array(uint32Array) 399 | }) 400 | foo = ser.getBuffer() 401 | } 402 | 403 | let found: Array<{ foo: string, bar: number }> 404 | { 405 | const des = createDes(foo) 406 | 407 | const itemDes = createDes(new ArrayBuffer(0)) 408 | found = des.deserializeArray((des) => { 409 | const buff = des.unsafeDeserializeUint32Array() 410 | itemDes.setBuffer(buff.buffer, buff.byteOffset, buff.byteLength) 411 | return deserializeItem(itemDes) 412 | }) 413 | } 414 | 415 | assert.deepStrictEqual(found, elementIndexes.map(i => arr[i])) 416 | }) 417 | 418 | await t.test('with option', async t => { 419 | await t.test('bufferSize', async () => { 420 | { 421 | const ser = createSer({ bufferSize: 4 }) 422 | assert.equal(ser.buffer.byteLength, 4) 423 | } 424 | { 425 | const ser = createSer({ bufferSize: 8 }) 426 | assert.equal(ser.buffer.byteLength, 8) 427 | } 428 | { 429 | const ser = createSer({ bufferSize: 2 ** 32 - 4 }) 430 | assert.equal(ser.buffer.byteLength, 2 ** 32 - 4) 431 | } 432 | assert.throws(() => createSer({ bufferSize: 2 ** 32 }), err => { 433 | return (err as Error).message.includes('bufferSize option must be strictly less than 2 ** 32') 434 | }) 435 | }) 436 | }) 437 | 438 | await t.test('random items', async t => { 439 | const elements: any[] = [ 440 | { item: 'foo', serializer: 'serializeString', deserializer: 'deserializeString' }, 441 | { item: 'bar', serializer: 'serializeString', deserializer: 'deserializeString' }, 442 | { item: 3, serializer: 'serializeUInt32', deserializer: 'deserializeUInt32' }, 443 | { item: 42, serializer: 'serializeUInt32', deserializer: 'deserializeUInt32' }, 444 | { item: 0.6, serializer: 'serializeFloat32', deserializer: 'deserializeFloat32' }, 445 | { item: -99.42, serializer: 'serializeFloat32', deserializer: 'deserializeFloat32' } 446 | ] 447 | 448 | const permutations: any[][] = permutator(elements) 449 | 450 | for (const permutation of permutations) { 451 | await t.test(`serialize ${JSON.stringify(permutation)}`, () => { 452 | const ser = createSer() as any 453 | for (const { item, serializer } of permutation) { 454 | ser[serializer](item) 455 | } 456 | 457 | const des = createDes(ser.getBuffer()) as any 458 | 459 | const actual = Object.entries(permutation.map(({ deserializer }) => des[deserializer]())) 460 | 461 | const notFloat = actual.filter(([, n]) => !isFloat(n)) 462 | assert.deepStrictEqual(notFloat, Object.entries(permutation.map(({ item }) => item)) 463 | .filter(([, n]) => !isFloat(n))) 464 | 465 | const expectedFloats = Object.entries(permutation.map(({ item }) => item)) 466 | .filter(([, n]) => isFloat(n)) 467 | .map(([, n]) => n) 468 | const floats = actual.filter(([, n]) => isFloat(n)) 469 | .map(([, n]) => n) 470 | for (let i = 0; i < expectedFloats.length; i++) { 471 | assert.ok(Math.abs(expectedFloats[i] - floats[i]) < 0.0001) 472 | } 473 | }) 474 | } 475 | }) 476 | 477 | function permutator (inputArr: any): any[][] { 478 | const results: any[] = [] 479 | 480 | function permute (arr: any, memo: any): any[] { 481 | let cur 482 | const c = memo 483 | 484 | for (let i = 0; i < arr.length; i++) { 485 | cur = arr.splice(i, 1) 486 | if (arr.length === 0) { 487 | results.push(c.concat(cur)) 488 | } 489 | permute(arr.slice(), c.concat(cur)) 490 | arr.splice(i, 0, cur[0]) 491 | } 492 | 493 | return results 494 | } 495 | 496 | return permute(inputArr, []) 497 | } 498 | 499 | function isFloat (n: number): boolean { 500 | return Number(n) === n && n % 1 !== 0 501 | } 502 | 503 | function assertFloat32Equals (a: number, b: number): void { 504 | assert.ok(Math.abs(a - b) < 0.0001) 505 | } 506 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "CommonJS", 5 | "declaration": true, 6 | "outDir": "./dist/cjs", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true 11 | }, 12 | "include": ["src/**/*"], 13 | "exclude": ["node_modules", "**/*.spec.ts"] 14 | } -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "ESNext", 5 | "declaration": true, 6 | "outDir": "./dist/esm", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true 11 | }, 12 | "include": ["src/**/*"], 13 | "exclude": ["node_modules", "**/*.spec.ts"] 14 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "NodeNext", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "moduleResolution": "nodenext", 8 | "rootDir": "./", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "lib": ["ES2023", "dom"] 13 | }, 14 | "include": [ 15 | "src/**/*", 16 | "examples/**/*", 17 | "tests/**/*", 18 | "benchmarks/isolated/*" 19 | ], 20 | "exclude": [ 21 | "node_modules", 22 | "benchmarks/e2e" 23 | ] 24 | } --------------------------------------------------------------------------------