sabcom 0.1.70 → 0.1.84

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
@@ -6,17 +6,17 @@
6
6
  [![Coverage Status][codecov-image]][codecov-url]
7
7
  [![Snyk][snyk-image]][snyk-url]
8
8
 
9
- A TypeScript/Node.js library for inter-thread communication using SharedArrayBuffer with atomic operations and V8 serialization.
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
11
  ## Features
12
12
 
13
- - **Thread-safe communication** using atomic operations
14
- - **Async and sync APIs** for different use cases
15
- - **Chunked data transfer** for large payloads
16
- - **V8 serialization** for complex data types
17
- - **Timeout handling** with configurable timeouts
18
- - **Zero-copy operations** where possible
19
- - **Generator-based** low-level API for custom implementations
13
+ - **Thread-safe communication** using `Atomics` for synchronization.
14
+ - **Async and Sync APIs** to suit different architectural needs.
15
+ - **Chunked Data Transfer** allows sending payloads larger than the buffer size.
16
+ - **Byte-only API** for explicit serialization control.
17
+ - **Configurable Timeouts** to prevent deadlocks.
18
+ - **Generator-based Low-level API** for custom flow control implementations.
19
+ - **Type-safe** with full TypeScript support.
20
20
 
21
21
  ## Installation
22
22
 
@@ -24,171 +24,176 @@ A TypeScript/Node.js library for inter-thread communication using SharedArrayBuf
24
24
  npm install sabcom
25
25
  ```
26
26
 
27
- ## Usage
28
-
29
- ### Async Example
30
-
31
- ```typescript
32
- import { write, read } from 'sabcom';
33
-
34
- // Create a shared buffer (1MB)
35
- const buffer = new SharedArrayBuffer(1024 * 1024);
27
+ ## Complete Example
36
28
 
37
- // Writer thread (async)
38
- const data = { message: 'Hello World', numbers: [1, 2, 3, 4, 5] };
39
- await write(data, buffer);
40
-
41
- // Reader thread (async)
42
- const received = await read(buffer);
43
- console.log(received); // { message: 'Hello World', numbers: [1, 2, 3, 4, 5] }
44
- ```
45
-
46
- ### Sync Example
29
+ Here is a complete example demonstrating communication between a main thread and a worker thread using Node.js `worker_threads`.
30
+ The `SharedArrayBuffer` is passed once via `workerData` so no `postMessage` is needed for data transfer.
47
31
 
32
+ **worker.ts**
48
33
  ```typescript
49
- import { writeSync, readSync } from 'sabcom';
50
-
51
- // Writer thread (sync)
52
- writeSync(data, buffer);
53
-
54
- // Reader thread (sync)
55
- const received = readSync(buffer);
34
+ import { workerData } from 'worker_threads';
35
+ import { readSync, writeSync } from 'sabcom';
36
+
37
+ const buffer = workerData as SharedArrayBuffer;
38
+
39
+ try {
40
+ console.log('Worker: Waiting for data...');
41
+ const receivedData = readSync(buffer);
42
+ const message = new TextDecoder().decode(receivedData);
43
+ console.log('Worker: Received message:', message);
44
+
45
+ const reply = new TextEncoder().encode(message.toUpperCase());
46
+ writeSync(reply, buffer);
47
+ } catch (err) {
48
+ console.error('Worker: Error', err);
49
+ process.exit(1);
50
+ }
56
51
  ```
57
52
 
58
- ### With Custom Timeout
59
-
53
+ **main.ts**
60
54
  ```typescript
