├── .github └── workflows │ ├── nodejs.yml │ ├── release.yml │ └── typedoc.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── eslint.config.js ├── package.json ├── src ├── .npmignore ├── __tests__ │ ├── __snapshots__ │ │ ├── read.test.ts.snap │ │ └── write.test.ts.snap │ ├── core.test.ts │ ├── offset.test.ts │ ├── read.test.ts │ ├── read_array.test.ts │ ├── to_array.test.ts │ └── write.test.ts ├── iobuffer.ts └── text.ts ├── tsconfig.build.json └── tsconfig.json /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | nodejs: 11 | # Documentation: https://github.com/zakodium/workflows#nodejs-ci 12 | uses: zakodium/workflows/.github/workflows/nodejs.yml@nodejs-v1 13 | with: 14 | lint-check-types: true 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | # Documentation: https://github.com/zakodium/workflows#release 11 | uses: zakodium/workflows/.github/workflows/release.yml@release-v1 12 | with: 13 | npm: true 14 | secrets: 15 | github-token: ${{ secrets.BOT_TOKEN }} 16 | npm-token: ${{ secrets.NPM_BOT_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/typedoc.yml: -------------------------------------------------------------------------------- 1 | name: Deploy TypeDoc on GitHub pages 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | env: 9 | NODE_VERSION: 22.x 10 | ENTRY_FILE: 'src/iobuffer.ts' 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ env.NODE_VERSION }} 20 | - name: Install dependencies 21 | run: npm install 22 | - name: Build documentation 23 | uses: zakodium/typedoc-action@v2 24 | with: 25 | entry: ${{ env.ENTRY_FILE }} 26 | - name: Deploy to GitHub pages 27 | uses: JamesIves/github-pages-deploy-action@releases/v4 28 | with: 29 | token: ${{ secrets.BOT_TOKEN }} 30 | branch: gh-pages 31 | folder: docs 32 | clean: true 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_STORE 4 | coverage 5 | lib 6 | lib-esm 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [5.4.0](https://github.com/image-js/iobuffer/compare/v5.3.2...v5.4.0) (2025-03-04) 4 | 5 | 6 | ### Features 7 | 8 | * add getWrittenByteLength function ([#76](https://github.com/image-js/iobuffer/issues/76)) ([093322c](https://github.com/image-js/iobuffer/commit/093322ce85fcc86e596df35b825609443bb8625c)) 9 | 10 | ## [5.3.2](https://github.com/image-js/iobuffer/compare/v5.3.1...v5.3.2) (2023-01-30) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * do not crash on initialization if bigint arrays don't exist ([#66](https://github.com/image-js/iobuffer/issues/66)) ([098c845](https://github.com/image-js/iobuffer/commit/098c8458f73f2f43a3a5499ed4a0c45ba41ca7d4)) 16 | 17 | ## [5.3.1](https://github.com/image-js/iobuffer/compare/v5.3.0...v5.3.1) (2022-12-05) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * **readArray:** account for endianness ([#62](https://github.com/image-js/iobuffer/issues/62)) ([2604862](https://github.com/image-js/iobuffer/commit/26048621abc60e8830ce690b31ebfcf0e38dd422)) 23 | 24 | ## [5.3.0](https://github.com/image-js/iobuffer/compare/v5.2.1...v5.3.0) (2022-11-29) 25 | 26 | 27 | ### Features 28 | 29 | * add readArray method to read any typed array ([#58](https://github.com/image-js/iobuffer/issues/58)) ([eda3e91](https://github.com/image-js/iobuffer/commit/eda3e918ce93e25c9f0e82e626b48bc01c0960ee)) 30 | 31 | ### [5.2.1](https://www.github.com/image-js/iobuffer/compare/v5.2.0...v5.2.1) (2022-10-07) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * remap browser version of text decoder ([4531fa9](https://www.github.com/image-js/iobuffer/commit/4531fa94120f8984029dbe37574c64986f172469)) 37 | 38 | ## [5.2.0](https://www.github.com/image-js/iobuffer/compare/v5.1.0...v5.2.0) (2022-10-04) 39 | 40 | 41 | ### Features 42 | 43 | * add IOBuffer.back() ([#52](https://www.github.com/image-js/iobuffer/issues/52)) ([49a2df9](https://www.github.com/image-js/iobuffer/commit/49a2df924ac512d96d394eecb7fe24fdd2469ead)) 44 | * decode text ([616b1a5](https://www.github.com/image-js/iobuffer/commit/616b1a5e841ceb1174a86c6eb87bcffe571aca2f)) 45 | 46 | ## [5.1.0](https://www.github.com/image-js/iobuffer/compare/v5.0.4...v5.1.0) (2021-12-17) 47 | 48 | 49 | ### Features 50 | 51 | * add int64 support ([3a306c0](https://www.github.com/image-js/iobuffer/commit/3a306c0d3fb62f88be7ad59ea1d202a623907426)) 52 | 53 | ### [5.0.4](https://www.github.com/image-js/iobuffer/compare/v5.0.3...v5.0.4) (2021-10-12) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * set TypeScript target to ES2020 ([4f4b412](https://www.github.com/image-js/iobuffer/commit/4f4b4120b90d0fcd67e6c36f9bd81ea7cbcd8c3d)) 59 | 60 | ### [5.0.3](https://github.com/image-js/iobuffer/compare/v5.0.2...v5.0.3) (2021-02-26) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * add browser field for lib/ version too ([#43](https://github.com/image-js/iobuffer/issues/43)) ([508083f](https://github.com/image-js/iobuffer/commit/508083f9ea2288df57992d1659cfe78e0b751a38)) 66 | 67 | ## [5.0.2](https://github.com/image-js/iobuffer/compare/v5.0.1...v5.0.2) (2019-11-12) 68 | 69 | 70 | ### Bug Fixes 71 | 72 | * include js file in the build ([3be165a](https://github.com/image-js/iobuffer/commit/3be165a05da7c7287c87cd17da87e2ab9549baf8)) 73 | 74 | 75 | 76 | ## [5.0.1](https://github.com/image-js/iobuffer/compare/v5.0.0...v5.0.1) (2019-11-12) 77 | 78 | 79 | 80 | # [5.0.0](https://github.com/image-js/iobuffer/compare/v4.0.1...v5.0.0) (2019-11-12) 81 | 82 | 83 | ### chore 84 | 85 | * update dependencies ([b7f51b8](https://github.com/image-js/iobuffer/commit/b7f51b8b5ca82f6d16e91273e0198f9650207acb)) 86 | 87 | 88 | ### BREAKING CHANGES 89 | 90 | * Node.js 6 and 8 are no longer supported. 91 | 92 | 93 | 94 | ## [4.0.1](https://github.com/image-js/iobuffer/compare/v4.0.0...v4.0.1) (2019-03-27) 95 | 96 | 97 | 98 | 99 | # [4.0.0](https://github.com/image-js/iobuffer/compare/v3.2.0...v4.0.0) (2018-08-22) 100 | 101 | 102 | ### Bug Fixes 103 | 104 | * fix test-travis script ([fd74496](https://github.com/image-js/iobuffer/commit/fd74496)) 105 | * remove implied undefined type from InputData ([0040962](https://github.com/image-js/iobuffer/commit/0040962)) 106 | 107 | 108 | ### Chores 109 | 110 | * remove support for Node 4 ([feabb42](https://github.com/image-js/iobuffer/commit/feabb42)) 111 | 112 | 113 | ### Features 114 | 115 | * convert to TypeScript ([b73c748](https://github.com/image-js/iobuffer/commit/b73c748)) 116 | * remove getBuffer method ([39dbc89](https://github.com/image-js/iobuffer/commit/39dbc89)) 117 | 118 | 119 | ### BREAKING CHANGES 120 | 121 | * The `getBuffer` method has been removed. Use `toArray` instead. 122 | * The IOBuffer constructor is now a named export. Access it with 123 | `require('iobuffer').IOBuffer` or `import { IOBuffer } from 'iobuffer'`. 124 | * Removed support for Node 4 125 | 126 | 127 | 128 | 129 | # [3.2.0](https://github.com/image-js/iobuffer/compare/v3.1.0...v3.2.0) (2016-12-27) 130 | 131 | 132 | ### Features 133 | 134 | * add readUtf8 and writeUtf8 ([6118a54](https://github.com/image-js/iobuffer/commit/6118a54)), closes [#31](https://github.com/image-js/iobuffer/issues/31) 135 | 136 | 137 | 138 | 139 | # [3.1.0](https://github.com/image-js/iobuffer/compare/v3.0.0...v3.1.0) (2016-12-15) 140 | 141 | 142 | ### Features 143 | 144 | * add getBuffer method ([c798093](https://github.com/image-js/iobuffer/commit/c798093)) 145 | 146 | 147 | 148 | 149 | # [3.0.0](https://github.com/image-js/iobuffer/compare/v2.1.0...v3.0.0) (2016-12-13) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * **iobuffer:** fix an edge case with ensureAvailable ([501dc48](https://github.com/image-js/iobuffer/commit/501dc48)) 155 | 156 | 157 | ### Features 158 | 159 | * **iobuffer:** add chainability ([bbac001](https://github.com/image-js/iobuffer/commit/bbac001)) 160 | * **iobuffer:** add pushMark and popMark API ([a69e228](https://github.com/image-js/iobuffer/commit/a69e228)), closes [#28](https://github.com/image-js/iobuffer/issues/28) 161 | 162 | 163 | 164 | 165 | # [2.1.0](https://github.com/image-js/iobuffer/compare/v2.0.0...v2.1.0) (2016-09-20) 166 | 167 | 168 | ### Features 169 | 170 | * add support for offset option ([ffedd73](https://github.com/image-js/iobuffer/commit/ffedd73)) 171 | 172 | 173 | 174 | 175 | # [2.0.0](https://github.com/image-js/iobuffer/compare/v2.0.0-1...v2.0.0) (2015-11-23) 176 | 177 | 178 | 179 | 180 | # [2.0.0-1](https://github.com/image-js/iobuffer/compare/v2.0.0-0...v2.0.0-1) (2015-10-16) 181 | 182 | 183 | 184 | 185 | # [2.0.0-0](https://github.com/image-js/iobuffer/compare/v1.1.0-0...v2.0.0-0) (2015-10-16) 186 | 187 | 188 | 189 | 190 | # [1.1.0-0](https://github.com/image-js/iobuffer/compare/v1.0.4...v1.1.0-0) (2015-10-02) 191 | 192 | 193 | 194 | 195 | ## [1.0.4](https://github.com/image-js/iobuffer/compare/v1.0.3...v1.0.4) (2015-09-26) 196 | 197 | 198 | 199 | 200 | ## [1.0.3](https://github.com/image-js/iobuffer/compare/v1.0.2...v1.0.3) (2015-09-24) 201 | 202 | 203 | 204 | 205 | ## [1.0.2](https://github.com/image-js/iobuffer/compare/v1.0.1...v1.0.2) (2015-09-24) 206 | 207 | 208 | 209 | 210 | ## [1.0.1](https://github.com/image-js/iobuffer/compare/v1.0.0...v1.0.1) (2015-09-24) 211 | 212 | 213 | 214 | 215 | # 1.0.0 (2015-09-23) 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michaël Zasso 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Zakodium logo 4 | 5 |

