@gjsify/worker_threads 0.1.0

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 ADDED
@@ -0,0 +1,27 @@
1
+ # @gjsify/worker_threads
2
+
3
+ GJS stub implementation of the Node.js `worker_threads` module. Currently provides isMainThread only.
4
+
5
+ Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @gjsify/worker_threads
11
+ # or
12
+ yarn add @gjsify/worker_threads
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { isMainThread } from '@gjsify/worker_threads';
19
+
20
+ if (isMainThread) {
21
+ console.log('Running in the main thread');
22
+ }
23
+ ```
24
+
25
+ ## License
26
+
27
+ MIT
@@ -0,0 +1,138 @@
1
+ import { MessagePort } from "./message-port.js";
2
+ import { Worker } from "./worker.js";
3
+ import { MessagePort as MessagePort2 } from "./message-port.js";
4
+ import { Worker as Worker2 } from "./worker.js";
5
+ const _ctx = globalThis.__gjsify_worker_context;
6
+ const isMainThread = !_ctx;
7
+ const parentPort = _ctx?.parentPort ?? null;
8
+ const workerData = _ctx?.workerData ?? null;
9
+ const threadId = _ctx?.threadId ?? 0;
10
+ const resourceLimits = {};
11
+ const SHARE_ENV = /* @__PURE__ */ Symbol("worker_threads.SHARE_ENV");
12
+ class MessageChannel {
13
+ port1;
14
+ port2;
15
+ constructor() {
16
+ this.port1 = new MessagePort2();
17
+ this.port2 = new MessagePort2();
18
+ this.port1._otherPort = this.port2;
19
+ this.port2._otherPort = this.port1;
20
+ }
21
+ }
22
+ const _broadcastRegistry = /* @__PURE__ */ new Map();
23
+ class BroadcastChannel {
24
+ name;
25
+ _closed = false;
26
+ onmessage = null;
27
+ onmessageerror = null;
28
+ constructor(name) {
29
+ this.name = String(name);
30
+ if (!_broadcastRegistry.has(this.name)) {
31
+ _broadcastRegistry.set(this.name, /* @__PURE__ */ new Set());
32
+ }
33
+ _broadcastRegistry.get(this.name).add(this);
34
+ }
35
+ postMessage(message) {
36
+ if (this._closed) {
37
+ throw new Error("BroadcastChannel is closed");
38
+ }
39
+ const channels = _broadcastRegistry.get(this.name);
40
+ if (!channels) return;
41
+ for (const channel of channels) {
42
+ if (channel !== this && !channel._closed) {
43
+ const cloned = structuredClone(message);
44
+ Promise.resolve().then(() => {
45
+ if (!channel._closed && channel.onmessage) {
46
+ channel.onmessage({ data: cloned });
47
+ }
48
+ });
49
+ }
50
+ }
51
+ }
52
+ close() {
53
+ if (this._closed) return;
54
+ this._closed = true;
55
+ const channels = _broadcastRegistry.get(this.name);
56
+ if (channels) {
57
+ channels.delete(this);
58
+ if (channels.size === 0) {
59
+ _broadcastRegistry.delete(this.name);
60
+ }
61
+ }
62
+ this.onmessage = null;
63
+ this.onmessageerror = null;
64
+ }
65
+ addEventListener(type, listener) {
66
+ if (type === "message") {
67
+ this.onmessage = listener;
68
+ } else if (type === "messageerror") {
69
+ this.onmessageerror = listener;
70
+ }
71
+ }
72
+ removeEventListener(type, _listener) {
73
+ if (type === "message") this.onmessage = null;
74
+ else if (type === "messageerror") this.onmessageerror = null;
75
+ }
76
+ dispatchEvent(_event) {
77
+ return true;
78
+ }
79
+ }
80
+ const _environmentData = /* @__PURE__ */ new Map();
81
+ function setEnvironmentData(key, value) {
82
+ if (value === void 0) {
83
+ _environmentData.delete(key);
84
+ } else {
85
+ _environmentData.set(key, value);
86
+ }
87
+ }
88
+ function getEnvironmentData(key) {
89
+ return _environmentData.get(key);
90
+ }
91
+ function receiveMessageOnPort(port) {
92
+ if (!port._hasQueuedMessages) return void 0;
93
+ return { message: port._dequeueMessage() };
94
+ }
95
+ function markAsUntransferable(_object) {
96
+ }
97
+ function markAsUncloneable(_object) {
98
+ }
99
+ function moveMessagePortToContext(port, _context) {
100
+ return port;
101
+ }
102
+ var index_default = {
103
+ isMainThread,
104
+ parentPort,
105
+ workerData,
106
+ threadId,
107
+ resourceLimits,
108
+ SHARE_ENV,
109
+ Worker: Worker2,
110
+ MessageChannel,
111
+ MessagePort: MessagePort2,
112
+ BroadcastChannel,
113
+ setEnvironmentData,
114
+ getEnvironmentData,
115
+ receiveMessageOnPort,
116
+ markAsUntransferable,
117
+ markAsUncloneable,
118
+ moveMessagePortToContext
119
+ };
120
+ export {
121
+ BroadcastChannel,
122
+ MessageChannel,
123
+ MessagePort,
124
+ SHARE_ENV,
125
+ Worker,
126
+ index_default as default,
127
+ getEnvironmentData,
128
+ isMainThread,
129
+ markAsUncloneable,
130
+ markAsUntransferable,
131
+ moveMessagePortToContext,
132
+ parentPort,
133
+ receiveMessageOnPort,
134
+ resourceLimits,
135
+ setEnvironmentData,
136
+ threadId,
137
+ workerData
138
+ };
@@ -0,0 +1,118 @@
1
+ import { EventEmitter } from "node:events";
2
+ class MessagePort extends EventEmitter {
3
+ _started = false;
4
+ _closed = false;
5
+ _messageQueue = [];
6
+ /** @internal Linked port for in-process communication */
7
+ _otherPort = null;
8
+ /** @internal Maps addEventListener listeners to their internal wrappers */
9
+ _aeWrappers = /* @__PURE__ */ new Map();
10
+ start() {
11
+ if (this._started || this._closed) return;
12
+ this._started = true;
13
+ this._drainQueue();
14
+ }
15
+ close() {
16
+ if (this._closed) return;
17
+ this._closed = true;
18
+ const other = this._otherPort;
19
+ this._otherPort = null;
20
+ if (other) other._otherPort = null;
21
+ this.emit("close");
22
+ this.removeAllListeners();
23
+ }
24
+ postMessage(value, _transferList) {
25
+ if (this._closed) return;
26
+ const target = this._otherPort;
27
+ if (!target) return;
28
+ let cloned;
29
+ try {
30
+ cloned = structuredClone(value);
31
+ } catch (err) {
32
+ this.emit("messageerror", err instanceof Error ? err : new Error("Could not clone message"));
33
+ return;
34
+ }
35
+ target._receiveMessage(cloned);
36
+ }
37
+ ref() {
38
+ return this;
39
+ }
40
+ unref() {
41
+ return this;
42
+ }
43
+ _receiveMessage(message) {
44
+ if (this._closed) return;
45
+ if (!this._started) {
46
+ this._messageQueue.push(message);
47
+ return;
48
+ }
49
+ this._dispatchMessage(message);
50
+ }
51
+ get _hasQueuedMessages() {
52
+ return this._messageQueue.length > 0;
53
+ }
54
+ _dequeueMessage() {
55
+ return this._messageQueue.shift();
56
+ }
57
+ _drainQueue() {
58
+ while (this._messageQueue.length > 0) {
59
+ this._dispatchMessage(this._messageQueue.shift());
60
+ }
61
+ }
62
+ _dispatchMessage(message) {
63
+ Promise.resolve().then(() => {
64
+ if (!this._closed) {
65
+ this.emit("message", message);
66
+ }
67
+ });
68
+ }
69
+ /**
70
+ * Web-compatible addEventListener. Wraps message data in a MessageEvent-like
71
+ * object `{ data, type }` before calling the listener.
72
+ * Requires explicit `port.start()` call (unlike `on('message')` which auto-starts).
73
+ */
74
+ addEventListener(type, listener) {
75
+ if (!listener) return;
76
+ if (type === "message") {
77
+ const wrapper = (data) => {
78
+ listener({ data, type: "message" });
79
+ };
80
+ this._aeWrappers.set(listener, wrapper);
81
+ super.on("message", wrapper);
82
+ } else {
83
+ super.on(type, listener);
84
+ }
85
+ }
86
+ removeEventListener(type, listener) {
87
+ if (!listener) return;
88
+ if (type === "message") {
89
+ const wrapper = this._aeWrappers.get(listener);
90
+ if (wrapper) {
91
+ super.off("message", wrapper);
92
+ this._aeWrappers.delete(listener);
93
+ }
94
+ } else {
95
+ super.off(type, listener);
96
+ }
97
+ }
98
+ on(event, listener) {
99
+ super.on(event, listener);
100
+ if (event === "message" && !this._started) {
101
+ this.start();
102
+ }
103
+ return this;
104
+ }
105
+ addListener(event, listener) {
106
+ return this.on(event, listener);
107
+ }
108
+ once(event, listener) {
109
+ super.once(event, listener);
110
+ if (event === "message" && !this._started) {
111
+ this.start();
112
+ }
113
+ return this;
114
+ }
115
+ }
116
+ export {
117
+ MessagePort
118
+ };
@@ -0,0 +1,315 @@
1
+ import Gio from "@girs/gio-2.0";
2
+ import GLib from "@girs/glib-2.0";
3
+ import { EventEmitter } from "node:events";
4
+ let _nextThreadId = 1;
5
+ const _encoder = new TextEncoder();
6
+ const BOOTSTRAP_CODE = `import GLib from 'gi://GLib';
7
+ import Gio from 'gi://Gio';
8
+
9
+ const loop = new GLib.MainLoop(null, false);
10
+ const stdinStream = Gio.UnixInputStream.new(0, false);
11
+ const dataIn = Gio.DataInputStream.new(stdinStream);
12
+ const stdoutStream = Gio.UnixOutputStream.new(1, false);
13
+
14
+ function send(obj) {
15
+ const line = JSON.stringify(obj) + '\\n';
16
+ stdoutStream.write_all(_encoder.encode(line), null);
17
+ }
18
+
19
+ // Read init data (first line, blocking)
20
+ const [initLine] = dataIn.read_line_utf8(null);
21
+ const init = JSON.parse(initLine);
22
+
23
+ // Simplified EventEmitter for parentPort
24
+ const _listeners = new Map();
25
+ const parentPort = {
26
+ on(ev, fn) {
27
+ if (!_listeners.has(ev)) _listeners.set(ev, []);
28
+ _listeners.get(ev).push(fn);
29
+ return this;
30
+ },
31
+ once(ev, fn) {
32
+ const w = (...a) => { parentPort.off(ev, w); fn(...a); };
33
+ return parentPort.on(ev, w);
34
+ },
35
+ off(ev, fn) {
36
+ const a = _listeners.get(ev);
37
+ if (a) _listeners.set(ev, a.filter(f => f !== fn));
38
+ return this;
39
+ },
40
+ emit(ev, ...a) { (_listeners.get(ev) || []).forEach(fn => fn(...a)); },
41
+ postMessage(data) { send({ type: 'message', data }); },
42
+ close() { send({ type: 'exit', code: 0 }); loop.quit(); },
43
+ removeAllListeners(ev) {
44
+ if (ev) _listeners.delete(ev); else _listeners.clear();
45
+ return this;
46
+ },
47
+ };
48
+
49
+ // Set worker context globals (read by @gjsify/worker_threads when imported by user script)
50
+ globalThis.__gjsify_worker_context = {
51
+ isMainThread: false,
52
+ parentPort,
53
+ workerData: init.workerData ?? null,
54
+ threadId: init.threadId ?? 0,
55
+ };
56
+
57
+ send({ type: 'online' });
58
+
59
+ // Async stdin reader for messages from parent
60
+ function readNext() {
61
+ dataIn.read_line_async(GLib.PRIORITY_DEFAULT, null, (source, result) => {
62
+ try {
63
+ const [line] = source.read_line_finish_utf8(result);
64
+ if (line === null) { loop.quit(); return; }
65
+ const msg = JSON.parse(line);
66
+ if (msg.type === 'message') parentPort.emit('message', msg.data);
67
+ else if (msg.type === 'terminate') { send({ type: 'exit', code: 1 }); loop.quit(); return; }
68
+ readNext();
69
+ } catch { loop.quit(); }
70
+ });
71
+ }
72
+ readNext();
73
+
74
+ // Execute worker code
75
+ try {
76
+ if (init.eval) {
77
+ const AsyncFn = Object.getPrototypeOf(async function(){}).constructor;
78
+ await new AsyncFn('parentPort', 'workerData', 'threadId', init.code)(
79
+ parentPort, init.workerData, init.threadId
80
+ );
81
+ } else {
82
+ await import(init.filename);
83
+ }
84
+ } catch (error) {
85
+ send({ type: 'error', message: error.message, stack: error.stack || '' });
86
+ }
87
+
88
+ loop.run();
89
+ `;
90
+ const BOOTSTRAP_BYTES = _encoder.encode(BOOTSTRAP_CODE);
91
+ class Worker extends EventEmitter {
92
+ threadId;
93
+ resourceLimits;
94
+ _subprocess = null;
95
+ _stdinPipe = null;
96
+ _exited = false;
97
+ _bootstrapFile = null;
98
+ constructor(filename, options) {
99
+ super();
100
+ this.threadId = _nextThreadId++;
101
+ this.resourceLimits = options?.resourceLimits || {};
102
+ const isEval = options?.eval === true;
103
+ const resolvedFilename = Worker._resolveFilename(filename, isEval);
104
+ const tmpDir = GLib.get_tmp_dir();
105
+ const bootstrapPath = `${tmpDir}/gjsify-worker-${this.threadId}-${Date.now()}.mjs`;
106
+ this._bootstrapFile = Gio.File.new_for_path(bootstrapPath);
107
+ try {
108
+ this._bootstrapFile.replace_contents(
109
+ BOOTSTRAP_BYTES,
110
+ null,
111
+ false,
112
+ Gio.FileCreateFlags.REPLACE_DESTINATION,
113
+ null
114
+ );
115
+ } catch (err) {
116
+ throw new Error(`Failed to create worker bootstrap: ${err instanceof Error ? err.message : err}`);
117
+ }
118
+ const launcher = new Gio.SubprocessLauncher({
119
+ flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
120
+ });
121
+ if (options?.env && typeof options.env === "object") {
122
+ for (const [key, value] of Object.entries(options.env)) {
123
+ launcher.setenv(key, String(value), true);
124
+ }
125
+ }
126
+ try {
127
+ this._subprocess = launcher.spawnv(["gjs", "-m", bootstrapPath]);
128
+ } catch (err) {
129
+ this._cleanup();
130
+ throw new Error(`Failed to spawn worker: ${err instanceof Error ? err.message : err}`);
131
+ }
132
+ this._stdinPipe = this._subprocess.get_stdin_pipe();
133
+ const stdoutPipe = this._subprocess.get_stdout_pipe();
134
+ if (!isEval) {
135
+ const filePath = resolvedFilename.startsWith("file://") ? resolvedFilename.slice(7) : resolvedFilename;
136
+ const file = Gio.File.new_for_path(filePath);
137
+ if (!file.query_exists(null)) {
138
+ this._cleanup();
139
+ const err = new Error(`Cannot find module '${filePath}'`);
140
+ err.code = "ERR_MODULE_NOT_FOUND";
141
+ Promise.resolve().then(() => {
142
+ this.emit("error", err);
143
+ this._exited = true;
144
+ this.emit("exit", 1);
145
+ });
146
+ return;
147
+ }
148
+ }
149
+ const initData = JSON.stringify({
150
+ threadId: this.threadId,
151
+ workerData: options?.workerData ?? null,
152
+ eval: isEval,
153
+ filename: isEval ? void 0 : resolvedFilename,
154
+ code: isEval ? resolvedFilename : void 0
155
+ }) + "\n";
156
+ try {
157
+ this._stdinPipe.write_all(_encoder.encode(initData), null);
158
+ } catch (err) {
159
+ this._cleanup();
160
+ throw new Error(`Failed to send init data: ${err instanceof Error ? err.message : err}`);
161
+ }
162
+ if (stdoutPipe) {
163
+ const dataStream = Gio.DataInputStream.new(stdoutPipe);
164
+ this._readMessages(dataStream);
165
+ }
166
+ const stderrPipe = this._subprocess.get_stderr_pipe();
167
+ if (stderrPipe) {
168
+ this._readStderr(Gio.DataInputStream.new(stderrPipe));
169
+ }
170
+ this._subprocess.wait_async(null, () => {
171
+ this._onExit();
172
+ });
173
+ }
174
+ postMessage(value, _transferList) {
175
+ if (this._exited || !this._stdinPipe) return;
176
+ try {
177
+ const line = JSON.stringify({ type: "message", data: value }) + "\n";
178
+ this._stdinPipe.write_all(_encoder.encode(line), null);
179
+ } catch {
180
+ }
181
+ }
182
+ terminate() {
183
+ if (this._exited) return Promise.resolve(0);
184
+ const exitPromise = new Promise((resolve) => {
185
+ this.once("exit", (code) => resolve(code));
186
+ });
187
+ try {
188
+ if (this._stdinPipe) {
189
+ const msg = JSON.stringify({ type: "terminate" }) + "\n";
190
+ this._stdinPipe.write_all(_encoder.encode(msg), null);
191
+ }
192
+ } catch {
193
+ }
194
+ setTimeout(() => {
195
+ if (!this._exited && this._subprocess) {
196
+ this._subprocess.force_exit();
197
+ }
198
+ }, 500);
199
+ return exitPromise;
200
+ }
201
+ ref() {
202
+ return this;
203
+ }
204
+ unref() {
205
+ return this;
206
+ }
207
+ /**
208
+ * Resolve a worker filename to an absolute path or file:// URL.
209
+ *
210
+ * - URL instances → href string
211
+ * - file:// URLs → kept as-is
212
+ * - Absolute paths → converted to file:// URL
213
+ * - Relative paths (./foo, ../bar) → resolved relative to cwd, converted to file:// URL
214
+ * - eval mode → returned as-is (it's code, not a path)
215
+ */
216
+ static _resolveFilename(filename, isEval) {
217
+ if (isEval) return String(filename);
218
+ if (filename instanceof URL) return filename.href;
219
+ const str = String(filename);
220
+ if (str.startsWith("file://") || str.startsWith("http://") || str.startsWith("https://")) {
221
+ return str;
222
+ }
223
+ if (str.startsWith("/")) {
224
+ return "file://" + str;
225
+ }
226
+ if (str.startsWith("./") || str.startsWith("../") || !str.includes("/")) {
227
+ const cwd = GLib.get_current_dir();
228
+ const resolved = GLib.build_filenamev([cwd, str]);
229
+ const file = Gio.File.new_for_path(resolved);
230
+ const canonical = file.get_path();
231
+ return "file://" + (canonical || resolved);
232
+ }
233
+ return "file://" + str;
234
+ }
235
+ _readMessages(dataStream) {
236
+ dataStream.read_line_async(
237
+ GLib.PRIORITY_DEFAULT,
238
+ null,
239
+ (_source, result) => {
240
+ try {
241
+ const [line] = dataStream.read_line_finish_utf8(result);
242
+ if (line === null) return;
243
+ const msg = JSON.parse(line);
244
+ switch (msg.type) {
245
+ case "online":
246
+ this.emit("online");
247
+ break;
248
+ case "message":
249
+ this.emit("message", msg.data);
250
+ break;
251
+ case "error": {
252
+ const err = new Error(msg.message);
253
+ if (msg.stack) err.stack = msg.stack;
254
+ this.emit("error", err);
255
+ break;
256
+ }
257
+ }
258
+ this._readMessages(dataStream);
259
+ } catch {
260
+ }
261
+ }
262
+ );
263
+ }
264
+ _stderrChunks = [];
265
+ _readStderr(dataStream) {
266
+ dataStream.read_line_async(
267
+ GLib.PRIORITY_DEFAULT,
268
+ null,
269
+ (_source, result) => {
270
+ try {
271
+ const [line] = dataStream.read_line_finish_utf8(result);
272
+ if (line === null) {
273
+ if (this._stderrChunks.length > 0) {
274
+ const stderrText = this._stderrChunks.join("\n");
275
+ if (this.listenerCount("error") === 0) {
276
+ this.emit("error", new Error(stderrText));
277
+ }
278
+ }
279
+ return;
280
+ }
281
+ this._stderrChunks.push(line);
282
+ this._readStderr(dataStream);
283
+ } catch {
284
+ }
285
+ }
286
+ );
287
+ }
288
+ _onExit() {
289
+ if (this._exited) return;
290
+ this._exited = true;
291
+ const exitCode = this._subprocess?.get_if_exited() ? this._subprocess.get_exit_status() : 1;
292
+ this._cleanup();
293
+ this.emit("exit", exitCode);
294
+ }
295
+ _cleanup() {
296
+ if (this._bootstrapFile) {
297
+ try {
298
+ this._bootstrapFile.delete(null);
299
+ } catch {
300
+ }
301
+ this._bootstrapFile = null;
302
+ }
303
+ if (this._stdinPipe) {
304
+ try {
305
+ this._stdinPipe.close(null);
306
+ } catch {
307
+ }
308
+ this._stdinPipe = null;
309
+ }
310
+ this._subprocess = null;
311
+ }
312
+ }
313
+ export {
314
+ Worker
315
+ };
@@ -0,0 +1,59 @@
1
+ export { MessagePort } from './message-port.ts';
2
+ export { Worker } from './worker.ts';
3
+ export type { WorkerOptions } from './worker.ts';
4
+ import { MessagePort } from './message-port.ts';
5
+ import { Worker } from './worker.ts';
6
+ export declare const isMainThread: boolean;
7
+ export declare const parentPort: MessagePort | null;
8
+ export declare const workerData: unknown;
9
+ export declare const threadId: number;
10
+ export declare const resourceLimits: Record<string, unknown>;
11
+ export declare const SHARE_ENV: unique symbol;
12
+ export declare class MessageChannel {
13
+ readonly port1: MessagePort;
14
+ readonly port2: MessagePort;
15
+ constructor();
16
+ }
17
+ export declare class BroadcastChannel {
18
+ readonly name: string;
19
+ private _closed;
20
+ onmessage: ((event: {
21
+ data: unknown;
22
+ }) => void) | null;
23
+ onmessageerror: ((event: {
24
+ data: unknown;
25
+ }) => void) | null;
26
+ constructor(name: string);
27
+ postMessage(message: unknown): void;
28
+ close(): void;
29
+ addEventListener(type: string, listener: ((event: unknown) => void) | null): void;
30
+ removeEventListener(type: string, _listener: ((event: unknown) => void) | null): void;
31
+ dispatchEvent(_event: Event): boolean;
32
+ }
33
+ export declare function setEnvironmentData(key: string, value: unknown): void;
34
+ export declare function getEnvironmentData(key: string): unknown;
35
+ export declare function receiveMessageOnPort(port: MessagePort): {
36
+ message: unknown;
37
+ } | undefined;
38
+ export declare function markAsUntransferable(_object: unknown): void;
39
+ export declare function markAsUncloneable(_object: unknown): void;
40
+ export declare function moveMessagePortToContext(port: MessagePort, _context: unknown): MessagePort;
41
+ declare const _default: {
42
+ isMainThread: boolean;
43
+ parentPort: MessagePort;
44
+ workerData: unknown;
45
+ threadId: number;
46
+ resourceLimits: Record<string, unknown>;
47
+ SHARE_ENV: symbol;
48
+ Worker: typeof Worker;
49
+ MessageChannel: typeof MessageChannel;
50
+ MessagePort: typeof MessagePort;
51
+ BroadcastChannel: typeof BroadcastChannel;
52
+ setEnvironmentData: typeof setEnvironmentData;
53
+ getEnvironmentData: typeof getEnvironmentData;
54
+ receiveMessageOnPort: typeof receiveMessageOnPort;
55
+ markAsUntransferable: typeof markAsUntransferable;
56
+ markAsUncloneable: typeof markAsUncloneable;
57
+ moveMessagePortToContext: typeof moveMessagePortToContext;
58
+ };
59
+ export default _default;
@@ -0,0 +1,30 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export declare class MessagePort extends EventEmitter {
3
+ private _started;
4
+ private _closed;
5
+ private _messageQueue;
6
+ /** @internal Linked port for in-process communication */
7
+ _otherPort: MessagePort | null;
8
+ /** @internal Maps addEventListener listeners to their internal wrappers */
9
+ private _aeWrappers;
10
+ start(): void;
11
+ close(): void;
12
+ postMessage(value: unknown, _transferList?: unknown[]): void;
13
+ ref(): this;
14
+ unref(): this;
15
+ _receiveMessage(message: unknown): void;
16
+ get _hasQueuedMessages(): boolean;
17
+ _dequeueMessage(): unknown | undefined;
18
+ private _drainQueue;
19
+ private _dispatchMessage;
20
+ /**
21
+ * Web-compatible addEventListener. Wraps message data in a MessageEvent-like
22
+ * object `{ data, type }` before calling the listener.
23
+ * Requires explicit `port.start()` call (unlike `on('message')` which auto-starts).
24
+ */
25
+ addEventListener(type: string, listener: ((event: unknown) => void) | null): void;
26
+ removeEventListener(type: string, listener: ((event: unknown) => void) | null): void;
27
+ on(event: string | symbol, listener: (...args: unknown[]) => void): this;
28
+ addListener(event: string | symbol, listener: (...args: unknown[]) => void): this;
29
+ once(event: string | symbol, listener: (...args: unknown[]) => void): this;
30
+ }