61
- // 10 second timeout
62
- await write(data, buffer, { timeout: 10000 });
63
- const received = await read(buffer, { timeout: 10000 });
64
-
65
- // Sync with timeout
66
- writeSync(data, buffer, { timeout: 10000 });
67
- const received = readSync(buffer, { timeout: 10000 });
68
- ```
69
-
70
- ## API Reference
71
-
72
- ### Async Functions
73
-
74
- #### `write(data: unknown, buffer: SharedArrayBuffer, options?: Options): Promise<void>`
75
-
76
- Asynchronously writes data to the shared buffer using chunked transfer.
55
+ import { Worker } from 'worker_threads';
56
+ import { write, read } from 'sabcom';
57
+ import path from 'path';
77
58
 
78
- - `data` - Any serializable data
79
- - `buffer` - SharedArrayBuffer for communication
80
- - `options` - Optional configuration object
81
- - `timeout` - Timeout in milliseconds (default: 5000)
59
+ async function main() {
60
+ // 1. Create a SharedArrayBuffer (must be multiple of 4)
61
+ // 4KB buffer
62
+ const buffer = new SharedArrayBuffer(4096);
82
63
 
83
- **Throws:**
84
- - `Error` - On handshake or chunk timeout
64
+ // 2. Start the worker and pass the buffer via workerData
65
+ const worker = new Worker(path.resolve(__dirname, 'worker.ts'), { workerData: buffer });
85
66
 
86
- #### `read(buffer: SharedArrayBuffer, options?: Options): Promise<unknown>`
67
+ // 3. Prepare data
68
+ const text = "Hello from the main thread! ".repeat(500); // Larger than buffer
69
+ const data = new TextEncoder().encode(text);
87
70
 
88
- Asynchronously reads data from the shared buffer.
71
+ console.log(`Main: Sending ${data.byteLength} bytes...`);
89
72
 
90
- - `buffer` - SharedArrayBuffer for communication
91
- - `options` - Optional configuration object
92
- - `timeout` - Timeout in milliseconds (default: 5000)
73
+ // 4. Write data to the shared buffer
74
+ // The 'read' operation in the worker will pick this up.
75
+ await write(data, buffer);
93
76
 
94
- **Returns:** Promise resolving to deserialized data
77
+ const reply = await read(buffer);
78
+ console.log('Main: Reply:', new TextDecoder().decode(reply));
79
+ }
95
80
 
96
- **Throws:**
97
- - `Error` - On timeout or integrity failure
81
+ main().catch(console.error);
82
+ ```
98
83
 
99
- ### Sync Functions
84
+ ## Usage
100
85
 
101
- #### `writeSync(data: unknown, buffer: SharedArrayBuffer, options?: Options): void`
86
+ ### Async API
87
+ Best for non-blocking operations in the main thread or event-loop driven workers.
102
88
 
103
- Synchronously writes data to the shared buffer using chunked transfer.
89
+ ```typescript
90
+ import { write, read } from 'sabcom';
104
91
 
105
- - `data` - Any serializable data
106
- - `buffer` - SharedArrayBuffer for communication
107
- - `options` - Optional configuration object
108
- - `timeout` - Timeout in milliseconds (default: 5000)
92
+ // Writer
93
+ await write(data, buffer);
109
94
 
110
- **Throws:**
111
- - `Error` - On handshake or chunk timeout
95
+ // Reader
96
+ const result = await read(buffer);
97
+ ```
112
98
 
113
- #### `readSync(buffer: SharedArrayBuffer, options?: Options): unknown`
99
+ ### Sync API
100
+ Best for CPU-bound workers where blocking is acceptable or preferred.
114
101
 
115
- Synchronously reads data from the shared buffer.
102
+ ```typescript
103
+ import { writeSync, readSync } from 'sabcom';
116
104
 
117
- - `buffer` - SharedArrayBuffer for communication
118
- - `options` - Optional configuration object
119
- - `timeout` - Timeout in milliseconds (default: 5000)
105
+ // Writer
106
+ writeSync(data, buffer);
120
107
 
121
- **Returns:** Deserialized data
108
+ // Reader
109
+ const result = readSync(buffer);
110
+ ```
122
111
 
123
- **Throws:**
124
- - `Error` - On timeout or integrity failure
112
+ ### Options
125
113
 
126
- ### Low-level Generator Functions
114
+ All functions accept an optional options object:
127
115
 
128
- #### `writeGenerator(data: unknown, buffer: SharedArrayBuffer, options?: Options): Generator<WaitRequest, void, WaitResponse>`
116
+ ```typescript
117
+ await write(data, buffer, {
118
+ timeout: 10000 // Timeout in milliseconds (default: 5000)
119
+ });
120
+ ```
129
121
 
130
- Generator function for custom write implementations.
122
+ ## Buffer Sizing & Requirements
131
123
 
132
- #### `readGenerator(buffer: SharedArrayBuffer, options?: Options): Generator<WaitRequest, unknown, WaitResponse>`
124
+ 1. **Multiple of 4**: The `byteLength` of the `SharedArrayBuffer` **must** be a multiple of 4 (e.g., 1024, 4096).
125
+ 2. **Header Overhead**: The library uses a small portion of the buffer for a header. The buffer must be larger than `HEADER_SIZE` (exported).
126
+ 3. **Performance Trade-off**:
127
+ * **Larger Buffer**: Fewer chunks, less synchronization overhead, faster for large data.
128
+ * **Smaller Buffer**: Less memory usage, more context switches/atomic operations.
129
+ * **Recommendation**: Start with 4KB - 64KB (`4096` - `65536`) depending on your average payload size.
133
130
 
