sabcom 0.0.1
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/build/index.cjs +125 -0
- package/build/index.cjs.map +1 -0
- package/build/index.d.ts +19 -0
- package/build/index.js +89 -0
- package/build/index.js.map +1 -0
- package/package.json +45 -0
- package/src/index.ts +124 -0
package/build/index.cjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get HEADER_SIZE () {
|
|
13
|
+
return HEADER_SIZE;
|
|
14
|
+
},
|
|
15
|
+
get HEADER_VALUES () {
|
|
16
|
+
return HEADER_VALUES;
|
|
17
|
+
},
|
|
18
|
+
get Handshake () {
|
|
19
|
+
return Handshake;
|
|
20
|
+
},
|
|
21
|
+
get Header () {
|
|
22
|
+
return Header;
|
|
23
|
+
},
|
|
24
|
+
get SEMAPHORE () {
|
|
25
|
+
return SEMAPHORE;
|
|
26
|
+
},
|
|
27
|
+
get Semaphore () {
|
|
28
|
+
return Semaphore;
|
|
29
|
+
},
|
|
30
|
+
get read () {
|
|
31
|
+
return read;
|
|
32
|
+
},
|
|
33
|
+
get write () {
|
|
34
|
+
return write;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const _nodev8 = require("node:v8");
|
|
38
|
+
const SEMAPHORE = 0;
|
|
39
|
+
var Semaphore = /*#__PURE__*/ function(Semaphore) {
|
|
40
|
+
Semaphore[Semaphore["READY"] = 0] = "READY";
|
|
41
|
+
Semaphore[Semaphore["HANDSHAKE"] = 1] = "HANDSHAKE";
|
|
42
|
+
Semaphore[Semaphore["PAYLOAD"] = 2] = "PAYLOAD";
|
|
43
|
+
return Semaphore;
|
|
44
|
+
}({});
|
|
45
|
+
var Handshake = /*#__PURE__*/ function(Handshake) {
|
|
46
|
+
Handshake[Handshake["TOTAL_SIZE"] = 1] = "TOTAL_SIZE";
|
|
47
|
+
Handshake[Handshake["TOTAL_CHUNKS"] = 2] = "TOTAL_CHUNKS";
|
|
48
|
+
return Handshake;
|
|
49
|
+
}({});
|
|
50
|
+
var Header = /*#__PURE__*/ function(Header) {
|
|
51
|
+
Header[Header["CHUNK_INDEX"] = 1] = "CHUNK_INDEX";
|
|
52
|
+
Header[Header["CHUNK_OFFSET"] = 2] = "CHUNK_OFFSET";
|
|
53
|
+
Header[Header["CHUNK_SIZE"] = 3] = "CHUNK_SIZE";
|
|
54
|
+
return Header;
|
|
55
|
+
}({});
|
|
56
|
+
const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
|
|
57
|
+
const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
|
|
58
|
+
const write = (data, buffer, timeout = 5000)=>{
|
|
59
|
+
const serialized = (0, _nodev8.serialize)(data);
|
|
60
|
+
const chunkSize = buffer.byteLength - HEADER_SIZE;
|
|
61
|
+
const totalSize = serialized.length;
|
|
62
|
+
const totalChunks = Math.ceil(totalSize / chunkSize);
|
|
63
|
+
const header = new Int32Array(buffer);
|
|
64
|
+
header[1] = totalSize;
|
|
65
|
+
header[2] = totalChunks;
|
|
66
|
+
Atomics.store(header, SEMAPHORE, 1);
|
|
67
|
+
Atomics.notify(header, SEMAPHORE);
|
|
68
|
+
try {
|
|
69
|
+
if (Atomics.wait(header, SEMAPHORE, 1, timeout) === "timed-out") {
|
|
70
|
+
throw new Error("Reader handshake timeout");
|
|
71
|
+
}
|
|
72
|
+
const payload = new Uint8Array(buffer, HEADER_SIZE);
|
|
73
|
+
for(let i = 0; i < totalChunks; i++){
|
|
74
|
+
const start = i * chunkSize;
|
|
75
|
+
const end = Math.min(start + chunkSize, totalSize);
|
|
76
|
+
const size = end - start;
|
|
77
|
+
payload.set(serialized.subarray(start, end), 0);
|
|
78
|
+
header[1] = i;
|
|
79
|
+
header[2] = start;
|
|
80
|
+
header[3] = size;
|
|
81
|
+
Atomics.store(header, SEMAPHORE, 2);
|
|
82
|
+
Atomics.notify(header, SEMAPHORE);
|
|
83
|
+
if (Atomics.wait(header, SEMAPHORE, 2, timeout) === "timed-out") {
|
|
84
|
+
throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} finally{
|
|
88
|
+
Atomics.store(header, SEMAPHORE, 0);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const read = (buffer, timeout = 5000)=>{
|
|
92
|
+
const header = new Int32Array(buffer);
|
|
93
|
+
if (Atomics.wait(header, SEMAPHORE, 0, timeout) === "timed-out") {
|
|
94
|
+
throw new Error("Handshake timeout");
|
|
95
|
+
}
|
|
96
|
+
if (header[SEMAPHORE] !== 1) {
|
|
97
|
+
throw new Error("Invalid handshake state");
|
|
98
|
+
}
|
|
99
|
+
const totalSize = header[1];
|
|
100
|
+
const totalChunks = header[2];
|
|
101
|
+
const data = new Uint8Array(totalSize);
|
|
102
|
+
Atomics.store(header, SEMAPHORE, 0);
|
|
103
|
+
Atomics.notify(header, SEMAPHORE);
|
|
104
|
+
const payload = new Uint8Array(buffer, HEADER_SIZE);
|
|
105
|
+
for(let i = 0; i < totalChunks; i++){
|
|
106
|
+
if (Atomics.wait(header, SEMAPHORE, 0, timeout) === "timed-out") {
|
|
107
|
+
throw new Error(`Writer timeout waiting for chunk ${i}`);
|
|
108
|
+
}
|
|
109
|
+
if (header[SEMAPHORE] !== 2) {
|
|
110
|
+
throw new Error(`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`);
|
|
111
|
+
}
|
|
112
|
+
const chunkIndex = header[1];
|
|
113
|
+
if (i !== chunkIndex) {
|
|
114
|
+
throw new Error(`Reader integrity failure for chunk ${chunkIndex} expected ${i}`);
|
|
115
|
+
}
|
|
116
|
+
const offset = header[2];
|
|
117
|
+
const size = header[3];
|
|
118
|
+
data.set(payload.subarray(0, size), offset);
|
|
119
|
+
Atomics.store(header, SEMAPHORE, 0);
|
|
120
|
+
Atomics.notify(header, SEMAPHORE);
|
|
121
|
+
}
|
|
122
|
+
return (0, _nodev8.deserialize)(data);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { serialize, deserialize } from \"node:v8\";\n\nexport 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 =\n 1 +\n Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;\nexport const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;\n\nexport const write = (\n data: unknown,\n buffer: SharedArrayBuffer,\n timeout = 5000,\n) => {\n const serialized = serialize(data);\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n const totalSize = serialized.length;\n const totalChunks = Math.ceil(totalSize / chunkSize);\n\n const header = new Int32Array(buffer);\n header[Handshake.TOTAL_SIZE] = totalSize;\n header[Handshake.TOTAL_CHUNKS] = totalChunks;\n\n Atomics.store(header, SEMAPHORE, Semaphore.HANDSHAKE);\n Atomics.notify(header, SEMAPHORE);\n\n try {\n if (\n Atomics.wait(header, SEMAPHORE, Semaphore.HANDSHAKE, timeout) ===\n \"timed-out\"\n ) {\n throw new Error(\"Reader handshake timeout\");\n }\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n\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\n payload.set(serialized.subarray(start, end), 0);\n\n header[Header.CHUNK_INDEX] = i;\n header[Header.CHUNK_OFFSET] = start;\n header[Header.CHUNK_SIZE] = size;\n\n Atomics.store(header, SEMAPHORE, Semaphore.PAYLOAD);\n Atomics.notify(header, SEMAPHORE);\n\n if (\n Atomics.wait(header, SEMAPHORE, Semaphore.PAYLOAD, timeout) ===\n \"timed-out\"\n ) {\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 const read = (buffer: SharedArrayBuffer, timeout = 5000): unknown => {\n const header = new Int32Array(buffer);\n if (\n Atomics.wait(header, SEMAPHORE, Semaphore.READY, timeout) === \"timed-out\"\n ) {\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 if (\n Atomics.wait(header, SEMAPHORE, Semaphore.READY, timeout) === \"timed-out\"\n ) {\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(\n `Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`,\n );\n }\n const chunkIndex = header[Header.CHUNK_INDEX];\n if (i !== chunkIndex) {\n throw new Error(\n `Reader integrity failure for chunk ${chunkIndex} expected ${i}`,\n );\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 deserialize(data);\n};\n"],"names":["HEADER_SIZE","HEADER_VALUES","Handshake","Header","SEMAPHORE","Semaphore","read","write","Math","max","Object","values","length","Uint32Array","BYTES_PER_ELEMENT","data","buffer","timeout","serialized","serialize","chunkSize","byteLength","totalSize","totalChunks","ceil","header","Int32Array","Atomics","store","notify","wait","Error","payload","Uint8Array","i","start","end","min","size","set","subarray","chunkIndex","offset","deserialize"],"mappings":"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"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const SEMAPHORE = 0;
|
|
2
|
+
export declare enum Semaphore {
|
|
3
|
+
READY = 0,
|
|
4
|
+
HANDSHAKE = 1,
|
|
5
|
+
PAYLOAD = 2
|
|
6
|
+
}
|
|
7
|
+
export declare enum Handshake {
|
|
8
|
+
TOTAL_SIZE = 1,
|
|
9
|
+
TOTAL_CHUNKS = 2
|
|
10
|
+
}
|
|
11
|
+
export declare enum Header {
|
|
12
|
+
CHUNK_INDEX = 1,
|
|
13
|
+
CHUNK_OFFSET = 2,
|
|
14
|
+
CHUNK_SIZE = 3
|
|
15
|
+
}
|
|
16
|
+
export declare const HEADER_VALUES: number;
|
|
17
|
+
export declare const HEADER_SIZE: number;
|
|
18
|
+
export declare const write: (data: unknown, buffer: SharedArrayBuffer, timeout?: number) => void;
|
|
19
|
+
export declare const read: (buffer: SharedArrayBuffer, timeout?: number) => unknown;
|
package/build/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { serialize, deserialize } from "node:v8";
|
|
2
|
+
export const SEMAPHORE = 0;
|
|
3
|
+
export var Semaphore = /*#__PURE__*/ function(Semaphore) {
|
|
4
|
+
Semaphore[Semaphore["READY"] = 0] = "READY";
|
|
5
|
+
Semaphore[Semaphore["HANDSHAKE"] = 1] = "HANDSHAKE";
|
|
6
|
+
Semaphore[Semaphore["PAYLOAD"] = 2] = "PAYLOAD";
|
|
7
|
+
return Semaphore;
|
|
8
|
+
}({});
|
|
9
|
+
export var Handshake = /*#__PURE__*/ function(Handshake) {
|
|
10
|
+
Handshake[Handshake["TOTAL_SIZE"] = 1] = "TOTAL_SIZE";
|
|
11
|
+
Handshake[Handshake["TOTAL_CHUNKS"] = 2] = "TOTAL_CHUNKS";
|
|
12
|
+
return Handshake;
|
|
13
|
+
}({});
|
|
14
|
+
export var Header = /*#__PURE__*/ function(Header) {
|
|
15
|
+
Header[Header["CHUNK_INDEX"] = 1] = "CHUNK_INDEX";
|
|
16
|
+
Header[Header["CHUNK_OFFSET"] = 2] = "CHUNK_OFFSET";
|
|
17
|
+
Header[Header["CHUNK_SIZE"] = 3] = "CHUNK_SIZE";
|
|
18
|
+
return Header;
|
|
19
|
+
}({});
|
|
20
|
+
export const HEADER_VALUES = 1 + Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
|
|
21
|
+
export const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
|
|
22
|
+
export const write = (data, buffer, timeout = 5000)=>{
|
|
23
|
+
const serialized = serialize(data);
|
|
24
|
+
const chunkSize = buffer.byteLength - HEADER_SIZE;
|
|
25
|
+
const totalSize = serialized.length;
|
|
26
|
+
const totalChunks = Math.ceil(totalSize / chunkSize);
|
|
27
|
+
const header = new Int32Array(buffer);
|
|
28
|
+
header[1] = totalSize;
|
|
29
|
+
header[2] = totalChunks;
|
|
30
|
+
Atomics.store(header, SEMAPHORE, 1);
|
|
31
|
+
Atomics.notify(header, SEMAPHORE);
|
|
32
|
+
try {
|
|
33
|
+
if (Atomics.wait(header, SEMAPHORE, 1, timeout) === "timed-out") {
|
|
34
|
+
throw new Error("Reader handshake timeout");
|
|
35
|
+
}
|
|
36
|
+
const payload = new Uint8Array(buffer, HEADER_SIZE);
|
|
37
|
+
for(let i = 0; i < totalChunks; i++){
|
|
38
|
+
const start = i * chunkSize;
|
|
39
|
+
const end = Math.min(start + chunkSize, totalSize);
|
|
40
|
+
const size = end - start;
|
|
41
|
+
payload.set(serialized.subarray(start, end), 0);
|
|
42
|
+
header[1] = i;
|
|
43
|
+
header[2] = start;
|
|
44
|
+
header[3] = size;
|
|
45
|
+
Atomics.store(header, SEMAPHORE, 2);
|
|
46
|
+
Atomics.notify(header, SEMAPHORE);
|
|
47
|
+
if (Atomics.wait(header, SEMAPHORE, 2, timeout) === "timed-out") {
|
|
48
|
+
throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} finally{
|
|
52
|
+
Atomics.store(header, SEMAPHORE, 0);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
export const read = (buffer, timeout = 5000)=>{
|
|
56
|
+
const header = new Int32Array(buffer);
|
|
57
|
+
if (Atomics.wait(header, SEMAPHORE, 0, timeout) === "timed-out") {
|
|
58
|
+
throw new Error("Handshake timeout");
|
|
59
|
+
}
|
|
60
|
+
if (header[SEMAPHORE] !== 1) {
|
|
61
|
+
throw new Error("Invalid handshake state");
|
|
62
|
+
}
|
|
63
|
+
const totalSize = header[1];
|
|
64
|
+
const totalChunks = header[2];
|
|
65
|
+
const data = new Uint8Array(totalSize);
|
|
66
|
+
Atomics.store(header, SEMAPHORE, 0);
|
|
67
|
+
Atomics.notify(header, SEMAPHORE);
|
|
68
|
+
const payload = new Uint8Array(buffer, HEADER_SIZE);
|
|
69
|
+
for(let i = 0; i < totalChunks; i++){
|
|
70
|
+
if (Atomics.wait(header, SEMAPHORE, 0, timeout) === "timed-out") {
|
|
71
|
+
throw new Error(`Writer timeout waiting for chunk ${i}`);
|
|
72
|
+
}
|
|
73
|
+
if (header[SEMAPHORE] !== 2) {
|
|
74
|
+
throw new Error(`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`);
|
|
75
|
+
}
|
|
76
|
+
const chunkIndex = header[1];
|
|
77
|
+
if (i !== chunkIndex) {
|
|
78
|
+
throw new Error(`Reader integrity failure for chunk ${chunkIndex} expected ${i}`);
|
|
79
|
+
}
|
|
80
|
+
const offset = header[2];
|
|
81
|
+
const size = header[3];
|
|
82
|
+
data.set(payload.subarray(0, size), offset);
|
|
83
|
+
Atomics.store(header, SEMAPHORE, 0);
|
|
84
|
+
Atomics.notify(header, SEMAPHORE);
|
|
85
|
+
}
|
|
86
|
+
return deserialize(data);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { serialize, deserialize } from \"node:v8\";\n\nexport 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 =\n 1 +\n Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;\nexport const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;\n\nexport const write = (\n data: unknown,\n buffer: SharedArrayBuffer,\n timeout = 5000,\n) => {\n const serialized = serialize(data);\n const chunkSize = buffer.byteLength - HEADER_SIZE;\n const totalSize = serialized.length;\n const totalChunks = Math.ceil(totalSize / chunkSize);\n\n const header = new Int32Array(buffer);\n header[Handshake.TOTAL_SIZE] = totalSize;\n header[Handshake.TOTAL_CHUNKS] = totalChunks;\n\n Atomics.store(header, SEMAPHORE, Semaphore.HANDSHAKE);\n Atomics.notify(header, SEMAPHORE);\n\n try {\n if (\n Atomics.wait(header, SEMAPHORE, Semaphore.HANDSHAKE, timeout) ===\n \"timed-out\"\n ) {\n throw new Error(\"Reader handshake timeout\");\n }\n\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n\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\n payload.set(serialized.subarray(start, end), 0);\n\n header[Header.CHUNK_INDEX] = i;\n header[Header.CHUNK_OFFSET] = start;\n header[Header.CHUNK_SIZE] = size;\n\n Atomics.store(header, SEMAPHORE, Semaphore.PAYLOAD);\n Atomics.notify(header, SEMAPHORE);\n\n if (\n Atomics.wait(header, SEMAPHORE, Semaphore.PAYLOAD, timeout) ===\n \"timed-out\"\n ) {\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 const read = (buffer: SharedArrayBuffer, timeout = 5000): unknown => {\n const header = new Int32Array(buffer);\n if (\n Atomics.wait(header, SEMAPHORE, Semaphore.READY, timeout) === \"timed-out\"\n ) {\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 if (\n Atomics.wait(header, SEMAPHORE, Semaphore.READY, timeout) === \"timed-out\"\n ) {\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(\n `Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`,\n );\n }\n const chunkIndex = header[Header.CHUNK_INDEX];\n if (i !== chunkIndex) {\n throw new Error(\n `Reader integrity failure for chunk ${chunkIndex} expected ${i}`,\n );\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 deserialize(data);\n};\n"],"names":["serialize","deserialize","SEMAPHORE","Semaphore","Handshake","Header","HEADER_VALUES","Math","max","Object","values","length","HEADER_SIZE","Uint32Array","BYTES_PER_ELEMENT","write","data","buffer","timeout","serialized","chunkSize","byteLength","totalSize","totalChunks","ceil","header","Int32Array","Atomics","store","notify","wait","Error","payload","Uint8Array","i","start","end","min","size","set","subarray","read","chunkIndex","offset"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sabcom",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"main": "build/index.cjs",
|
|
8
|
+
"module": "build/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
"require": "./build/index.cjs",
|
|
11
|
+
"import": "./build/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"build",
|
|
15
|
+
"src/index.ts"
|
|
16
|
+
],
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Ivan Zakharchanka",
|
|
20
|
+
"email": "3axap4eHko@gmail.com",
|
|
21
|
+
"url": "https://linkedin.com/in/3axap4eHko"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@eslint/js": "^9.29.0",
|
|
25
|
+
"@swc/jest": "^0.2.38",
|
|
26
|
+
"@types/jest": "^29.5.14",
|
|
27
|
+
"@types/node": "^24.0.1",
|
|
28
|
+
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
|
29
|
+
"@typescript-eslint/parser": "^8.34.0",
|
|
30
|
+
"eslint": "^9.29.0",
|
|
31
|
+
"eslint-config-prettier": "^10.1.5",
|
|
32
|
+
"eslint-plugin-prettier": "^5.4.1",
|
|
33
|
+
"husky": "^9.1.7",
|
|
34
|
+
"inop": "^0.7.8",
|
|
35
|
+
"jest": "^30.0.0",
|
|
36
|
+
"prettier": "^3.5.3",
|
|
37
|
+
"typescript": "^5.8.3",
|
|
38
|
+
"typescript-eslint": "^8.34.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "rm -rf build && inop src build -i __tests__ -i *.tmp.ts && tsc --declaration --emitDeclarationOnly",
|
|
42
|
+
"lint": "eslint src",
|
|
43
|
+
"test": "jest"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { serialize, deserialize } from "node:v8";
|
|
2
|
+
|
|
3
|
+
export const SEMAPHORE = 0;
|
|
4
|
+
|
|
5
|
+
export enum Semaphore {
|
|
6
|
+
READY,
|
|
7
|
+
HANDSHAKE,
|
|
8
|
+
PAYLOAD,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export enum Handshake {
|
|
12
|
+
TOTAL_SIZE = 1,
|
|
13
|
+
TOTAL_CHUNKS,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export enum Header {
|
|
17
|
+
CHUNK_INDEX = 1,
|
|
18
|
+
CHUNK_OFFSET,
|
|
19
|
+
CHUNK_SIZE,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const HEADER_VALUES =
|
|
23
|
+
1 +
|
|
24
|
+
Math.max(Object.values(Handshake).length, Object.values(Header).length) / 2;
|
|
25
|
+
export const HEADER_SIZE = Uint32Array.BYTES_PER_ELEMENT * HEADER_VALUES;
|
|
26
|
+
|
|
27
|
+
export const write = (
|
|
28
|
+
data: unknown,
|
|
29
|
+
buffer: SharedArrayBuffer,
|
|
30
|
+
timeout = 5000,
|
|
31
|
+
) => {
|
|
32
|
+
const serialized = serialize(data);
|
|
33
|
+
const chunkSize = buffer.byteLength - HEADER_SIZE;
|
|
34
|
+
const totalSize = serialized.length;
|
|
35
|
+
const totalChunks = Math.ceil(totalSize / chunkSize);
|
|
36
|
+
|
|
37
|
+
const header = new Int32Array(buffer);
|
|
38
|
+
header[Handshake.TOTAL_SIZE] = totalSize;
|
|
39
|
+
header[Handshake.TOTAL_CHUNKS] = totalChunks;
|
|
40
|
+
|
|
41
|
+
Atomics.store(header, SEMAPHORE, Semaphore.HANDSHAKE);
|
|
42
|
+
Atomics.notify(header, SEMAPHORE);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
if (
|
|
46
|
+
Atomics.wait(header, SEMAPHORE, Semaphore.HANDSHAKE, timeout) ===
|
|
47
|
+
"timed-out"
|
|
48
|
+
) {
|
|
49
|
+
throw new Error("Reader handshake timeout");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const payload = new Uint8Array(buffer, HEADER_SIZE);
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
55
|
+
const start = i * chunkSize;
|
|
56
|
+
const end = Math.min(start + chunkSize, totalSize);
|
|
57
|
+
const size = end - start;
|
|
58
|
+
|
|
59
|
+
payload.set(serialized.subarray(start, end), 0);
|
|
60
|
+
|
|
61
|
+
header[Header.CHUNK_INDEX] = i;
|
|
62
|
+
header[Header.CHUNK_OFFSET] = start;
|
|
63
|
+
header[Header.CHUNK_SIZE] = size;
|
|
64
|
+
|
|
65
|
+
Atomics.store(header, SEMAPHORE, Semaphore.PAYLOAD);
|
|
66
|
+
Atomics.notify(header, SEMAPHORE);
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
Atomics.wait(header, SEMAPHORE, Semaphore.PAYLOAD, timeout) ===
|
|
70
|
+
"timed-out"
|
|
71
|
+
) {
|
|
72
|
+
throw new Error(`Reader timeout on chunk ${i}/${totalChunks - 1}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
Atomics.store(header, SEMAPHORE, Semaphore.READY);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const read = (buffer: SharedArrayBuffer, timeout = 5000): unknown => {
|
|
81
|
+
const header = new Int32Array(buffer);
|
|
82
|
+
if (
|
|
83
|
+
Atomics.wait(header, SEMAPHORE, Semaphore.READY, timeout) === "timed-out"
|
|
84
|
+
) {
|
|
85
|
+
throw new Error("Handshake timeout");
|
|
86
|
+
}
|
|
87
|
+
if (header[SEMAPHORE] !== Semaphore.HANDSHAKE) {
|
|
88
|
+
throw new Error("Invalid handshake state");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const totalSize = header[Handshake.TOTAL_SIZE];
|
|
92
|
+
const totalChunks = header[Handshake.TOTAL_CHUNKS];
|
|
93
|
+
const data = new Uint8Array(totalSize);
|
|
94
|
+
|
|
95
|
+
Atomics.store(header, SEMAPHORE, Semaphore.READY);
|
|
96
|
+
Atomics.notify(header, SEMAPHORE);
|
|
97
|
+
|
|
98
|
+
const payload = new Uint8Array(buffer, HEADER_SIZE);
|
|
99
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
100
|
+
if (
|
|
101
|
+
Atomics.wait(header, SEMAPHORE, Semaphore.READY, timeout) === "timed-out"
|
|
102
|
+
) {
|
|
103
|
+
throw new Error(`Writer timeout waiting for chunk ${i}`);
|
|
104
|
+
}
|
|
105
|
+
// @ts-expect-error does not infer number
|
|
106
|
+
if (header[SEMAPHORE] !== Semaphore.PAYLOAD) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Expected payload header, received ${Semaphore[header[SEMAPHORE]]}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const chunkIndex = header[Header.CHUNK_INDEX];
|
|
112
|
+
if (i !== chunkIndex) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Reader integrity failure for chunk ${chunkIndex} expected ${i}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const offset = header[Header.CHUNK_OFFSET];
|
|
118
|
+
const size = header[Header.CHUNK_SIZE];
|
|
119
|
+
data.set(payload.subarray(0, size), offset);
|
|
120
|
+
Atomics.store(header, SEMAPHORE, Semaphore.READY);
|
|
121
|
+
Atomics.notify(header, SEMAPHORE);
|
|
122
|
+
}
|
|
123
|
+
return deserialize(data);
|
|
124
|
+
};
|