sabcom 0.1.94 → 0.1.123

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.
@@ -128,6 +140,100 @@ await write(data, buffer, {
128
140
  * **Smaller Buffer**: Less memory usage, more context switches/atomic operations.
129
141
  * **Recommendation**: Start with 4KB - 64KB (`4096` - `65536`) depending on your average payload size.
130
142
 
143
+ ## Multi-Worker Architecture
144
+
145
+ When multiple workers need to communicate with the main thread simultaneously, create a separate `SharedArrayBuffer` for each worker. Each buffer is an independent communication channel.
146
+
147
+ **main.ts**
148
+ ```typescript
149
+ import { Worker } from 'worker_threads';
150
+ import { write, read } from 'sabcom';
151
+
152
+ interface WorkerChannel {
153
+ worker: Worker;
154
+ buffer: SharedArrayBuffer;
155
+ }
156
+
157
+ async function createWorker(id: number): Promise<WorkerChannel> {
158
+ const buffer = new SharedArrayBuffer(4096);
159
+ const worker = new Worker('./worker.js', {
160
+ workerData: { id, buffer }
161
+ });
162
+ return { worker, buffer };
163
+ }
164
+
165
+ async function main() {
166
+ // Create multiple workers, each with its own buffer
167
+ const channels: WorkerChannel[] = await Promise.all([
168
+ createWorker(0),
169
+ createWorker(1),
170
+ createWorker(2),
171
+ ]);
172
+
173
+ // Send data to all workers in parallel
174
+ const tasks = ['task-a', 'task-b', 'task-c'];
175
+ await Promise.all(
176
+ channels.map((ch, i) =>
177
+ write(new TextEncoder().encode(tasks[i]), ch.buffer)
178
+ )
179
+ );
180
+
181
+ // Read responses from all workers in parallel
182
+ const responses = await Promise.all(
183
+ channels.map(ch => read(ch.buffer))
184
+ );
185
+
186
+ responses.forEach((data, i) => {
187
+ console.log(`Worker ${i}: ${new TextDecoder().decode(data)}`);
188
+ });
189
+
190
+ // Cleanup
191
+ await Promise.all(channels.map(ch => ch.worker.terminate()));
192
+ }
193
+
194
+ main();
195
+ ```
196
+
197
+ **worker.ts**
198
+ ```typescript
199
+ import { workerData, parentPort } from 'worker_threads';
200
+ import { readSync, writeSync } from 'sabcom';
201
+
202
+ const { id, buffer } = workerData as { id: number; buffer: SharedArrayBuffer };
203
+
204
+ // Receive task from main
205
+ const task = new TextDecoder().decode(readSync(buffer));
206
+ console.log(`Worker ${id} received: ${task}`);
207
+
208
+ // Process and respond
209
+ const result = `${task.toUpperCase()}-done`;
210
+ writeSync(new TextEncoder().encode(result), buffer);
211
+ ```
212
+
213
+ ### Key Points for Multi-Worker Setup
214
+
215
+ 1. **One buffer per worker** - Each worker needs its own `SharedArrayBuffer`. A single buffer can only handle one reader-writer pair at a time.
216
+
217
+ 2. **Buffer ownership** - Each buffer represents a bidirectional channel between main thread and one worker. Don't share a buffer between multiple workers.
218
+
219
+ 3. **Parallel operations** - Use `Promise.all()` with async API (`write`/`read`) to communicate with multiple workers concurrently.
220
+
221
+ 4. **Buffer pool pattern** - For dynamic worker counts, maintain a pool of buffers:
222
+
223
+ ```typescript
224
+ class BufferPool {
225
+ private available: SharedArrayBuffer[] = [];
226
+
227
+ acquire(size = 4096): SharedArrayBuffer {
228
+ return this.available.pop() ?? new SharedArrayBuffer(size);
229
+ }
230
+
231
+ release(buffer: SharedArrayBuffer): void {
232
+ this.available.push(buffer);
233
+ }
234
+ }
235
+ ```
236
+
131
237
  ## Advanced: Generators
132
238
 
133
239
  If you need fine-grained control over the transfer process (e.g., to implement a progress bar, cancellation, or custom scheduling), you can use the generator functions directly.
@@ -175,6 +281,85 @@ The communication follows a strict handshake:
175
281
 
176
282
  *Note: The `SharedArrayBuffer` is reusable after a successful transfer.*
177
283
 
284
+ ## FAQ
285
+
286
+ ### What is the minimum buffer size?
287
+
288
+ `HEADER_SIZE` is 16 bytes. Your buffer must be larger to have usable payload space:
289
+
290
+ ```typescript
291
+ import { HEADER_SIZE } from 'sabcom';
292
+
293
+ // Minimum: HEADER_SIZE + at least 1 byte for payload
294
+ // Practical minimum: 1024 bytes (1KB)
295
+ const buffer = new SharedArrayBuffer(1024);
296
+ ```
297
+
298
+ ### How do I send JSON or objects?
299
+
300
+ sabcom transfers raw bytes only. Serialize before sending:
301
+
302
+ ```typescript
303
+ // Writer
304
+ const obj = { hello: 'world', count: 42 };
305
+ const json = JSON.stringify(obj);
306
+ await write(new TextEncoder().encode(json), buffer);
307
+
308
+ // Reader
309
+ const data = await read(buffer);
310
+ const obj = JSON.parse(new TextDecoder().decode(data));
311
+ ```
312
+
313
+ ### Does sabcom work in browsers?
314
+
315
+ Yes, with Web Workers. However, `SharedArrayBuffer` requires cross-origin isolation headers on your server:
316
+
317
+ ```
318
+ Cross-Origin-Opener-Policy: same-origin
319
+ Cross-Origin-Embedder-Policy: require-corp
320
+ ```
321
+
322
+ ### How do I handle errors?
323
+
324
+ ```typescript
325
+ try {
326
+ await write(data, buffer, { timeout: 5000 });
327
+ } catch (err) {
328
+ if (err.message.includes('timeout')) {
329
+ console.error('Reader did not respond in time');
330
+ }
331
+ }
332
+ ```
333
+
334
+ ### Can I cancel a transfer mid-way?
335
+
336
+ Use generators with `for...of` - breaking out automatically triggers cleanup:
337
+
338
+ ```typescript
339
+ const gen = writeGenerator(data, buffer);
340
+ for (const request of gen) {
341
+ if (shouldCancel) break; // finally block resets buffer to READY
342
+ const result = Atomics.wait(request.target, request.index, request.value, request.timeout);
343
+ if (result === 'timed-out') break;
344
+ }
345
+ ```
346
+
347
+ ### Can I reuse the buffer after a transfer?
348
+
349
+ Yes. After a successful transfer (or error), the buffer resets to `READY` state and can be used again:
350
+
351
+ ```typescript
352
+ const buffer = new SharedArrayBuffer(4096);
353
+
354
+ // First transfer
355
+ await write(data1, buffer);
356
+ const result1 = await read(buffer);
357
+
358
+ // Second transfer - same buffer
359
+ await write(data2, buffer);
360
+ const result2 = await read(buffer);
361
+ ```
362
+
178
363
  ## Development
179
364
 
180
365
  ```bash
@@ -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 = 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\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\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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"}
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
@@ -23,14 +23,71 @@ export interface Options {
23
23
  /** Max wait time in milliseconds before timeout error. Default: 5000 */
24
24
  timeout?: number;
25
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
+ */
26
30
  export interface WaitRequest {
31
+ /** Int32Array view of the SharedArrayBuffer header */
27
32
  target: Int32Array;
33
+ /** Index in the array to wait on (always SEMAPHORE = 0) */
28
34
  index: number;
35
+ /** Value to compare against before waiting */
29
36
  value: number;
37
+ /** Timeout in milliseconds */
30
38
  timeout?: number;
31
39
  }
32
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
+ */
33
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
+ */
34
91
  export declare function readGenerator(buffer: SharedArrayBuffer, { timeout }?: Options): Generator<WaitRequest, Uint8Array, WaitResponse>;
35
92
  /**
36
93
  * Synchronously writes data to a SharedArrayBuffer for cross-thread transfer.
@@ -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 = 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\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\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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"}
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.94",
3
+ "version": "0.1.123",
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",
@@ -41,20 +41,20 @@
41
41
  "url": "https://linkedin.com/in/3axap4eHko"
42
42
  },
43
43
  "devDependencies": {
44
- "@eslint/js": "^9.39.2",
45
- "@types/node": "^25.0.10",
46
- "@typescript-eslint/eslint-plugin": "^8.53.1",
47
- "@typescript-eslint/parser": "^8.53.1",
48
- "@vitest/coverage-v8": "^4.0.17",
49
- "eslint": "^9.39.2",
44
+ "@eslint/js": "^9.39.4",
45
+ "@types/node": "^25.5.0",
46
+ "@typescript-eslint/eslint-plugin": "^8.58.0",
47
+ "@typescript-eslint/parser": "^8.58.0",
48
+ "@vitest/coverage-v8": "^4.1.2",
49
+ "eslint": "^9.39.4",
50
50
  "eslint-config-prettier": "^10.1.8",
51
51
  "eslint-plugin-prettier": "^5.5.5",
52
52
  "husky": "^9.1.7",
53
- "inop": "^0.8.10",
53
+ "inop": "^0.9.0",
54
54
  "prettier": "^3.8.1",
55
55
  "typescript": "^5.9.3",
56
- "typescript-eslint": "^8.53.1",
57
- "vitest": "^4.0.17"
56
+ "typescript-eslint": "^8.58.0",
57
+ "vitest": "^4.1.2"
58
58
  },
59
59
  "scripts": {
60
60
  "build": "rm -rf build && inop src build -i __tests__ -i *.tmp.ts && tsc --declaration --emitDeclarationOnly",
package/src/index.ts CHANGED
@@ -30,15 +30,47 @@ export interface Options {
30
30
  timeout?: number;
31
31
  }
32
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
+ */
33
37
  export interface WaitRequest {
38
+ /** Int32Array view of the SharedArrayBuffer header */
34
39
  target: Int32Array;
40
+ /** Index in the array to wait on (always SEMAPHORE = 0) */
35
41
  index: number;
42
+ /** Value to compare against before waiting */
36
43
  value: number;
44
+ /** Timeout in milliseconds */
37
45
  timeout?: number;
38
46
  }
39
47
 
40
48
  export type WaitResponse = ReturnType<typeof Atomics.wait>;
41
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
+ */
42
74
  export function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, void, WaitResponse> {
43
75
  if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
44
76
  throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
@@ -95,6 +127,31 @@ export function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { t
95
127
  }
96
128
  }
97
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
+ */
98
155
  export function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {
99
156
  if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
100
157
  throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');