134
- Generator function for custom read implementations.
131
+ ## Advanced: Generators
135
132
 
136
- ### Types
133
+ 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.
137
134
 
138
135
  ```typescript
139
- interface Options {
140
- timeout?: number;
141
- }
142
-
143
- interface WaitRequest {
144
- target: Int32Array;
145
- index: number;
146
- value: number;
147
- timeout?: number;
136
+ import { writeGenerator } from 'sabcom';
137
+
138
+ const gen = writeGenerator(data, buffer);
139
+ let result = gen.next();
140
+
141
+ while (!result.done) {
142
+ // Perform custom logic here (e.g. check for cancellation)
143
+
144
+ // Wait for the reader signal
145
+ const request = result.value;
146
+ const waitResult = Atomics.wait(request.target, request.index, request.value, request.timeout);
147
+
148
+ // Resume generator
149
+ result = gen.next(waitResult);
148
150
  }
149
-
150
- type WaitResponse = ReturnType<typeof Atomics.wait>;
151
151
  ```
152
152
 
153
- ## Protocol
153
+ ## API Reference
154
154
 
155
- The library uses a header-based protocol with atomic operations:
155
+ ### `write(data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): Promise<void>`
156
+ Writes bytes to the buffer. Resolves when the reader has received all data.
156
157
 
157
- 1. **READY** - Buffer available for new transfer
158
- 2. **HANDSHAKE** - Writer sends total size and chunk count
159
- 3. **PAYLOAD** - Chunked data transfer with integrity checks
158
+ ### `read(buffer: SharedArrayBuffer, options?: Options): Promise<Uint8Array>`
159
+ Waits for and reads bytes from the buffer. Resolves with the complete data.
160
160
 
161
- ### Buffer Layout
161
+ ### `writeSync(data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): void`
162
+ Synchronous version of `write`. Blocks until completion.
162
163
 
163
- ```
164
- [Header: 4 * HEADER_VALUES bytes] [Payload: remaining bytes]
165
- ```
164
+ ### `readSync(buffer: SharedArrayBuffer, options?: Options): Uint8Array`
165
+ Synchronous version of `read`. Blocks until data is received.
166
166
 
167
- ## Error Handling
167
+ ## Protocol Details
168
168
 
169
- - **Handshake timeout** - Reader not ready within timeout
170
- - **Chunk timeout** - Individual chunk transfer timeout
171
- - **Integrity failure** - Chunk index mismatch
172
- - **Invalid state** - Unexpected semaphore state
169
+ The communication follows a strict handshake:
170
+ 1. **Writer** acquires lock, writes metadata (total size, chunk count) -> `HANDSHAKE`.
171
+ 2. **Reader** acknowledges -> `READY`.
172
+ 3. **Writer** writes chunk -> `PAYLOAD`.
173
+ 4. **Reader** reads chunk, acknowledges -> `READY`.
174
+ 5. Repeat 3-4 until done.
173
175
 
174
- ## Thread Safety
176
+ *Note: The `SharedArrayBuffer` is reusable after a successful transfer.*
175
177
 
176
- - **Async functions** (`write`, `read`) use `Atomics.waitAsync()` for non-blocking operations
177
- - **Sync functions** (`writeSync`, `readSync`) use `Atomics.wait()` for blocking operations
178
- - All functions use `Atomics.store()` and `Atomics.notify()` for synchronization
179
- - Requires SharedArrayBuffer support and proper threading context
178
+ ## Development
180
179
 
181
- ## Requirements
180
+ ```bash
181
+ # Install dependencies
182
+ pnpm install
182
183
 
183
- - Node.js with SharedArrayBuffer support
184
- - Multi-threaded environment (Worker threads, etc.)
185
- - V8 engine for serialization
184
+ # Build
185
+ pnpm build
186
186
 
187
+ # Run tests
188
+ pnpm test
189
+
190
+ # Lint
191
+ pnpm lint
192
+ ```
187
193
 
188
194
  ## License
189
195
 
