sabcom 0.1.71 → 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 +130 -125
- package/build/index.cjs +26 -0
- package/build/index.cjs.map +1 -1
- package/build/index.js +26 -0
- package/build/index.js.map +1 -1
- package/package.json +18 -17
- package/src/index.ts +26 -0
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
|
|
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
|
|
14
|
-
- **Async and
|
|
15
|
-
- **Chunked
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
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
|
-
##
|
|
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
|
-
|
|
38
|
-
|
|
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 {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
53
|
+
**main.ts**
|
|
60
54
|
```typescript
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
+
console.log(`Main: Sending ${data.byteLength} bytes...`);
|
|
89
72
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
77
|
+
const reply = await read(buffer);
|
|
78
|
+
console.log('Main: Reply:', new TextDecoder().decode(reply));
|
|
79
|
+
}
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
main().catch(console.error);
|
|
82
|
+
```
|
|
98
83
|
|
|
99
|
-
|
|
84
|
+
## Usage
|
|
100
85
|
|
|
101
|
-
|
|
86
|
+
### Async API
|
|
87
|
+
Best for non-blocking operations in the main thread or event-loop driven workers.
|
|
102
88
|
|
|
103
|
-
|
|
89
|
+
```typescript
|
|
90
|
+
import { write, read } from 'sabcom';
|
|
104
91
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- `options` - Optional configuration object
|
|
108
|
-
- `timeout` - Timeout in milliseconds (default: 5000)
|
|
92
|
+
// Writer
|
|
93
|
+
await write(data, buffer);
|
|
109
94
|
|
|
110
|
-
|
|
111
|
-
|
|
95
|
+
// Reader
|
|
96
|
+
const result = await read(buffer);
|
|
97
|
+
```
|
|
112
98
|
|
|
113
|
-
|
|
99
|
+
### Sync API
|
|
100
|
+
Best for CPU-bound workers where blocking is acceptable or preferred.
|
|
114
101
|
|
|
115
|
-
|
|
102
|
+
```typescript
|
|
103
|
+
import { writeSync, readSync } from 'sabcom';
|
|
116
104
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- `timeout` - Timeout in milliseconds (default: 5000)
|
|
105
|
+
// Writer
|
|
106
|
+
writeSync(data, buffer);
|
|
120
107
|
|
|
121
|
-
|
|
108
|
+
// Reader
|
|
109
|
+
const result = readSync(buffer);
|
|
110
|
+
```
|
|
122
111
|
|
|
123
|
-
|
|
124
|
-
- `Error` - On timeout or integrity failure
|
|
112
|
+
### Options
|
|
125
113
|
|
|
126
|
-
|
|
114
|
+
All functions accept an optional options object:
|
|
127
115
|
|
|
128
|
-
|
|
116
|
+
```typescript
|
|
117
|
+
await write(data, buffer, {
|
|
118
|
+
timeout: 10000 // Timeout in milliseconds (default: 5000)
|
|
119
|
+
});
|
|
120
|
+
```
|
|
129
121
|
|
|
130
|
-
|
|
122
|
+
## Buffer Sizing & Requirements
|
|
131
123
|
|
|
132
|
-
|
|
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
|
-
|
|
131
|
+
## Advanced: Generators
|
|
135
132
|
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
##
|
|
153
|
+
## API Reference
|
|
154
154
|
|
|
155
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
###
|
|
161
|
+
### `writeSync(data: Uint8Array, buffer: SharedArrayBuffer, options?: Options): void`
|
|
162
|
+
Synchronous version of `write`. Blocks until completion.
|
|
162
163
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
```
|
|
164
|
+
### `readSync(buffer: SharedArrayBuffer, options?: Options): Uint8Array`
|
|
165
|
+
Synchronous version of `read`. Blocks until data is received.
|
|
166
166
|
|
|
167
|
-
##
|
|
167
|
+
## Protocol Details
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
176
|
+
*Note: The `SharedArrayBuffer` is reusable after a successful transfer.*
|
|
175
177
|
|
|
176
|
-
|
|
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
|
-
|
|
180
|
+
```bash
|
|
181
|
+
# Install dependencies
|
|
182
|
+
pnpm install
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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);
|
package/build/index.cjs.map
CHANGED
|
@@ -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);
|
package/build/index.js.map
CHANGED
|
@@ -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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sabcom",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
45
|
-
"@types/node": "^
|
|
46
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
47
|
-
"@typescript-eslint/parser": "^8.
|
|
48
|
-
"@vitest/coverage-v8": "^
|
|
49
|
-
"eslint": "^9.39.
|
|
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.
|
|
59
|
+
"inop": "^0.8.10",
|
|
54
60
|
"prettier": "^3.7.4",
|
|
55
61
|
"typescript": "^5.9.3",
|
|
56
|
-
"typescript-eslint": "^8.
|
|
57
|
-
"vitest": "^
|
|
62
|
+
"typescript-eslint": "^8.52.0",
|
|
63
|
+
"vitest": "^4.0.16"
|
|
58
64
|
},
|
|
59
|
-
"
|
|
60
|
-
|
|
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);
|