sabcom 0.1.91 → 0.1.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,18 @@
8
8
 
9
9
  A TypeScript/Node.js library for high-performance inter-thread communication using `SharedArrayBuffer` with atomic operations. It provides both synchronous and asynchronous APIs to transfer byte data between threads (e.g., Main thread and Worker threads) without the overhead of structured cloning or memory copying.
10
10
 
11
+ ## What sabcom Does
12
+
13
+ sabcom is a **protocol layer** for SharedArrayBuffer. It handles:
14
+ - Synchronization between reader and writer via Atomics
15
+ - Chunking large data that exceeds buffer size
16
+ - Timeout detection to prevent deadlocks
17
+
18
+ sabcom does **NOT**:
19
+ - Create worker threads (you create them with `worker_threads` or `new Worker()`)
20
+ - Transfer the SharedArrayBuffer between threads (you pass it via `workerData` or `postMessage`)
21
+ - Serialize data (you encode to `Uint8Array` before calling write, e.g., with `TextEncoder` or `JSON.stringify`)
22
+
11
23
  ## Features
12
24
 
13
25
  - **Thread-safe communication** using `Atomics` for synchronization.
@@ -175,6 +187,85 @@ The communication follows a strict handshake:
175
187
 
176
188
  *Note: The `SharedArrayBuffer` is reusable after a successful transfer.*
177
189
 
190
+ ## FAQ
191
+
192
+ ### What is the minimum buffer size?
193
+
194
+ `HEADER_SIZE` is 16 bytes. Your buffer must be larger to have usable payload space:
195
+
196
+ ```typescript
197
+ import { HEADER_SIZE } from 'sabcom';
198
+
199
+ // Minimum: HEADER_SIZE + at least 1 byte for payload
200
+ // Practical minimum: 1024 bytes (1KB)
201
+ const buffer = new SharedArrayBuffer(1024);
202
+ ```
203
+
204
+ ### How do I send JSON or objects?
205
+
206
+ sabcom transfers raw bytes only. Serialize before sending:
207
+
208
+ ```typescript
209
+ // Writer
210
+ const obj = { hello: 'world', count: 42 };
211
+ const json = JSON.stringify(obj);
212
+ await write(new TextEncoder().encode(json), buffer);
213
+
214
+ // Reader
215
+ const data = await read(buffer);
216
+ const obj = JSON.parse(new TextDecoder().decode(data));
217
+ ```
218
+
219
+ ### Does sabcom work in browsers?
220
+
221
+ Yes, with Web Workers. However, `SharedArrayBuffer` requires cross-origin isolation headers on your server:
222
+
223
+ ```
224
+ Cross-Origin-Opener-Policy: same-origin
225
+ Cross-Origin-Embedder-Policy: require-corp
226
+ ```
227
+
228
+ ### How do I handle errors?
229
+
230
+ ```typescript
231
+ try {
232
+ await write(data, buffer, { timeout: 5000 });
233
+ } catch (err) {
234
+ if (err.message.includes('timeout')) {
235
+ console.error('Reader did not respond in time');
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### Can I cancel a transfer mid-way?
241
+
242
+ Use generators with `for...of` - breaking out automatically triggers cleanup:
243
+
244
+ ```typescript
245
+ const gen = writeGenerator(data, buffer);
246
+ for (const request of gen) {
247
+ if (shouldCancel) break; // finally block resets buffer to READY
248
+ const result = Atomics.wait(request.target, request.index, request.value, request.timeout);
249
+ if (result === 'timed-out') break;
250
+ }
251
+ ```
252
+
253
+ ### Can I reuse the buffer after a transfer?
254
+
255
+ Yes. After a successful transfer (or error), the buffer resets to `READY` state and can be used again:
256
+
257
+ ```typescript
258
+ const buffer = new SharedArrayBuffer(4096);
259
+
260
+ // First transfer
261
+ await write(data1, buffer);
262
+ const result1 = await read(buffer);
263
+
264
+ // Second transfer - same buffer
265
+ await write(data2, buffer);
266
+ const result2 = await read(buffer);
267
+ ```
268
+
178
269
  ## Development
179
270
 
180
271
  ```bash
package/build/index.cjs CHANGED
@@ -64,7 +64,7 @@ var Header = /*#__PURE__*/ function(Header) {
64
64
  Header[Header["CHUNK_SIZE"] = 3] = "CHUNK_SIZE";
65
65
  return Header;
66
66
  }({});
67
- const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
67
+ const HEADER_VALUES = 4;
68
68
  const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