6 | Maintained by Zakodium 7 |

8 |

9 | 10 | # iobuffer 11 | 12 | [![NPM version](https://img.shields.io/npm/v/iobuffer.svg)](https://www.npmjs.com/package/iobuffer) 13 | [![npm download](https://img.shields.io/npm/dm/iobuffer.svg)](https://www.npmjs.com/package/iobuffer) 14 | [![test coverage](https://img.shields.io/codecov/c/github/image-js/iobuffer.svg)](https://codecov.io/gh/image-js/iobuffer) 15 | [![license](https://img.shields.io/npm/l/tiff.svg)](https://github.com/image-js/tiff/blob/main/LICENSE) 16 | 17 | Read and write binary data in ArrayBuffers. 18 | 19 | ## Installation 20 | 21 | ```console 22 | npm i iobuffer 23 | ``` 24 | 25 | ## API 26 | 27 | Complete [API documentation](http://image-js.github.io/iobuffer/) 28 | 29 | ## Usage example 30 | 31 | ```js 32 | const { IOBuffer } = require('iobuffer'); 33 | 34 | const io = new IOBuffer(); 35 | // Pointer offset is 0 36 | io.writeChars('Hello world') // Write 11 chars, pointer offset now 11 37 | .writeUint32(42) // Write 32-bit int (default is little-endian), pointer offset now 15 38 | .setBigEndian() // Switch to big-endian mode 39 | .writeUint32(24) // Write another 32-bit int, but big-endian, pointer offset now 19 40 | .mark() // Bookmark current pointer offset (19) 41 | .skip(2) // Pointer offset now 21 42 | .writeBoolean(true) // Write 0xff, pointer offset now 22 43 | .reset() // Go to bookmarked pointer offset, pointer offset now 19 44 | .setLittleEndian() // Go back to little endian mode 45 | .writeUint16(18) // Write 16-bit unsigned integer in the previously skipped 2 bytes, pointer offset now 21 46 | .rewind() // Pointer offset back to 0 47 | .toArray(); // Get a Uint8Array over the written part [0-21] of the internal ArrayBuffer 48 | ``` 49 | 50 | ## License 51 | 52 | [MIT](./LICENSE) 53 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from 'eslint/config'; 2 | import cheminfo from 'eslint-config-cheminfo-typescript'; 3 | 4 | export default defineConfig(globalIgnores(['coverage', 'lib']), cheminfo); 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobuffer", 3 | "version": "5.4.0", 4 | "description": "Read and write binary data on ArrayBuffers", 5 | "type": "module", 6 | "exports": "./lib/iobuffer.js", 7 | "files": [ 8 | "lib", 9 | "src" 10 | ], 11 | "scripts": { 12 | "check-types": "tsc --noEmit", 13 | "clean": "rimraf lib", 14 | "eslint": "eslint .", 15 | "eslint-fix": "npm run eslint -- --fix", 16 | "prepack": "npm run tsc", 17 | "prettier": "prettier --check .", 18 | "prettier-write": "prettier --write .", 19 | "test": "npm run test-only && npm run check-types && npm run eslint && npm run prettier", 20 | "test-only": "vitest run --coverage", 21 | "tsc": "npm run clean && npm run tsc-build", 22 | "tsc-build": "tsc --project tsconfig.build.json" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/image-js/iobuffer.git" 27 | }, 28 | "author": "Michaël Zasso", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/image-js/iobuffer/issues" 32 | }, 33 | "homepage": "https://github.com/image-js/iobuffer#readme", 34 | "devDependencies": { 35 | "@types/node": "^22.15.29", 36 | "@vitest/coverage-v8": "^3.1.4", 37 | "eslint": "^9.28.0", 38 | "eslint-config-cheminfo-typescript": "^18.0.1", 39 | "prettier": "^3.5.3", 40 | "rimraf": "^6.0.1", 41 | "typescript": "^5.8.3", 42 | "vitest": "^3.1.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | .npmignore 3 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/read.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`read data > readBigInt64 1`] = `71777218556133120n`; 4 | 5 | exports[`read data > readBigUint64 1`] = `71777218556133120n`; 6 | 7 | exports[`read data > readFloat32 1`] = `-1.714652191593956e+38`; 8 | 9 | exports[`read data > readFloat32 2`] = `2.3418408851396162e-38`; 10 | 11 | exports[`read data > readFloat64 1`] = `7.0641644724607326e-304`; 12 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/write.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`write data > writeBigInt64 1`] = `-1234567890n`; 4 | 5 | exports[`write data > writeBigUint64 1`] = `1234567890n`; 6 | 7 | exports[`write data > writeFloat32 1`] = `-1.7100000153031665e+38`; 8 | 9 | exports[`write data > writeFloat32 2`] = `2.3399999993470327e-38`; 10 | 11 | exports[`write data > writeFloat64 1`] = `7.06e-304`; 12 | -------------------------------------------------------------------------------- /src/__tests__/core.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, it } from 'vitest'; 2 | 3 | import { IOBuffer } from '../iobuffer.ts'; 4 | 5 | describe('core methods', () => { 6 | let buffer: IOBuffer; 7 | beforeEach(() => { 8 | buffer = new IOBuffer(); 9 | }); 10 | 11 | it('should start at 0', () => { 12 | expect(buffer.offset).toBe(0); 13 | }); 14 | 15 | it('should report availability', () => { 16 | buffer.length = 15; 17 | expect(buffer.available(15)).toBe(true); 18 | expect(buffer.available(20)).toBe(false); 19 | buffer.skip(1); 20 | expect(buffer.available(15)).toBe(false); 21 | expect(buffer.available(14)).toBe(true); 22 | expect(buffer.available(5)).toBe(true); 23 | buffer.seek(14); 24 | expect(buffer.available()).toBe(true); 25 | expect(buffer.available(1)).toBe(true); 26 | expect(buffer.available(2)).toBe(false); 27 | }); 28 | 29 | it('get/set endianess', () => { 30 | expect(buffer.isLittleEndian()).toBe(true); 31 | expect(buffer.isBigEndian()).toBe(false); 32 | buffer.setBigEndian(); 33 | expect(buffer.isLittleEndian()).toBe(false); 34 | expect(buffer.isBigEndian()).toBe(true); 35 | buffer.setLittleEndian(); 36 | expect(buffer.isLittleEndian()).toBe(true); 37 | expect(buffer.isBigEndian()).toBe(false); 38 | }); 39 | 40 | it('skip', () => { 41 | buffer.skip(); 42 | expect(buffer.offset).toBe(1); 43 | buffer.skip(); 44 | buffer.skip(1); 45 | expect(buffer.offset).toBe(3); 46 | buffer.skip(5); 47 | expect(buffer.offset).toBe(8); 48 | }); 49 | 50 | it('back', () => { 51 | buffer.offset = 8; 52 | buffer.back(5); 53 | expect(buffer.offset).toBe(3); 54 | buffer.back(1); 55 | buffer.back(); 56 | expect(buffer.offset).toBe(1); 57 | buffer.back(); 58 | expect(buffer.offset).toBe(0); 59 | }); 60 | 61 | it('seek', () => { 62 | buffer.seek(0); 63 | expect(buffer.offset).toBe(0); 64 | buffer.seek(12); 65 | expect(buffer.offset).toBe(12); 66 | }); 67 | 68 | it('mark/reset', () => { 69 | buffer.seek(12); 70 | buffer.mark(); 71 | buffer.skip(2); 72 | buffer.seek(3); 73 | buffer.reset(); 74 | expect(buffer.offset).toBe(12); 75 | }); 76 | 77 | it('pop and push marks', () => { 78 | buffer.seek(5); 79 | buffer.pushMark(); 80 | buffer.seek(10); 81 | buffer.popMark(); 82 | expect(buffer.offset).toBe(5); 83 | buffer.pushMark(); 84 | buffer.seek(12); 85 | buffer.pushMark(); 86 | buffer.skip(1); 87 | expect(buffer.offset).toBe(13); 88 | buffer.popMark(); 89 | expect(buffer.offset).toBe(12); 90 | buffer.popMark(); 91 | expect(buffer.offset).toBe(5); 92 | expect(() => { 93 | buffer.popMark(); 94 | }).toThrow(/Mark stack empty/); 95 | }); 96 | 97 | it('rewind', () => { 98 | buffer.seek(10); 99 | buffer.rewind(); 100 | expect(buffer.offset).toBe(0); 101 | }); 102 | 103 | it('is chainable', () => { 104 | const io = new IOBuffer(); 105 | expect(() => { 106 | io.writeChars('abc') 107 | .writeUint32(10) 108 | .writeBoolean(true) 109 | .writeByte(2) 110 | .writeChar('x') 111 | .rewind() 112 | .skip(5) 113 | .mark() 114 | .pushMark() 115 | .seek(30) 116 | .reset() 117 | .popMark() 118 | .ensureAvailable(100) 119 | .setLittleEndian() 120 | .setBigEndian() 121 | .reset(); 122 | }).not.toThrow(); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /src/__tests__/offset.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { IOBuffer } from '../iobuffer.ts'; 4 | 5 | describe('test with offset', () => { 6 | it('should accept other IOBuffer with offset option', () => { 7 | const io1 = new IOBuffer(10); 8 | io1.writeBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 9 | const io2 = new IOBuffer(io1, { offset: 4 }); 10 | expect(io2).toHaveLength(6); 11 | expect(io2.readByte()).toBe(4); 12 | expect(io2.readByte()).toBe(5); 13 | expect(io2.readByte()).toBe(6); 14 | expect(io2.readByte()).toBe(7); 15 | }); 16 | it('should add offset for new data', () => { 17 | const io = new IOBuffer(128, { offset: 10 }); 18 | expect(io.byteLength).toBe(118); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/__tests__/read.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, it } from 'vitest'; 2 | 3 | import { IOBuffer } from '../iobuffer.ts'; 4 | 5 | describe('read data', () => { 6 | const data = new Uint32Array([0xff00ff00, 0x00ff00ff]); 7 | let buffer: IOBuffer; 8 | beforeEach(() => { 9 | buffer = new IOBuffer(data); 10 | }); 11 | 12 | it('construct', () => { 13 | // ArrayBuffer 14 | let theBuffer = new IOBuffer(new ArrayBuffer(4)); 15 | expect(theBuffer).toHaveLength(4); 16 | // Typed array 17 | theBuffer = new IOBuffer(new Uint8Array(2)); 18 | expect(theBuffer).toHaveLength(2); 19 | theBuffer = new IOBuffer(new Uint16Array(2)); 20 | expect(theBuffer).toHaveLength(4); 21 | // Node.js buffer 22 | theBuffer = new IOBuffer(Buffer.alloc(5)); 23 | expect(theBuffer).toHaveLength(5); 24 | }); 25 | 26 | it('read too far', () => { 27 | buffer.readUint16(); 28 | buffer.readUint32(); 29 | expect(() => buffer.readUint32()).toThrow(RangeError); 30 | }); 31 | 32 | it('readBoolean', () => { 33 | expect(buffer.readBoolean()).toBe(false); 34 | expect(buffer.readBoolean()).toBe(true); 35 | expect(buffer.readBoolean()).toBe(false); 36 | expect(buffer.readBoolean()).toBe(true); 37 | }); 38 | 39 | it('readInt8', () => { 40 | expect(buffer.readInt8()).toBe(0); 41 | expect(buffer.readInt8()).toBe(-1); 42 | expect(buffer.readInt8()).toBe(0); 43 | expect(buffer.readInt8()).toBe(-1); 44 | expect(buffer.readInt8()).toBe(-1); 45 | expect(buffer.readInt8()).toBe(0); 46 | expect(buffer.readInt8()).toBe(-1); 47 | expect(buffer.readInt8()).toBe(0); 48 | }); 49 | 50 | it('readUint8 / readByte / readBytes', () => { 51 | expect(buffer.readUint8()).toBe(0); 52 | expect(buffer.readUint8()).toBe(255); 53 | expect(buffer.readByte()).toBe(0); 54 | expect(buffer.readByte()).toBe(255); 55 | expect(Array.from(buffer.readBytes())).toStrictEqual([255]); 56 | expect(Array.from(buffer.readBytes(3))).toStrictEqual([0, 255, 0]); 57 | }); 58 | 59 | it('readInt16', () => { 60 | expect(buffer.readInt16()).toBe(-256); 61 | expect(buffer.readInt16()).toBe(-256); 62 | expect(buffer.readInt16()).toBe(255); 63 | expect(buffer.readInt16()).toBe(255); 64 | }); 65 | 66 | it('readUint16', () => { 67 | expect(buffer.readUint16()).toBe(65280); 68 | expect(buffer.readUint16()).toBe(65280); 69 | expect(buffer.readUint16()).toBe(255); 70 | expect(buffer.readUint16()).toBe(255); 71 | }); 72 | 73 | it('readInt32', () => { 74 | expect(buffer.readInt32()).toBe(-16711936); 75 | expect(buffer.readInt32()).toBe(16711935); 76 | }); 77 | 78 | it('readUint32', () => { 79 | expect(buffer.readUint32()).toBe(4278255360); 80 | expect(buffer.readUint32()).toBe(16711935); 81 | }); 82 | 83 | it('readFloat32', () => { 84 | expect(buffer.readFloat32()).toMatchSnapshot(); 85 | expect(buffer.readFloat32()).toMatchSnapshot(); 86 | }); 87 | 88 | it('readFloat64', () => { 89 | expect(buffer.readFloat64()).toMatchSnapshot(); 90 | }); 91 | 92 | it('readBigInt64', () => { 93 | expect(buffer.readBigInt64()).toMatchSnapshot(); 94 | }); 95 | 96 | it('readBigUint64', () => { 97 | expect(buffer.readBigUint64()).toMatchSnapshot(); 98 | }); 99 | 100 | it('readChar(s)', () => { 101 | const chars = 'hello' 102 | .split('') 103 | .map((char) => char.codePointAt(0) as number); 104 | const theBuffer = new IOBuffer(new Uint8Array(chars)); 105 | expect(theBuffer.readChar()).toBe('h'); 106 | expect(theBuffer.readChars()).toBe('e'); 107 | expect(theBuffer.readChars(3)).toBe('llo'); 108 | }); 109 | 110 | it('readUtf8', () => { 111 | const theBuffer = new IOBuffer( 112 | Buffer.from([42, 0x34, 0x32, 0xe2, 0x82, 0xac, 42]), 113 | ); 114 | expect(theBuffer.readByte()).toBe(42); 115 | const str = theBuffer.readUtf8(5); 116 | expect(str).toBe('42€'); 117 | expect(theBuffer.readByte()).toBe(42); 118 | theBuffer.seek(1); 119 | expect(theBuffer.readUtf8()).toBe('4'); 120 | }); 121 | 122 | it('decodeText', () => { 123 | const theBuffer = new IOBuffer( 124 | Buffer.from([ 125 | 42, 0x34, 0x32, 0xe2, 0x82, 0xac, 42, 0x72, 0x75, 0x6e, 0x21, 0xcf, 126 | 0x79, 0x6f, 0x73, 0x65, 0x6d, 0x69, 0x74, 0x65, 127 | ]), 128 | ); 129 | expect(theBuffer.readByte()).toBe(42); 130 | const strE1 = theBuffer.decodeText(5); 131 | expect(strE1).toBe('42€'); 132 | expect(theBuffer.readByte()).toBe(42); 133 | const strE2 = theBuffer.decodeText(4, 'windows-1251'); 134 | expect(strE2).toBe('run!'); 135 | expect(theBuffer.decodeText(1, 'windows-1251')).toBe('П'); 136 | expect(theBuffer.decodeText(8, 'ISO-8859-2')).toBe('yosemite'); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /src/__tests__/read_array.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { IOBuffer } from '../iobuffer.ts'; 4 | 5 | describe('readArray', () => { 6 | it('0 bytes', () => { 7 | const buffer = new IOBuffer(new Uint8Array([1, 2])); 8 | 9 | const result = buffer.readArray(0, 'int8'); 10 | // return empty typed array 11 | expect(result).toStrictEqual(new Int8Array([])); 12 | // do not change offset 13 | expect(buffer.offset).toBe(0); 14 | }); 15 | 16 | it('uint8', () => { 17 | const buffer = new IOBuffer(new Uint8Array([1, 2, 3, 4])); 18 | buffer.setLittleEndian(); 19 | buffer.readArray(0, 'int8'); 20 | const sameLE = buffer.readArray(4, 'uint8'); 21 | expect(buffer.offset).toBe(4); 22 | 23 | buffer.setBigEndian(); 24 | buffer.offset = 0; 25 | const sameBE = buffer.readArray(4, 'uint8'); 26 | expect(buffer.offset).toBe(4); 27 | 28 | expect(sameLE).toStrictEqual(sameBE); 29 | }); 30 | 31 | it('uint 16', () => { 32 | /* 33 | LE system stores [258, 259] as [2, 1, 3, 1] 34 | BE system as [1, 2, 1, 3] 35 | */ 36 | const dataFromLE = new Uint8Array([2, 1, 3, 1]); 37 | const dataFromBE = new Uint8Array([1, 2, 1, 3]); 38 | const firstNumber = 258; 39 | const secondNumber = 259; 40 | 41 | // little endian 42 | let buffer = new IOBuffer(dataFromLE); 43 | const LeRes = buffer.readArray(2, 'uint16'); 44 | expect(buffer.offset).toBe(4); 45 | expect(LeRes[0]).toBe(firstNumber); 46 | expect(LeRes[1]).toBe(secondNumber); 47 | 48 | // big endian 49 | buffer = new IOBuffer(dataFromBE); 50 | buffer.setBigEndian(); 51 | const BeRes = buffer.readArray(2, 'uint16'); 52 | expect(buffer.offset).toBe(4); 53 | expect(BeRes[0]).toBe(firstNumber); 54 | expect(BeRes[1]).toBe(secondNumber); 55 | }); 56 | 57 | it('int 32', () => { 58 | //numbers taken from Buffer.readInt32LE in Node.js 59 | const dataFromLE = new Uint8Array([1, 5, 3, 31, 3, 4, 40, 8]); 60 | const dataFromBE = new Uint8Array([31, 3, 5, 1, 8, 40, 4, 3]); 61 | const firstNumber = 520291585; 62 | const secondNumber = 136840195; 63 | 64 | // little endian 65 | let buffer = new IOBuffer(dataFromLE); 66 | const LeRes = buffer.readArray(2, 'int32'); 67 | expect(buffer.offset).toBe(8); 68 | expect(LeRes[0]).toBe(firstNumber); 69 | expect(LeRes[1]).toBe(secondNumber); 70 | 71 | // big endian 72 | buffer = new IOBuffer(dataFromBE); 73 | buffer.setBigEndian(); 74 | const BeRes = buffer.readArray(2, 'int32'); 75 | expect(buffer.offset).toBe(8); 76 | expect(BeRes[0]).toBe(firstNumber); 77 | expect(BeRes[1]).toBe(secondNumber); 78 | }); 79 | it('uint 64', () => { 80 | //numbers taken from Buffer.readBigUIntLE in Node.js 81 | const dataFromLE = new Uint8Array([ 82 | 1, 5, 3, 31, 3, 4, 40, 8, 1, 5, 3, 31, 6, 4, 45, 9, 83 | ]); 84 | const dataFromBE = new Uint8Array([ 85 | 8, 40, 4, 3, 31, 3, 5, 1, 9, 45, 4, 6, 31, 3, 5, 1, 86 | ]); 87 | const firstNumber = 587724162823554305n; 88 | const secondNumber = 661189144629937409n; 89 | 90 | // little endian 91 | let buffer = new IOBuffer(dataFromLE); 92 | const LeRes = buffer.readArray(2, 'uint64'); 93 | expect(buffer.offset).toBe(16); 94 | expect(LeRes[0]).toBe(firstNumber); 95 | expect(LeRes[1]).toBe(secondNumber); 96 | 97 | // big endian 98 | buffer = new IOBuffer(dataFromBE); 99 | buffer.setBigEndian(); 100 | const BeRes = buffer.readArray(2, 'uint64'); 101 | expect(buffer.offset).toBe(16); 102 | expect(BeRes[0]).toBe(firstNumber); 103 | expect(BeRes[1]).toBe(secondNumber); 104 | }); 105 | it('float 32', () => { 106 | //numbers taken from Buffer.readFloatLE in Node.js 107 | const dataFromLE = new Uint8Array([1, 5, 3, 31, 3, 4, 40, 8]); 108 | const dataFromBE = new Uint8Array([31, 3, 5, 1, 8, 40, 4, 3]); 109 | 110 | const firstNumber = 2.774446815681537e-20; 111 | const secondNumber = 5.056037679289265e-34; 112 | 113 | // little endian 114 | let buffer = new IOBuffer(dataFromLE); 115 | const res = buffer.readArray(2, 'float32'); 116 | expect(buffer.offset).toBe(8); 117 | expect(res[0]).toBe(firstNumber); 118 | expect(res[1]).toBe(secondNumber); 119 | 120 | // big endian 121 | buffer = new IOBuffer(dataFromBE); 122 | buffer.offset = 0; 123 | buffer.setBigEndian(); 124 | const resBE = buffer.readArray(2, 'float32'); 125 | expect(buffer.offset).toBe(8); 126 | expect(resBE[0]).toBe(firstNumber); 127 | expect(resBE[1]).toBe(secondNumber); 128 | }); 129 | it('float 64', () => { 130 | //numbers taken from Buffer.readDoubleLE in Node.js 131 | const dataFromLE = new Uint8Array([ 132 | 1, 5, 3, 31, 3, 4, 40, 8, 1, 5, 3, 31, 6, 4, 45, 9, 133 | ]); 134 | const dataFromBE = new Uint8Array([ 135 | 8, 40, 4, 3, 31, 3, 5, 1, 9, 45, 4, 6, 31, 3, 5, 1, 136 | ]); 137 | const firstNumber = 2.272943520084162e-269; 138 | const secondNumber = 1.7997291369381062e-264; 139 | 140 | // little endian 141 | let buffer = new IOBuffer(dataFromLE); 142 | const res = buffer.readArray(2, 'float64'); 143 | expect(buffer.offset).toBe(16); 144 | expect(res[0]).toBe(firstNumber); 145 | expect(res[1]).toBe(secondNumber); 146 | 147 | // big endian 148 | buffer = new IOBuffer(dataFromBE); 149 | buffer.offset = 0; 150 | buffer.setBigEndian(); 151 | const resBE = buffer.readArray(2, 'float64'); 152 | expect(buffer.offset).toBe(16); 153 | expect(resBE[0]).toBe(firstNumber); 154 | expect(resBE[1]).toBe(secondNumber); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /src/__tests__/to_array.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { IOBuffer } from '../iobuffer.ts'; 4 | 5 | describe('test toArray', () => { 6 | it('from scratch', () => { 7 | const io1 = new IOBuffer(); 8 | expect(io1.toArray().byteLength).toBe(0); 9 | io1.writeUint32(100); 10 | io1.skip(10); 11 | io1.writeChars('abc'); 12 | io1.seek(4); 13 | io1.writeUint32(200); 14 | const arr1 = io1.toArray(); 15 | expect(arr1.byteLength).toBe(17); 16 | expect(String.fromCodePoint(arr1[14])).toBe('a'); 17 | 18 | const io2 = new IOBuffer(10); 19 | expect(io2.toArray().byteLength).toBe(0); 20 | io2.writeUint32(10); 21 | expect(io2.toArray().byteLength).toBe(4); 22 | }); 23 | 24 | it('from value', () => { 25 | { 26 | const io = new IOBuffer(new ArrayBuffer(7)); 27 | expect(io.toArray().byteLength).toBe(7); 28 | } 29 | 30 | const io2 = new IOBuffer(new Uint8Array(11)); 31 | expect(io2.toArray().byteLength).toBe(11); 32 | 33 | const io3 = new IOBuffer(new IOBuffer(9)); 34 | expect(io3.toArray().byteLength).toBe(9); 35 | 36 | { 37 | const io = new IOBuffer(new IOBuffer(13), { offset: 5 }); 38 | expect(io.toArray().byteLength).toBe(8); 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/__tests__/write.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, it } from 'vitest'; 2 | 3 | import { IOBuffer } from '../iobuffer.ts'; 4 | 5 | describe('write data', () => { 6 | let buffer: IOBuffer; 7 | beforeEach(() => { 8 | buffer = new IOBuffer(16); 9 | }); 10 | 11 | it('writeBoolean', () => { 12 | buffer.writeBoolean(null); 13 | buffer.writeBoolean(true); 14 | buffer.writeBoolean(false); 15 | buffer.writeBoolean(1); 16 | buffer.writeBoolean('a'); 17 | buffer.writeBoolean(0); 18 | buffer.writeBoolean({}); 19 | buffer.writeBoolean(''); 20 | check(buffer); 21 | }); 22 | 23 | it('writeInt8', () => { 24 | buffer.writeInt8(0); 25 | buffer.writeInt8(-1); 26 | buffer.writeInt8(0); 27 | buffer.writeInt8(-1); 28 | buffer.writeInt8(-1); 29 | buffer.writeInt8(0); 30 | buffer.writeInt8(-1); 31 | buffer.writeInt8(0); 32 | check(buffer); 33 | }); 34 | 35 | it('writeUint8 / writeByte / writeBytes', () => { 36 | buffer.writeUint8(0); 37 | buffer.writeUint8(255); 38 | buffer.writeByte(0); 39 | buffer.writeByte(255); 40 | buffer.writeBytes([255]); 41 | buffer.writeBytes([0, 255, 0]); 42 | check(buffer); 43 | }); 44 | 45 | it('writeInt16', () => { 46 | buffer.writeInt16(-256); 47 | buffer.writeInt16(-256); 48 | buffer.writeInt16(255); 49 | buffer.writeInt16(255); 50 | check(buffer); 51 | }); 52 | 53 | it('writeUint16', () => { 54 | buffer.writeUint16(65280); 55 | buffer.writeUint16(65280); 56 | buffer.writeUint16(255); 57 | buffer.writeUint16(255); 58 | check(buffer); 59 | }); 60 | 61 | it('writeInt32', () => { 62 | buffer.writeInt32(-16711936); 63 | buffer.writeInt32(16711935); 64 | check(buffer); 65 | }); 66 | 67 | it('writeUint32', () => { 68 | buffer.writeUint32(4278255360); 69 | buffer.writeUint32(16711935); 70 | check(buffer); 71 | }); 72 | 73 | it('writeFloat32', () => { 74 | buffer.writeFloat32(-1.71e38); 75 | buffer.writeFloat32(2.34e-38); 76 | buffer.rewind(); 77 | expect(buffer.readFloat32()).toMatchSnapshot(); 78 | expect(buffer.readFloat32()).toMatchSnapshot(); 79 | }); 80 | 81 | it('writeFloat64', () => { 82 | buffer.writeFloat64(7.06e-304); 83 | buffer.rewind(); 84 | expect(buffer.readFloat64()).toMatchSnapshot(); 85 | }); 86 | 87 | it('writeBigInt64', () => { 88 | buffer.writeBigInt64(-1234567890n); 89 | buffer.rewind(); 90 | expect(buffer.readBigInt64()).toMatchSnapshot(); 91 | }); 92 | 93 | it('writeBigUint64', () => { 94 | buffer.writeBigUint64(1234567890n); 95 | buffer.rewind(); 96 | expect(buffer.readBigInt64()).toMatchSnapshot(); 97 | }); 98 | 99 | it('writeChar(s)', () => { 100 | const theBuffer = new IOBuffer(5); 101 | theBuffer.writeChar('h'); 102 | theBuffer.writeChars('e'); 103 | theBuffer.writeChars('llo'); 104 | theBuffer.rewind(); 105 | expect(theBuffer.readChars(5)).toBe('hello'); 106 | }); 107 | 108 | it('write with too small AB', () => { 109 | const theBuffer = new IOBuffer(1); 110 | theBuffer.writeFloat64(1); 111 | expect(theBuffer.byteLength).toBeGreaterThanOrEqual(4); 112 | expect(theBuffer).toHaveLength(theBuffer.byteLength); 113 | }); 114 | 115 | it('ensureAvailable', () => { 116 | const theBuffer = new IOBuffer(2); 117 | theBuffer.ensureAvailable(); 118 | expect(theBuffer.byteLength).toBe(2); 119 | theBuffer.skip(2); 120 | theBuffer.ensureAvailable(); 121 | expect(theBuffer.byteLength).toBeGreaterThanOrEqual(3); 122 | theBuffer.seek(20); 123 | theBuffer.ensureAvailable(30); 124 | expect(theBuffer.byteLength).toBeGreaterThanOrEqual(50); 125 | }); 126 | 127 | it('writeUtf8', () => { 128 | const theBuffer = new IOBuffer(); 129 | theBuffer.writeByte(42); 130 | theBuffer.writeUtf8('42€'); 131 | theBuffer.writeByte(42); 132 | const uint8 = theBuffer.toArray(); 133 | expect(uint8).toHaveLength(7); 134 | expect(uint8).toStrictEqual( 135 | Uint8Array.of(42, 0x34, 0x32, 0xe2, 0x82, 0xac, 42), 136 | ); 137 | }); 138 | 139 | it('check getWrittenByteLength', () => { 140 | const theBuffer = new IOBuffer(); 141 | theBuffer.writeByte(42); 142 | theBuffer.writeUtf8('42€'); 143 | theBuffer.writeByte(42); 144 | expect(theBuffer.getWrittenByteLength()).toEqual(7); 145 | }); 146 | 147 | it('check getWrittenByteLength with offset', () => { 148 | const theBuffer = new IOBuffer(); 149 | theBuffer.writeByte(42); 150 | theBuffer.writeUtf8('42€'); 151 | theBuffer.writeByte(42); 152 | theBuffer.byteOffset = 3; 153 | expect(theBuffer.getWrittenByteLength()).toEqual(4); 154 | }); 155 | }); 156 | 157 | const good = new Uint8Array(new Uint32Array([0xff00ff00, 0x00ff00ff]).buffer); 158 | 159 | function check(buffer: IOBuffer): void { 160 | expect(buffer).toHaveLength(16); 161 | const ta = buffer.toArray(); 162 | expect(ta).toStrictEqual(good); 163 | } 164 | -------------------------------------------------------------------------------- /src/iobuffer.ts: -------------------------------------------------------------------------------- 1 | import { decode, encode } from './text.ts'; 2 | 3 | const defaultByteLength = 1024 * 8; 4 | 5 | const hostBigEndian = (() => { 6 | const array = new Uint8Array(4); 7 | const view = new Uint32Array(array.buffer); 8 | return !((view[0] = 1) & array[0]); 9 | })(); 10 | 11 | type InputData = number | ArrayBufferLike | ArrayBufferView | IOBuffer; 12 | 13 | const typedArrays = { 14 | int8: globalThis.Int8Array, 15 | uint8: globalThis.Uint8Array, 16 | int16: globalThis.Int16Array, 17 | uint16: globalThis.Uint16Array, 18 | int32: globalThis.Int32Array, 19 | uint32: globalThis.Uint32Array, 20 | uint64: globalThis.BigUint64Array, 21 | int64: globalThis.BigInt64Array, 22 | float32: globalThis.Float32Array, 23 | float64: globalThis.Float64Array, 24 | }; 25 | 26 | type TypedArrays = typeof typedArrays; 27 | 28 | interface IOBufferOptions { 29 | /** 30 | * Ignore the first n bytes of the ArrayBuffer. 31 | */ 32 | offset?: number; 33 | } 34 | 35 | export class IOBuffer { 36 | /** 37 | * Reference to the internal ArrayBuffer object. 38 | */ 39 | public buffer: ArrayBufferLike; 40 | 41 | /** 42 | * Byte length of the internal ArrayBuffer. 43 | */ 44 | public byteLength: number; 45 | 46 | /** 47 | * Byte offset of the internal ArrayBuffer. 48 | */ 49 | public byteOffset: number; 50 | 51 | /** 52 | * Byte length of the internal ArrayBuffer. 53 | */ 54 | public length: number; 55 | 56 | /** 57 | * The current offset of the buffer's pointer. 58 | */ 59 | public offset: number; 60 | 61 | private lastWrittenByte: number; 62 | private littleEndian: boolean; 63 | 64 | private _data: DataView; 65 | private _mark: number; 66 | private _marks: number[]; 67 | 68 | /** 69 | * Create a new IOBuffer. 70 | * @param data - The data to construct the IOBuffer with. 71 | * If data is a number, it will be the new buffer's length
72 | * If data is `undefined`, the buffer will be initialized with a default length of 8Kb
73 | * If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance, 74 | * or a Node.js Buffer, a view will be created over the underlying ArrayBuffer. 75 | * @param options - An object for the options. 76 | * @returns A new IOBuffer instance. 77 | */ 78 | public constructor( 79 | data: InputData = defaultByteLength, 80 | options: IOBufferOptions = {}, 81 | ) { 82 | let dataIsGiven = false; 83 | if (typeof data === 'number') { 84 | data = new ArrayBuffer(data); 85 | } else { 86 | dataIsGiven = true; 87 | this.lastWrittenByte = data.byteLength; 88 | } 89 | const offset = options.offset ? options.offset >>> 0 : 0; 90 | const byteLength = data.byteLength - offset; 91 | let dvOffset = offset; 92 | if (ArrayBuffer.isView(data) || data instanceof IOBuffer) { 93 | if (data.byteLength !== data.buffer.byteLength) { 94 | dvOffset = data.byteOffset + offset; 95 | } 96 | data = data.buffer; 97 | } 98 | if (dataIsGiven) { 99 | this.lastWrittenByte = byteLength; 100 | } else { 101 | this.lastWrittenByte = 0; 102 | } 103 | this.buffer = data; 104 | this.length = byteLength; 105 | this.byteLength = byteLength; 106 | this.byteOffset = dvOffset; 107 | this.offset = 0; 108 | this.littleEndian = true; 109 | this._data = new DataView(this.buffer, dvOffset, byteLength); 110 | this._mark = 0; 111 | this._marks = []; 112 | } 113 | 114 | /** 115 | * Checks if the memory allocated to the buffer is sufficient to store more 116 | * bytes after the offset. 117 | * @param byteLength - The needed memory in bytes. 118 | * @returns `true` if there is sufficient space and `false` otherwise. 119 | */ 120 | public available(byteLength = 1): boolean { 121 | return this.offset + byteLength <= this.length; 122 | } 123 | 124 | /** 125 | * Check if little-endian mode is used for reading and writing multi-byte 126 | * values. 127 | * @returns `true` if little-endian mode is used, `false` otherwise. 128 | */ 129 | public isLittleEndian(): boolean { 130 | return this.littleEndian; 131 | } 132 | 133 | /** 134 | * Set little-endian mode for reading and writing multi-byte values. 135 | * @returns This. 136 | */ 137 | public setLittleEndian(): this { 138 | this.littleEndian = true; 139 | return this; 140 | } 141 | 142 | /** 143 | * Check if big-endian mode is used for reading and writing multi-byte values. 144 | * @returns `true` if big-endian mode is used, `false` otherwise. 145 | */ 146 | public isBigEndian(): boolean { 147 | return !this.littleEndian; 148 | } 149 | 150 | /** 151 | * Switches to big-endian mode for reading and writing multi-byte values. 152 | * @returns This. 153 | */ 154 | public setBigEndian(): this { 155 | this.littleEndian = false; 156 | return this; 157 | } 158 | 159 | /** 160 | * Move the pointer n bytes forward. 161 | * @param n - Number of bytes to skip. 162 | * @returns This. 163 | */ 164 | public skip(n = 1): this { 165 | this.offset += n; 166 | return this; 167 | } 168 | 169 | /** 170 | * Move the pointer n bytes backward. 171 | * @param n - Number of bytes to move back. 172 | * @returns This. 173 | */ 174 | public back(n = 1): this { 175 | this.offset -= n; 176 | return this; 177 | } 178 | 179 | /** 180 | * Move the pointer to the given offset. 181 | * @param offset - The offset to move to. 182 | * @returns This. 183 | */ 184 | public seek(offset: number): this { 185 | this.offset = offset; 186 | return this; 187 | } 188 | 189 | /** 190 | * Store the current pointer offset. 191 | * @see {@link IOBuffer#reset} 192 | * @returns This. 193 | */ 194 | public mark(): this { 195 | this._mark = this.offset; 196 | return this; 197 | } 198 | 199 | /** 200 | * Move the pointer back to the last pointer offset set by mark. 201 | * @see {@link IOBuffer#mark} 202 | * @returns This. 203 | */ 204 | public reset(): this { 205 | this.offset = this._mark; 206 | return this; 207 | } 208 | 209 | /** 210 | * Push the current pointer offset to the mark stack. 211 | * @see {@link IOBuffer#popMark} 212 | * @returns This. 213 | */ 214 | public pushMark(): this { 215 | this._marks.push(this.offset); 216 | return this; 217 | } 218 | 219 | /** 220 | * Pop the last pointer offset from the mark stack, and set the current 221 | * pointer offset to the popped value. 222 | * @see {@link IOBuffer#pushMark} 223 | * @returns This. 224 | */ 225 | public popMark(): this { 226 | const offset = this._marks.pop(); 227 | if (offset === undefined) { 228 | throw new Error('Mark stack empty'); 229 | } 230 | this.seek(offset); 231 | return this; 232 | } 233 | 234 | /** 235 | * Move the pointer offset back to 0. 236 | * @returns This. 237 | */ 238 | public rewind(): this { 239 | this.offset = 0; 240 | return this; 241 | } 242 | 243 | /** 244 | * Make sure the buffer has sufficient memory to write a given byteLength at 245 | * the current pointer offset. 246 | * If the buffer's memory is insufficient, this method will create a new 247 | * buffer (a copy) with a length that is twice (byteLength + current offset). 248 | * @param byteLength - The needed memory in bytes. 249 | * @returns This. 250 | */ 251 | public ensureAvailable(byteLength = 1): this { 252 | if (!this.available(byteLength)) { 253 | const lengthNeeded = this.offset + byteLength; 254 | const newLength = lengthNeeded * 2; 255 | const newArray = new Uint8Array(newLength); 256 | newArray.set(new Uint8Array(this.buffer)); 257 | this.buffer = newArray.buffer; 258 | this.length = newLength; 259 | this.byteLength = newLength; 260 | this._data = new DataView(this.buffer); 261 | } 262 | return this; 263 | } 264 | 265 | /** 266 | * Read a byte and return false if the byte's value is 0, or true otherwise. 267 | * Moves pointer forward by one byte. 268 | * @returns The read boolean. 269 | */ 270 | public readBoolean(): boolean { 271 | return this.readUint8() !== 0; 272 | } 273 | 274 | /** 275 | * Read a signed 8-bit integer and move pointer forward by 1 byte. 276 | * @returns The read byte. 277 | */ 278 | public readInt8(): number { 279 | return this._data.getInt8(this.offset++); 280 | } 281 | 282 | /** 283 | * Read an unsigned 8-bit integer and move pointer forward by 1 byte. 284 | * @returns The read byte. 285 | */ 286 | public readUint8(): number { 287 | return this._data.getUint8(this.offset++); 288 | } 289 | 290 | /** 291 | * Alias for {@link IOBuffer#readUint8}. 292 | * @returns The read byte. 293 | */ 294 | public readByte(): number { 295 | return this.readUint8(); 296 | } 297 | 298 | /** 299 | * Read `n` bytes and move pointer forward by `n` bytes. 300 | * @param n - Number of bytes to read. 301 | * @returns The read bytes. 302 | */ 303 | public readBytes(n = 1): Uint8Array { 304 | return this.readArray(n, 'uint8'); 305 | } 306 | 307 | /** 308 | * Creates an array of corresponding to the type `type` and size `size`. 309 | * For example type `uint8` will create a `Uint8Array`. 310 | * @param size - size of the resulting array 311 | * @param type - number type of elements to read 312 | * @returns The read array. 313 | */ 314 | public readArray( 315 | size: number, 316 | type: T, 317 | ): InstanceType { 318 | const bytes = typedArrays[type].BYTES_PER_ELEMENT * size; 319 | const offset = this.byteOffset + this.offset; 320 | const slice = this.buffer.slice(offset, offset + bytes); 321 | if ( 322 | this.littleEndian === hostBigEndian && 323 | type !== 'uint8' && 324 | type !== 'int8' 325 | ) { 326 | const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes)); 327 | slice.reverse(); 328 | const returnArray = new typedArrays[type](slice.buffer); 329 | this.offset += bytes; 330 | returnArray.reverse(); 331 | return returnArray as InstanceType; 332 | } 333 | const returnArray = new typedArrays[type](slice); 334 | this.offset += bytes; 335 | return returnArray as InstanceType; 336 | } 337 | 338 | /** 339 | * Read a 16-bit signed integer and move pointer forward by 2 bytes. 340 | * @returns The read value. 341 | */ 342 | public readInt16(): number { 343 | const value = this._data.getInt16(this.offset, this.littleEndian); 344 | this.offset += 2; 345 | return value; 346 | } 347 | 348 | /** 349 | * Read a 16-bit unsigned integer and move pointer forward by 2 bytes. 350 | * @returns The read value. 351 | */ 352 | public readUint16(): number { 353 | const value = this._data.getUint16(this.offset, this.littleEndian); 354 | this.offset += 2; 355 | return value; 356 | } 357 | 358 | /** 359 | * Read a 32-bit signed integer and move pointer forward by 4 bytes. 360 | * @returns The read value. 361 | */ 362 | public readInt32(): number { 363 | const value = this._data.getInt32(this.offset, this.littleEndian); 364 | this.offset += 4; 365 | return value; 366 | } 367 | 368 | /** 369 | * Read a 32-bit unsigned integer and move pointer forward by 4 bytes. 370 | * @returns The read value. 371 | */ 372 | public readUint32(): number { 373 | const value = this._data.getUint32(this.offset, this.littleEndian); 374 | this.offset += 4; 375 | return value; 376 | } 377 | 378 | /** 379 | * Read a 32-bit floating number and move pointer forward by 4 bytes. 380 | * @returns The read value. 381 | */ 382 | public readFloat32(): number { 383 | const value = this._data.getFloat32(this.offset, this.littleEndian); 384 | this.offset += 4; 385 | return value; 386 | } 387 | 388 | /** 389 | * Read a 64-bit floating number and move pointer forward by 8 bytes. 390 | * @returns The read value. 391 | */ 392 | public readFloat64(): number { 393 | const value = this._data.getFloat64(this.offset, this.littleEndian); 394 | this.offset += 8; 395 | return value; 396 | } 397 | 398 | /** 399 | * Read a 64-bit signed integer number and move pointer forward by 8 bytes. 400 | * @returns The read value. 401 | */ 402 | public readBigInt64(): bigint { 403 | const value = this._data.getBigInt64(this.offset, this.littleEndian); 404 | this.offset += 8; 405 | return value; 406 | } 407 | 408 | /** 409 | * Read a 64-bit unsigned integer number and move pointer forward by 8 bytes. 410 | * @returns The read value. 411 | */ 412 | public readBigUint64(): bigint { 413 | const value = this._data.getBigUint64(this.offset, this.littleEndian); 414 | this.offset += 8; 415 | return value; 416 | } 417 | 418 | /** 419 | * Read a 1-byte ASCII character and move pointer forward by 1 byte. 420 | * @returns The read character. 421 | */ 422 | public readChar(): string { 423 | // eslint-disable-next-line unicorn/prefer-code-point 424 | return String.fromCharCode(this.readInt8()); 425 | } 426 | 427 | /** 428 | * Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes. 429 | * @param n - Number of characters to read. 430 | * @returns The read characters. 431 | */ 432 | public readChars(n = 1): string { 433 | let result = ''; 434 | for (let i = 0; i < n; i++) { 435 | result += this.readChar(); 436 | } 437 | return result; 438 | } 439 | 440 | /** 441 | * Read the next `n` bytes, return a UTF-8 decoded string and move pointer 442 | * forward by `n` bytes. 443 | * @param n - Number of bytes to read. 444 | * @returns The decoded string. 445 | */ 446 | public readUtf8(n = 1): string { 447 | return decode(this.readBytes(n)); 448 | } 449 | 450 | /** 451 | * Read the next `n` bytes, return a string decoded with `encoding` and move pointer 452 | * forward by `n` bytes. 453 | * If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8} 454 | * @param n - Number of bytes to read. 455 | * @param encoding - The encoding to use. Default is 'utf8'. 456 | * @returns The decoded string. 457 | */ 458 | public decodeText(n = 1, encoding = 'utf8'): string { 459 | return decode(this.readBytes(n), encoding); 460 | } 461 | 462 | /** 463 | * Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer 464 | * forward by 1 byte. 465 | * @param value - The value to write. 466 | * @returns This. 467 | */ 468 | public writeBoolean(value: unknown): this { 469 | this.writeUint8(value ? 0xff : 0x00); 470 | return this; 471 | } 472 | 473 | /** 474 | * Write `value` as an 8-bit signed integer and move pointer forward by 1 byte. 475 | * @param value - The value to write. 476 | * @returns This. 477 | */ 478 | public writeInt8(value: number): this { 479 | this.ensureAvailable(1); 480 | this._data.setInt8(this.offset++, value); 481 | this._updateLastWrittenByte(); 482 | return this; 483 | } 484 | 485 | /** 486 | * Write `value` as an 8-bit unsigned integer and move pointer forward by 1 487 | * byte. 488 | * @param value - The value to write. 489 | * @returns This. 490 | */ 491 | public writeUint8(value: number): this { 492 | this.ensureAvailable(1); 493 | this._data.setUint8(this.offset++, value); 494 | this._updateLastWrittenByte(); 495 | return this; 496 | } 497 | 498 | /** 499 | * An alias for {@link IOBuffer#writeUint8}. 500 | * @param value - The value to write. 501 | * @returns This. 502 | */ 503 | public writeByte(value: number): this { 504 | return this.writeUint8(value); 505 | } 506 | 507 | /** 508 | * Write all elements of `bytes` as uint8 values and move pointer forward by 509 | * `bytes.length` bytes. 510 | * @param bytes - The array of bytes to write. 511 | * @returns This. 512 | */ 513 | public writeBytes(bytes: ArrayLike): this { 514 | this.ensureAvailable(bytes.length); 515 | // eslint-disable-next-line @typescript-eslint/prefer-for-of 516 | for (let i = 0; i < bytes.length; i++) { 517 | this._data.setUint8(this.offset++, bytes[i]); 518 | } 519 | this._updateLastWrittenByte(); 520 | return this; 521 | } 522 | 523 | /** 524 | * Write `value` as a 16-bit signed integer and move pointer forward by 2 525 | * bytes. 526 | * @param value - The value to write. 527 | * @returns This. 528 | */ 529 | public writeInt16(value: number): this { 530 | this.ensureAvailable(2); 531 | this._data.setInt16(this.offset, value, this.littleEndian); 532 | this.offset += 2; 533 | this._updateLastWrittenByte(); 534 | return this; 535 | } 536 | 537 | /** 538 | * Write `value` as a 16-bit unsigned integer and move pointer forward by 2 539 | * bytes. 540 | * @param value - The value to write. 541 | * @returns This. 542 | */ 543 | public writeUint16(value: number): this { 544 | this.ensureAvailable(2); 545 | this._data.setUint16(this.offset, value, this.littleEndian); 546 | this.offset += 2; 547 | this._updateLastWrittenByte(); 548 | return this; 549 | } 550 | 551 | /** 552 | * Write `value` as a 32-bit signed integer and move pointer forward by 4 553 | * bytes. 554 | * @param value - The value to write. 555 | * @returns This. 556 | */ 557 | public writeInt32(value: number): this { 558 | this.ensureAvailable(4); 559 | this._data.setInt32(this.offset, value, this.littleEndian); 560 | this.offset += 4; 561 | this._updateLastWrittenByte(); 562 | return this; 563 | } 564 | 565 | /** 566 | * Write `value` as a 32-bit unsigned integer and move pointer forward by 4 567 | * bytes. 568 | * @param value - The value to write. 569 | * @returns This. 570 | */ 571 | public writeUint32(value: number): this { 572 | this.ensureAvailable(4); 573 | this._data.setUint32(this.offset, value, this.littleEndian); 574 | this.offset += 4; 575 | this._updateLastWrittenByte(); 576 | return this; 577 | } 578 | 579 | /** 580 | * Write `value` as a 32-bit floating number and move pointer forward by 4 581 | * bytes. 582 | * @param value - The value to write. 583 | * @returns This. 584 | */ 585 | public writeFloat32(value: number): this { 586 | this.ensureAvailable(4); 587 | this._data.setFloat32(this.offset, value, this.littleEndian); 588 | this.offset += 4; 589 | this._updateLastWrittenByte(); 590 | return this; 591 | } 592 | 593 | /** 594 | * Write `value` as a 64-bit floating number and move pointer forward by 8 595 | * bytes. 596 | * @param value - The value to write. 597 | * @returns This. 598 | */ 599 | public writeFloat64(value: number): this { 600 | this.ensureAvailable(8); 601 | this._data.setFloat64(this.offset, value, this.littleEndian); 602 | this.offset += 8; 603 | this._updateLastWrittenByte(); 604 | return this; 605 | } 606 | 607 | /** 608 | * Write `value` as a 64-bit signed bigint and move pointer forward by 8 609 | * bytes. 610 | * @param value - The value to write. 611 | * @returns This. 612 | */ 613 | public writeBigInt64(value: bigint): this { 614 | this.ensureAvailable(8); 615 | this._data.setBigInt64(this.offset, value, this.littleEndian); 616 | this.offset += 8; 617 | this._updateLastWrittenByte(); 618 | return this; 619 | } 620 | 621 | /** 622 | * Write `value` as a 64-bit unsigned bigint and move pointer forward by 8 623 | * bytes. 624 | * @param value - The value to write. 625 | * @returns This. 626 | */ 627 | public writeBigUint64(value: bigint): this { 628 | this.ensureAvailable(8); 629 | this._data.setBigUint64(this.offset, value, this.littleEndian); 630 | this.offset += 8; 631 | this._updateLastWrittenByte(); 632 | return this; 633 | } 634 | 635 | /** 636 | * Write the charCode of `str`'s first character as an 8-bit unsigned integer 637 | * and move pointer forward by 1 byte. 638 | * @param str - The character to write. 639 | * @returns This. 640 | */ 641 | public writeChar(str: string): this { 642 | // eslint-disable-next-line unicorn/prefer-code-point 643 | return this.writeUint8(str.charCodeAt(0)); 644 | } 645 | 646 | /** 647 | * Write the charCodes of all `str`'s characters as 8-bit unsigned integers 648 | * and move pointer forward by `str.length` bytes. 649 | * @param str - The characters to write. 650 | * @returns This. 651 | */ 652 | public writeChars(str: string): this { 653 | for (let i = 0; i < str.length; i++) { 654 | // eslint-disable-next-line unicorn/prefer-code-point 655 | this.writeUint8(str.charCodeAt(i)); 656 | } 657 | return this; 658 | } 659 | 660 | /** 661 | * UTF-8 encode and write `str` to the current pointer offset and move pointer 662 | * forward according to the encoded length. 663 | * @param str - The string to write. 664 | * @returns This. 665 | */ 666 | public writeUtf8(str: string): this { 667 | return this.writeBytes(encode(str)); 668 | } 669 | 670 | /** 671 | * Export a Uint8Array view of the internal buffer. 672 | * The view starts at the byte offset and its length 673 | * is calculated to stop at the last written byte or the original length. 674 | * @returns A new Uint8Array view. 675 | */ 676 | public toArray(): Uint8Array { 677 | return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte); 678 | } 679 | 680 | /** 681 | * Get the total number of bytes written so far, regardless of the current offset. 682 | * @returns - Total number of bytes. 683 | */ 684 | public getWrittenByteLength() { 685 | return this.lastWrittenByte - this.byteOffset; 686 | } 687 | 688 | /** 689 | * Update the last written byte offset 690 | * @private 691 | */ 692 | private _updateLastWrittenByte(): void { 693 | if (this.offset > this.lastWrittenByte) { 694 | this.lastWrittenByte = this.offset; 695 | } 696 | } 697 | } 698 | -------------------------------------------------------------------------------- /src/text.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Decode bytes to text 3 | * @param bytes - Bytes to decode 4 | * @param encoding - Text encoding 5 | * @returns The decoded text 6 | */ 7 | export function decode(bytes: Uint8Array, encoding = 'utf8'): string { 8 | const decoder = new TextDecoder(encoding); 9 | return decoder.decode(bytes); 10 | } 11 | 12 | const encoder = new TextEncoder(); 13 | 14 | /** 15 | * Encode text to utf8 16 | * @param str - Text to encode 17 | * @returns The encoded bytes 18 | */ 19 | export function encode(str: string): Uint8Array { 20 | return encoder.encode(str); 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": {}, 4 | "exclude": ["**/__tests__"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ES2022", "WebWorker"], 4 | "types": [], 5 | "target": "ES2022", 6 | "outDir": "lib", 7 | "jsx": "react-jsx", 8 | "module": "NodeNext", 9 | "rewriteRelativeImportExtensions": true, 10 | "strict": true, 11 | "skipLibCheck": false, 12 | "resolveJsonModule": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "allowJs": false, 15 | "isolatedModules": true, 16 | "verbatimModuleSyntax": true, 17 | "sourceMap": true, 18 | "declaration": true, 19 | "declarationMap": true 20 | }, 21 | "include": ["src"] 22 | } 23 | --------------------------------------------------------------------------------