@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/README.md ADDED
@@ -0,0 +1,1156 @@
1
+ # thread
2
+
3
+ **Enterprise Web Worker & GPU Compute Framework**
4
+
5
+ A modular, feature-rich library for running CPU-intensive work in **Web Workers** and **WebGPU compute shaders**. Includes a thread pool with work-stealing, dependency tracking, health checks, 57+ built-in GPU operations, and framework adapters for Preact, React, Svelte, Vue, Solid, Zustand, Redux, and Preact Signals.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Environment Compatibility](#environment-compatibility)
14
+ - [Supported Runtimes](#supported-runtimes)
15
+ - [Environment-Specific Entry Points](#environment-specific-entry-points)
16
+ - [Environment Detection](#environment-detection)
17
+ - [Config in Different Environments](#config-in-different-environments)
18
+ - [GPU in Different Environments](#gpu-in-different-environments)
19
+ - [Configuration](#configuration)
20
+ - [Config File Format](#config-file-format)
21
+ - [Supported Frameworks](#supported-frameworks)
22
+ - [Supported State Managers](#supported-state-managers)
23
+ - [Custom Framework / State Manager](#custom-framework--state-manager)
24
+ - [Architecture](#architecture)
25
+ - [CPU Workers](#cpu-workers)
26
+ - [Single Thread](#single-thread)
27
+ - [Thread Pool](#thread-pool)
28
+ - [Stateful Workers](#stateful-workers)
29
+ - [Streaming](#streaming)
30
+ - [Function Chaining](#function-chaining)
31
+ - [GPU Compute](#gpu-compute)
32
+ - [One-Liner Ops](#one-liner-ops)
33
+ - [Multi-Op Pipelines](#multi-op-pipelines)
34
+ - [Built-in Operations](#built-in-operations)
35
+ - [Pipeline Chaining](#pipeline-chaining)
36
+ - [Fluent Data Pipelines](#fluent-data-pipelines)
37
+ - [Map (Parallel Transform)](#map-parallel-transform)
38
+ - [Reductions](#reductions)
39
+ - [Profiling & Auto-Tuning](#profiling--auto-tuning)
40
+ - [Framework Integration](#framework-integration)
41
+ - [Hooks](#hooks)
42
+ - [Adapters](#adapters)
43
+ - [Error Handling](#error-handling)
44
+ - [TypeScript](#typescript)
45
+ - [Examples](#examples)
46
+ - [Real-Life Use Cases](#real-life-use-cases)
47
+ - [API Reference](#api-reference)
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ npm install thread
55
+ # or
56
+ bun add thread
57
+ ```
58
+
59
+ Peer dependency (optional — only needed for hooks):
60
+ ```bash
61
+ npm install preact # or react
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Quick Start
67
+
68
+ ### CPU Workers
69
+
70
+ ```js
71
+ import { createThread, createPool } from 'thread';
72
+
73
+ // Single thread — run a function in a Web Worker
74
+ const t = createThread((x) => x * 2);
75
+ console.log(await t.run(5)); // 10
76
+
77
+ // Thread pool — distribute work across 4 workers
78
+ const pool = createPool(4, (x) => x + 1);
79
+ const { id, promise } = pool.run(1);
80
+ console.log(await promise); // 2
81
+ ```
82
+
83
+ ### GPU Compute
84
+
85
+ ```js
86
+ import { createGPUOp } from 'thread';
87
+
88
+ // Define a GPU operation from a plain JS function
89
+ const gpu = createGPUOp('double', (data) => data.value * 2);
90
+
91
+ const result = await gpu.run('double', {
92
+ inputs: { data: new Float32Array([1, 2, 3]) },
93
+ outputs: { result: 3 },
94
+ });
95
+ console.log(result.result); // Float32Array [2, 4, 6]
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Environment Compatibility
101
+
102
+ thread works across **5+ JavaScript runtimes** out of the box. The library auto-detects your environment and uses the appropriate APIs for Workers, GPU, and config loading.
103
+
104
+ ### Supported Runtimes
105
+
106
+ | Runtime | Worker API | GPU API | Config Loading | Import Path |
107
+ |---------|-----------|---------|---------------|-------------|
108
+ | **Browser** | Web Workers (Blob URLs) | `navigator.gpu` | Filesystem (Node) or programmatic | `thread` |
109
+ | **Node.js** | `worker_threads` (eval) | `@aspect-build/webgpu-node` | `fs.readFileSync` | `thread/node` |
110
+ | **Bun** | `worker_threads` (eval) | `bun-webgpu` (Dawn FFI) or `Bun.gpu` | `fs.readFileSync` | `thread/node` |
111
+ | **Deno** | Deno Workers (Blob URLs) | `Deno.gpu` | `Deno.readTextFileSync` | `thread/deno` |
112
+ | **Edge** | Web Workers (Blob URLs) | `navigator.gpu` | Programmatic only | `thread/edge` |
113
+ | **Cloudflare Workers** | No Worker support | `navigator.gpu` | Programmatic only | `thread/edge` |
114
+
115
+ ### Environment-Specific Entry Points
116
+
117
+ Use the appropriate import for your runtime:
118
+
119
+ ```js
120
+ // Browser (auto-detected)
121
+ import { Thread, ThreadPool } from 'thread';
122
+
123
+ // Node.js / Bun
124
+ import { Thread, ThreadPool } from 'thread/node';
125
+
126
+ // Deno
127
+ import { Thread, ThreadPool } from 'thread/deno';
128
+
129
+ // Edge (Cloudflare Workers, Vercel Edge, Netlify Edge)
130
+ import { Thread, ThreadPool } from 'thread/edge';
131
+ ```
132
+
133
+ The main `thread` export uses **conditional exports** — your bundler/runtime automatically picks the right entry point based on the `browser`, `node`, `bun`, `deno`, and `edge` conditions.
134
+
135
+ ### Environment Detection
136
+
137
+ Use the `env` object to detect your runtime and available features:
138
+
139
+ ```js
140
+ import { env } from 'thread/env';
141
+
142
+ console.log(env.runtime); // 'browser' | 'node' | 'bun' | 'deno' | 'edge'
143
+ console.log(env.isNode); // true
144
+ console.log(env.hasWorker); // true
145
+ console.log(env.hasGPU); // false (unless WebGPU is enabled)
146
+ console.log(env.hasFS); // true (Node/Bun only)
147
+ console.log(env.isMainThread); // true
148
+
149
+ // Platform-specific helpers
150
+ const cwd = env.getCwd(); // '/home/user/project'
151
+ const fs = env.requireModule('node:fs'); // fs module or null
152
+ const exists = env.fileExists('./config.js'); // true/false
153
+ const path = env.resolvePath('src', 'index.js'); // '/home/user/project/src/index.js'
154
+ ```
155
+
156
+ ### Config in Different Environments
157
+
158
+ **Browser/Edge:** Config must be set programmatically (no filesystem access):
159
+
160
+ ```js
161
+ import { setProgrammaticConfig } from 'thread/config';
162
+
163
+ setProgrammaticConfig({
164
+ framework: 'react',
165
+ stateManager: 'zustand',
166
+ gpu: { workgroupSize: 512 },
167
+ });
168
+ ```
169
+
170
+ **Node.js/Bun/Deno:** Use the standard `thread.config.js` file in your project root:
171
+
172
+ ```js
173
+ // thread.config.js
174
+ import { defineConfig } from 'thread/config';
175
+
176
+ export default defineConfig({
177
+ framework: 'preact',
178
+ stateManager: 'zustand',
179
+ });
180
+ ```
181
+
182
+ ### GPU in Different Environments
183
+
184
+ WebGPU availability varies by runtime:
185
+
186
+ ```js
187
+ import { isGPUAvailable, gpuEnv } from 'thread/gpu/env';
188
+
189
+ // Async check (recommended)
190
+ if (await isGPUAvailable()) {
191
+ const gpu = new GPUCompute({ shader: myShader });
192
+ await gpu.init();
193
+ } else {
194
+ // Fall back to CPU
195
+ const cpu = createGPUWithFallback(shader, cpuFn);
196
+ }
197
+
198
+ // Operations defined with a JS function auto-fallback to CPU when GPU
199
+ // dispatch fails (e.g. adapter found but shader execution unsupported).
200
+ gpu.define('double', (data) => data.value * 2);
201
+ const result = await gpu.run('double', { inputs: { data } }); // works either way
202
+
203
+ // Sync check (fast, less reliable)
204
+ if (gpuEnv.sync) {
205
+ console.log('WebGPU detected');
206
+ }
207
+
208
+ // Detailed diagnostics
209
+ const info = await gpuEnv.info();
210
+ console.log(info);
211
+ // { available: true, runtime: 'browser', adapterName: 'NVIDIA RTX 4090', ... }
212
+ ```
213
+
214
+ **Node.js GPU:** Install a WebGPU binding:
215
+ ```bash
216
+ npm install @aspect-build/webgpu-node
217
+ # or
218
+ npm install node-webgpu
219
+ ```
220
+
221
+ **Bun GPU:** Install `bun-webgpu` (Dawn FFI bindings):
222
+ ```bash
223
+ bun add bun-webgpu
224
+ ```
225
+
226
+ The library auto-detects `bun-webgpu` and calls `setupGlobals()` to expose `navigator.gpu`. You can also call it eagerly for sync detection:
227
+ ```js
228
+ import { setupGlobals } from 'bun-webgpu';
229
+ setupGlobals();
230
+ ```
231
+
232
+ **GPU fallback:** When a GPU adapter is detected but shader dispatch fails (e.g. missing DXC/Dawn binaries), operations defined with a JS function automatically fall back to the CPU path. No manual fallback code needed.
233
+
234
+ ---
235
+
236
+ ## Configuration
237
+
238
+ thread uses a config file (`thread.config.js`) in your project root to
239
+ configure the library globally. **All fields are optional** — thread
240
+ works out of the box with sensible defaults (Preact + Zustand).
241
+
242
+ ### Config File Format
243
+
244
+ ```js
245
+ // thread.config.js
246
+ import { defineConfig } from 'thread/config';
247
+
248
+ export default defineConfig({
249
+ // UI framework — hooks are auto-resolved at load time
250
+ framework: 'react', // 'preact' | 'react' | 'svelte' | 'vue' | 'solid' | 'angular' | 'custom'
251
+
252
+ // State manager — determines which adapter shortcuts are available
253
+ stateManager: 'zustand', // 'zustand' | 'signals' | 'redux' | 'jotai' | 'mobx' | 'vanilla' | 'custom'
254
+
255
+ // GPU compute defaults (applied to all GPUCompute instances)
256
+ gpu: {
257
+ workgroupSize: 256, // Must match @workgroup_size(N) in shaders
258
+ maxBufferSize: 256 * 1024 * 1024, // 256 MB max buffer
259
+ powerPreference: 'high-performance', // 'low-power' | 'high-performance'
260
+ bunWebgpu: true, // Auto-import bun-webgpu in Bun (if installed)
261
+ fallbackAdapter: true, // Request software adapter if no GPU found
262
+ // cpuFallback: (input) => { ... }, // Called when WebGPU unavailable
263
+ },
264
+
265
+ // Thread defaults (applied to all threads via factories)
266
+ thread: {
267
+ timeout: 30_000, // 30 second task timeout
268
+ // idleTimeout: 60_000, // Auto-terminate idle threads
269
+ // healthCheckInterval: 10_000, // Ping threads every 10s
270
+ },
271
+
272
+ // Pool defaults (applied to all pools via factories)
273
+ pool: {
274
+ autoRestart: true, // Replace crashed workers
275
+ enableStealing: true, // Work-stealing between threads
276
+ // maxSize: 16, // Cap thread count
277
+ },
278
+
279
+ // Development options
280
+ dev: {
281
+ log: false, // Forward worker logs to main thread
282
+ metrics: false, // Enable metrics collection
283
+ warnOnLongTask: 0, // Warn if task > N ms (0 = disabled)
284
+ },
285
+ });
286
+ ```
287
+
288
+ ### Supported Frameworks
289
+
290
+ | Framework | Import path | Hook support | Notes |
291
+ |-----------|------------|-------------|-------|
292
+ | Preact | `preact/hooks` | Full | Default. Identical API to React. |
293
+ | React | `react` | Full | Works with React 18+. |
294
+ | Svelte | `svelte/reactivity` | Partial | Svelte 5 runes. Some hooks are shims. |
295
+ | Vue | `vue` | Partial | Composition API. `ref()` → `useState`. |
296
+ | Solid | `solid-js` | Full | Exports hooks directly. |
297
+ | Angular | — | — | Coming soon. Use `custom` with a shim. |
298
+ | Custom | — | — | User provides `customHookSource`. |
299
+
300
+ ### Supported State Managers
301
+
302
+ | State Manager | Adapter type | Thread adapter | GPU adapter | Notes |
303
+ |--------------|-------------|---------------|-------------|-------|
304
+ | Zustand | action | `createZustandBinder` | `createGPUBinder` | Default. Action-based. |
305
+ | Signals | signal | `createSignalBinder` | `createGPUSignalBinder` | Preact Signals. `.value` based. |
306
+ | Redux | setter | `createStoreBinder` | `createGPUStoreBinder` | Generic setter callback. |
307
+ | Jotai | setter | `createStoreBinder` | `createGPUStoreBinder` | Uses Redux adapter. |
308
+ | MobX | setter | `createStoreBinder` | `createGPUStoreBinder` | Uses Redux adapter. |
309
+ | Vanilla | setter | `createStoreBinder` | `createGPUStoreBinder` | Any setter function. |
310
+ | Custom | — | — | — | User provides `customAdapter`. |
311
+
312
+ ### Custom Framework / State Manager
313
+
314
+ ```js
315
+ // thread.config.js
316
+ import { defineConfig } from 'thread/config';
317
+
318
+ export default defineConfig({
319
+ framework: 'custom',
320
+ customHookSource: async () => {
321
+ // Import your framework's hooks
322
+ const mod = await import('my-framework/hooks');
323
+ return {
324
+ useState: mod.useState,
325
+ useEffect: mod.useEffect,
326
+ useRef: mod.useRef,
327
+ useCallback: mod.useCallback,
328
+ useMemo: mod.useMemo,
329
+ };
330
+ },
331
+
332
+ stateManager: 'custom',
333
+ customAdapter: (instance, store, action) => ({
334
+ run: async (...args) => {
335
+ const result = await instance.run(...args);
336
+ store.dispatch({ type: action, payload: result });
337
+ },
338
+ destroy: () => {},
339
+ }),
340
+ });
341
+ ```
342
+
343
+ ---
344
+
345
+ ## Architecture
346
+
347
+ ```
348
+ thread/
349
+ ├── package.json
350
+ ├── README.md
351
+ ├── thread.config.js.example Example config file
352
+ ├── src/
353
+ │ ├── index.js Main barrel export (re-exports everything)
354
+ │ ├── config.js Config entry (thread/config)
355
+ │ ├── types.js TypeScript type definitions
356
+ │ ├── thread.js Thread class — single worker lifecycle
357
+ │ ├── pool.js ThreadPool — priority queue, deps, work-stealing
358
+ │ ├── metrix.js Metrics — performance counters
359
+ │ ├── serializer.js Serializer — JSON-safe with function support
360
+ │ ├── error.js Error hierarchy (ThreadError, GPUComputeError, …)
361
+ │ ├── factory.js Factory functions (createThread, createPool, …)
362
+ │ ├── hooks.js Framework-agnostic hooks (useThread, usePool, …)
363
+ │ ├── adapters.js Framework adapters (Zustand, Signals, …)
364
+ │ ├── config/
365
+ │ │ ├── index.js Config loader (getConfig, setConfig)
366
+ │ │ ├── define.js defineConfig() helper
367
+ │ │ ├── schema.js Defaults, validation, mergeWithDefaults
368
+ │ │ ├── frameworks.js Framework → hooks resolver
369
+ │ │ └── adapters.js State manager → adapter registry
370
+ │ └── gpu/
371
+ │ ├── index.js GPU barrel export
372
+ │ ├── gpu.js GPUCompute class — WebGPU executor
373
+ │ ├── shaders.js Built-in ops, shader DSL (57+ ops)
374
+ │ ├── helpers.js Auto-boxing, transpile, jsToWgsl
375
+ │ ├── chains.js PipelineChain + DataPipelineChain
376
+ │ ├── special.js Multi-pass: matmul, reduce, histogram, scan
377
+ │ ├── hooks.js GPU-specific hooks (useGPU, useGPURun, …)
378
+ │ └── adapters.js GPU-specific adapters (Zustand, Signals, …)
379
+ └── tests/
380
+ └── thread.spec.js Full test suite (238 tests, 759 expects)
381
+ ```
382
+
383
+ ---
384
+
385
+ ## CPU Workers
386
+
387
+ ### Single Thread
388
+
389
+ Run a function in a dedicated Web Worker. The thread is created lazily
390
+ on the first `run()` call and can be terminated when no longer needed.
391
+
392
+ ```js
393
+ import { createThread } from 'thread';
394
+
395
+ // Simple function — auto-serialized and sent to a worker
396
+ const t = createThread((a, b) => a + b);
397
+ console.log(await t.run(3, 4)); // 7
398
+
399
+ // With options
400
+ const t2 = createThread((data) => process(data), {
401
+ timeout: 10_000, // reject if task takes >10s
402
+ idleTimeout: 60_000, // auto-terminate after 60s idle
403
+ concurrency: 2, // allow 2 concurrent tasks
404
+ onLog: (msg) => console.log('[worker]', msg),
405
+ onTiming: (ms) => console.log(`Done in ${ms.toFixed(1)}ms`),
406
+ });
407
+
408
+ // Cleanup
409
+ await t.terminate();
410
+ ```
411
+
412
+ ### Thread Pool
413
+
414
+ Distribute work across multiple workers with a priority queue,
415
+ dependency tracking, and work-stealing.
416
+
417
+ ```js
418
+ import { createPool } from 'thread';
419
+
420
+ const pool = createPool(4, (n) => {
421
+ let sum = 0;
422
+ for (let i = 0; i < n; i++) sum += Math.sqrt(i);
423
+ return sum;
424
+ });
425
+
426
+ // Run 20 tasks across 4 workers
427
+ const results = await Promise.all(
428
+ Array.from({ length: 20 }, (_, i) => pool.run(1_000_000 + i).promise)
429
+ );
430
+
431
+ // Priority queue — lower number = higher priority
432
+ pool.run(urgentData, { priority: 0 });
433
+ pool.run(batchData, { priority: 10 });
434
+
435
+ // Dependencies — chain tasks
436
+ const step1 = pool.run('raw-data');
437
+ const step2 = pool.run(step1.id, { dependsOn: [step1.id] });
438
+ await step2.promise;
439
+
440
+ // Dynamic resizing
441
+ pool.scaleTo(8); // burst mode
442
+ await pool.drain();
443
+ pool.scaleTo(2); // back to normal
444
+ ```
445
+
446
+ ### Stateful Workers
447
+
448
+ Workers with persistent state across tasks (setup → exec → cleanup):
449
+
450
+ ```js
451
+ import { createThread } from 'thread';
452
+
453
+ const db = createThread({
454
+ setup() {
455
+ return { conn: openDatabase(), cache: new Map() };
456
+ },
457
+ async exec(state, query, ctx) {
458
+ ctx.log(`Running: ${query}`);
459
+ ctx.reportProgress(0);
460
+ if (state.cache.has(query)) return state.cache.get(query);
461
+ const result = await state.conn.query(query);
462
+ state.cache.set(query, result);
463
+ ctx.reportProgress(1);
464
+ return result;
465
+ },
466
+ async cleanup(state) {
467
+ await state.conn.close();
468
+ },
469
+ }, { timeout: 10_000 });
470
+
471
+ const rows = await db.run('SELECT * FROM users');
472
+ await db.terminateGracefully(); // cleanup runs automatically
473
+ ```
474
+
475
+ ### Streaming
476
+
477
+ Process large arrays in chunks, yielding results as they complete:
478
+
479
+ ```js
480
+ const t = createThread((chunk) => chunk.map((x) => x * x));
481
+ const data = Array.from({ length: 10_000 }, (_, i) => i);
482
+
483
+ // Process in chunks of 1000
484
+ for (let i = 0; i < data.length; i += 1000) {
485
+ const chunk = data.slice(i, i + 1000);
486
+ const result = await t.run(chunk);
487
+ console.log(`Chunk ${i / 1000 + 1} done:`, result);
488
+ }
489
+ ```
490
+
491
+ ### Function Chaining
492
+
493
+ Chain multiple operations on the same thread:
494
+
495
+ ```js
496
+ const t = createThread((x) => x * 2);
497
+
498
+ const chain = t.runChain(5); // start with 5
499
+ chain.run((x) => x + 1); // → 11
500
+ chain.run((x) => x * 3); // → 33
501
+ const final = await chain.run((x) => x - 1); // → 32
502
+ ```
503
+
504
+ ---
505
+
506
+ ## GPU Compute
507
+
508
+ ### One-Liner Ops
509
+
510
+ Define GPU operations from plain JavaScript functions. The function is
511
+ automatically transpiled to WGSL and executed on the GPU:
512
+
513
+ ```js
514
+ import { createGPUOp } from 'thread';
515
+
516
+ const gpu = createGPUOp('double', (data) => data.value * 2);
517
+
518
+ const result = await gpu.run('double', {
519
+ inputs: { data: new Float32Array([1, 2, 3, 4]) },
520
+ outputs: { result: 4 },
521
+ });
522
+ console.log(result.result); // Float32Array [2, 4, 6, 8]
523
+ ```
524
+
525
+ ### Multi-Op Pipelines
526
+
527
+ Register multiple operations and switch between them:
528
+
529
+ ```js
530
+ import { createGPUPipeline } from 'thread';
531
+
532
+ const gpu = createGPUPipeline([
533
+ ['double', (data) => data.value * 2],
534
+ ['negate', (data) => -data.value],
535
+ ['sqrt', (data) => Math.sqrt(data.value)],
536
+ ]);
537
+
538
+ // Run any registered op
539
+ await gpu.run('double', { inputs: { data }, outputs: { result: 4 } });
540
+ await gpu.run('sqrt', { inputs: { data }, outputs: { result: 4 } });
541
+ ```
542
+
543
+ ### Built-in Operations
544
+
545
+ 57+ built-in operations — zero shader code required:
546
+
547
+ ```js
548
+ import { GPUCompute } from 'thread';
549
+
550
+ const gpu = new GPUCompute();
551
+
552
+ // Arithmetic
553
+ await gpu.run('multiply', { inputs: { a, b }, outputs: { result: N } });
554
+ await gpu.run('add', { inputs: { a, b }, outputs: { result: N } });
555
+ await gpu.run('subtract', { inputs: { a, b }, outputs: { result: N } });
556
+ await gpu.run('divide', { inputs: { a, b }, outputs: { result: N } });
557
+ await gpu.run('power', { inputs: { a, b }, outputs: { result: N } });
558
+
559
+ // Math
560
+ await gpu.run('sqrt', { inputs: { data }, outputs: { result: N } });
561
+ await gpu.run('abs', { inputs: { data }, outputs: { result: N } });
562
+ await gpu.run('sin', { inputs: { data }, outputs: { result: N } });
563
+ await gpu.run('exp', { inputs: { data }, outputs: { result: N } });
564
+ await gpu.run('log', { inputs: { data }, outputs: { result: N } });
565
+
566
+ // Comparison
567
+ await gpu.run('clamp', { inputs: { data }, uniforms: { min, max }, outputs: { result: N } });
568
+ await gpu.run('lerp', { inputs: { a, b }, uniforms: { t }, outputs: { result: N } });
569
+
570
+ // Aggregation
571
+ await gpu.run('reduce_sum', { inputs: { data }, outputs: { result: N } });
572
+ await gpu.run('reduce_max', { inputs: { data }, outputs: { result: N } });
573
+ await gpu.run('reduce_min', { inputs: { data }, outputs: { result: N } });
574
+
575
+ // Matrix
576
+ await gpu.run('matmul', { inputs: { A, B }, uniforms: { M, N, K }, outputs: { C } });
577
+ ```
578
+
579
+ ### Pipeline Chaining
580
+
581
+ Chain multiple GPU operations without copying data back to the CPU:
582
+
583
+ ```js
584
+ const gpu = new GPUCompute();
585
+ gpu.define('scale', (data, { factor }) => data.value * factor);
586
+ gpu.define('offset', (data, { bias }) => data.value + bias);
587
+
588
+ const chain = gpu.pipe('scale', 4)
589
+ .run({ data: new Float32Array([1, 2, 3, 4]) }, { factor: new Float32Array([2]) })
590
+ .pipe('offset', 4)
591
+ .run({}, { bias: new Float32Array([10]) });
592
+
593
+ const result = await chain.result();
594
+ // [12, 14, 16, 18]
595
+ ```
596
+
597
+ ### Fluent Data Pipelines
598
+
599
+ Use named methods for a fluent API:
600
+
601
+ ```js
602
+ const gpu = new GPUCompute();
603
+ gpu.define('normalize', (data, { mean, std }) => (data.value - mean) / std);
604
+ gpu.define('clamp', (data, { min, max }) => Math.min(Math.max(data.value, min), max));
605
+
606
+ const pipeline = gpu.pipe('normalize', 1000)
607
+ .normalize({ mean: new Float32Array([50]), std: new Float32Array([15]) })
608
+ .clamp({ min: new Float32Array([0]), max: new Float32Array([100]) })
609
+ .result();
610
+
611
+ const normalized = await pipeline;
612
+ ```
613
+
614
+ ### Map (Parallel Transform)
615
+
616
+ Apply a function to every element in parallel:
617
+
618
+ ```js
619
+ const gpu = new GPUCompute();
620
+ const data = new Float32Array(1_000_000);
621
+
622
+ // Parallel map — runs on GPU
623
+ const result = await gpu.map(data, (x) => x * x + 1);
624
+ ```
625
+
626
+ ### Reductions
627
+
628
+ Sum, min, max across large datasets:
629
+
630
+ ```js
631
+ import { createGPUReducer } from 'thread';
632
+
633
+ const gpu = createGPUReducer();
634
+ const data = new Float32Array(1_000_000);
635
+
636
+ const sum = await gpu.run('reduce_sum', { inputs: { data }, outputs: { result: 1 } });
637
+ const max = await gpu.run('reduce_max', { inputs: { data }, outputs: { result: 1 } });
638
+ const min = await gpu.run('reduce_min', { inputs: { data }, outputs: { result: 1 } });
639
+
640
+ console.log(`Sum: ${sum.result[0]}, Min: ${min.result[0]}, Max: ${max.result[0]}`);
641
+ ```
642
+
643
+ ### Profiling & Auto-Tuning
644
+
645
+ Measure GPU performance and auto-tune workgroup size:
646
+
647
+ ```js
648
+ const gpu = new GPUCompute();
649
+
650
+ // Profile a single op
651
+ const profile = await gpu.profile('multiply', {
652
+ inputs: { a: new Float32Array(1024), b: new Float32Array(1024) },
653
+ outputs: { result: 1024 },
654
+ });
655
+ console.log(profile); // { avg: 0.12, min: 0.11, max: 0.15, ... }
656
+
657
+ // Auto-tune workgroup size
658
+ const optimal = await gpu.autoTune('multiply', {
659
+ inputs: { a: new Float32Array(1024), b: new Float32Array(1024) },
660
+ outputs: { result: 1024 },
661
+ });
662
+ console.log(`Optimal workgroup size: ${optimal.workgroupSize}`);
663
+ ```
664
+
665
+ ---
666
+
667
+ ## Framework Integration
668
+
669
+ ### Hooks
670
+
671
+ Lifecycle-bound hooks for Preact/React. The framework is resolved
672
+ automatically from your `thread.config.js`:
673
+
674
+ ```jsx
675
+ import { useThread, usePool, useGPU } from 'thread';
676
+
677
+ // Thread hook — auto-creates and auto-destroys a thread
678
+ function DataProcessor({ data }) {
679
+ const { run, loading, error, result } = useThread(
680
+ (items) => items.map((x) => x * 2),
681
+ { timeout: 10_000 }
682
+ );
683
+
684
+ return (
685
+ <div>
686
+ {loading && <Spinner />}
687
+ {error && <Error message={error} />}
688
+ {result && <Results data={result} />}
689
+ <button onClick={() => run(data)} disabled={loading}>Process</button>
690
+ </div>
691
+ );
692
+ }
693
+
694
+ // Pool hook — metrics update automatically
695
+ function ParallelWorker({ items }) {
696
+ const { run, status, metrics } = usePool(4, (item) => process(item));
697
+
698
+ return (
699
+ <div>
700
+ <button onClick={() => items.forEach((item) => run(item))}>Process all</button>
701
+ <p>{status().busy} threads busy</p>
702
+ <p>Avg: {metrics.avg?.toFixed(1)}ms</p>
703
+ </div>
704
+ );
705
+ }
706
+
707
+ // GPU hook — reactive GPU state
708
+ function GPUDashboard() {
709
+ const { run, result, loading, status } = useGPU();
710
+
711
+ return (
712
+ <div>
713
+ <p>GPU: {status}</p>
714
+ <button onClick={() => run('multiply', data1, data2)} disabled={loading}>
715
+ {loading ? 'Computing...' : 'Run GPU'}
716
+ </button>
717
+ {result && <pre>{JSON.stringify(Array.from(result))}</pre>}
718
+ </div>
719
+ );
720
+ }
721
+ ```
722
+
723
+ ### Adapters
724
+
725
+ Bind threads/GPUs to your state manager. Results flow automatically:
726
+
727
+ ```js
728
+ import { create } from 'zustand';
729
+ import { createThread, createZustandBinder } from 'thread';
730
+
731
+ // Zustand store
732
+ const useStore = create((set) => ({
733
+ data: null,
734
+ error: null,
735
+ setData: (data) => set({ data, error: null }),
736
+ setError: (error) => set({ error }),
737
+ }));
738
+
739
+ // Thread + adapter
740
+ const thread = createThread((query) => db.query(query));
741
+ const binder = createZustandBinder(thread, useStore, 'setData', {
742
+ errorAction: 'setError',
743
+ });
744
+
745
+ // Every run() result flows to the store
746
+ await binder.run('SELECT * FROM users');
747
+ // → useStore.getState().data is now the result
748
+ ```
749
+
750
+ ---
751
+
752
+ ## Error Handling
753
+
754
+ ```js
755
+ import {
756
+ ThreadError,
757
+ ThreadTimeoutError,
758
+ ThreadAbortError,
759
+ ThreadTerminatedError,
760
+ ThreadHealthError,
761
+ ThreadDependencyError,
762
+ GPUComputeError,
763
+ } from 'thread';
764
+
765
+ try {
766
+ await thread.run(data);
767
+ } catch (err) {
768
+ if (err instanceof ThreadTimeoutError) {
769
+ console.error('Task timed out');
770
+ } else if (err instanceof ThreadAbortError) {
771
+ console.error('Task was aborted');
772
+ } else if (err instanceof GPUComputeError) {
773
+ console.error('GPU failed:', err.message);
774
+ } else {
775
+ throw err; // re-throw non-thread errors
776
+ }
777
+ }
778
+ ```
779
+
780
+ | Error | When |
781
+ |--------------------------|-------------------------------------------|
782
+ | `ThreadTimeoutError` | Task exceeded its timeout |
783
+ | `ThreadAbortError` | Task was aborted via AbortController |
784
+ | `ThreadTerminatedError` | Thread/pool was terminated |
785
+ | `ThreadHealthError` | Health check failed (internal) |
786
+ | `ThreadDependencyError` | A dependency task failed |
787
+ | `GPUComputeError` | Shader compilation, buffer, or dispatch |
788
+
789
+ ---
790
+
791
+ ## TypeScript
792
+
793
+ ```ts
794
+ import type {
795
+ // Thread & Pool
796
+ ThreadDefinition,
797
+ ThreadOptions,
798
+ ThreadRunOptions,
799
+ PoolOptions,
800
+ PoolRunOptions,
801
+ PoolTaskResult,
802
+ PoolStatus,
803
+ // GPU
804
+ GPUComputeOptions,
805
+ GPUComputeInput,
806
+ GPUComputeSnapshot,
807
+ OpDeclaration,
808
+ // Hooks
809
+ UseThreadReturn,
810
+ UsePoolReturn,
811
+ UseGPUReturn,
812
+ UseGPURunReturn,
813
+ // Adapters
814
+ ZustandBinderOptions,
815
+ SignalBinderOptions,
816
+ StoreBinderOptions,
817
+ BinderHandle,
818
+ Signal,
819
+ // Common
820
+ MetricsSnapshot,
821
+ ThreadEventName,
822
+ // Config
823
+ threadConfig,
824
+ threadGPUConfig,
825
+ threadThreadConfig,
826
+ threadPoolConfig,
827
+ threadDevConfig,
828
+ } from 'thread/types';
829
+ ```
830
+
831
+ ---
832
+
833
+ ## Examples
834
+
835
+ 18 runnable examples covering every feature — run them with `bun run examples/<file>`.
836
+
837
+ ### Basic (01-06)
838
+
839
+ | Example | File | Description |
840
+ |---------|------|-------------|
841
+ | Basic Thread | `01-basic-thread.js` | Single worker, function definition, error handling |
842
+ | Thread Pool | `02-thread-pool.js` | Multi-worker pool with priorities and scaling |
843
+ | Stateful Worker | `03-stateful-worker.js` | Worker with persistent state (setup/exec/cleanup) |
844
+ | Serializer | `04-serializer.js` | JSON-safe serialization with function support |
845
+ | Error Handling | `05-error-handling.js` | Error hierarchy, catch patterns, abort/cancel |
846
+ | Metrics | `06-metrics.js` | Performance counters, throughput, error rates |
847
+
848
+ ### Advanced (07-12)
849
+
850
+ | Example | File | Description |
851
+ |---------|------|-------------|
852
+ | Streaming | `07-streaming.js` | Chunked processing with AbortController |
853
+ | Function Chain | `08-function-chain.js` | Chain multiple ops on same thread |
854
+ | Hot-Swap & Events | `09-hot-swap-events.js` | Replace worker function at runtime, event subscriptions |
855
+ | Pool Pipeline | `10-pool-pipeline.js` | Chain pool tasks with dependency tracking |
856
+ | Dynamic Pool | `11-dynamic-pool.js` | Scale pool up/down, drain, work stealing |
857
+ | Managed Worker | `12-managed-worker.js` | Auto-logging, metrics, health checks |
858
+
859
+ ### GPU Compute (13-18)
860
+
861
+ | Example | File | Description |
862
+ |---------|------|-------------|
863
+ | GPU Basics | `13-gpu-compute-basics.js` | define, run, map, inspect — all with CPU fallback |
864
+ | Custom Ops | `14-gpu-custom-ops.js` | Uniforms, multi-input, index-aware ops |
865
+ | Pipeline Chains | `15-gpu-pipeline-chains.js` | pipe(), PipelineChain, DataPipelineChain |
866
+ | Special Ops | `16-gpu-special-ops.js` | reduce, scan, histogram, argmax, matmul |
867
+ | Batch & Profile | `17-gpu-batch-profile.js` | runBatch, profile, runMany |
868
+ | Fallback Workflow | `18-gpu-fallback-workflow.js` | isGPUAvailable, createGPUWithFallback, helpers |
869
+
870
+ ---
871
+
872
+ ## Real-Life Use Cases
873
+
874
+ ### 1. Financial Data Processing (Real-time EMA)
875
+
876
+ Compute Exponential Moving Averages on streaming price data using the GPU.
877
+ Processing 1M price ticks that would take seconds on CPU completes in
878
+ milliseconds on GPU:
879
+
880
+ ```js
881
+ import { createGPUOp } from 'thread';
882
+
883
+ const gpu = createGPUOp('ema', (data, { alpha }) =>
884
+ data.value * alpha + data.index * (1 - alpha)
885
+ );
886
+
887
+ // Process 1M price ticks in parallel
888
+ const prices = new Float32Array(priceTicks);
889
+ const ema = await gpu.run('ema', {
890
+ inputs: { data: prices },
891
+ uniforms: { alpha: new Float32Array([0.1]) },
892
+ outputs: { result: prices.length },
893
+ });
894
+ ```
895
+
896
+ ### 2. Image Processing Pipeline
897
+
898
+ Process image pixels through a chain of filters without copying data
899
+ back to the CPU between steps:
900
+
901
+ ```js
902
+ import { GPUCompute } from 'thread';
903
+
904
+ const gpu = new GPUCompute();
905
+ gpu.define('grayscale', (data) => data.value * 0.299 + data.value * 0.587 + data.value * 0.114);
906
+ gpu.define('contrast', (data, { factor }) => (data.value - 0.5) * factor + 0.5);
907
+ gpu.define('clamp01', (data) => Math.min(Math.max(data.value, 0), 1));
908
+
909
+ const output = await gpu.pipe(pixelData)
910
+ .grayscale()
911
+ .contrast({ factor: new Float32Array([1.2]) })
912
+ .clamp01()
913
+ .result();
914
+ ```
915
+
916
+ ### 3. Scientific Computing — Matrix Multiply
917
+
918
+ Multiply two matrices on the GPU. The built-in `matmul` op handles
919
+ the tiling and parallel reduction automatically:
920
+
921
+ ```js
922
+ import { GPUCompute } from 'thread';
923
+
924
+ const gpu = new GPUCompute();
925
+ const A = new Float32Array([1, 2, 3, 4, 5, 6]); // 2x3
926
+ const B = new Float32Array([7, 8, 9, 10, 11, 12]); // 3x2
927
+
928
+ const result = await gpu.run('matmul', {
929
+ inputs: { A, B },
930
+ uniforms: {
931
+ M: new Float32Array([2]),
932
+ N: new Float32Array([2]),
933
+ K: new Float32Array([3]),
934
+ },
935
+ outputs: { C: 4 },
936
+ });
937
+ // C = [58, 64, 139, 154]
938
+ ```
939
+
940
+ ### 4. Data Analytics — Histogram & Statistics
941
+
942
+ Build a histogram and find extremes in sensor data. Three GPU passes
943
+ process millions of data points in parallel:
944
+
945
+ ```js
946
+ import { createGPUReducer } from 'thread';
947
+
948
+ const gpu = createGPUReducer();
949
+ const sensorData = new Float32Array(sensorReadings);
950
+
951
+ // Find min, max, sum in parallel
952
+ const sum = await gpu.run('reduce_sum', { inputs: { data: sensorData }, outputs: { result: sensorData.length } });
953
+ const max = await gpu.run('reduce_max', { inputs: { data: sensorData }, outputs: { result: sensorData.length } });
954
+ const min = await gpu.run('reduce_min', { inputs: { data: sensorData }, outputs: { result: sensorData.length } });
955
+
956
+ const mean = sum.result[0] / sensorData.length;
957
+ ```
958
+
959
+ ### 5. Parallel File Processing (Thread Pool)
960
+
961
+ Process thousands of files across multiple workers. The pool
962
+ automatically distributes work and handles failures:
963
+
964
+ ```js
965
+ import { createPool } from 'thread';
966
+
967
+ const pool = createPool(8, async (filePath) => {
968
+ const text = await fetch(filePath).then(r => r.text());
969
+ return { path: filePath, wordCount: text.split(/\s+/).length };
970
+ });
971
+
972
+ const files = await glob('**/*.md');
973
+ const results = await Promise.all(
974
+ files.map(f => pool.run(f).promise)
975
+ );
976
+
977
+ console.log(`Processed ${results.length} files`);
978
+ ```
979
+
980
+ ### 6. Real-Time Dashboard (React + Zustand)
981
+
982
+ Bind GPU compute results to a Zustand store for reactive UI updates.
983
+ The adapter handles the bridge between GPU and React state:
984
+
985
+ ```jsx
986
+ import { create } from 'zustand';
987
+ import { createGPUOp, createGPUBinder } from 'thread';
988
+
989
+ const useStore = create((set) => ({
990
+ metrics: null,
991
+ setMetrics: (data) => set({ metrics: data }),
992
+ }));
993
+
994
+ const gpu = createGPUOp('normalize', (data, { mean, std }) =>
995
+ (data.value - mean) / std
996
+ );
997
+
998
+ const binder = createGPUBinder(gpu, useStore, 'setMetrics');
999
+
1000
+ // In a component — click triggers GPU compute, result flows to store
1001
+ function Dashboard() {
1002
+ const metrics = useStore(s => s.metrics);
1003
+ return (
1004
+ <div>
1005
+ <button onClick={() => binder.run('normalize', { inputs: { data: rawData }, ... })}>
1006
+ Normalize
1007
+ </button>
1008
+ {metrics && <pre>{JSON.stringify(metrics)}</pre>}
1009
+ </div>
1010
+ );
1011
+ }
1012
+ ```
1013
+
1014
+ ### 7. WebGPU + CPU Fallback (Universal)
1015
+
1016
+ Gracefully fall back to CPU when WebGPU is unavailable. Works on any
1017
+ device — GPU if available, CPU otherwise:
1018
+
1019
+ ```js
1020
+ import { createGPUWithFallback } from 'thread';
1021
+
1022
+ const gpu = createGPUWithFallback(
1023
+ `@compute @workgroup_size(256) fn main(...) { ... }`,
1024
+ (input) => {
1025
+ // CPU fallback — same logic, slower
1026
+ const { data } = input.inputs;
1027
+ const result = new Float32Array(data.length);
1028
+ for (let i = 0; i < data.length; i++) result[i] = data[i] * 2;
1029
+ return { result };
1030
+ }
1031
+ );
1032
+
1033
+ // Works on any device — GPU if available, CPU otherwise
1034
+ const result = await gpu.computeWithFallback({
1035
+ inputs: { data: new Float32Array([1, 2, 3]) },
1036
+ outputBuffers: { result: 3 },
1037
+ });
1038
+ ```
1039
+
1040
+ ### 8. Search Engine — Parallel Indexing (React + Pool)
1041
+
1042
+ Build a search index by processing documents in parallel across a thread
1043
+ pool, with real-time progress in the UI:
1044
+
1045
+ ```jsx
1046
+ import { createPool, usePool, usePoolMetrics } from 'thread';
1047
+
1048
+ // Create pool outside component (shared across renders)
1049
+ const indexPool = createPool(4, async (doc) => {
1050
+ const tokens = tokenize(doc.content);
1051
+ const embeddings = await computeEmbeddings(tokens);
1052
+ return { id: doc.id, tokens, embeddings };
1053
+ });
1054
+
1055
+ function SearchIndexer({ documents }) {
1056
+ const { run, status } = usePool(indexPool);
1057
+ const metrics = usePoolMetrics(indexPool, 500);
1058
+
1059
+ const handleIndex = () => {
1060
+ documents.forEach((doc) => run(doc));
1061
+ };
1062
+
1063
+ return (
1064
+ <div>
1065
+ <button onClick={handleIndex}>Index {documents.length} docs</button>
1066
+ <p>{status().busy}/{status().total} workers busy</p>
1067
+ <p>{metrics.count}/{documents.length} indexed</p>
1068
+ <p>{metrics.throughput?.toFixed(0)} docs/sec</p>
1069
+ </div>
1070
+ );
1071
+ }
1072
+ ```
1073
+
1074
+ ---
1075
+
1076
+ ## API Reference
1077
+
1078
+ ### Classes
1079
+
1080
+ | Class | Description |
1081
+ |-------|-------------|
1082
+ | `Thread` | Single Web Worker with full lifecycle |
1083
+ | `ThreadPool` | Worker pool with priorities, deps, work-stealing |
1084
+ | `GPUCompute` | WebGPU compute shader executor |
1085
+ | `PipelineChain` | Fluent multi-step GPU pipeline |
1086
+ | `DataPipelineChain` | Data-carrying pipeline with named methods |
1087
+ | `Metrics` | Performance counter (avg, throughput, errors) |
1088
+ | `Serializer` | JSON-safe serialization with function support |
1089
+
1090
+ ### Factories
1091
+
1092
+ | Function | Description |
1093
+ |----------|-------------|
1094
+ | `createThread(def, opts)` | Create a validated thread |
1095
+ | `createPool(size, def, opts)` | Create a validated pool |
1096
+ | `createWorker(fn, opts)` | One-liner thread with error logging |
1097
+ | `createWorkerDef(def)` | Reusable worker definition |
1098
+ | `createManagedThread(def, opts)` | Thread with auto logging, metrics, health |
1099
+ | `createGPUCompute(opts)` | Create a GPU instance |
1100
+ | `createGPUOp(name, fn, opts)` | GPU with a single JS-defined op |
1101
+ | `createGPUPipeline(ops, opts)` | GPU with multiple pre-registered ops |
1102
+ | `createGPUReducer(opts)` | GPU ready for reductions |
1103
+ | `createGPUWithFallback(shader, fn, opts)` | GPU with CPU fallback |
1104
+
1105
+ ### Hooks
1106
+
1107
+ | Hook | Description |
1108
+ |------|-------------|
1109
+ | `useThread(def, opts)` | Lifecycle-bound thread with reactive state |
1110
+ | `usePool(size, def, opts)` | Lifecycle-bound pool with metrics |
1111
+ | `useThreadMetrics(thread, ms)` | Poll thread metrics |
1112
+ | `usePoolMetrics(pool, ms)` | Poll pool metrics |
1113
+ | `useThreadWorker(thread)` | Stable `run` for external thread |
1114
+ | `useThreadEvent(thread, event, handler)` | Subscribe to thread events |
1115
+ | `useGPU(opts)` | Lifecycle-bound GPU with reactive state |
1116
+ | `useGPURun(gpu)` | Stable `run` for external GPU |
1117
+ | `useGPUMetrics(gpu, ms)` | Poll GPU metrics |
1118
+ | `useGPUStatus(gpu, ms)` | Reactive GPU status string |
1119
+
1120
+ ### Adapters
1121
+
1122
+ | Function | Description |
1123
+ |----------|-------------|
1124
+ | `createZustandBinder(thread, store, action, opts)` | Bind thread → Zustand |
1125
+ | `createSignalBinder(thread, signal, opts)` | Bind thread → Signal |
1126
+ | `createStoreBinder(thread, setter, opts)` | Bind thread → any setter |
1127
+ | `createPoolBinder(pool, setter, opts)` | Bind pool → any setter |
1128
+ | `createGPUBinder(gpu, store, action, opts)` | Bind GPU → Zustand |
1129
+ | `createGPUSignalBinder(gpu, signal, opts)` | Bind GPU → Signal |
1130
+ | `createGPUStoreBinder(gpu, setter, opts)` | Bind GPU → any setter |
1131
+
1132
+ ### Config
1133
+
1134
+ | Function | Description |
1135
+ |----------|-------------|
1136
+ | `defineConfig(config)` | Create a validated, frozen config object |
1137
+ | `getConfig()` | Get the resolved config (cached) |
1138
+ | `setConfig()` | Clear caches and force re-resolution |
1139
+
1140
+ ### Errors
1141
+
1142
+ | Error | When |
1143
+ |-------|------|
1144
+ | `ThreadError` | Base class for all threads errors |
1145
+ | `ThreadTimeoutError` | Task exceeded its timeout |
1146
+ | `ThreadAbortError` | Task was aborted |
1147
+ | `ThreadTerminatedError` | Thread/pool was terminated |
1148
+ | `ThreadHealthError` | Health check failed |
1149
+ | `ThreadDependencyError` | A dependency task failed |
1150
+ | `GPUComputeError` | GPU shader, buffer, or dispatch failure |
1151
+
1152
+ ---
1153
+
1154
+ ## License
1155
+
1156
+ MIT — Nadrif LLC