@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
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
|