190
- License [Apache-2.0](./LICENSE)
191
- Copyright (c) 2025 Ivan Zakharchanka
196
+ Apache-2.0 © [Ivan Zakharchanka](https://linkedin.com/in/3axap4eHko)
192
197
 
193
198
  [npm-url]: https://www.npmjs.com/package/sabcom
194
199
  [downloads-image]: https://img.shields.io/npm/dw/sabcom.svg?maxAge=43200
package/build/index.cjs CHANGED
@@ -67,7 +67,13 @@ var Header = /*#__PURE__*/ function(Header) {
67
67
  const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
68
68
  const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
69
69
  function* writeGenerator(data, buffer, { timeout = 5000 } = {}) {
70
+ if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
71
+ throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
72
+ }
70
73
  const chunkSize = buffer.byteLength - HEADER_SIZE;
74
+ if (chunkSize <= 0) {
75
+ throw new Error('SharedArrayBuffer too small for header');
76
+ }
71
77
  const totalSize = data.length;
72
78
  const totalChunks = Math.ceil(totalSize / chunkSize);
73
79
  const header = new Int32Array(buffer);
@@ -108,9 +114,17 @@ function* writeGenerator(data, buffer, { timeout = 5000 } = {}) {
108
114
  }
109
115
  } finally{
110
116
  Atomics.store(header, SEMAPHORE, 0);
117
+ Atomics.notify(header, SEMAPHORE);
111
118
  }
112
119
  }
113
120
  function* readGenerator(buffer, { timeout = 5000 } = {}) {
121
+ if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
122
+ throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
123
+ }
124
+ const chunkSize = buffer.byteLength - HEADER_SIZE;
125
+ if (chunkSize <= 0) {
126
+ throw new Error('SharedArrayBuffer too small for header');
127
+ }
114
128
  const header = new Int32Array(buffer);
115
129
  const handshakeResult = yield {
116
130
  target: header,
@@ -126,6 +140,15 @@ function* readGenerator(buffer, { timeout = 5000 } = {}) {
126
140
  }
127
141
  const totalSize = header[1];
128
142
  const totalChunks = header[2];
143
+ if (totalSize < 0 || totalChunks < 0) {
144
+ throw new Error('Invalid handshake values');
145
+ }
146
+ if (totalSize === 0 && totalChunks !== 0) {
147
+ throw new Error('Invalid handshake values');
148
+ }
149
+ if (totalSize > totalChunks * chunkSize) {
150
+ throw new Error('Invalid handshake values');
151
+ }
129
152
  const data = new Uint8Array(totalSize);
130
153
  Atomics.store(header, SEMAPHORE, 0);
131
154
  Atomics.notify(header, SEMAPHORE);
@@ -149,6 +172,9 @@ function* readGenerator(buffer, { timeout = 5000 } = {}) {
149
172
  }
150
173
  const offset = header[2];
151
174
  const size = header[3];
175
+ if (offset < 0 || size <= 0 || size > chunkSize || offset + size > totalSize) {
176
+ throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);
177
+ }
152
178
  data.set(payload.subarray(0, size), offset);
153
179
  Atomics.store(header, SEMAPHORE, 0);
154
180
  Atomics.notify(header, SEMAPHORE);
@@ -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 const chunkSize = buffer.byteLength - HEADER_SIZE;\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 }\n}\n\nexport function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {\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 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 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) => {\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) => {\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","chunkSize","byteLength","totalSize","totalChunks","ceil","header","Int32Array","Atomics","store","notify","handshakeResult","target","index","value","Error","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 = 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) => {\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) => {\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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"}
package/build/index.js CHANGED
@@ -19,7 +19,13 @@ export var Header = /*#__PURE__*/ function(Header) {
19
19
  export const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
20
20
  export const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
21
21
  export function* writeGenerator(data, buffer, { timeout = 5000 } = {}) {
22
+ if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
23
+ throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
24
+ }
22
25
  const chunkSize = buffer.byteLength - HEADER_SIZE;
26
+ if (chunkSize <= 0) {
27
+ throw new Error('SharedArrayBuffer too small for header');
28
+ }
23
29
  const totalSize = data.length;
24
30
  const totalChunks = Math.ceil(totalSize / chunkSize);
25
31
  const header = new Int32Array(buffer);
@@ -60,9 +66,17 @@ export function* writeGenerator(data, buffer, { timeout = 5000 } = {}) {
60
66
  }
61
67
  } finally{
62
68
  Atomics.store(header, SEMAPHORE, 0);
69
+ Atomics.notify(header, SEMAPHORE);
63
70
  }
64
71
  }
65
72
  export function* readGenerator(buffer, { timeout = 5000 } = {}) {
73
+ if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
74
+ throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
75
+ }
76
+ const chunkSize = buffer.byteLength - HEADER_SIZE;
77
+ if (chunkSize <= 0) {
78
+ throw new Error('SharedArrayBuffer too small for header');
79
+ }
66
80
  const header = new Int32Array(buffer);
