@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/src/env.js ADDED
@@ -0,0 +1,361 @@
1
+ /**
2
+ * @file Environment detection for the thread library.
3
+ *
4
+ * Detects the current JavaScript runtime and exposes flags for
5
+ * conditional code paths. Used internally by Thread, GPUCompute,
6
+ * and the config loader to pick the right APIs.
7
+ *
8
+ * Supported environments:
9
+ * - **Browser** — Web Workers, Blob URLs, `navigator.gpu`
10
+ * - **Node.js** — `worker_threads`, `node:worker_threads`
11
+ * - **Bun** — `bun:ffi`, `node:worker_threads` (Bun's built-in)
12
+ * - **Deno** — `Deno.Worker`, `Deno.openKv`, `navigator.gpu`
13
+ * - **Cloudflare Workers** — `workers-runtime` (no `Worker` constructor)
14
+ * - **Edge runtimes** — Vercel Edge, Netlify Edge, Deno Deploy
15
+ *
16
+ * @example
17
+ * ```js
18
+ * import { env } from './env.js';
19
+ *
20
+ * if (env.isNode) {
21
+ * const { Worker } = await import('node:worker_threads');
22
+ * } else if (env.isBrowser) {
23
+ * const worker = new Worker(url);
24
+ * }
25
+ * ```
26
+ *
27
+ * @module env
28
+ */
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Runtime detection
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /**
35
+ * @type {'browser'|'node'|'bun'|'deno'|'edge'|'unknown'}
36
+ * Detected runtime identifier.
37
+ */
38
+ const runtime = (() => {
39
+ // Bun — check first because Bun also satisfies Node checks
40
+ if (typeof globalThis.Bun !== 'undefined') return 'bun';
41
+ // Deno
42
+ if (typeof globalThis.Deno !== 'undefined') return 'deno';
43
+ // Node.js
44
+ if (typeof globalThis.process !== 'undefined' && typeof globalThis.process.versions?.node !== 'undefined') return 'node';
45
+ // Browser (including Web Workers, Service Workers)
46
+ if (typeof globalThis.navigator !== 'undefined' || typeof globalThis.self !== 'undefined') {
47
+ // Cloudflare Workers / Vercel Edge — no full `Worker` constructor
48
+ if (typeof globalThis.caches !== 'undefined' && typeof globalThis.XMLHttpRequest === 'undefined') return 'edge';
49
+ return 'browser';
50
+ }
51
+ // Edge runtime fallback
52
+ if (typeof globalThis.EdgeRuntime !== 'undefined') return 'edge';
53
+ return 'unknown';
54
+ })();
55
+
56
+ /**
57
+ * @type {'main'|'worker'|'service-worker'|'unknown'}
58
+ * Current execution context.
59
+ */
60
+ const context = (() => {
61
+ // Service Worker
62
+ if (typeof ServiceWorkerGlobalScope !== 'undefined' && globalThis instanceof ServiceWorkerGlobalScope) return 'service-worker';
63
+ // Web Worker / Deno Worker / Node worker_threads
64
+ if (typeof DedicatedWorkerGlobalScope !== 'undefined' && globalThis instanceof DedicatedWorkerGlobalScope) return 'worker';
65
+ if (typeof SharedWorkerGlobalScope !== 'undefined' && globalThis instanceof SharedWorkerGlobalScope) return 'worker';
66
+ // Node.js worker_threads
67
+ if (typeof globalThis.process !== 'undefined' && globalThis.process.env?.IS_WORKER_THREAD === '1') return 'worker';
68
+ // Bun worker_threads
69
+ if (typeof globalThis.Bun !== 'undefined' && typeof globalThis.Bun?.sleep === 'function' && typeof globalThis.self === 'undefined') return 'worker';
70
+ // Main thread
71
+ if (typeof window !== 'undefined' || typeof globalThis.window !== 'undefined') return 'main';
72
+ if (typeof globalThis.process !== 'undefined' && typeof globalThis.process.argv !== 'undefined') return 'main';
73
+ if (typeof globalThis.Deno !== 'undefined') return 'main';
74
+ return 'unknown';
75
+ })();
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Feature detection (cached)
79
+ // ---------------------------------------------------------------------------
80
+
81
+ /** @type {boolean} `true` if the native `Worker` constructor is available. */
82
+ const hasWorker = (() => {
83
+ if (typeof Worker !== 'undefined') return true;
84
+ // Node.js worker_threads has Worker in newer versions
85
+ try {
86
+ if (typeof globalThis.require === 'function') {
87
+ const wt = globalThis.require('node:worker_threads');
88
+ return typeof wt.Worker === 'function';
89
+ }
90
+ } catch { /* ignore */ }
91
+ return false;
92
+ })();
93
+
94
+ /** @type {boolean} `true` if WebGPU (`navigator.gpu`) is available. */
95
+ const hasGPU = (() => {
96
+ if (typeof navigator !== 'undefined' && navigator.gpu) return true;
97
+ // Deno
98
+ if (typeof globalThis.Deno !== 'undefined' && globalThis.Deno?.gpu) return true;
99
+ // Bun — check built-in gpu API
100
+ if (typeof globalThis.Bun !== 'undefined' && globalThis.Bun?.gpu) return true;
101
+ return false;
102
+ })();
103
+
104
+ /** @type {boolean} `true` if `fs` module is available (Node/Bun). */
105
+ const hasFS = (() => {
106
+ if (typeof globalThis.require === 'function') {
107
+ try { globalThis.require('node:fs'); return true; } catch { return false; }
108
+ }
109
+ if (typeof globalThis.Bun !== 'undefined') return true;
110
+ return false;
111
+ })();
112
+
113
+ /** @type {boolean} `true` if `path` module is available (Node/Bun). */
114
+ const hasPath = (() => {
115
+ if (typeof globalThis.require === 'function') {
116
+ try { globalThis.require('node:path'); return true; } catch { return false; }
117
+ }
118
+ return false;
119
+ })();
120
+
121
+ /** @type {boolean} `true` if `performance.memory` is available (Chrome). */
122
+ const hasMemoryAPI = (() => {
123
+ return typeof performance !== 'undefined' && typeof performance.memory !== 'undefined';
124
+ })();
125
+
126
+ /** @type {boolean} `true` if Blob and URL.createObjectURL are available. */
127
+ const hasBlob = (() => {
128
+ return typeof Blob !== 'undefined' && typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function';
129
+ })();
130
+
131
+ /** @type {boolean} `true` if dynamic `import()` is available. */
132
+ const hasDynamicImport = (() => {
133
+ try {
134
+ return typeof globalThis.import === 'function' || typeof Function('return import') === 'function';
135
+ } catch { return false; }
136
+ })();
137
+
138
+ /** @type {boolean} `true` if the runtime is Bun. */
139
+ const isBun = runtime === 'bun';
140
+
141
+ /** @type {boolean} `true` if the runtime is Node.js. */
142
+ const isNode = runtime === 'node';
143
+
144
+ /** @type {boolean} `true` if the runtime is Deno. */
145
+ const isDeno = runtime === 'deno';
146
+
147
+ /** @type {boolean} `true` if the runtime is a browser (including Web Workers). */
148
+ const isBrowser = runtime === 'browser';
149
+
150
+ /** @type {boolean} `true` if the runtime is an edge function. */
151
+ const isEdge = runtime === 'edge';
152
+
153
+ /** @type {boolean} `true` if running in a worker context (not main thread). */
154
+ const isWorker = context === 'worker';
155
+
156
+ /** @type {boolean} `true` if running on the main thread. */
157
+ const isMainThread = context === 'main';
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Platform-specific require/import helpers
161
+ // ---------------------------------------------------------------------------
162
+
163
+ /**
164
+ * Safely require a Node.js built-in module.
165
+ *
166
+ * Returns the module if available, `null` otherwise. Works in Node.js,
167
+ * Bun, and environments where `globalThis.require` is defined.
168
+ *
169
+ * @param {string} moduleId - Module name (e.g. `'node:fs'`, `'node:path'`).
170
+ * @returns {any|null} The required module, or `null`.
171
+ *
172
+ * @example
173
+ * ```js
174
+ * const fs = requireModule('node:fs');
175
+ * if (fs) {
176
+ * const data = fs.readFileSync('config.json', 'utf-8');
177
+ * }
178
+ * ```
179
+ */
180
+ function requireModule(moduleId) {
181
+ try {
182
+ if (typeof globalThis.require === 'function') {
183
+ return globalThis.require(moduleId);
184
+ }
185
+ } catch { /* ignore */ }
186
+ return null;
187
+ }
188
+
189
+ /**
190
+ * Get the current working directory.
191
+ *
192
+ * Works across Node.js, Bun, Deno, and browser environments.
193
+ *
194
+ * @returns {string} The best-effort working directory.
195
+ *
196
+ * @example
197
+ * ```js
198
+ * const cwd = getCwd();
199
+ * console.log('Working from:', cwd);
200
+ * ```
201
+ */
202
+ function getCwd() {
203
+ // Node.js / Bun
204
+ if (typeof process !== 'undefined' && typeof process.cwd === 'function') {
205
+ return process.cwd();
206
+ }
207
+ // Deno
208
+ if (typeof globalThis.Deno !== 'undefined' && typeof globalThis.Deno.cwd === 'function') {
209
+ return globalThis.Deno.cwd();
210
+ }
211
+ // Browser — use document.baseURI or location
212
+ if (typeof document !== 'undefined' && document.baseURI) {
213
+ try { return new URL(document.baseURI).pathname; } catch { /* ignore */ }
214
+ }
215
+ if (typeof location !== 'undefined' && location.href) {
216
+ try { return new URL(location.href).pathname; } catch { /* ignore */ }
217
+ }
218
+ return '.';
219
+ }
220
+
221
+ /**
222
+ * Read a file synchronously.
223
+ *
224
+ * Uses `fs.readFileSync` in Node/Bun, `Deno.readTextFileSync` in Deno,
225
+ * or returns `null` in browser environments.
226
+ *
227
+ * @param {string} filePath - Absolute or relative file path.
228
+ * @returns {string|null} File contents, or `null` if not readable.
229
+ *
230
+ * @example
231
+ * ```js
232
+ * const content = readFileSync('./config.json');
233
+ * if (content) {
234
+ * const config = JSON.parse(content);
235
+ * }
236
+ * ```
237
+ */
238
+ function readFileSync(filePath) {
239
+ // Node.js / Bun
240
+ const fs = requireModule('node:fs');
241
+ if (fs && typeof fs.readFileSync === 'function') {
242
+ return fs.readFileSync(filePath, 'utf-8');
243
+ }
244
+ // Deno
245
+ if (typeof globalThis.Deno !== 'undefined' && typeof globalThis.Deno.readTextFileSync === 'function') {
246
+ try { return globalThis.Deno.readTextFileSync(filePath); } catch { return null; }
247
+ }
248
+ return null;
249
+ }
250
+
251
+ /**
252
+ * Check if a file exists.
253
+ *
254
+ * @param {string} filePath - Path to check.
255
+ * @returns {boolean} `true` if the file exists and is accessible.
256
+ *
257
+ * @example
258
+ * ```js
259
+ * if (fileExists('./thread.config.js')) {
260
+ * loadConfig();
261
+ * }
262
+ * ```
263
+ */
264
+ function fileExists(filePath) {
265
+ // Node.js / Bun
266
+ const fs = requireModule('node:fs');
267
+ if (fs && typeof fs.existsSync === 'function') {
268
+ return fs.existsSync(filePath);
269
+ }
270
+ // Deno
271
+ if (typeof globalThis.Deno !== 'undefined' && typeof globalThis.Deno.statSync === 'function') {
272
+ try { globalThis.Deno.statSync(filePath); return true; } catch { return false; }
273
+ }
274
+ return false;
275
+ }
276
+
277
+ /**
278
+ * Resolve a file path relative to the current working directory.
279
+ *
280
+ * @param {string} ...segments - Path segments to join.
281
+ * @returns {string} Resolved absolute path.
282
+ *
283
+ * @example
284
+ * ```js
285
+ * const configPath = resolvePath('thread.config.js');
286
+ * const fullPath = resolvePath('src', 'config', 'index.js');
287
+ * ```
288
+ */
289
+ function resolvePath(...segments) {
290
+ // Node.js / Bun
291
+ const path = requireModule('node:path');
292
+ if (path) {
293
+ return path.resolve(getCwd(), ...segments);
294
+ }
295
+ // Deno
296
+ if (typeof globalThis.Deno !== 'undefined' && typeof globalThis.Deno.resolve === 'function') {
297
+ return globalThis.Deno.resolve(...segments);
298
+ }
299
+ // Fallback — simple join
300
+ return [getCwd(), ...segments].join('/').replace(/\/+/g, '/');
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Exports
305
+ // ---------------------------------------------------------------------------
306
+
307
+ /**
308
+ * Comprehensive environment information for the thread library.
309
+ *
310
+ * Provides runtime detection, feature flags, and platform-specific
311
+ * helpers for cross-environment compatibility.
312
+ *
313
+ * @type {{
314
+ * runtime: 'browser'|'node'|'bun'|'deno'|'edge'|'unknown',
315
+ * context: 'main'|'worker'|'service-worker'|'unknown',
316
+ * isBrowser: boolean,
317
+ * isNode: boolean,
318
+ * isBun: boolean,
319
+ * isDeno: boolean,
320
+ * isEdge: boolean,
321
+ * isWorker: boolean,
322
+ * isMainThread: boolean,
323
+ * hasWorker: boolean,
324
+ * hasGPU: boolean,
325
+ * hasFS: boolean,
326
+ * hasPath: boolean,
327
+ * hasMemoryAPI: boolean,
328
+ * hasBlob: boolean,
329
+ * hasDynamicImport: boolean,
330
+ * requireModule: function(string): any|null,
331
+ * getCwd: function(): string,
332
+ * readFileSync: function(string): string|null,
333
+ * fileExists: function(string): boolean,
334
+ * resolvePath: function(...string): string
335
+ * }}
336
+ */
337
+ export const env = Object.freeze({
338
+ runtime,
339
+ context,
340
+ isBrowser,
341
+ isNode,
342
+ isBun,
343
+ isDeno,
344
+ isEdge,
345
+ isWorker,
346
+ isMainThread,
347
+ hasWorker,
348
+ hasGPU,
349
+ hasFS,
350
+ hasPath,
351
+ hasMemoryAPI,
352
+ hasBlob,
353
+ hasDynamicImport,
354
+ requireModule,
355
+ getCwd,
356
+ readFileSync,
357
+ fileExists,
358
+ resolvePath,
359
+ });
360
+
361
+ export default env;
package/src/error.js ADDED
@@ -0,0 +1,340 @@
1
+ /**
2
+ * @file Error hierarchy for the thread module.
3
+ *
4
+ * Every error in the thread system extends {@link ThreadError}, so a single
5
+ * `catch (e) { if (e instanceof ThreadError) }` block handles them all.
6
+ * Use the more specific subclasses to differentiate timeout, abort,
7
+ * termination, health, and dependency failures.
8
+ *
9
+ * @example
10
+ * ```js
11
+ * import {
12
+ * ThreadError,
13
+ * ThreadTimeoutError,
14
+ * ThreadAbortError,
15
+ * ThreadTerminatedError,
16
+ * ThreadHealthError,
17
+ * ThreadDependencyError,
18
+ * } from './error.js';
19
+ *
20
+ * try {
21
+ * await thread.run(hugeDataset, { timeout: 5000 });
22
+ * } catch (err) {
23
+ * if (err instanceof ThreadTimeoutError) {
24
+ * console.warn('Task took too long – retrying with more time');
25
+ * } else if (err instanceof ThreadAbortError) {
26
+ * console.log('User cancelled – cleaning up');
27
+ * } else if (err instanceof ThreadTerminatedError) {
28
+ * console.error('Thread was killed');
29
+ * } else if (err instanceof ThreadError) {
30
+ * console.error('Worker error:', err.message);
31
+ * } else {
32
+ * throw err; // re-throw non-thread errors
33
+ * }
34
+ * }
35
+ * ```
36
+ *
37
+ * @module error
38
+ */
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // ThreadError – base class
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /**
45
+ * Base error class for all thread-related issues.
46
+ *
47
+ * This is the **parent** of every error the thread module can throw.
48
+ * Catching `ThreadError` lets you handle any thread-specific failure in
49
+ * one place; catching the subclasses lets you respond to specific failure
50
+ * modes.
51
+ *
52
+ * @example
53
+ * ```js
54
+ * import { ThreadError } from './error.js';
55
+ *
56
+ * // Generic catch-all
57
+ * try {
58
+ * await thread.run(data);
59
+ * } catch (err) {
60
+ * if (err instanceof ThreadError) {
61
+ * // any thread error – safe to handle generically
62
+ * console.error(`[thread] ${err.name}: ${err.message}`);
63
+ * }
64
+ * }
65
+ * ```
66
+ */
67
+ export class ThreadError extends Error {
68
+ /**
69
+ * @param {string} message - Human-readable description of the error.
70
+ */
71
+ constructor(message) {
72
+ super(message);
73
+ /** @readonly @type {'ThreadError'} */
74
+ this.name = 'ThreadError';
75
+ }
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // ThreadTimeoutError
80
+ // ---------------------------------------------------------------------------
81
+
82
+ /**
83
+ * Thrown when a task exceeds its allowed execution time.
84
+ *
85
+ * Every `thread.run()` and `pool.run()` accepts a `timeout` option (in ms).
86
+ * If the worker does not respond within that window the task promise is
87
+ * rejected with this error.
88
+ *
89
+ * **Common causes:**
90
+ * - The worker function has an infinite loop or is blocking on I/O.
91
+ * - The task is genuinely slow and needs a longer timeout.
92
+ * - The worker crashed before it could respond (the error message will
93
+ * include the timeout value).
94
+ *
95
+ * @example
96
+ * ```js
97
+ * import { createThread, ThreadTimeoutError } from './index.js';
98
+ *
99
+ * const t = createThread((n) => {
100
+ * // Simulate a slow task
101
+ * const end = Date.now() + n;
102
+ * while (Date.now() < end) { busy_wait }
103
+ * return 'done';
104
+ * });
105
+ *
106
+ * try {
107
+ * const result = await t.run(60_000, { timeout: 5000 });
108
+ * } catch (err) {
109
+ * if (err instanceof ThreadTimeoutError) {
110
+ * console.error('Task exceeded 5s timeout:', err.message);
111
+ * // "Timed out after 5000ms"
112
+ * }
113
+ * }
114
+ * ```
115
+ */
116
+ export class ThreadTimeoutError extends ThreadError {
117
+ /**
118
+ * @param {string} message - Timeout description (usually includes ms).
119
+ */
120
+ constructor(message) {
121
+ super(message);
122
+ /** @readonly @type {'ThreadTimeoutError'} */
123
+ this.name = 'ThreadTimeoutError';
124
+ }
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // ThreadAbortError
129
+ // ---------------------------------------------------------------------------
130
+
131
+ /**
132
+ * Thrown when a task is cancelled via an `AbortController` or the pool's
133
+ * `cancel()` method.
134
+ *
135
+ * There are two ways to abort a task:
136
+ * 1. **AbortController** – pass `signal` in run options.
137
+ * 2. **Pool cancel** – call `pool.cancel(taskId)`.
138
+ *
139
+ * In both cases the task promise rejects with this error.
140
+ *
141
+ * @example
142
+ * ```js
143
+ * import { createThread, ThreadAbortError } from './index.js';
144
+ *
145
+ * const t = createThread((data) => process(data));
146
+ * const controller = new AbortController();
147
+ *
148
+ * // Start a long-running task
149
+ * const promise = t.run(hugeData, { signal: controller.signal });
150
+ *
151
+ * // User clicks "Cancel"
152
+ * document.getElementById('cancel').onclick = () => controller.abort();
153
+ *
154
+ * try {
155
+ * await promise;
156
+ * } catch (err) {
157
+ * if (err instanceof ThreadAbortError) {
158
+ * console.log('Task was cancelled by user');
159
+ * }
160
+ * }
161
+ * ```
162
+ */
163
+ export class ThreadAbortError extends ThreadError {
164
+ /**
165
+ * @param {string} message - Abort description.
166
+ */
167
+ constructor(message) {
168
+ super(message);
169
+ /** @readonly @type {'ThreadAbortError'} */
170
+ this.name = 'ThreadAbortError';
171
+ }
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // ThreadTerminatedError
176
+ // ---------------------------------------------------------------------------
177
+
178
+ /**
179
+ * Thrown when you try to use a thread that has been terminated, or when
180
+ * the pool shuts down while tasks are still queued.
181
+ *
182
+ * Once `thread.terminate()` or `pool.terminateAll()` is called, every
183
+ * pending and future task promise rejects with this error.
184
+ *
185
+ * @example
186
+ * ```js
187
+ * import { createThread, ThreadTerminatedError } from './index.js';
188
+ *
189
+ * const t = createThread((x) => x * 2);
190
+ * t.terminate();
191
+ *
192
+ * try {
193
+ * await t.run(5);
194
+ * } catch (err) {
195
+ * if (err instanceof ThreadTerminatedError) {
196
+ * console.error('Cannot use a terminated thread');
197
+ * }
198
+ * }
199
+ * ```
200
+ */
201
+ export class ThreadTerminatedError extends ThreadError {
202
+ /**
203
+ * @param {string} message - Termination description.
204
+ */
205
+ constructor(message) {
206
+ super(message);
207
+ /** @readonly @type {'ThreadTerminatedError'} */
208
+ this.name = 'ThreadTerminatedError';
209
+ }
210
+ }
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // ThreadHealthError
214
+ // ---------------------------------------------------------------------------
215
+
216
+ /**
217
+ * Thrown when a health check fails.
218
+ *
219
+ * When `healthCheckInterval` is set, the thread periodically pings the
220
+ * worker. If the worker does not respond within `healthCheckTimeout`,
221
+ * or if the ping throws, the thread considers the worker dead and emits
222
+ * this error.
223
+ *
224
+ * In practice this error is **not** thrown to your task promises. Instead
225
+ * it is logged internally and triggers an automatic restart. You will
226
+ * see it if you listen to the `error` event:
227
+ *
228
+ * @example
229
+ * ```js
230
+ * import { createThread, ThreadHealthError } from './index.js';
231
+ *
232
+ * const t = createThread((x) => x, {
233
+ * healthCheckInterval: 5000,
234
+ * healthCheckTimeout: 2000,
235
+ * });
236
+ *
237
+ * t.on('error', (info) => {
238
+ * if (info.error instanceof ThreadHealthError) {
239
+ * console.warn('Worker is unhealthy, restarting...');
240
+ * }
241
+ * });
242
+ * ```
243
+ */
244
+ export class ThreadHealthError extends ThreadError {
245
+ /**
246
+ * @param {string} message - Health check failure description.
247
+ */
248
+ constructor(message) {
249
+ super(message);
250
+ /** @readonly @type {'ThreadHealthError'} */
251
+ this.name = 'ThreadHealthError';
252
+ }
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // ThreadDependencyError
257
+ // ---------------------------------------------------------------------------
258
+
259
+ /**
260
+ * Thrown when a pool task's dependency fails.
261
+ *
262
+ * When you submit a task with `dependsOn: [taskId]` and the dependency
263
+ * task fails (for any reason), the dependent task is automatically
264
+ * rejected with this error instead of running.
265
+ *
266
+ * The error message includes the failed dependency's ID and the original
267
+ * error message so you can trace the failure chain.
268
+ *
269
+ * @example
270
+ * ```js
271
+ * import { createPool, ThreadDependencyError } from './index.js';
272
+ *
273
+ * const pool = createPool(2, (x) => {
274
+ * if (x < 0) throw new Error('Negative input');
275
+ * return x * 2;
276
+ * });
277
+ *
278
+ * const a = pool.run(-1); // will fail: "Negative input"
279
+ * const b = pool.run(10, {
280
+ * dependsOn: [a.id], // depends on 'a'
281
+ * });
282
+ *
283
+ * try {
284
+ * await b.promise;
285
+ * } catch (err) {
286
+ * if (err instanceof ThreadDependencyError) {
287
+ * // "Dependency task 0 failed: Negative input"
288
+ * console.error(err.message);
289
+ * }
290
+ * }
291
+ * ```
292
+ */
293
+ export class ThreadDependencyError extends ThreadError {
294
+ /**
295
+ * @param {string} message - Dependency failure description.
296
+ */
297
+ constructor(message) {
298
+ super(message);
299
+ /** @readonly @type {'ThreadDependencyError'} */
300
+ this.name = 'ThreadDependencyError';
301
+ }
302
+ }
303
+
304
+ // ---------------------------------------------------------------------------
305
+ // GPUComputeError
306
+ // ---------------------------------------------------------------------------
307
+
308
+ /**
309
+ * Thrown when a GPU compute operation fails.
310
+ *
311
+ * Covers all GPU-specific failures: shader compilation errors, buffer
312
+ * size violations, device loss, and dispatch failures. The error's
313
+ * `cause` property contains the original WebGPU error when available.
314
+ *
315
+ * @example
316
+ * ```js
317
+ * import { GPUComputeError } from './error.js';
318
+ *
319
+ * try {
320
+ * await gpu.compute({ inputs: {}, outputBuffers: {} });
321
+ * } catch (err) {
322
+ * if (err instanceof GPUComputeError) {
323
+ * console.error(`GPU failed: ${err.message}`);
324
+ * if (err.cause) console.error('Original:', err.cause);
325
+ * }
326
+ * }
327
+ * ```
328
+ */
329
+ export class GPUComputeError extends ThreadError {
330
+ /**
331
+ * @param {string} message - Human-readable GPU error description.
332
+ * @param {Error} [cause] - Original WebGPU error (if any).
333
+ */
334
+ constructor(message, cause = undefined) {
335
+ super(message);
336
+ /** @readonly @type {'GPUComputeError'} */
337
+ this.name = 'GPUComputeError';
338
+ if (cause !== undefined) this.cause = cause;
339
+ }
340
+ }