From c8a6d96d4b168031a966f539a8cb3e58a7651014 Mon Sep 17 00:00:00 2001 From: mktbsh <59120037+mktbsh@users.noreply.github.com> Date: Fri, 1 May 2026 09:38:04 +0900 Subject: [PATCH 1/3] refactor: replace ByteQueue class with createByteQueue function for improved instantiation Co-authored-by: Copilot --- src/byte-queue.test.ts | 16 ++--- src/byte-queue.ts | 114 ++++++++++++++++++--------------- src/cobs/public.ts | 4 +- src/encryption/transformers.ts | 12 ++-- src/png/transformers.ts | 14 ++-- 5 files changed, 83 insertions(+), 77 deletions(-) diff --git a/src/byte-queue.test.ts b/src/byte-queue.test.ts index a932ab9..1b69d81 100644 --- a/src/byte-queue.test.ts +++ b/src/byte-queue.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from "vitest"; -import { ByteQueue } from "./byte-queue"; +import { createByteQueue } from "./byte-queue"; import { readAllBytes, readableFromChunks } from "./index"; import { toU8Array } from "./shared/uint8array"; describe("ByteQueue", () => { it("reads across chunk boundaries without losing order", () => { - const queue = new ByteQueue(); + const queue = createByteQueue(); queue.append(toU8Array([1, 2])); queue.append(toU8Array([3, 4, 5])); @@ -15,7 +15,7 @@ describe("ByteQueue", () => { }); it("returns the original chunk when reading its full length", () => { - const queue = new ByteQueue(); + const queue = createByteQueue(); const chunk = toU8Array([7, 8, 9]); queue.append(chunk); @@ -24,7 +24,7 @@ describe("ByteQueue", () => { }); it("supports partial reads followed by the remainder", () => { - const queue = new ByteQueue(); + const queue = createByteQueue(); queue.append(toU8Array([1, 2, 3, 4])); expect(queue.read(2)).toEqual(toU8Array([1, 2])); @@ -33,14 +33,14 @@ describe("ByteQueue", () => { }); it("rejects reads larger than the buffered payload", () => { - const queue = new ByteQueue(); + const queue = createByteQueue(); queue.append(toU8Array([1])); expect(() => queue.read(2)).toThrow(); }); it("finds a byte across chunk boundaries without flattening the queue", () => { - const queue = new ByteQueue(); + const queue = createByteQueue(); queue.append(toU8Array([1, 2])); queue.append(toU8Array([3, 0, 4])); @@ -49,7 +49,7 @@ describe("ByteQueue", () => { }); it("discards buffered bytes without allocating a read result", () => { - const queue = new ByteQueue(); + const queue = createByteQueue(); queue.append(toU8Array([1, 2])); queue.append(toU8Array([3, 4, 5])); @@ -60,7 +60,7 @@ describe("ByteQueue", () => { }); it("preserves ordering after repeated reads trigger internal compaction", () => { - const queue = new ByteQueue(); + const queue = createByteQueue(); const source = toU8Array(96); for (let i = 0; i < source.length; i++) { diff --git a/src/byte-queue.ts b/src/byte-queue.ts index e983cee..13d11a0 100644 --- a/src/byte-queue.ts +++ b/src/byte-queue.ts @@ -1,62 +1,70 @@ import { throwError } from "./shared/error"; import { toU8Array } from "./shared/uint8array"; -export class ByteQueue { - #chunks: Uint8Array[] = []; - #head = 0; - #byteLength = 0; +export interface ByteQueue { + readonly byteLength: number; + append(chunk: Uint8Array): void; + discard(length: number): void; + indexOf(value: number): number; + read(length: number): Uint8Array; +} - get byteLength(): number { - return this.#byteLength; - } +export function createByteQueue(): ByteQueue { + const chunks: Uint8Array[] = []; + let head = 0; + let byteLength = 0; - append(chunk: Uint8Array): void { - if (chunk.byteLength === 0) { + const compact = (): void => { + if (head === 0) return; + if (head === chunks.length) { + chunks.length = 0; + head = 0; return; } - - this.#chunks.push(chunk); - this.#byteLength += chunk.byteLength; + if (head >= 32 && head * 2 >= chunks.length) { + chunks.splice(0, head); + head = 0; + } } - discard(length: number): void { - if (length < 0) { - throwError("Length must be non-negative"); - } - if (length > this.#byteLength) { - throwError("Length exceeds buffered data"); - } - if (length === 0) { - return; - } + const append: ByteQueue["append"] = (chunk) => { + const size = chunk.byteLength; + if (size === 0) return; + chunks.push(chunk); + byteLength += size; + }; + + const discard: ByteQueue["discard"] = (length) => { + if (length < 0) throwError("Length must be non-negative"); + if (length > byteLength) throwError("Length exceeds buffered data"); + if (length === 0) return; let remaining = length; while (remaining > 0) { - const chunk = this.#chunks[this.#head]; + const chunk = chunks[head]; if (!chunk) { throwError("Buffered data is inconsistent"); } if (chunk.byteLength <= remaining) { - this.#head++; + head++; remaining -= chunk.byteLength; continue; } - this.#chunks[this.#head] = chunk.subarray(remaining); + chunks[head] = chunk.subarray(remaining); remaining = 0; } - this.#byteLength -= length; - this.#compact(); + byteLength -= length; + compact(); } - indexOf(value: number): number { + const indexOf: ByteQueue["indexOf"] = (value) => { let offset = 0; - - for (let index = this.#head; index < this.#chunks.length; index++) { - const chunk = this.#chunks[index]; + for (let index = head; index < chunks.length; index++) { + const chunk = chunks[index]; if (!chunk) { break; } @@ -72,22 +80,22 @@ export class ByteQueue { return -1; } - read(length: number): Uint8Array { + const read: ByteQueue["read"] = (length) => { if (length < 0) { throwError("Length must be non-negative"); } - if (length > this.#byteLength) { + if (length > byteLength) { throwError("Length exceeds buffered data"); } if (length === 0) { return toU8Array(0); } - const firstChunk = this.#chunks[this.#head]; + const firstChunk = chunks[head]; if (firstChunk && firstChunk.byteLength === length) { - this.#head++; - this.#byteLength -= length; - this.#compact(); + head++; + byteLength -= length; + compact(); return firstChunk; } @@ -95,7 +103,7 @@ export class ByteQueue { let offset = 0; while (offset < length) { - const chunk = this.#chunks[this.#head]; + const chunk = chunks[head]; if (!chunk) { throwError("Buffered data is inconsistent"); } @@ -105,28 +113,28 @@ export class ByteQueue { offset += takeLength; if (takeLength === chunk.byteLength) { - this.#head++; + head++; continue; } - this.#chunks[this.#head] = chunk.subarray(takeLength); + chunks[head] = chunk.subarray(takeLength); } - this.#byteLength -= length; - this.#compact(); + byteLength -= length; + compact(); return value; } - #compact(): void { - if (this.#head === 0) return; - if (this.#head === this.#chunks.length) { - this.#chunks = []; - this.#head = 0; - return; - } - if (this.#head >= 32 && this.#head * 2 >= this.#chunks.length) { - this.#chunks = this.#chunks.slice(this.#head); - this.#head = 0; - } + + + return { + get byteLength(): number { + return byteLength; + }, + append, + discard, + indexOf, + read, } + } diff --git a/src/cobs/public.ts b/src/cobs/public.ts index a3eec21..af6c09f 100644 --- a/src/cobs/public.ts +++ b/src/cobs/public.ts @@ -1,4 +1,4 @@ -import { ByteQueue } from "../byte-queue"; +import { createByteQueue } from "../byte-queue"; import { throwError } from "../shared/error"; const COBS_DELIMITER = 0x00; @@ -88,7 +88,7 @@ export const createCOBSDecoderStream = (): TransformStream< Uint8Array, Uint8Array > => { - const buffer = new ByteQueue(); + const buffer = createByteQueue(); return new TransformStream({ flush() { diff --git a/src/encryption/transformers.ts b/src/encryption/transformers.ts index 3db4fdb..a067453 100644 --- a/src/encryption/transformers.ts +++ b/src/encryption/transformers.ts @@ -1,4 +1,4 @@ -import { ByteQueue } from "../byte-queue"; +import { createByteQueue } from "../byte-queue"; import { toArrayBuffer } from "../shared/array-buffer"; import { throwError } from "../shared/error"; import { toU8Array } from "../shared/uint8array"; @@ -23,9 +23,8 @@ interface EncryptionTransformerOptions { } export class EncryptionTransformer - implements Transformer -{ - #pending = new ByteQueue(); + implements Transformer { + #pending = createByteQueue(); #lastRecordCandidate: Uint8Array | undefined; #sequence = 0; #recordSize: number; @@ -124,9 +123,8 @@ export class EncryptionTransformer } export class DecryptionTransformer - implements Transformer -{ - #pending = new ByteQueue(); + implements Transformer { + #pending = createByteQueue(); #lastRecordCandidate: Uint8Array | undefined; #sequence = 0; #recordSize: number | undefined; diff --git a/src/png/transformers.ts b/src/png/transformers.ts index 40201a2..79c554c 100644 --- a/src/png/transformers.ts +++ b/src/png/transformers.ts @@ -1,4 +1,4 @@ -import { ByteQueue } from "../byte-queue"; +import { type ByteQueue, createByteQueue } from "../byte-queue"; import { throwError } from "../shared/error"; import { concatU8Arrays } from "../shared/uint8array"; import { @@ -33,7 +33,7 @@ function createDeferred(): Deferred { resolve = innerResolve; reject = innerReject; }); - void promise.catch(() => {}); + void promise.catch(() => { }); return { promise, reject, resolve }; } @@ -53,7 +53,7 @@ async function cancelSourceReader( ): Promise { try { await sourceReader.cancel(reason); - } catch {} + } catch { } } async function readPNGSignature( @@ -140,7 +140,7 @@ export function createPNGTextChunkWriterImpl( let payloadNotifier = createDeferred(); let outputNotifier = createDeferred(); const outputChunks: Uint8Array[] = []; - const sourceQueue = new ByteQueue(); + const sourceQueue = createByteQueue(); let sourcePump: Promise | null = null; let iendChunkRaw: Uint8Array | null = null; let outputClosed = false; @@ -195,7 +195,7 @@ export function createPNGTextChunkWriterImpl( await cancelSourceReader(sourceReader, reason); } })(); - void initializePromise.catch(() => {}); + void initializePromise.catch(() => { }); function startSourcePump(): void { if (sourcePump) { @@ -303,7 +303,7 @@ export function createPNGTextChunkWriterImpl( await cancelSourceReader(sourceReader, reason); } })(); - void sourcePump.catch(() => {}); + void sourcePump.catch(() => { }); } const readable = new ReadableStream({ @@ -432,7 +432,7 @@ export function streamPNGTextChunkImpl( let completed = false; try { - const sourceQueue = new ByteQueue(); + const sourceQueue = createByteQueue(); await readPNGSignature(sourceReader, sourceQueue); let expectedIndex = 0; From 4aec8839325e4f9811e986fe875a3e02cf4eabb0 Mon Sep 17 00:00:00 2001 From: mktbsh <59120037+mktbsh@users.noreply.github.com> Date: Fri, 1 May 2026 09:39:48 +0900 Subject: [PATCH 2/3] refactor: add changeset for replacing ByteQueue class with createByteQueue function --- .changeset/lovely-carpets-enjoy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lovely-carpets-enjoy.md diff --git a/.changeset/lovely-carpets-enjoy.md b/.changeset/lovely-carpets-enjoy.md new file mode 100644 index 0000000..ed5d624 --- /dev/null +++ b/.changeset/lovely-carpets-enjoy.md @@ -0,0 +1,5 @@ +--- +"@hsblabs/web-stream-extras": patch +--- + +refactor: replace ByteQueue class with createByteQueue function for improved instantiation From ab9c02bdae8b3ffe61062f9b46f6306a581f5646 Mon Sep 17 00:00:00 2001 From: mktbsh <59120037+mktbsh@users.noreply.github.com> Date: Fri, 1 May 2026 09:41:33 +0900 Subject: [PATCH 3/3] refactor: update JSON includes and fix formatting in multiple files --- biome.json | 2 +- src/byte-queue.ts | 13 +++++-------- src/encryption/transformers.ts | 6 ++++-- src/png/transformers.ts | 8 ++++---- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/biome.json b/biome.json index c0cabaf..636b165 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,7 @@ }, "files": { "ignoreUnknown": false, - "includes": ["**"] + "includes": ["**", "!.agents", "!.claude"] }, "formatter": { "enabled": true, diff --git a/src/byte-queue.ts b/src/byte-queue.ts index 13d11a0..fb62ea2 100644 --- a/src/byte-queue.ts +++ b/src/byte-queue.ts @@ -25,7 +25,7 @@ export function createByteQueue(): ByteQueue { chunks.splice(0, head); head = 0; } - } + }; const append: ByteQueue["append"] = (chunk) => { const size = chunk.byteLength; @@ -59,7 +59,7 @@ export function createByteQueue(): ByteQueue { byteLength -= length; compact(); - } + }; const indexOf: ByteQueue["indexOf"] = (value) => { let offset = 0; @@ -78,7 +78,7 @@ export function createByteQueue(): ByteQueue { } return -1; - } + }; const read: ByteQueue["read"] = (length) => { if (length < 0) { @@ -123,9 +123,7 @@ export function createByteQueue(): ByteQueue { byteLength -= length; compact(); return value; - } - - + }; return { get byteLength(): number { @@ -135,6 +133,5 @@ export function createByteQueue(): ByteQueue { discard, indexOf, read, - } - + }; } diff --git a/src/encryption/transformers.ts b/src/encryption/transformers.ts index a067453..ff4fbd7 100644 --- a/src/encryption/transformers.ts +++ b/src/encryption/transformers.ts @@ -23,7 +23,8 @@ interface EncryptionTransformerOptions { } export class EncryptionTransformer - implements Transformer { + implements Transformer +{ #pending = createByteQueue(); #lastRecordCandidate: Uint8Array | undefined; #sequence = 0; @@ -123,7 +124,8 @@ export class EncryptionTransformer } export class DecryptionTransformer - implements Transformer { + implements Transformer +{ #pending = createByteQueue(); #lastRecordCandidate: Uint8Array | undefined; #sequence = 0; diff --git a/src/png/transformers.ts b/src/png/transformers.ts index 79c554c..13337d8 100644 --- a/src/png/transformers.ts +++ b/src/png/transformers.ts @@ -33,7 +33,7 @@ function createDeferred(): Deferred { resolve = innerResolve; reject = innerReject; }); - void promise.catch(() => { }); + void promise.catch(() => {}); return { promise, reject, resolve }; } @@ -53,7 +53,7 @@ async function cancelSourceReader( ): Promise { try { await sourceReader.cancel(reason); - } catch { } + } catch {} } async function readPNGSignature( @@ -195,7 +195,7 @@ export function createPNGTextChunkWriterImpl( await cancelSourceReader(sourceReader, reason); } })(); - void initializePromise.catch(() => { }); + void initializePromise.catch(() => {}); function startSourcePump(): void { if (sourcePump) { @@ -303,7 +303,7 @@ export function createPNGTextChunkWriterImpl( await cancelSourceReader(sourceReader, reason); } })(); - void sourcePump.catch(() => { }); + void sourcePump.catch(() => {}); } const readable = new ReadableStream({