@nadrif/thread 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/LICENSE +21 -0
- package/README.md +1156 -0
- package/package.json +133 -0
- package/src/adapters.js +404 -0
- package/src/config/adapters.js +188 -0
- package/src/config/define.js +171 -0
- package/src/config/env.js +218 -0
- package/src/config/frameworks.js +268 -0
- package/src/config/index.js +256 -0
- package/src/config/schema.js +375 -0
- package/src/config.js +52 -0
- package/src/deno.js +29 -0
- package/src/edge.js +38 -0
- package/src/env.js +361 -0
- package/src/error.js +340 -0
- package/src/factory.js +591 -0
- package/src/gpu/adapters.js +227 -0
- package/src/gpu/chains.js +261 -0
- package/src/gpu/env.js +372 -0
- package/src/gpu/gpu.js +1199 -0
- package/src/gpu/helpers.js +240 -0
- package/src/gpu/hooks.js +284 -0
- package/src/gpu/index.js +16 -0
- package/src/gpu/shaders.js +834 -0
- package/src/gpu/special.js +493 -0
- package/src/hooks.js +448 -0
- package/src/index.js +557 -0
- package/src/metrix.js +222 -0
- package/src/node.js +36 -0
- package/src/pool.js +740 -0
- package/src/serializer.js +139 -0
- package/src/thread.js +1246 -0
- package/src/types.js +1354 -0
- package/src/worker-factory.js +347 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Cross-platform Worker factory for the thread library.
|
|
3
|
+
*
|
|
4
|
+
* Abstracts Worker creation across:
|
|
5
|
+
* - **Browser** — `new Worker(blobUrl)` with Blob URLs
|
|
6
|
+
* - **Node.js** — `worker_threads.Worker` with `eval` or `data:` URLs
|
|
7
|
+
* - **Bun** — `worker_threads.Worker` (Bun's built-in)
|
|
8
|
+
* - **Deno** — `new Worker(url)` (Deno's Worker API)
|
|
9
|
+
*
|
|
10
|
+
* The factory returns a **unified Worker interface** that exposes the
|
|
11
|
+
* same API (`postMessage`, `terminate`, `onmessage`, `onerror`) regardless
|
|
12
|
+
* of the underlying platform.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```js
|
|
16
|
+
* import { createWorker, terminateWorker, postToWorker } from './worker-factory.js';
|
|
17
|
+
*
|
|
18
|
+
* const worker = createWorker(scriptSource, { type: 'module' });
|
|
19
|
+
* worker.onmessage = (e) => console.log(e.data);
|
|
20
|
+
* postToWorker(worker, { id: 1, args: [42] });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @module worker-factory
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { env } from './env.js';
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Worker creation
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a platform-appropriate Worker from a script string.
|
|
34
|
+
*
|
|
35
|
+
* **Browser:** Creates a Blob URL and spawns a standard Web Worker.
|
|
36
|
+
* **Node.js/Bun:** Uses `worker_threads.Worker` with the script as eval code.
|
|
37
|
+
* **Deno:** Uses Deno's Worker API with a Blob URL.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} scriptSource - JavaScript source code for the worker.
|
|
40
|
+
* @param {Object} [options={}]
|
|
41
|
+
* @param {'classic'|'module'} [options.type='classic'] - Script module type.
|
|
42
|
+
* @param {string[]} [options.imports] - URLs to import before running the script.
|
|
43
|
+
* @returns {WorkerInterface} A worker with a unified interface.
|
|
44
|
+
* @throws {Error} If Worker creation fails in the current environment.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```js
|
|
48
|
+
* const worker = createWorker(`
|
|
49
|
+
* self.onmessage = function(e) {
|
|
50
|
+
* self.postMessage(e.data * 2);
|
|
51
|
+
* };
|
|
52
|
+
* `);
|
|
53
|
+
*
|
|
54
|
+
* worker.onmessage = (e) => console.log(e.data); // 84
|
|
55
|
+
* worker.postMessage(42);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function createWorker(scriptSource, options = {}) {
|
|
59
|
+
const { type = 'classic', imports = [] } = options;
|
|
60
|
+
|
|
61
|
+
// --- Browser ---
|
|
62
|
+
if (env.isBrowser || (env.runtime === 'deno' && !env.isEdge)) {
|
|
63
|
+
return _createBrowserWorker(scriptSource, { type, imports });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- Node.js / Bun ---
|
|
67
|
+
if (env.isNode || env.isBun) {
|
|
68
|
+
return _createNodeWorker(scriptSource, { type, imports });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- Deno (if not caught above) ---
|
|
72
|
+
if (env.isDeno) {
|
|
73
|
+
return _createDenoWorker(scriptSource, { type, imports });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error(
|
|
77
|
+
`thread: Worker creation is not supported in this environment (${env.runtime}/${env.context}). ` +
|
|
78
|
+
'Supported: browser, Node.js, Bun, Deno.'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Browser worker
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
function _createBrowserWorker(scriptSource, { type, imports }) {
|
|
90
|
+
// Prepend importScripts calls if needed
|
|
91
|
+
let fullScript = scriptSource;
|
|
92
|
+
if (imports.length > 0 && type === 'classic') {
|
|
93
|
+
const importLines = imports.map((u) => `importScripts('${u}');`).join('\n');
|
|
94
|
+
fullScript = importLines + '\n' + scriptSource;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const blob = new Blob([fullScript], { type: 'application/javascript' });
|
|
98
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const worker = new Worker(blobUrl, { type });
|
|
102
|
+
// Attach blob URL for later cleanup
|
|
103
|
+
worker._threadBlobUrl = blobUrl;
|
|
104
|
+
worker._threadType = 'browser';
|
|
105
|
+
return worker;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
URL.revokeObjectURL(blobUrl);
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Node.js / Bun worker
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
function _createNodeWorker(scriptSource, { type, imports }) {
|
|
120
|
+
let WorkerClass;
|
|
121
|
+
|
|
122
|
+
// Try node:worker_threads first (Node.js with globalThis.require)
|
|
123
|
+
try {
|
|
124
|
+
if (typeof globalThis.require === 'function') {
|
|
125
|
+
const wt = globalThis.require('node:worker_threads');
|
|
126
|
+
WorkerClass = wt.Worker;
|
|
127
|
+
}
|
|
128
|
+
} catch { /* ignore */ }
|
|
129
|
+
|
|
130
|
+
// Fallback: try bare require (Bun exposes require but not on globalThis)
|
|
131
|
+
if (!WorkerClass) {
|
|
132
|
+
try {
|
|
133
|
+
if (typeof require === 'function') {
|
|
134
|
+
const wt = require('node:worker_threads');
|
|
135
|
+
WorkerClass = wt.Worker;
|
|
136
|
+
}
|
|
137
|
+
} catch { /* ignore */ }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!WorkerClass) {
|
|
141
|
+
throw new Error('thread: worker_threads module is not available');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Build import preamble for Node workers
|
|
145
|
+
let fullScript = scriptSource;
|
|
146
|
+
if (imports.length > 0) {
|
|
147
|
+
const importLines = imports.map((u) => `importScripts('${u}');`).join('\n');
|
|
148
|
+
fullScript = importLines + '\n' + scriptSource;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Node worker_threads expects eval or a file path
|
|
152
|
+
// We use eval mode with the script as the worker code
|
|
153
|
+
const worker = new WorkerClass(fullScript, {
|
|
154
|
+
eval: true,
|
|
155
|
+
// Node.js worker_threads needs these for message compatibility
|
|
156
|
+
stdout: false,
|
|
157
|
+
stderr: false,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Wrap to match browser Worker API
|
|
161
|
+
const wrapped = _wrapNodeWorker(worker);
|
|
162
|
+
wrapped._threadType = 'node';
|
|
163
|
+
return wrapped;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Deno worker
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
function _createDenoWorker(scriptSource, { type, imports }) {
|
|
174
|
+
let fullScript = scriptSource;
|
|
175
|
+
if (imports.length > 0) {
|
|
176
|
+
const importLines = imports.map((u) => `import '${u}';`).join('\n');
|
|
177
|
+
fullScript = importLines + '\n' + scriptSource;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const blob = new Blob([fullScript], { type: 'application/javascript' });
|
|
181
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const worker = new Worker(blobUrl, { type: 'module' });
|
|
185
|
+
worker._threadBlobUrl = blobUrl;
|
|
186
|
+
worker._threadType = 'deno';
|
|
187
|
+
return worker;
|
|
188
|
+
} catch (err) {
|
|
189
|
+
URL.revokeObjectURL(blobUrl);
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Node.js worker_threads → browser Worker API adapter
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Wrap a Node.js `worker_threads.Worker` to expose the browser Worker API.
|
|
200
|
+
*
|
|
201
|
+
* Node's worker_threads uses a different event model (EventEmitter).
|
|
202
|
+
* This wrapper translates `.on('message', ...)` to `.onmessage = ...`.
|
|
203
|
+
*
|
|
204
|
+
* @param {import('worker_threads').Worker} nodeWorker
|
|
205
|
+
* @returns {WorkerInterface} Browser-compatible Worker wrapper.
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
function _wrapNodeWorker(nodeWorker) {
|
|
209
|
+
const wrapped = {
|
|
210
|
+
_nodeWorker: nodeWorker,
|
|
211
|
+
_threadType: 'node',
|
|
212
|
+
_onmessage: null,
|
|
213
|
+
_onerror: null,
|
|
214
|
+
_onmessageerror: null,
|
|
215
|
+
|
|
216
|
+
get onmessage() { return this._onmessage; },
|
|
217
|
+
set onmessage(fn) {
|
|
218
|
+
this._onmessage = fn;
|
|
219
|
+
// Node worker_threads uses 'message' event
|
|
220
|
+
nodeWorker.removeAllListeners('message');
|
|
221
|
+
if (fn) {
|
|
222
|
+
nodeWorker.on('message', (data) => {
|
|
223
|
+
// Wrap in a MessageEvent-like object
|
|
224
|
+
fn({ data });
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
get onerror() { return this._onerror; },
|
|
230
|
+
set onerror(fn) {
|
|
231
|
+
this._onerror = fn;
|
|
232
|
+
nodeWorker.removeAllListeners('error');
|
|
233
|
+
if (fn) {
|
|
234
|
+
nodeWorker.on('error', (err) => {
|
|
235
|
+
fn({ message: err.message, error: err, preventDefault() {} });
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
get onmessageerror() { return this._onmessageerror; },
|
|
241
|
+
set onmessageerror(fn) {
|
|
242
|
+
this._onmessageerror = fn;
|
|
243
|
+
// Node doesn't have messageerror — no-op
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
postMessage(data, transfer) {
|
|
247
|
+
// Node worker_threads postMessage
|
|
248
|
+
if (transfer && Array.isArray(transfer)) {
|
|
249
|
+
nodeWorker.postMessage(data, transfer);
|
|
250
|
+
} else {
|
|
251
|
+
nodeWorker.postMessage(data);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
terminate() {
|
|
256
|
+
try { nodeWorker.terminate(); } catch { /* already terminated */ }
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
// Bonus: Node-specific APIs for advanced usage
|
|
260
|
+
get threadId() {
|
|
261
|
+
return nodeWorker.threadId;
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Proxy any other method calls to the underlying worker
|
|
266
|
+
return new Proxy(wrapped, {
|
|
267
|
+
get(target, prop) {
|
|
268
|
+
if (prop in target) return target[prop];
|
|
269
|
+
// Delegate to the underlying worker for unknown properties
|
|
270
|
+
const val = nodeWorker[prop];
|
|
271
|
+
return typeof val === 'function' ? val.bind(nodeWorker) : val;
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Worker utilities
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Post a message to a worker, handling platform differences.
|
|
282
|
+
*
|
|
283
|
+
* @param {WorkerInterface} worker - The worker to send to.
|
|
284
|
+
* @param {*} data - Message data.
|
|
285
|
+
* @param {Transferable[]} [transfer=[]] - Transferable objects.
|
|
286
|
+
*/
|
|
287
|
+
export function postToWorker(worker, data, transfer = []) {
|
|
288
|
+
if (worker._threadType === 'node') {
|
|
289
|
+
worker.postMessage(data, transfer);
|
|
290
|
+
} else {
|
|
291
|
+
worker.postMessage(data, transfer);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Terminate a worker and clean up resources.
|
|
297
|
+
*
|
|
298
|
+
* Revokes Blob URLs (browser/Deno) and terminates the worker.
|
|
299
|
+
*
|
|
300
|
+
* @param {WorkerInterface} worker - The worker to terminate.
|
|
301
|
+
*/
|
|
302
|
+
export function terminateWorker(worker) {
|
|
303
|
+
// Revoke Blob URL if applicable
|
|
304
|
+
if (worker._threadBlobUrl) {
|
|
305
|
+
try { URL.revokeObjectURL(worker._threadBlobUrl); } catch { /* ignore */ }
|
|
306
|
+
}
|
|
307
|
+
worker.terminate();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Check if the current environment supports Worker creation.
|
|
312
|
+
*
|
|
313
|
+
* @returns {boolean} `true` if Workers can be created.
|
|
314
|
+
*/
|
|
315
|
+
export function supportsWorkers() {
|
|
316
|
+
return env.hasWorker || env.isNode || env.isBun || env.isDeno;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get a description of the current Worker support.
|
|
321
|
+
*
|
|
322
|
+
* @returns {{ supported: boolean, type: string, details: string }}
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```js
|
|
326
|
+
* const info = workerInfo();
|
|
327
|
+
* console.log(info); // { supported: true, type: 'browser', details: 'Web Workers via Blob URLs' }
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
export function workerInfo() {
|
|
331
|
+
if (env.isBrowser) {
|
|
332
|
+
return { supported: true, type: 'browser', details: 'Web Workers via Blob URLs' };
|
|
333
|
+
}
|
|
334
|
+
if (env.isBun) {
|
|
335
|
+
return { supported: true, type: 'bun', details: 'Bun worker_threads (eval mode)' };
|
|
336
|
+
}
|
|
337
|
+
if (env.isNode) {
|
|
338
|
+
return { supported: true, type: 'node', details: 'Node.js worker_threads (eval mode)' };
|
|
339
|
+
}
|
|
340
|
+
if (env.isDeno) {
|
|
341
|
+
return { supported: true, type: 'deno', details: 'Deno Workers via Blob URLs' };
|
|
342
|
+
}
|
|
343
|
+
return { supported: false, type: 'none', details: 'No Worker support detected' };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Re-export env for convenience
|
|
347
|
+
export { env } from './env.js';
|