67
81
  const handshakeResult = yield {
68
82
  target: header,
@@ -78,6 +92,15 @@ export function* readGenerator(buffer, { timeout = 5000 } = {}) {
78
92
  }
79
93
  const totalSize = header[1];
80
94
  const totalChunks = header[2];
95
+ if (totalSize < 0 || totalChunks < 0) {
96
+ throw new Error('Invalid handshake values');
97
+ }
98
+ if (totalSize === 0 && totalChunks !== 0) {
99
+ throw new Error('Invalid handshake values');
100
+ }
101
+ if (totalSize > totalChunks * chunkSize) {
102
+ throw new Error('Invalid handshake values');
103
+ }
81
104
  const data = new Uint8Array(totalSize);
82
105
  Atomics.store(header, SEMAPHORE, 0);
83
106
  Atomics.notify(header, SEMAPHORE);
@@ -101,6 +124,9 @@ export function* readGenerator(buffer, { timeout = 5000 } = {}) {
101
124
  }
102
125
  const offset = header[2];
103
126
  const size = header[3];
127
+ if (offset < 0 || size <= 0 || size > chunkSize || offset + size > totalSize) {
128
+ throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);
129
+ }
104
130
  data.set(payload.subarray(0, size), offset);
105
131
  Atomics.store(header, SEMAPHORE, 0);
106
132
  Atomics.notify(header, SEMAPHORE);
