diff --git a/lib/dynamic-buffer.js b/lib/dynamic-buffer.js index d0a989e..36ce1d9 100644 --- a/lib/dynamic-buffer.js +++ b/lib/dynamic-buffer.js @@ -16,16 +16,51 @@ const { sizeOfUnsignedVarInt64 } = require('./varint') -const instanceIdentifier = Symbol('plt.dynamicBuffer.instanceIdentifier') - +/** + * @type {unique symbol} + */ +const instanceIdentifier = Symbol.for('plt.dynamicBuffer.instanceIdentifier') + +/** + * @class + * @name DynamicBuffer + */ class DynamicBuffer { + /** + * @type {Buffer} + */ #readBuffer // This is used from the fixed length readers + /** + * @static + * @param {*} target + * @returns {target is DynamicBuffer} + */ static isDynamicBuffer (target) { return target?.[instanceIdentifier] === true } + /** + * @static + * @param {*} target + * @returns {target is DynamicBuffer} + */ + static [Symbol.hasInstance] (target) { + return target?.[instanceIdentifier] === true + } + + /** + * @type {boolean} + */ + [instanceIdentifier] = true + + /** + * @param {Buffer|Buffer[]} [buffers] + */ constructor (buffers) { + /** + * @type {Buffer[]} + */ this.buffers = [] this.length = 0 this.#readBuffer = Buffer.allocUnsafe(8) @@ -44,6 +79,9 @@ class DynamicBuffer { } } + /** + * @returns {Buffer} + */ get buffer () { if (this.buffers.length === 0) { return EMPTY_BUFFER @@ -55,6 +93,10 @@ class DynamicBuffer { return Buffer.concat(this.buffers, this.length) } + /** + * @param {Buffer} buffer + * @returns {this} + */ append (buffer) { this.buffers.push(buffer) this.length += buffer.length @@ -62,6 +104,10 @@ class DynamicBuffer { return this } + /** + * @param {Buffer} buffer + * @returns {this} + */ prepend (buffer) { this.buffers.unshift(buffer) this.length += buffer.length @@ -69,20 +115,33 @@ class DynamicBuffer { return this } - appendFrom (DynamicBuffer) { - this.buffers.push(...DynamicBuffer.buffers) - this.length += DynamicBuffer.length + /** + * @param {DynamicBuffer} dynamicBuffer + * @returns {this} + */ + appendFrom (dynamicBuffer) { + this.buffers.push(...dynamicBuffer.buffers) + this.length += dynamicBuffer.length return this } - prependFrom (DynamicBuffer) { - this.buffers.unshift(...DynamicBuffer.buffers) - this.length += DynamicBuffer.length + /** + * @param {DynamicBuffer} dynamicBuffer + * @returns {this} + */ + prependFrom (dynamicBuffer) { + this.buffers.unshift(...dynamicBuffer.buffers) + this.length += dynamicBuffer.length return this } + /** + * @param {number} [start=0] + * @param {number} [end] + * @returns {DynamicBuffer} + */ subarray (start = 0, end) { if (typeof end === 'undefined') { end = this.length @@ -120,6 +179,11 @@ class DynamicBuffer { return new DynamicBuffer(buffers) } + /** + * @param {number} [start=0] + * @param {number} [end] + * @returns {Buffer} + */ slice (start = 0, end) { if (typeof end === 'undefined') { end = this.length @@ -159,6 +223,10 @@ class DynamicBuffer { return buffer } + /** + * @param {boolean} [deep=false] + * @returns {DynamicBuffer} + */ clone (deep = false) { if (!deep) { return new DynamicBuffer(this.buffers) @@ -172,6 +240,10 @@ class DynamicBuffer { return new DynamicBuffer(buffers) } + /** + * @param {number} offset + * @returns {this} + */ consume (offset) { if (offset < 0 || offset > this.length) { throw new Error('Out of bounds.') @@ -202,10 +274,20 @@ class DynamicBuffer { return this } + /** + * @param {BufferEncoding} [encoding='utf-8'] + * @param {number} [start=0] + * @param {number} [end] + * @returns {string} + */ toString (encoding = 'utf-8', start = 0, end) { return this.slice(start, end).toString(encoding) } + /** + * @param {number} offset + * @returns {number} + */ get (offset) { if (offset < 0 || offset >= this.length) { throw new Error('Out of bounds.') @@ -215,6 +297,10 @@ class DynamicBuffer { return this.buffers[current][finalIndex] } + /** + * @param {number} [offset=0] + * @returns {number} + */ readUInt8 (offset = 0) { if (offset < 0 || offset >= this.length) { throw new Error('Out of bounds.') @@ -226,36 +312,64 @@ class DynamicBuffer { return this.#readBuffer.readUInt8(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readUInt16BE (offset = 0) { this.#readMultiple(offset, 2) return this.#readBuffer.readUInt16BE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readUInt16LE (offset = 0) { this.#readMultiple(offset, 2) return this.#readBuffer.readUInt16LE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readUInt32BE (offset = 0) { this.#readMultiple(offset, 4) return this.#readBuffer.readUInt32BE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readUInt32LE (offset = 0) { this.#readMultiple(offset, 4) return this.#readBuffer.readUInt32LE(0) } + /** + * @param {number} [offset=0] + * @returns {bigint} + */ readBigUInt64BE (offset = 0) { this.#readMultiple(offset, 8) return this.#readBuffer.readBigUInt64BE(0) } + /** + * @param {number} [offset=0] + * @returns {bigint} + */ readBigUInt64LE (offset = 0) { this.#readMultiple(offset, 8) return this.#readBuffer.readBigUInt64LE(0) } + /** + * @param {number} [offset=0] + * @returns {[number, number]} + */ readUnsignedVarInt (offset = 0) { let i = 0 let byte @@ -285,6 +399,10 @@ class DynamicBuffer { return [value, read] } + /** + * @param {number} [offset=0] + * @returns {[bigint, number]} + */ readUnsignedVarInt64 (offset = 0) { let i = 0n let byte @@ -314,6 +432,10 @@ class DynamicBuffer { return [value, read] } + /** + * @param {number} [offset=0] + * @returns {number} + */ readInt8 (offset = 0) { if (offset < 0 || offset >= this.length) { throw new Error('Out of bounds.') @@ -325,66 +447,119 @@ class DynamicBuffer { return this.#readBuffer.readInt8(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readInt16BE (offset = 0) { this.#readMultiple(offset, INT16_SIZE) return this.#readBuffer.readInt16BE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readInt16LE (offset = 0) { this.#readMultiple(offset, INT16_SIZE) return this.#readBuffer.readInt16LE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readInt32BE (offset = 0) { this.#readMultiple(offset, INT32_SIZE) return this.#readBuffer.readInt32BE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readInt32LE (offset = 0) { this.#readMultiple(offset, INT32_SIZE) return this.#readBuffer.readInt32LE(0) } + /** + * @param {number} [offset=0] + * @returns {bigint} + */ readBigInt64BE (offset = 0) { this.#readMultiple(offset, INT64_SIZE) return this.#readBuffer.readBigInt64BE(0) } + /** + * @param {number} [offset=0] + * @returns {bigint} + */ readBigInt64LE (offset = 0) { this.#readMultiple(offset, INT64_SIZE) return this.#readBuffer.readBigInt64LE(0) } + /** + * @param {number} [offset=0] + * @returns {[number, number]} + */ readVarInt (offset) { const [value, read] = this.readUnsignedVarInt(offset) return [intZigZagDecode(value), read] } + /** + * @param {number} [offset=0] + * @returns {[bigint, number]} + */ readVarInt64 (offset) { const [value, read] = this.readUnsignedVarInt64(offset) return [int64ZigZagDecode(value), read] } + /** + * @param {number} [offset=0] + * @returns {number} + */ readFloatBE (offset = 0) { this.#readMultiple(offset, INT32_SIZE) return this.#readBuffer.readFloatBE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readFloatLE (offset = 0) { this.#readMultiple(offset, INT32_SIZE) return this.#readBuffer.readFloatLE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readDoubleBE (offset = 0) { this.#readMultiple(offset, INT64_SIZE) return this.#readBuffer.readDoubleBE(0) } + /** + * @param {number} [offset=0] + * @returns {number} + */ readDoubleLE (offset = 0) { this.#readMultiple(offset, INT64_SIZE) return this.#readBuffer.readDoubleLE(0) } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeUInt8 (value, append = true) { const buffer = Buffer.allocUnsafe(INT8_SIZE) buffer.writeUInt8(value) @@ -398,6 +573,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeUInt16BE (value, append = true) { const buffer = Buffer.allocUnsafe(INT16_SIZE) buffer.writeUInt16BE(value) @@ -411,6 +591,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeUInt16LE (value, append = true) { const buffer = Buffer.allocUnsafe(INT16_SIZE) buffer.writeUInt16LE(value) @@ -424,6 +609,11 @@ class DynamicBuffer { return this } + /** + * @param {*} value + * @param {*} append + * @returns {this} + */ writeUInt32BE (value, append = true) { const buffer = Buffer.allocUnsafe(INT32_SIZE) buffer.writeUInt32BE(value) @@ -437,6 +627,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeUInt32LE (value, append = true) { const buffer = Buffer.allocUnsafe(INT32_SIZE) buffer.writeUInt32LE(value) @@ -450,6 +645,11 @@ class DynamicBuffer { return this } + /** + * @param {bigint} value + * @param {boolean} [append=true] + * @returns {this} + */ writeBigUInt64BE (value, append = true) { const buffer = Buffer.allocUnsafe(INT64_SIZE) buffer.writeBigUInt64BE(value) @@ -463,6 +663,11 @@ class DynamicBuffer { return this } + /** + * @param {bigint} value + * @param {boolean} [append=true] + * @returns {this} + */ writeBigUInt64LE (value, append = true) { const buffer = Buffer.allocUnsafe(INT64_SIZE) buffer.writeBigUInt64LE(value) @@ -476,6 +681,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {void} + */ writeUnsignedVarInt (value, append = true) { const buffer = Buffer.alloc(sizeOfUnsignedVarInt(value)) let position = 0 @@ -495,6 +705,11 @@ class DynamicBuffer { } } + /** + * @param {bigint} value + * @param {boolean} [append=true] + * @returns {void} + */ writeUnsignedVarInt64 (value, append = true) { const buffer = Buffer.alloc(sizeOfUnsignedVarInt64(value)) let position = 0 @@ -514,6 +729,11 @@ class DynamicBuffer { } } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeInt8 (value, append = true) { const buffer = Buffer.allocUnsafe(INT8_SIZE) buffer.writeInt8(value) @@ -527,6 +747,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeInt16BE (value, append = true) { const buffer = Buffer.allocUnsafe(INT16_SIZE) buffer.writeInt16BE(value) @@ -540,6 +765,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeInt16LE (value, append = true) { const buffer = Buffer.allocUnsafe(INT16_SIZE) buffer.writeInt16LE(value) @@ -553,6 +783,11 @@ class DynamicBuffer { return this } + /** + * @param {*} value + * @param {*} append + * @returns {this} + */ writeInt32BE (value, append = true) { const buffer = Buffer.allocUnsafe(INT32_SIZE) buffer.writeInt32BE(value) @@ -566,6 +801,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeInt32LE (value, append = true) { const buffer = Buffer.allocUnsafe(INT32_SIZE) buffer.writeInt32LE(value) @@ -579,6 +819,11 @@ class DynamicBuffer { return this } + /** + * @param {*} value + * @param {*} append + * @returns {this} + */ writeBigInt64BE (value, append = true) { const buffer = Buffer.allocUnsafe(INT64_SIZE) buffer.writeBigInt64BE(value) @@ -592,6 +837,11 @@ class DynamicBuffer { return this } + /** + * @param {bigint} value + * @param {boolean} [append=true] + * @returns {this} + */ writeBigInt64LE (value, append = true) { const buffer = Buffer.allocUnsafe(INT64_SIZE) buffer.writeBigInt64LE(value) @@ -605,14 +855,29 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {void} + */ writeVarInt (value, append = true) { this.writeUnsignedVarInt(intZigZagEncode(value), append) } + /** + * @param {bigint} value + * @param {boolean} [append=true] + * @returns {void} + */ writeVarInt64 (value, append = true) { this.writeUnsignedVarInt64(int64ZigZagEncode(value), append) } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeFloatBE (value, append = true) { const buffer = Buffer.allocUnsafe(INT32_SIZE) buffer.writeFloatBE(value) @@ -626,6 +891,12 @@ class DynamicBuffer { return this } + /** + * + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeFloatLE (value, append = true) { const buffer = Buffer.allocUnsafe(INT32_SIZE) buffer.writeFloatLE(value) @@ -639,6 +910,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeDoubleBE (value, append = true) { const buffer = Buffer.allocUnsafe(INT64_SIZE) buffer.writeDoubleBE(value) @@ -652,6 +928,11 @@ class DynamicBuffer { return this } + /** + * @param {number} value + * @param {boolean} [append=true] + * @returns {this} + */ writeDoubleLE (value, append = true) { const buffer = Buffer.allocUnsafe(INT64_SIZE) buffer.writeDoubleLE(value) @@ -665,6 +946,10 @@ class DynamicBuffer { return this } + /** + * @param {number} start + * @returns {[number, number]} + */ #findInitialBuffer (start) { let current = 0 @@ -677,6 +962,11 @@ class DynamicBuffer { return [start, current] } + /** + * @param {number} index + * @param {number} length + * @returns {void} + */ #readMultiple (index, length) { if (index < 0 || index + length > this.length) { throw new Error('Out of bounds.') @@ -695,4 +985,6 @@ class DynamicBuffer { } } +module.exports = DynamicBuffer module.exports.DynamicBuffer = DynamicBuffer +module.exports.default = DynamicBuffer diff --git a/lib/varint.js b/lib/varint.js index 72e06d3..d187967 100644 --- a/lib/varint.js +++ b/lib/varint.js @@ -5,27 +5,51 @@ const MOST_SIGNIFICANT_BIT_FLAG_64 = 0x80n // 128 or 1000 0000 const LEAST_SIGNIFICANT_7_BITS = 0x7f // 127 or 0111 1111 const LEAST_SIGNIFICANT_7_BITS_64 = 0x7fn // 127 or 0111 1111 -// This is used in varint to check if there are any other bits set after the first 7 bits, -// which means it still needs more than a byte to represent the number in varint encoding -const BITS_8PLUS_MASK = 0xffffffff - 0x7f -const BITS_8PLUS_MASK_64 = 0xffffffffn - 0x7fn +/** + * This is used in varint to check if there are any other bits set after the first 7 bits, + * which means it still needs more than a byte to represent the number in varint encoding + * + * 0xffffffff - 0x7f + */ +const BITS_8PLUS_MASK = 0xffffff80 +const BITS_8PLUS_MASK_64 = 0xffffff80n +/** + * @param {number} value + * @returns {number} + */ function intZigZagEncode (value) { return (value << 1) ^ (value >> 31) } +/** + * @param {number} value + * @returns {number} + */ function intZigZagDecode (value) { return (value >> 1) ^ -(value & 1) } +/** + * @param {bigint} value + * @returns {bigint} + */ function int64ZigZagEncode (value) { return (value << 1n) ^ (value >> 31n) } +/** + * @param {bigint} value + * @returns {bigint} + */ function int64ZigZagDecode (value) { return (value >> 1n) ^ -(value & 1n) } +/** + * @param {number} value + * @returns {number} + */ function sizeOfUnsignedVarInt (value) { let bytes = 1 @@ -37,6 +61,10 @@ function sizeOfUnsignedVarInt (value) { return bytes } +/** + * @param {bigint} value + * @returns {number} + */ function sizeOfUnsignedVarInt64 (value) { let bytes = 1 diff --git a/package.json b/package.json index 5e8e526..a40a875 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "main": "lib/dynamic-buffer.js", "type": "commonjs", "scripts": { + "build:types": "tsc -p tsconfig.types.json", "lint:fix": "eslint --cache --fix", "test": "eslint --cache && node --test", "benchmark": "node benchmark.js" @@ -28,9 +29,11 @@ "url": "https://github.com/platformatic/dynamic-buffer/issues" }, "devDependencies": { + "@types/node": "^22.15.30", + "neostandard": "^0.12.1", "bl": "^6.1.0", "eslint": "^9.28.0", "mitata": "^1.0.34", - "neostandard": "^0.12.1" + "typescript": "^5.8.3" } } diff --git a/test/dynamic-buffer.test.js b/test/dynamic-buffer.test.js index 05f42bb..a9b7510 100644 --- a/test/dynamic-buffer.test.js +++ b/test/dynamic-buffer.test.js @@ -2,7 +2,7 @@ const { deepStrictEqual, notStrictEqual, ok, strictEqual, throws } = require('node:assert') const { test } = require('node:test') -const { DynamicBuffer } = require('..') +const { DynamicBuffer } = require('../lib/dynamic-buffer.js') const { EMPTY_BUFFER } = require('../lib/definitions.js') test('static isDynamicBuffer', () => { @@ -10,6 +10,11 @@ test('static isDynamicBuffer', () => { ok(!DynamicBuffer.isDynamicBuffer('STRING')) }) +test('instanceof DynamicBuffer', () => { + ok((new DynamicBuffer() instanceof DynamicBuffer)) + ok(!(new Date() instanceof DynamicBuffer)) +}) + test('constructor', () => { // Test empty constructor const emptyBuffer = new DynamicBuffer() @@ -629,6 +634,9 @@ test('readInt8', () => { ) }) +/** + * @type {Array<[string, number]>} + */ const fixedLengths = [ ['UInt16', 2], ['Int16', 2], @@ -1164,8 +1172,11 @@ test('writeInt8', () => { strictEqual(dynamicBuffer2.readInt8(1), 42) // 42 should be second }) -// Test for fixed width numeric methods -// Parameters for test cases: [method name, value, expected size] +/** + * Test for fixed width numeric methods + * Parameters for test cases: [method name, value, expected size] + * @type {Array<[string, number | bigint, number]>} + */ const writeFixedMethodTests = [ ['writeUInt16BE', 0xabcd, 2], ['writeUInt16LE', 0xabcd, 2], @@ -1217,7 +1228,10 @@ for (const [methodName, value, expectedSize] of writeFixedMethodTests) { }) } -// Test for floating point methods +/** + * Test for floating point methods + * @type {Array<[string, number, number]>} + */ const writeFloatMethodTests = [ ['writeFloatBE', 123.456, 4], ['writeFloatLE', 123.456, 4], diff --git a/test/varint.test.js b/test/varint.test.js index eaa6a87..8edb229 100644 --- a/test/varint.test.js +++ b/test/varint.test.js @@ -1,6 +1,6 @@ 'use strict' const { strictEqual } = require('node:assert') -const test = require('node:test') +const { test } = require('node:test') const { int64ZigZagDecode, int64ZigZagEncode, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..35ec22f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2020", + "allowJs": true, + "checkJs": true, + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "noEmit": true + } +} diff --git a/tsconfig.types.json b/tsconfig.types.json new file mode 100644 index 0000000..a2548d8 --- /dev/null +++ b/tsconfig.types.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outFile": "./dynamic-buffer.d.ts", + "noEmit": false + }, + "include": [ + "./lib/dynamic-buffer.js" + ] +}