69
69
  function* writeGenerator(data, buffer, { timeout = 5000 } = {}) {
70
70
  if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const SEMAPHORE = 0;\n\nexport enum Semaphore {\n READY,\n HANDSHAKE,\n PAYLOAD,\n}\n\nexport enum Handshake {\n TOTAL_SIZE = 1,\n TOTAL_CHUNKS,\n}\n\nexport enum Header {\n CHUNK_INDEX = 1,\n CHUNK_OFFSET,\n CHUNK_SIZE,\n}\n\nexport const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;\nexport const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;\n\nexport interface Options {\n timeout?: number;\n}\n\nexport interface WaitRequest {\n target: Int32Array;\n index: number;\n value: number;\n timeout?: number;\n}\n\nexport type WaitResponse = ReturnType<typeof Atomics.wait>;\n\nexport function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, void, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const totalSize = data.length;\n const totalChunks = Math.ceil(totalSize / chunkSize);\n const header = new Int32Array(buffer);\n\n header[Handshake.TOTAL_SIZE] = totalSize;\n header[Handshake.TOTAL_CHUNKS] = totalChunks;\n Atomics.store(header, SEMAPHORE, Semaphore.HANDSHAKE);\n Atomics.notify(header, SEMAPHORE);\n\n try {\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.HANDSHAKE,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Reader handshake timeout');\n }\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, totalSize);\n const size = end - start;\n payload.set(data.subarray(start, end), 0);\n header[Header.CHUNK_INDEX] = i;\n header[Header.CHUNK_OFFSET] = start;\n header[Header.CHUNK_SIZE] = size;\n Atomics.store(header, SEMAPHORE, Semaphore.PAYLOAD);\n Atomics.notify(header, SEMAPHORE);\n\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.PAYLOAD,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);\n }\n }\n } finally {\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n}\n\nexport function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const header = new Int32Array(buffer);\n\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Handshake timeout');\n }\n if (header[SEMAPHORE] !== Semaphore.HANDSHAKE) {\n throw new Error('Invalid handshake state');\n }\n\n const totalSize = header[Handshake.TOTAL_SIZE];\n const totalChunks = header[Handshake.TOTAL_CHUNKS];\n if (totalSize < 0 || totalChunks < 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize === 0 && totalChunks !== 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize > totalChunks * chunkSize) {\n throw new Error('Invalid handshake values');\n }\n const data = new Uint8Array(totalSize);\n\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Writer timeout waiting for chunk ${i}`);\n }\n // @ts-expect-error does not infer number\n if (header[SEMAPHORE] !== Semaphore.PAYLOAD) {\n throw new Error(`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`);\n }\n const chunkIndex = header[Header.CHUNK_INDEX];\n if (i !== chunkIndex) {\n throw new Error(`Reader integrity failure for chunk ${chunkIndex} expected ${i}`);\n }\n const offset = header[Header.CHUNK_OFFSET];\n const size = header[Header.CHUNK_SIZE];\n if (offset < 0 || size <= 0 || size > chunkSize || offset + size > totalSize) {\n throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);\n }\n data.set(payload.subarray(0, size), offset);\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n return data;\n}\n\nexport const writeSync = (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): void => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n};\n\nexport const write = async (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): Promise<void> => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n};\n\nexport const readSync = (buffer: SharedArrayBuffer, options?: Options): Uint8Array => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n return result.value;\n};\n\nexport const read = async (buffer: SharedArrayBuffer, options?: Options): Promise<Uint8Array> => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n return result.value;\n};\n"],"names":["HEADER_SIZE","HEADER_VALUES","Handshake","Header","SEMAPHORE","Semaphore","read","readGenerator","readSync","write","writeGenerator","writeSync","Math","max","Object","values","length","Uint32Array","BYTES_PER_ELEMENT","data","buffer","timeout","byteLength","Int32Array","Error","chunkSize","totalSize","totalChunks","ceil","header","Atomics","store","notify","handshakeResult","target","index","value","payload","Uint8Array","i","start","end","min","size","set","subarray","chunkResult","chunkIndex","offset","options","gen","result","next","done","waitResult","wait","request","waitAsync"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const SEMAPHORE = 0;\n\nexport enum Semaphore {\n READY,\n HANDSHAKE,\n PAYLOAD,\n}\n\nexport enum Handshake {\n TOTAL_SIZE = 1,\n TOTAL_CHUNKS,\n}\n\nexport enum Header {\n CHUNK_INDEX = 1,\n CHUNK_OFFSET,\n CHUNK_SIZE,\n}\n\nexport const HEADER_VALUES = 4;\n\n/**\n * Size in bytes reserved for protocol header in the SharedArrayBuffer.\n * The usable payload size is `buffer.byteLength - HEADER_SIZE`.\n */\nexport const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;\n\nexport interface Options {\n /** Max wait time in milliseconds before timeout error. Default: 5000 */\n timeout?: number;\n}\n\n/**\n * Request yielded by generator functions for Atomics.wait/waitAsync.\n * Pass to Atomics.wait() for sync or Atomics.waitAsync() for async waiting.\n */\nexport interface WaitRequest {\n /** Int32Array view of the SharedArrayBuffer header */\n target: Int32Array;\n /** Index in the array to wait on (always SEMAPHORE = 0) */\n index: number;\n /** Value to compare against before waiting */\n value: number;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\nexport type WaitResponse = ReturnType<typeof Atomics.wait>;\n\n/**\n * Low-level generator for writing data with custom flow control.\n * Use for progress tracking, cancellation, or custom scheduling.\n * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)\n * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @yields {WaitRequest} Request to wait for reader acknowledgment\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Reader handshake timeout\" - reader didn't respond in time\n * @throws {Error} \"Reader timeout on chunk N/M\" - reader stopped responding mid-transfer\n * @example\n * ```typescript\n * import { writeGenerator } from 'sabcom';\n *\n * const gen = writeGenerator(data, buffer);\n * let chunks = 0;\n * for (const request of gen) {\n * const result = Atomics.wait(request.target, request.index, request.value, request.timeout);\n * if (result === 'timed-out') throw new Error('Timeout');\n * console.log(`Chunk ${++chunks} sent`);\n * }\n * ```\n */\nexport function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, void, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const totalSize = data.length;\n const totalChunks = Math.ceil(totalSize / chunkSize);\n const header = new Int32Array(buffer);\n\n header[Handshake.TOTAL_SIZE] = totalSize;\n header[Handshake.TOTAL_CHUNKS] = totalChunks;\n Atomics.store(header, SEMAPHORE, Semaphore.HANDSHAKE);\n Atomics.notify(header, SEMAPHORE);\n\n try {\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.HANDSHAKE,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Reader handshake timeout');\n }\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, totalSize);\n const size = end - start;\n payload.set(data.subarray(start, end), 0);\n header[Header.CHUNK_INDEX] = i;\n header[Header.CHUNK_OFFSET] = start;\n header[Header.CHUNK_SIZE] = size;\n Atomics.store(header, SEMAPHORE, Semaphore.PAYLOAD);\n Atomics.notify(header, SEMAPHORE);\n\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.PAYLOAD,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);\n }\n }\n } finally {\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n}\n\n/**\n * Low-level generator for reading data with custom flow control.\n * Use for progress tracking, cancellation, or custom scheduling.\n * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @yields {WaitRequest} Request to wait for writer data\n * @returns Complete data as Uint8Array\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Handshake timeout\" - writer didn't send data in time\n * @throws {Error} \"Invalid handshake state\" - protocol error\n * @throws {Error} \"Writer timeout waiting for chunk N\" - writer stopped responding mid-transfer\n * @example\n * ```typescript\n * import { readGenerator } from 'sabcom';\n *\n * const gen = readGenerator(buffer);\n * let result = gen.next();\n * while (!result.done) {\n * const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n * result = gen.next(waitResult);\n * }\n * const data = result.value; // Uint8Array\n * ```\n */\nexport function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const header = new Int32Array(buffer);\n\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Handshake timeout');\n }\n if (header[SEMAPHORE] !== Semaphore.HANDSHAKE) {\n throw new Error('Invalid handshake state');\n }\n\n const totalSize = header[Handshake.TOTAL_SIZE];\n const totalChunks = header[Handshake.TOTAL_CHUNKS];\n if (totalSize < 0 || totalChunks < 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize === 0 && totalChunks !== 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize > totalChunks * chunkSize) {\n throw new Error('Invalid handshake values');\n }\n const data = new Uint8Array(totalSize);\n\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Writer timeout waiting for chunk ${i}`);\n }\n // @ts-expect-error does not infer number\n if (header[SEMAPHORE] !== Semaphore.PAYLOAD) {\n throw new Error(`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`);\n }\n const chunkIndex = header[Header.CHUNK_INDEX];\n if (i !== chunkIndex) {\n throw new Error(`Reader integrity failure for chunk ${chunkIndex} expected ${i}`);\n }\n const offset = header[Header.CHUNK_OFFSET];\n const size = header[Header.CHUNK_SIZE];\n if (offset < 0 || size <= 0 || size > chunkSize || offset + size > totalSize) {\n throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);\n }\n data.set(payload.subarray(0, size), offset);\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n return data;\n}\n\n/**\n * Synchronously writes data to a SharedArrayBuffer for cross-thread transfer.\n * Blocks until the reader receives all data. Use in workers where blocking is acceptable.\n * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)\n * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Reader handshake timeout\" - reader didn't respond in time\n * @throws {Error} \"Reader timeout on chunk N/M\" - reader stopped responding mid-transfer\n * @example\n * ```typescript\n * import { workerData } from 'worker_threads';\n * import { writeSync } from 'sabcom';\n *\n * const buffer = workerData as SharedArrayBuffer;\n * const data = new TextEncoder().encode('Hello from worker');\n * writeSync(data, buffer);\n * ```\n */\nexport const writeSync = (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): void => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n};\n\n/**\n * Asynchronously writes data to a SharedArrayBuffer for cross-thread transfer.\n * Non-blocking, suitable for main thread. Resolves when the reader receives all data.\n * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)\n * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Reader handshake timeout\" - reader didn't respond in time\n * @throws {Error} \"Reader timeout on chunk N/M\" - reader stopped responding mid-transfer\n * @example\n * ```typescript\n * import { Worker } from 'worker_threads';\n * import { write } from 'sabcom';\n *\n * const buffer = new SharedArrayBuffer(4096);\n * const worker = new Worker('./worker.js', { workerData: buffer });\n * const data = new TextEncoder().encode('Hello from main');\n * await write(data, buffer);\n * ```\n */\nexport const write = async (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): Promise<void> => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n};\n\n/**\n * Synchronously reads data from a SharedArrayBuffer written by another thread.\n * Blocks until all data is received. Use in workers where blocking is acceptable.\n * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @returns Complete data as Uint8Array\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Handshake timeout\" - writer didn't send data in time\n * @throws {Error} \"Invalid handshake state\" - protocol error\n * @throws {Error} \"Writer timeout waiting for chunk N\" - writer stopped responding mid-transfer\n * @example\n * ```typescript\n * import { workerData } from 'worker_threads';\n * import { readSync } from 'sabcom';\n *\n * const buffer = workerData as SharedArrayBuffer;\n * const data = readSync(buffer);\n * const message = new TextDecoder().decode(data);\n * ```\n */\nexport const readSync = (buffer: SharedArrayBuffer, options?: Options): Uint8Array => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n return result.value;\n};\n\n/**\n * Asynchronously reads data from a SharedArrayBuffer written by another thread.\n * Non-blocking, suitable for main thread. Resolves when all data is received.\n * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @returns Complete data as Uint8Array\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Handshake timeout\" - writer didn't send data in time\n * @throws {Error} \"Invalid handshake state\" - protocol error\n * @throws {Error} \"Writer timeout waiting for chunk N\" - writer stopped responding mid-transfer\n * @example\n * ```typescript\n * import { Worker } from 'worker_threads';\n * import { read } from 'sabcom';\n *\n * const buffer = new SharedArrayBuffer(4096);\n * const worker = new Worker('./worker.js', { workerData: buffer });\n * // Worker writes data...\n * const data = await read(buffer);\n * const message = new TextDecoder().decode(data);\n * ```\n */\nexport const read = async (buffer: SharedArrayBuffer, options?: Options): Promise<Uint8Array> => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n return result.value;\n};\n"],"names":["HEADER_SIZE","HEADER_VALUES","Handshake","Header","SEMAPHORE","Semaphore","read","readGenerator","readSync","write","writeGenerator","writeSync","Uint32Array","BYTES_PER_ELEMENT","data","buffer","timeout","byteLength","Int32Array","Error","chunkSize","totalSize","length","totalChunks","Math","ceil","header","Atomics","store","notify","handshakeResult","target","index","value","payload","Uint8Array","i","start","end","min","size","set","subarray","chunkResult","chunkIndex","offset","options","gen","result","next","done","waitResult","wait","request","waitAsync"],"mappings":"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"}