@@ -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 const chunkSize = buffer.byteLength - HEADER_SIZE;\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 }\n}\n\nexport function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {\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 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 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) => {\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) => {\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","chunkSize","byteLength","totalSize","totalChunks","ceil","header","Int32Array","Atomics","store","notify","handshakeResult","target","index","value","Error","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 = 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) => {\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) => {\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":"AAAA,OAAO,MAAMA,YAAY,EAAE;AAE3B,OAAO,IAAA,AAAKC,mCAAAA;;;;WAAAA;MAIX;AAED,OAAO,IAAA,AAAKC,mCAAAA;;;WAAAA;MAGX;AAED,OAAO,IAAA,AAAKC,gCAAAA;;;;WAAAA;MAIX;AAED,OAAO,MAAMC,gBAAgB,IAAIC,KAAKC,GAAG,CAACC,OAAOC,MAAM,CAACN,WAAWO,MAAM,EAAEF,OAAOC,MAAM,CAACL,QAAQM,MAAM,IAAI,EAAE;AAC7G,OAAO,MAAMC,cAAcC,YAAYC,iBAAiB,GAAGR,cAAc;AAezE,OAAO,UAAUS,eAAeC,IAAgB,EAAEC,MAAyB,EAAE,EAAEC,UAAU,IAAI,EAAW,GAAG,CAAC,CAAC;IAC3G,IAAID,OAAOE,UAAU,GAAGC,WAAWN,iBAAiB,KAAK,GAAG;QAC1D,MAAM,IAAIO,MAAM;IAClB;IACA,MAAMC,YAAYL,OAAOE,UAAU,GAAGP;IACtC,IAAIU,aAAa,GAAG;QAClB,MAAM,IAAID,MAAM;IAClB;IACA,MAAME,YAAYP,KAAKL,MAAM;IAC7B,MAAMa,cAAcjB,KAAKkB,IAAI,CAACF,YAAYD;IAC1C,MAAMI,SAAS,IAAIN,WAAWH;IAE9BS,MAAM,GAAsB,GAAGH;IAC/BG,MAAM,GAAwB,GAAGF;IACjCG,QAAQC,KAAK,CAACF,QAAQxB;IACtByB,QAAQE,MAAM,CAACH,QAAQxB;IAEvB,IAAI;QACF,MAAM4B,kBAAgC,MAAM;YAC1CC,QAAQL;YACRM,OAAO9B;YACP+B,KAAK;YACLf;QACF;QACA,IAAIY,oBAAoB,aAAa;YACnC,MAAM,IAAIT,MAAM;QAClB;QAEA,MAAMa,UAAU,IAAIC,WAAWlB,QAAQL;QACvC,IAAK,IAAIwB,IAAI,GAAGA,IAAIZ,aAAaY,IAAK;YACpC,MAAMC,QAAQD,IAAId;YAClB,MAAMgB,MAAM/B,KAAKgC,GAAG,CAACF,QAAQf,WAAWC;YACxC,MAAMiB,OAAOF,MAAMD;YACnBH,QAAQO,GAAG,CAACzB,KAAK0B,QAAQ,CAACL,OAAOC,MAAM;YACvCZ,MAAM,GAAoB,GAAGU;YAC7BV,MAAM,GAAqB,GAAGW;YAC9BX,MAAM,GAAmB,GAAGc;YAC5Bb,QAAQC,KAAK,CAACF,QAAQxB;YACtByB,QAAQE,MAAM,CAACH,QAAQxB;YAEvB,MAAMyC,cAA4B,MAAM;gBACtCZ,QAAQL;gBACRM,OAAO9B;gBACP+B,KAAK;gBACLf;YACF;YACA,IAAIyB,gBAAgB,aAAa;gBAC/B,MAAM,IAAItB,MAAM,CAAC,wBAAwB,EAAEe,EAAE,CAAC,EAAEZ,cAAc,GAAG;YACnE;QACF;IACF,SAAU;QACRG,QAAQC,KAAK,CAACF,QAAQxB;QACtByB,QAAQE,MAAM,CAACH,QAAQxB;IACzB;AACF;AAEA,OAAO,UAAU0C,cAAc3B,MAAyB,EAAE,EAAEC,UAAU,IAAI,EAAW,GAAG,CAAC,CAAC;IACxF,IAAID,OAAOE,UAAU,GAAGC,WAAWN,iBAAiB,KAAK,GAAG;QAC1D,MAAM,IAAIO,MAAM;IAClB;IACA,MAAMC,YAAYL,OAAOE,UAAU,GAAGP;IACtC,IAAIU,aAAa,GAAG;QAClB,MAAM,IAAID,MAAM;IAClB;IACA,MAAMK,SAAS,IAAIN,WAAWH;IAE9B,MAAMa,kBAAgC,MAAM;QAC1CC,QAAQL;QACRM,OAAO9B;QACP+B,KAAK;QACLf;IACF;IACA,IAAIY,oBAAoB,aAAa;QACnC,MAAM,IAAIT,MAAM;IAClB;IACA,IAAIK,MAAM,CAACxB,UAAU,QAA0B;QAC7C,MAAM,IAAImB,MAAM;IAClB;IAEA,MAAME,YAAYG,MAAM,GAAsB;IAC9C,MAAMF,cAAcE,MAAM,GAAwB;IAClD,IAAIH,YAAY,KAAKC,cAAc,GAAG;QACpC,MAAM,IAAIH,MAAM;IAClB;IACA,IAAIE,cAAc,KAAKC,gBAAgB,GAAG;QACxC,MAAM,IAAIH,MAAM;IAClB;IACA,IAAIE,YAAYC,cAAcF,WAAW;QACvC,MAAM,IAAID,MAAM;IAClB;IACA,MAAML,OAAO,IAAImB,WAAWZ;IAE5BI,QAAQC,KAAK,CAACF,QAAQxB;IACtByB,QAAQE,MAAM,CAACH,QAAQxB;IAEvB,MAAMgC,UAAU,IAAIC,WAAWlB,QAAQL;IACvC,IAAK,IAAIwB,IAAI,GAAGA,IAAIZ,aAAaY,IAAK;QACpC,MAAMO,cAA4B,MAAM;YACtCZ,QAAQL;YACRM,OAAO9B;YACP+B,KAAK;YACLf;QACF;QACA,IAAIyB,gBAAgB,aAAa;YAC/B,MAAM,IAAItB,MAAM,CAAC,iCAAiC,EAAEe,GAAG;QACzD;QAEA,IAAIV,MAAM,CAACxB,UAAU,QAAwB;YAC3C,MAAM,IAAImB,MAAM,CAAC,kCAAkC,EAAElB,SAAS,CAACuB,MAAM,CAACxB,UAAU,CAAC,EAAE;QACrF;QACA,MAAM2C,aAAanB,MAAM,GAAoB;QAC7C,IAAIU,MAAMS,YAAY;YACpB,MAAM,IAAIxB,MAAM,CAAC,mCAAmC,EAAEwB,WAAW,UAAU,EAAET,GAAG;QAClF;QACA,MAAMU,SAASpB,MAAM,GAAqB;QAC1C,MAAMc,OAAOd,MAAM,GAAmB;QACtC,IAAIoB,SAAS,KAAKN,QAAQ,KAAKA,OAAOlB,aAAawB,SAASN,OAAOjB,WAAW;YAC5E,MAAM,IAAIF,MAAM,CAAC,iCAAiC,EAAEwB,YAAY;QAClE;QACA7B,KAAKyB,GAAG,CAACP,QAAQQ,QAAQ,CAAC,GAAGF,OAAOM;QACpCnB,QAAQC,KAAK,CAACF,QAAQxB;QACtByB,QAAQE,MAAM,CAACH,QAAQxB;IACzB;IACA,OAAOc;AACT;AAEA,OAAO,MAAM+B,YAAY,CAAC/B,MAAkBC,QAA2B+B;IACrE,MAAMC,MAAMlC,eAAeC,MAAMC,QAAQ+B;IACzC,IAAIE,SAASD,IAAIE,IAAI;IACrB,MAAO,CAACD,OAAOE,IAAI,CAAE;QACnB,MAAMC,aAAa1B,QAAQ2B,IAAI,CAACJ,OAAOjB,KAAK,CAACF,MAAM,EAAEmB,OAAOjB,KAAK,CAACD,KAAK,EAAEkB,OAAOjB,KAAK,CAACA,KAAK,EAAEiB,OAAOjB,KAAK,CAACf,OAAO;QACjHgC,SAASD,IAAIE,IAAI,CAACE;IACpB;AACF,EAAE;AAEF,OAAO,MAAME,QAAQ,OAAOvC,MAAkBC,QAA2B+B;IACvE,MAAMC,MAAMlC,eAAeC,MAAMC,QAAQ+B;IACzC,IAAIE,SAASD,IAAIE,IAAI;IACrB,MAAO,CAACD,OAAOE,IAAI,CAAE;QACnB,MAAMI,UAAUN,OAAOjB,KAAK;QAC5B,MAAMoB,aAAa,MAAM1B,QAAQ8B,SAAS,CAACD,QAAQzB,MAAM,EAAEyB,QAAQxB,KAAK,EAAEwB,QAAQvB,KAAK,EAAEuB,QAAQtC,OAAO,EAAEe,KAAK;QAC/GiB,SAASD,IAAIE,IAAI,CAACE;IACpB;AACF,EAAE;AAEF,OAAO,MAAMK,WAAW,CAACzC,QAA2B+B;IAClD,MAAMC,MAAML,cAAc3B,QAAQ+B;IAClC,IAAIE,SAASD,IAAIE,IAAI;IACrB,MAAO,CAACD,OAAOE,IAAI,CAAE;QACnB,MAAMC,aAAa1B,QAAQ2B,IAAI,CAACJ,OAAOjB,KAAK,CAACF,MAAM,EAAEmB,OAAOjB,KAAK,CAACD,KAAK,EAAEkB,OAAOjB,KAAK,CAACA,KAAK,EAAEiB,OAAOjB,KAAK,CAACf,OAAO;QACjHgC,SAASD,IAAIE,IAAI,CAACE;IACpB;IACA,OAAOH,OAAOjB,KAAK;AACrB,EAAE;AAEF,OAAO,MAAM0B,OAAO,OAAO1C,QAA2B+B;IACpD,MAAMC,MAAML,cAAc3B,QAAQ+B;IAClC,IAAIE,SAASD,IAAIE,IAAI;IACrB,MAAO,CAACD,OAAOE,IAAI,CAAE;QACnB,MAAMI,UAAUN,OAAOjB,KAAK;QAC5B,MAAMoB,aAAa,MAAM1B,QAAQ8B,SAAS,CAACD,QAAQzB,MAAM,EAAEyB,QAAQxB,KAAK,EAAEwB,QAAQvB,KAAK,EAAEuB,QAAQtC,OAAO,EAAEe,KAAK;QAC/GiB,SAASD,IAAIE,IAAI,CAACE;IACpB;IACA,OAAOH,OAAOjB,KAAK;AACrB,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sabcom",
3
- "version": "0.1.70",
3
+ "version": "0.1.84",
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",
@@ -14,6 +14,12 @@
14
14
  "build",
15
15
  "src/index.ts"
16
16
  ],
