├── .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 |
6 | Maintained by Zakodium 7 |
8 | 9 | 10 | # iobuffer 11 | 12 | [](https://www.npmjs.com/package/iobuffer) 13 | [](https://www.npmjs.com/package/iobuffer) 14 | [](https://codecov.io/gh/image-js/iobuffer) 15 | [](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