package/build/index.d.ts CHANGED
@@ -13,21 +13,168 @@ export declare enum Header {
13
13
  CHUNK_OFFSET = 2,
14
14
  CHUNK_SIZE = 3
15
15
  }
16
- export declare const HEADER_VALUES: number;
16
+ export declare const HEADER_VALUES = 4;
17
+ /**
18
+ * Size in bytes reserved for protocol header in the SharedArrayBuffer.
19
+ * The usable payload size is `buffer.byteLength - HEADER_SIZE`.
20
+ */
17
21
  export declare const HEADER_SIZE: number;
18
22
  export interface Options {
23
+ /** Max wait time in milliseconds before timeout error. Default: 5000 */
19
24
  timeout?: number;
20
25
  }
26
+ /**
27
+ * Request yielded by generator functions for Atomics.wait/waitAsync.
28
+ * Pass to Atomics.wait() for sync or Atomics.waitAsync() for async waiting.
29
+ */
21
30
  export interface WaitRequest {
31
+ /** Int32Array view of the SharedArrayBuffer header */
22
32
  target: Int32Array;
33
+ /** Index in the array to wait on (always SEMAPHORE = 0) */
23
34
  index: number;
35
+ /** Value to compare against before waiting */
24
36
  value: number;
37
+ /** Timeout in milliseconds */
25
38
  timeout?: number;
26
39
  }
27
40
  export type WaitResponse = ReturnType<typeof Atomics.wait>;
