A zero-dependency, immutable, and bounds-safe wrapper around JavaScript’s DataView, designed for zero-copy sequential binary parsing and serialization with an internal cursor.
Unlike raw DataView, bytecursor tracks your position automatically, decodes and encodes UTF-8 strings directly in-buffer, and enforces strict bounds checking—making binary protocol implementation fast, safe, and readable.
- ⏩ Automatic cursor: Read/write sequentially without manual offset math
- ⚡ Zero-copy operations: Read byte sequences and strings directly from buffer views without memory cloning
- 🛡️ Strict bounds checking: Prevents out-of-range reads and writes at runtime
- 🔤 High-performance UTF-8: Direct encoding via
TextEncoder.prototype.encodeIntoand zero-copy decoding withTextDecoder - 📏 View slicing & subarray views: Work with subsections of an
ArrayBufferefficiently - 🔄 Method chaining: All write operations return the instance for fluent APIs
- 🧊 Immutable & frozen: The API object and its properties are
Object.freezed for safety - 📦 Pure ES module: Zero dependencies, modern JavaScript only
⚠️ Note: All operations happen sequentially at the current cursor position.
Place src/bytecursor.js in your project and import it:
importbytecursorfrom'./bytecursor.js';importbytecursorfrom'./bytecursor.js';// Create a 16-byte bufferconstcursor=bytecursor(newArrayBuffer(16));// Write data sequentiallycursor.writeString("OK")// UTF-8 encoded directly in-buffer → 2 bytes.writeUint8(200)// → 1 byte.writeInt32(12345,true);// little-endian → 4 bytesconsole.log(cursor.tell());// 7// Read it backcursor.rewind();console.log(cursor.getString(2));// "OK" (zero-copy decode)console.log(cursor.getUint8());// 200console.log(cursor.getInt32(true));// 12345bytecursor(buffer,[viewOffset=0],[viewLength])buffer: Must be anArrayBuffer(throwsTypeErrorotherwise)- Returns a frozen API object with a cursor starting at
0(relative to the view)
| Property | Description |
|---|---|
.buffer | The underlying ArrayBuffer |
.view | The underlying DataView (with offset/length as provided) |
.length | Byte length of the active view (number, not a method) |
| Method | Description |
|---|---|
.tell() | Returns current cursor position (0-based, relative to view start) |
.seek(pos) | Move cursor to absolute position pos (within view bounds) |
.rewind() | Reset cursor to 0 |
.skip(n) | Advance cursor by n bytes |
.eof() | Returns true if cursor ≥ view length |
All cursor methods (except tell and eof) return the API instance for chaining.
All read methods advance the cursor by the size of the type.
| Method | Size | Description |
|---|---|---|
.getUint8() | 1 | Unsigned 8-bit integer |
.getInt8() | 1 | Signed 8-bit integer |
.getUint16(littleEndian?) | 2 | Unsigned 16-bit integer |
.getInt16(littleEndian?) | 2 | Signed 16-bit integer |
.getUint32(littleEndian?) | 4 | Unsigned 32-bit integer |
.getInt32(littleEndian?) | 4 | Signed 32-bit integer |
.getFloat32(littleEndian?) | 4 | 32-bit float |
.getFloat64(littleEndian?) | 8 | 64-bit float |
All write methods advance the cursor and return the API for chaining.
| Method | Example |
|---|---|
.writeUint8(v) | cursor.writeUint8(255) |
.writeInt8(v) | cursor.writeInt8(-128) |
.writeUint16(v, littleEndian?) | cursor.writeUint16(65535, true) |
.writeInt16(v, littleEndian?) | cursor.writeInt16(-32768, true) |
.writeUint32(v, littleEndian?) | cursor.writeUint32(4294967295, true) |
.writeInt32(v, littleEndian?) | cursor.writeInt32(-2147483648, true) |
.writeFloat32(v, littleEndian?) | cursor.writeFloat32(3.14159, true) |
.writeFloat64(v, littleEndian?) | cursor.writeFloat64(2.718281828, true) |
| Method | Description |
|---|---|
.getBytes([length]) | Returns a zero-copy Uint8Array subarray view from cursor (default: remaining view length) |
.getString(length) | Decodes length bytes directly from the buffer as a UTF-8 string |
.writeBytes(uint8Array) | Copies a Uint8Array into the buffer at cursor position |
.writeString(str) | Encodes UTF-8 string directly into buffer using encodeInto (zero intermediate allocation) |
🌐 Uses browser-native
TextEncoderandTextDecoderwith high-performance UTF-8 stream processing.
| Method | Description |
|---|---|
.slice(start?, end?) | Returns an isolated copied clone of the underlying buffer from view.byteOffset + start to view.byteOffset + end |
💡 Tip: Use
.getBytes()for zero-copy slice viewing, and.slice()when you need an independent clonedArrayBuffer.
This library includes a zero-dependency, comprehensive browser-based verification suite (82 assertions covering 100% of methods, boundary guards, and error conditions).
To run the test suite:
- Serve the repository using any static web server (e.g., Nginx, Caddy, or Python's
http.server). - Open
tests/index.htmlin your browser (e.g.,http://localhost/tests/index.html). - View results visually on the page or open Developer Tools (
F12-> Console) to inspect grouped log outputs and execution metrics.
- ❌ No random-access reading/writing (e.g.,
getUint32(12)) - ❌ No support for non-UTF-8 encodings
- ❌ No automatic length-prefix handling (you manage string/byte lengths)
This keeps the API minimal, predictable, and focused on stream-like binary parsing.
See LICENSE for details.