17
+ "scripts": {
18
+ "build": "rm -rf build && inop src build -i __tests__ -i *.tmp.ts && tsc --declaration --emitDeclarationOnly",
19
+ "lint": "eslint src",
20
+ "test": "vitest run",
21
+ "test:coverage": "vitest run --coverage"
22
+ },
17
23
  "repository": {
18
24
  "type": "git",
19
25
  "url": "git+https://github.com/3axap4eHko/sabcom.git"
@@ -41,25 +47,20 @@
41
47
  "url": "https://linkedin.com/in/3axap4eHko"
42
48
  },
43
49
  "devDependencies": {
44
- "@eslint/js": "^9.39.1",
45
- "@types/node": "^24.10.1",
46
- "@typescript-eslint/eslint-plugin": "^8.48.1",
47
- "@typescript-eslint/parser": "^8.48.1",
48
- "@vitest/coverage-v8": "^3.2.4",
49
- "eslint": "^9.39.1",
50
+ "@eslint/js": "^9.39.2",
51
+ "@types/node": "^25.0.3",
52
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
53
+ "@typescript-eslint/parser": "^8.52.0",
54
+ "@vitest/coverage-v8": "^4.0.16",
55
+ "eslint": "^9.39.2",
50
56
  "eslint-config-prettier": "^10.1.8",
51
57
  "eslint-plugin-prettier": "^5.5.4",
52
58
  "husky": "^9.1.7",
53
- "inop": "^0.8.2",
59
+ "inop": "^0.8.10",
54
60
  "prettier": "^3.7.4",
55
61
  "typescript": "^5.9.3",
56
- "typescript-eslint": "^8.48.1",
57
- "vitest": "^3.2.4"
62
+ "typescript-eslint": "^8.52.0",
63
+ "vitest": "^4.0.16"
58
64
  },