41
+ /**
42
+ * Low-level generator for writing data with custom flow control.
43
+ * Use for progress tracking, cancellation, or custom scheduling.
44
+ * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)
45
+ * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
46
+ * @param options - Optional configuration
47
+ * @yields {WaitRequest} Request to wait for reader acknowledgment
48
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
49
+ * @throws {Error} "SharedArrayBuffer too small for header"
50
+ * @throws {Error} "Reader handshake timeout" - reader didn't respond in time
51
+ * @throws {Error} "Reader timeout on chunk N/M" - reader stopped responding mid-transfer
52
+ * @example
53
+ * ```typescript
54
+ * import { writeGenerator } from 'sabcom';
55
+ *
56
+ * const gen = writeGenerator(data, buffer);
57
+ * let chunks = 0;
58
+ * for (const request of gen) {
59
+ * const result = Atomics.wait(request.target, request.index, request.value, request.timeout);
60
+ * if (result === 'timed-out') throw new Error('Timeout');
61
+ * console.log(`Chunk ${++chunks} sent`);
62
+ * }
63
+ * ```
64
+ */
28
65
  export declare function writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout }?: Options): Generator<WaitRequest, void, WaitResponse>;
66
+ /**
67
+ * Low-level generator for reading data with custom flow control.
68
+ * Use for progress tracking, cancellation, or custom scheduling.
69
+ * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
70
+ * @param options - Optional configuration
71
+ * @yields {WaitRequest} Request to wait for writer data
72
+ * @returns Complete data as Uint8Array
73
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
74
+ * @throws {Error} "SharedArrayBuffer too small for header"
75
+ * @throws {Error} "Handshake timeout" - writer didn't send data in time
76
+ * @throws {Error} "Invalid handshake state" - protocol error
77
+ * @throws {Error} "Writer timeout waiting for chunk N" - writer stopped responding mid-transfer
78
+ * @example
79
+ * ```typescript
80
+ * import { readGenerator } from 'sabcom';
81
+ *
82
+ * const gen = readGenerator(buffer);
83
+ * let result = gen.next();
84
+ * while (!result.done) {
85
+ * const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);
86
+ * result = gen.next(waitResult);
87
+ * }
88
+ * const data = result.value; // Uint8Array
89
+ * ```
90
+ */
29
91
  export declare function readGenerator(buffer: SharedArrayBuffer, { timeout }?: Options): Generator<WaitRequest, Uint8Array, WaitResponse>;
92
+ /**
93
+ * Synchronously writes data to a SharedArrayBuffer for cross-thread transfer.
94
+ * Blocks until the reader receives all data. Use in workers where blocking is acceptable.
95
+ * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)
96
+ * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
97
+ * @param options - Optional configuration
98
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
99
+ * @throws {Error} "SharedArrayBuffer too small for header"
100
+ * @throws {Error} "Reader handshake timeout" - reader didn't respond in time
101
+ * @throws {Error} "Reader timeout on chunk N/M" - reader stopped responding mid-transfer
102
+ * @example
103
+ * ```typescript
104
+ * import { workerData } from 'worker_threads';
105
+ * import { writeSync } from 'sabcom';
106
+ *
107
+ * const buffer = workerData as SharedArrayBuffer;
108
+ * const data = new TextEncoder().encode('Hello from worker');
109
+ * writeSync(data, buffer);
110
+ * ```
111
+ */
30
112
  export declare const writeSync: (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options) => void;
113
+ /**
114
+ * Asynchronously writes data to a SharedArrayBuffer for cross-thread transfer.
115
+ * Non-blocking, suitable for main thread. Resolves when the reader receives all data.
116
+ * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)
117
+ * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
118
+ * @param options - Optional configuration
119
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
120
+ * @throws {Error} "SharedArrayBuffer too small for header"
121
+ * @throws {Error} "Reader handshake timeout" - reader didn't respond in time
122
+ * @throws {Error} "Reader timeout on chunk N/M" - reader stopped responding mid-transfer
123
+ * @example
124
+ * ```typescript
125
+ * import { Worker } from 'worker_threads';
126
+ * import { write } from 'sabcom';
127
+ *
128
+ * const buffer = new SharedArrayBuffer(4096);
129
+ * const worker = new Worker('./worker.js', { workerData: buffer });
130
+ * const data = new TextEncoder().encode('Hello from main');
131
+ * await write(data, buffer);
132
+ * ```
133
+ */
31
134
  export declare const write: (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options) => Promise<void>;
135
+ /**
136
+ * Synchronously reads data from a SharedArrayBuffer written by another thread.
137
+ * Blocks until all data is received. Use in workers where blocking is acceptable.
138
+ * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
139
+ * @param options - Optional configuration
140
+ * @returns Complete data as Uint8Array
141
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
142
+ * @throws {Error} "SharedArrayBuffer too small for header"
143
+ * @throws {Error} "Handshake timeout" - writer didn't send data in time
144
+ * @throws {Error} "Invalid handshake state" - protocol error
145
+ * @throws {Error} "Writer timeout waiting for chunk N" - writer stopped responding mid-transfer
146
+ * @example
147
+ * ```typescript
148
+ * import { workerData } from 'worker_threads';
149
+ * import { readSync } from 'sabcom';
150
+ *
151
+ * const buffer = workerData as SharedArrayBuffer;
152
+ * const data = readSync(buffer);
153
+ * const message = new TextDecoder().decode(data);
154
+ * ```
155
+ */
32
156
  export declare const readSync: (buffer: SharedArrayBuffer, options?: Options) => Uint8Array;
157
+ /**
158
+ * Asynchronously reads data from a SharedArrayBuffer written by another thread.
159
+ * Non-blocking, suitable for main thread. Resolves when all data is received.
160
+ * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
161
+ * @param options - Optional configuration
162
+ * @returns Complete data as Uint8Array
163
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
164
+ * @throws {Error} "SharedArrayBuffer too small for header"
165
+ * @throws {Error} "Handshake timeout" - writer didn't send data in time
166
+ * @throws {Error} "Invalid handshake state" - protocol error
167
+ * @throws {Error} "Writer timeout waiting for chunk N" - writer stopped responding mid-transfer
168
+ * @example
169
+ * ```typescript
170
+ * import { Worker } from 'worker_threads';
171
+ * import { read } from 'sabcom';
172
+ *
173
+ * const buffer = new SharedArrayBuffer(4096);
174
+ * const worker = new Worker('./worker.js', { workerData: buffer });
175
+ * // Worker writes data...
176
+ * const data = await read(buffer);
177
+ * const message = new TextDecoder().decode(data);
178
+ * ```
179
+ */
33
180
  export declare const read: (buffer: SharedArrayBuffer, options?: Options) => Promise<Uint8Array>;
package/build/index.js CHANGED
@@ -16,7 +16,7 @@ export var Header = /*#__PURE__*/ function(Header) {
16
16
  Header[Header["CHUNK_SIZE"] = 3] = "CHUNK_SIZE";
17
17
  return Header;
18
18
  }({});
19
- export const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
19
+ export const HEADER_VALUES = 4;
20
20
  export const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
21
21
  export function* writeGenerator(data, buffer, { timeout = 5000 } = {}) {
22
22
  if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const SEMAPHORE = 0;\n\nexport enum Semaphore {\n READY,\n HANDSHAKE,\n PAYLOAD,\n}\n\nexport enum Handshake {\n TOTAL_SIZE = 1,\n TOTAL_CHUNKS,\n}\n\nexport enum Header {\n CHUNK_INDEX = 1,\n CHUNK_OFFSET,\n CHUNK_SIZE,\n}\n\nexport const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;\nexport const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;\n\nexport interface Options {\n timeout?: number;\n}\n\nexport interface WaitRequest {\n target: Int32Array;\n index: number;\n value: number;\n timeout?: number;\n}\n\nexport type WaitResponse = ReturnType<typeof Atomics.wait>;\n\nexport function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, void, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const totalSize = data.length;\n const totalChunks = Math.ceil(totalSize / chunkSize);\n const header = new Int32Array(buffer);\n\n header[Handshake.TOTAL_SIZE] = totalSize;\n header[Handshake.TOTAL_CHUNKS] = totalChunks;\n Atomics.store(header, SEMAPHORE, Semaphore.HANDSHAKE);\n Atomics.notify(header, SEMAPHORE);\n\n try {\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.HANDSHAKE,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Reader handshake timeout');\n }\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, totalSize);\n const size = end - start;\n payload.set(data.subarray(start, end), 0);\n header[Header.CHUNK_INDEX] = i;\n header[Header.CHUNK_OFFSET] = start;\n header[Header.CHUNK_SIZE] = size;\n Atomics.store(header, SEMAPHORE, Semaphore.PAYLOAD);\n Atomics.notify(header, SEMAPHORE);\n\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.PAYLOAD,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);\n }\n }\n } finally {\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n}\n\nexport function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const header = new Int32Array(buffer);\n\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Handshake timeout');\n }\n if (header[SEMAPHORE] !== Semaphore.HANDSHAKE) {\n throw new Error('Invalid handshake state');\n }\n\n const totalSize = header[Handshake.TOTAL_SIZE];\n const totalChunks = header[Handshake.TOTAL_CHUNKS];\n if (totalSize < 0 || totalChunks < 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize === 0 && totalChunks !== 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize > totalChunks * chunkSize) {\n throw new Error('Invalid handshake values');\n }\n const data = new Uint8Array(totalSize);\n\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Writer timeout waiting for chunk ${i}`);\n }\n // @ts-expect-error does not infer number\n if (header[SEMAPHORE] !== Semaphore.PAYLOAD) {\n throw new Error(`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`);\n }\n const chunkIndex = header[Header.CHUNK_INDEX];\n if (i !== chunkIndex) {\n throw new Error(`Reader integrity failure for chunk ${chunkIndex} expected ${i}`);\n }\n const offset = header[Header.CHUNK_OFFSET];\n const size = header[Header.CHUNK_SIZE];\n if (offset < 0 || size <= 0 || size > chunkSize || offset + size > totalSize) {\n throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);\n }\n data.set(payload.subarray(0, size), offset);\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n return data;\n}\n\nexport const writeSync = (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): void => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n};\n\nexport const write = async (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): Promise<void> => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n};\n\nexport const readSync = (buffer: SharedArrayBuffer, options?: Options): Uint8Array => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n return result.value;\n};\n\nexport const read = async (buffer: SharedArrayBuffer, options?: Options): Promise<Uint8Array> => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n return result.value;\n};\n"],"names":["SEMAPHORE","Semaphore","Handshake","Header","HEADER_VALUES","Math","max","Object","values","length","HEADER_SIZE","Uint32Array","BYTES_PER_ELEMENT","writeGenerator","data","buffer","timeout","byteLength","Int32Array","Error","chunkSize","totalSize","totalChunks","ceil","header","Atomics","store","notify","handshakeResult","target","index","value","payload","Uint8Array","i","start","end","min","size","set","subarray","chunkResult","readGenerator","chunkIndex","offset","writeSync","options","gen","result","next","done","waitResult","wait","write","request","waitAsync","readSync","read"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const SEMAPHORE = 0;\n\nexport enum Semaphore {\n READY,\n HANDSHAKE,\n PAYLOAD,\n}\n\nexport enum Handshake {\n TOTAL_SIZE = 1,\n TOTAL_CHUNKS,\n}\n\nexport enum Header {\n CHUNK_INDEX = 1,\n CHUNK_OFFSET,\n CHUNK_SIZE,\n}\n\nexport const HEADER_VALUES = 4;\n\n/**\n * Size in bytes reserved for protocol header in the SharedArrayBuffer.\n * The usable payload size is `buffer.byteLength - HEADER_SIZE`.\n */\nexport const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;\n\nexport interface Options {\n /** Max wait time in milliseconds before timeout error. Default: 5000 */\n timeout?: number;\n}\n\n/**\n * Request yielded by generator functions for Atomics.wait/waitAsync.\n * Pass to Atomics.wait() for sync or Atomics.waitAsync() for async waiting.\n */\nexport interface WaitRequest {\n /** Int32Array view of the SharedArrayBuffer header */\n target: Int32Array;\n /** Index in the array to wait on (always SEMAPHORE = 0) */\n index: number;\n /** Value to compare against before waiting */\n value: number;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\nexport type WaitResponse = ReturnType<typeof Atomics.wait>;\n\n/**\n * Low-level generator for writing data with custom flow control.\n * Use for progress tracking, cancellation, or custom scheduling.\n * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)\n * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @yields {WaitRequest} Request to wait for reader acknowledgment\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Reader handshake timeout\" - reader didn't respond in time\n * @throws {Error} \"Reader timeout on chunk N/M\" - reader stopped responding mid-transfer\n * @example\n * ```typescript\n * import { writeGenerator } from 'sabcom';\n *\n * const gen = writeGenerator(data, buffer);\n * let chunks = 0;\n * for (const request of gen) {\n * const result = Atomics.wait(request.target, request.index, request.value, request.timeout);\n * if (result === 'timed-out') throw new Error('Timeout');\n * console.log(`Chunk ${++chunks} sent`);\n * }\n * ```\n */\nexport function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, void, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const totalSize = data.length;\n const totalChunks = Math.ceil(totalSize / chunkSize);\n const header = new Int32Array(buffer);\n\n header[Handshake.TOTAL_SIZE] = totalSize;\n header[Handshake.TOTAL_CHUNKS] = totalChunks;\n Atomics.store(header, SEMAPHORE, Semaphore.HANDSHAKE);\n Atomics.notify(header, SEMAPHORE);\n\n try {\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.HANDSHAKE,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Reader handshake timeout');\n }\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, totalSize);\n const size = end - start;\n payload.set(data.subarray(start, end), 0);\n header[Header.CHUNK_INDEX] = i;\n header[Header.CHUNK_OFFSET] = start;\n header[Header.CHUNK_SIZE] = size;\n Atomics.store(header, SEMAPHORE, Semaphore.PAYLOAD);\n Atomics.notify(header, SEMAPHORE);\n\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.PAYLOAD,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);\n }\n }\n } finally {\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n}\n\n/**\n * Low-level generator for reading data with custom flow control.\n * Use for progress tracking, cancellation, or custom scheduling.\n * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @yields {WaitRequest} Request to wait for writer data\n * @returns Complete data as Uint8Array\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Handshake timeout\" - writer didn't send data in time\n * @throws {Error} \"Invalid handshake state\" - protocol error\n * @throws {Error} \"Writer timeout waiting for chunk N\" - writer stopped responding mid-transfer\n * @example\n * ```typescript\n * import { readGenerator } from 'sabcom';\n *\n * const gen = readGenerator(buffer);\n * let result = gen.next();\n * while (!result.done) {\n * const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n * result = gen.next(waitResult);\n * }\n * const data = result.value; // Uint8Array\n * ```\n */\nexport function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {\n if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {\n throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');\n }\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n if (chunkSize <= 0) {\n throw new Error('SharedArrayBuffer too small for header');\n }\n const header = new Int32Array(buffer);\n\n const handshakeResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (handshakeResult === 'timed-out') {\n throw new Error('Handshake timeout');\n }\n if (header[SEMAPHORE] !== Semaphore.HANDSHAKE) {\n throw new Error('Invalid handshake state');\n }\n\n const totalSize = header[Handshake.TOTAL_SIZE];\n const totalChunks = header[Handshake.TOTAL_CHUNKS];\n if (totalSize < 0 || totalChunks < 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize === 0 && totalChunks !== 0) {\n throw new Error('Invalid handshake values');\n }\n if (totalSize > totalChunks * chunkSize) {\n throw new Error('Invalid handshake values');\n }\n const data = new Uint8Array(totalSize);\n\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const chunkResult: WaitResponse = yield {\n target: header,\n index: SEMAPHORE,\n value: Semaphore.READY,\n timeout,\n };\n if (chunkResult === 'timed-out') {\n throw new Error(`Writer timeout waiting for chunk ${i}`);\n }\n // @ts-expect-error does not infer number\n if (header[SEMAPHORE] !== Semaphore.PAYLOAD) {\n throw new Error(`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`);\n }\n const chunkIndex = header[Header.CHUNK_INDEX];\n if (i !== chunkIndex) {\n throw new Error(`Reader integrity failure for chunk ${chunkIndex} expected ${i}`);\n }\n const offset = header[Header.CHUNK_OFFSET];\n const size = header[Header.CHUNK_SIZE];\n if (offset < 0 || size <= 0 || size > chunkSize || offset + size > totalSize) {\n throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);\n }\n data.set(payload.subarray(0, size), offset);\n Atomics.store(header, SEMAPHORE, Semaphore.READY);\n Atomics.notify(header, SEMAPHORE);\n }\n return data;\n}\n\n/**\n * Synchronously writes data to a SharedArrayBuffer for cross-thread transfer.\n * Blocks until the reader receives all data. Use in workers where blocking is acceptable.\n * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)\n * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Reader handshake timeout\" - reader didn't respond in time\n * @throws {Error} \"Reader timeout on chunk N/M\" - reader stopped responding mid-transfer\n * @example\n * ```typescript\n * import { workerData } from 'worker_threads';\n * import { writeSync } from 'sabcom';\n *\n * const buffer = workerData as SharedArrayBuffer;\n * const data = new TextEncoder().encode('Hello from worker');\n * writeSync(data, buffer);\n * ```\n */\nexport const writeSync = (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): void => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n};\n\n/**\n * Asynchronously writes data to a SharedArrayBuffer for cross-thread transfer.\n * Non-blocking, suitable for main thread. Resolves when the reader receives all data.\n * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)\n * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Reader handshake timeout\" - reader didn't respond in time\n * @throws {Error} \"Reader timeout on chunk N/M\" - reader stopped responding mid-transfer\n * @example\n * ```typescript\n * import { Worker } from 'worker_threads';\n * import { write } from 'sabcom';\n *\n * const buffer = new SharedArrayBuffer(4096);\n * const worker = new Worker('./worker.js', { workerData: buffer });\n * const data = new TextEncoder().encode('Hello from main');\n * await write(data, buffer);\n * ```\n */\nexport const write = async (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): Promise<void> => {\n const gen = writeGenerator(data, buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n};\n\n/**\n * Synchronously reads data from a SharedArrayBuffer written by another thread.\n * Blocks until all data is received. Use in workers where blocking is acceptable.\n * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @returns Complete data as Uint8Array\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Handshake timeout\" - writer didn't send data in time\n * @throws {Error} \"Invalid handshake state\" - protocol error\n * @throws {Error} \"Writer timeout waiting for chunk N\" - writer stopped responding mid-transfer\n * @example\n * ```typescript\n * import { workerData } from 'worker_threads';\n * import { readSync } from 'sabcom';\n *\n * const buffer = workerData as SharedArrayBuffer;\n * const data = readSync(buffer);\n * const message = new TextDecoder().decode(data);\n * ```\n */\nexport const readSync = (buffer: SharedArrayBuffer, options?: Options): Uint8Array => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);\n result = gen.next(waitResult);\n }\n return result.value;\n};\n\n/**\n * Asynchronously reads data from a SharedArrayBuffer written by another thread.\n * Non-blocking, suitable for main thread. Resolves when all data is received.\n * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)\n * @param options - Optional configuration\n * @returns Complete data as Uint8Array\n * @throws {Error} \"SharedArrayBuffer byteLength must be a multiple of 4\"\n * @throws {Error} \"SharedArrayBuffer too small for header\"\n * @throws {Error} \"Handshake timeout\" - writer didn't send data in time\n * @throws {Error} \"Invalid handshake state\" - protocol error\n * @throws {Error} \"Writer timeout waiting for chunk N\" - writer stopped responding mid-transfer\n * @example\n * ```typescript\n * import { Worker } from 'worker_threads';\n * import { read } from 'sabcom';\n *\n * const buffer = new SharedArrayBuffer(4096);\n * const worker = new Worker('./worker.js', { workerData: buffer });\n * // Worker writes data...\n * const data = await read(buffer);\n * const message = new TextDecoder().decode(data);\n * ```\n */\nexport const read = async (buffer: SharedArrayBuffer, options?: Options): Promise<Uint8Array> => {\n const gen = readGenerator(buffer, options);\n let result = gen.next();\n while (!result.done) {\n const request = result.value;\n const waitResult = await Atomics.waitAsync(request.target, request.index, request.value, request.timeout).value;\n result = gen.next(waitResult);\n }\n return result.value;\n};\n"],"names":["SEMAPHORE","Semaphore","Handshake","Header","HEADER_VALUES","HEADER_SIZE","Uint32Array","BYTES_PER_ELEMENT","writeGenerator","data","buffer","timeout","byteLength","Int32Array","Error","chunkSize","totalSize","length","totalChunks","Math","ceil","header","Atomics","store","notify","handshakeResult","target","index","value","payload","Uint8Array","i","start","end","min","size","set","subarray","chunkResult","readGenerator","chunkIndex","offset","writeSync","options","gen","result","next","done","waitResult","wait","write","request","waitAsync","readSync","read"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sabcom",