59
- "scripts": {
60
- "build": "rm -rf build && inop src build -i __tests__ -i *.tmp.ts && tsc --declaration --emitDeclarationOnly",
61
- "lint": "eslint src",
62
- "test": "vitest run",
63
- "test:coverage": "vitest run --coverage"
64
- }
65
- }
65
+ "packageManager": "pnpm@10.27.0"
66
+ }
package/src/index.ts CHANGED
@@ -34,7 +34,13 @@ export interface WaitRequest {
34
34
  export type WaitResponse = ReturnType<typeof Atomics.wait>;
35
35
 
36
36
  export function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, void, WaitResponse> {
37
+ if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
38
+ throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
39
+ }
37
40
  const chunkSize = buffer.byteLength - HEADER_SIZE;
41
+ if (chunkSize <= 0) {
42
+ throw new Error('SharedArrayBuffer too small for header');
43
+ }
38
44
  const totalSize = data.length;
39
45
  const totalChunks = Math.ceil(totalSize / chunkSize);
40
46
  const header = new Int32Array(buffer);
@@ -79,10 +85,18 @@ export function* writeGenerator(data: Uint8Array, buffer: SharedArrayBuffer, { t
79
85
  }
80
86
  } finally {
81
87
  Atomics.store(header, SEMAPHORE, Semaphore.READY);
88
+ Atomics.notify(header, SEMAPHORE);
82
89
  }
83
90
  }
84
91
 
85
92
  export function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Options = {}): Generator<WaitRequest, Uint8Array, WaitResponse> {
93
+ if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) {
94
+ throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
95
+ }
96
+ const chunkSize = buffer.byteLength - HEADER_SIZE;
97
+ if (chunkSize <= 0) {
98
+ throw new Error('SharedArrayBuffer too small for header');
99
+ }
86
100
  const header = new Int32Array(buffer);
87
101
 
88
102
  const handshakeResult: WaitResponse = yield {
@@ -100,6 +114,15 @@ export function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Op
100
114
 
101
115
  const totalSize = header[Handshake.TOTAL_SIZE];
102
116
  const totalChunks = header[Handshake.TOTAL_CHUNKS];
117
+ if (totalSize < 0 || totalChunks < 0) {
118
+ throw new Error('Invalid handshake values');
119
+ }
120
+ if (totalSize === 0 && totalChunks !== 0) {
121
+ throw new Error('Invalid handshake values');
122
+ }
123
+ if (totalSize > totalChunks * chunkSize) {
124
+ throw new Error('Invalid handshake values');
125
+ }
103
126
  const data = new Uint8Array(totalSize);
104
127
 
105
128
  Atomics.store(header, SEMAPHORE, Semaphore.READY);
@@ -126,6 +149,9 @@ export function* readGenerator(buffer: SharedArrayBuffer, { timeout = 5000 }: Op
126
149
  }
127
150
  const offset = header[Header.CHUNK_OFFSET];
128
151
  const size = header[Header.CHUNK_SIZE];
152
+ if (offset < 0 || size <= 0 || size > chunkSize || offset + size > totalSize) {
153
+ throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);
154
+ }
129
155
  data.set(payload.subarray(0, size), offset);
130
156
  Atomics.store(header, SEMAPHORE, Semaphore.READY);
131
157
  Atomics.notify(header, SEMAPHORE);