3
- "version": "0.1.91",
3
+ "version": "0.1.95",
4
4
  "description": "A TypeScript/Node.js library for inter-thread communication using SharedArrayBuffer with atomic operations for raw buffer data transfer",
5
5
  "type": "module",
6
6
  "types": "build/index.d.ts",
@@ -42,18 +42,18 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@eslint/js": "^9.39.2",
45
- "@types/node": "^25.0.9",
46
- "@typescript-eslint/eslint-plugin": "^8.53.0",
47
- "@typescript-eslint/parser": "^8.53.0",
45
+ "@types/node": "^25.0.10",
46
+ "@typescript-eslint/eslint-plugin": "^8.53.1",
47
+ "@typescript-eslint/parser": "^8.53.1",
48
48
  "@vitest/coverage-v8": "^4.0.17",
49
49
  "eslint": "^9.39.2",
50
50
  "eslint-config-prettier": "^10.1.8",
51
51
  "eslint-plugin-prettier": "^5.5.5",
52
52
  "husky": "^9.1.7",
53
53
  "inop": "^0.8.10",
54
- "prettier": "^3.8.0",
54
+ "prettier": "^3.8.1",
55
55
  "typescript": "^5.9.3",
56
- "typescript-eslint": "^8.53.0",
56
+ "typescript-eslint": "^8.53.1",
57
57
  "vitest": "^4.0.17"
58
58
  },
59
59
  "scripts": {
package/src/index.ts CHANGED
@@ -17,22 +17,60 @@ export enum Header {
17
17
  CHUNK_SIZE,
18
18
  }
19
19
 
20
- export const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
20
+ export const HEADER_VALUES = 4;
21
+
22
+ /**
23
+ * Size in bytes reserved for protocol header in the SharedArrayBuffer.
24
+ * The usable payload size is `buffer.byteLength - HEADER_SIZE`.
25
+ */
21
26
  export const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
22
27
 
23
28
  export interface Options {
29
+ /** Max wait time in milliseconds before timeout error. Default: 5000 */
24
30
  timeout?: number;
25
31
  }
26
32
 
33
+ /**
34
+ * Request yielded by generator functions for Atomics.wait/waitAsync.
35
+ * Pass to Atomics.wait() for sync or Atomics.waitAsync() for async waiting.
36
+ */
27
37
  export interface WaitRequest {
38
+ /** Int32Array view of the SharedArrayBuffer header */
28
39
  target: Int32Array;
40
+ /** Index in the array to wait on (always SEMAPHORE = 0) */
29
41
  index: number;
42
+ /** Value to compare against before waiting */
30
43
  value: number;
44
+ /** Timeout in milliseconds */
31
45
  timeout?: number;
32
46
  }
33
47
 
34
48
  export type WaitResponse = ReturnType<typeof Atomics.wait>;
35
49
 
50
+ /**
51
+ * Low-level generator for writing data with custom flow control.
52
+ * Use for progress tracking, cancellation, or custom scheduling.
53
+ * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)
54
+ * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
55
+ * @param options - Optional configuration
56
+ * @yields {WaitRequest} Request to wait for reader acknowledgment
57
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
58
+ * @throws {Error} "SharedArrayBuffer too small for header"
59
+ * @throws {Error} "Reader handshake timeout" - reader didn't respond in time
60
+ * @throws {Error} "Reader timeout on chunk N/M" - reader stopped responding mid-transfer
61
+ * @example
62
+ * ```typescript
63
+ * import { writeGenerator } from 'sabcom';
64
+ *
65
+ * const gen = writeGenerator(data, buffer);
66
+ * let chunks = 0;
67
+ * for (const request of gen) {
68
+ * const result = Atomics.wait(request.target, request.index, request.value, request.timeout);
69
+ * if (result === 'timed-out') throw new Error('Timeout');
70
+ * console.log(`Chunk ${++chunks} sent`);
71
+ * }
72
+ * ```
73
+ */
36
74
  export function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, void, WaitResponse> {
37
75
  if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
38
76
  throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
@@ -89,6 +127,31 @@ export function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { t
89
127
  }
90
128
  }
91
129
 
130
+ /**
131
+ * Low-level generator for reading data with custom flow control.
132
+ * Use for progress tracking, cancellation, or custom scheduling.
133
+ * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
134
+ * @param options - Optional configuration
135
+ * @yields {WaitRequest} Request to wait for writer data
136
+ * @returns Complete data as Uint8Array
137
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
138
+ * @throws {Error} "SharedArrayBuffer too small for header"
139
+ * @throws {Error} "Handshake timeout" - writer didn't send data in time
140
+ * @throws {Error} "Invalid handshake state" - protocol error
141
+ * @throws {Error} "Writer timeout waiting for chunk N" - writer stopped responding mid-transfer
142
+ * @example
143
+ * ```typescript
144
+ * import { readGenerator } from 'sabcom';
145
+ *
146
+ * const gen = readGenerator(buffer);
147
+ * let result = gen.next();
148
+ * while (!result.done) {
149
+ * const waitResult = Atomics.wait(result.value.target, result.value.index, result.value.value, result.value.timeout);
150
+ * result = gen.next(waitResult);
151
+ * }
152
+ * const data = result.value; // Uint8Array
153
+ * ```
154
+ */
92
155
  export function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {
93
156
  if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
94
157
  throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
@@ -159,6 +222,26 @@ export function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Op
159
222
  return data;
160
223
  }
161
224
 
225
+ /**
226
+ * Synchronously writes data to a SharedArrayBuffer for cross-thread transfer.
227
+ * Blocks until the reader receives all data. Use in workers where blocking is acceptable.
228
+ * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)
229
+ * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
230
+ * @param options - Optional configuration
231
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
232
+ * @throws {Error} "SharedArrayBuffer too small for header"
233
+ * @throws {Error} "Reader handshake timeout" - reader didn't respond in time
234
+ * @throws {Error} "Reader timeout on chunk N/M" - reader stopped responding mid-transfer
235
+ * @example
236
+ * ```typescript
237
+ * import { workerData } from 'worker_threads';
238
+ * import { writeSync } from 'sabcom';
239
+ *
240
+ * const buffer = workerData as SharedArrayBuffer;
241
+ * const data = new TextEncoder().encode('Hello from worker');
242
+ * writeSync(data, buffer);
243
+ * ```
244
+ */
162
245
  export const writeSync = (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): void => {
163
246
  const gen = writeGenerator(data, buffer, options);
164
247
  let result = gen.next();
@@ -168,6 +251,27 @@ export const writeSync = (data: Uint8Array, buffer: SharedArrayBuffer, options?:
168
251
  }
169
252
  };
170
253
 
254
+ /**
255
+ * Asynchronously writes data to a SharedArrayBuffer for cross-thread transfer.
256
+ * Non-blocking, suitable for main thread. Resolves when the reader receives all data.
257
+ * @param data - Bytes to send (can be larger than buffer, will be chunked automatically)
258
+ * @param buffer - SharedArrayBuffer shared with the reader thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
259
+ * @param options - Optional configuration
260
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
261
+ * @throws {Error} "SharedArrayBuffer too small for header"
262
+ * @throws {Error} "Reader handshake timeout" - reader didn't respond in time
263
+ * @throws {Error} "Reader timeout on chunk N/M" - reader stopped responding mid-transfer
264
+ * @example
265
+ * ```typescript
266
+ * import { Worker } from 'worker_threads';
267
+ * import { write } from 'sabcom';
268
+ *
269
+ * const buffer = new SharedArrayBuffer(4096);
270
+ * const worker = new Worker('./worker.js', { workerData: buffer });
271
+ * const data = new TextEncoder().encode('Hello from main');
272
+ * await write(data, buffer);
273
+ * ```
274
+ */
171
275
  export const write = async (data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): Promise<void> => {
172
276
  const gen = writeGenerator(data, buffer, options);
173
277
  let result = gen.next();
@@ -178,6 +282,27 @@ export const write = async (data: Uint8Array, buffer: SharedArrayBuffer, options
178
282
  }
179
283
  };
180
284
 
285
+ /**
286
+ * Synchronously reads data from a SharedArrayBuffer written by another thread.
287
+ * Blocks until all data is received. Use in workers where blocking is acceptable.
288
+ * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
289
+ * @param options - Optional configuration
290
+ * @returns Complete data as Uint8Array
291
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
292
+ * @throws {Error} "SharedArrayBuffer too small for header"
293
+ * @throws {Error} "Handshake timeout" - writer didn't send data in time
294
+ * @throws {Error} "Invalid handshake state" - protocol error
295
+ * @throws {Error} "Writer timeout waiting for chunk N" - writer stopped responding mid-transfer
296
+ * @example
297
+ * ```typescript
298
+ * import { workerData } from 'worker_threads';
299
+ * import { readSync } from 'sabcom';
300
+ *
301
+ * const buffer = workerData as SharedArrayBuffer;
302
+ * const data = readSync(buffer);
303
+ * const message = new TextDecoder().decode(data);
304
+ * ```
305
+ */
181
306
  export const readSync = (buffer: SharedArrayBuffer, options?: Options): Uint8Array => {
182
307
  const gen = readGenerator(buffer, options);
183
308
  let result = gen.next();
@@ -188,6 +313,29 @@ export const readSync = (buffer: SharedArrayBuffer, options?: Options): Uint8Arr
188
313
  return result.value;
189
314
  };
190
315
 
316
+ /**
317
+ * Asynchronously reads data from a SharedArrayBuffer written by another thread.
318
+ * Non-blocking, suitable for main thread. Resolves when all data is received.
319
+ * @param buffer - SharedArrayBuffer shared with the writer thread (byteLength must be multiple of 4 and larger than HEADER_SIZE)
320
+ * @param options - Optional configuration
321
+ * @returns Complete data as Uint8Array
322
+ * @throws {Error} "SharedArrayBuffer byteLength must be a multiple of 4"
323
+ * @throws {Error} "SharedArrayBuffer too small for header"
324
+ * @throws {Error} "Handshake timeout" - writer didn't send data in time
325
+ * @throws {Error} "Invalid handshake state" - protocol error
326
+ * @throws {Error} "Writer timeout waiting for chunk N" - writer stopped responding mid-transfer
327
+ * @example
328
+ * ```typescript
329
+ * import { Worker } from 'worker_threads';
330
+ * import { read } from 'sabcom';
331
+ *
332
+ * const buffer = new SharedArrayBuffer(4096);
333
+ * const worker = new Worker('./worker.js', { workerData: buffer });
334
+ * // Worker writes data...
335
+ * const data = await read(buffer);
336
+ * const message = new TextDecoder().decode(data);
337
+ * ```
338
+ */
191
339
  export const read = async (buffer: SharedArrayBuffer, options?: Options): Promise<Uint8Array> => {
192
340
  const gen = readGenerator(buffer, options);
193
341
  let result = gen.next();