From 3009df67327e73ad18ac003ea3050e29c5795c54 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 18:44:52 +0000
Subject: [PATCH 01/37] fix(.gitignore): add .npmrc to ignore list to prevent
committing npm tokens
---
.gitignore | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.gitignore b/.gitignore
index 07a8410..e355f9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,9 @@ yarn-error.log*
.env.local
.env.*.local
+# npm tokens - NEVER commit these!
+.npmrc
+
# Temporary files
*.tmp
*.temp
From db7a1e39db97105e377023b6df0b9d16184d66e6 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 18:46:41 +0000
Subject: [PATCH 02/37] fix(ci): update publish conditions and refine package
publishing steps
---
.github/workflows/ci.yml | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 306957b..c0efb76 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,7 +38,7 @@ jobs:
publish:
needs: build
runs-on: ubuntu-latest
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
@@ -53,9 +53,14 @@ jobs:
run: npm ci
- name: Build packages
- run: npm run build
+ run: npm run build:packages
+
+ - name: Publish @computekit/core
+ run: cd packages/core && npm publish --access public || echo "Already published or failed"
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- - name: Publish to npm
- run: npm publish --workspaces --access public
+ - name: Publish @computekit/react
+ run: cd packages/react && npm publish --access public || echo "Already published or failed"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
From 45100c07f4d864c66aeb5c7c829cb830b025abc7 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 18:51:35 +0000
Subject: [PATCH 03/37] chore: trigger publish workflow
From 9e5acea7379290ed04a82eaeeea0fb75fde465b3 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 18:58:31 +0000
Subject: [PATCH 04/37] ci: add dedicated publish workflow
---
.github/workflows/publish.yml | 37 +++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 .github/workflows/publish.yml
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..2fa05bf
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,37 @@
+name: Publish to npm
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [main]
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js 22.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build packages
+ run: npm run build:packages
+
+ - name: Publish @computekit/core
+ run: cd packages/core && npm publish --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ continue-on-error: true
+
+ - name: Publish @computekit/react
+ run: cd packages/react && npm publish --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ continue-on-error: true
From 88b920c3f618e44321a7b0acb64f5a2178330b6f Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 23:10:02 +0000
Subject: [PATCH 05/37] refactor: remove vanilla demo example and associated
files
- Deleted the `package.json`, `vite.config.ts`, and `src/main.js` files from the `examples/vanilla-demo` directory.
- Updated the main `package.json` to remove the build and dev commands related to the vanilla demo.
- Removed the `wasm` package and its associated files, including `asconfig.json`, `index.ts`, and `package.json`.
- Added a new `blur.ts` file in the `compute` directory with optimized blur functions.
- Updated the `react` package to include optional peer dependencies for `@types/react`.
- Made minor adjustments to the `react` package's `index.tsx` and `tsconfig.json` for improved TypeScript support.
- Added comprehensive README files for both `@computekit/core` and `@computekit/react` packages.
---
README.md | 7 +-
.../wasm/assembly/index.ts => compute/blur.ts | 0
compute/index.ts | 1 +
examples/react-demo/public/compute.d.ts | 12 +
examples/react-demo/public/compute.js | 6 +
examples/react-demo/public/direct-test.html | 96 -----
examples/react-demo/public/minimal-test.html | 154 --------
examples/react-demo/public/test-worker.js | 72 ----
examples/react-demo/public/test.html | 149 --------
examples/vanilla-demo/index.html | 350 ------------------
examples/vanilla-demo/package.json | 17 -
examples/vanilla-demo/src/main.js | 156 --------
examples/vanilla-demo/vite.config.ts | 14 -
package.json | 3 +-
packages/core/README.md | 220 +++++++++++
packages/react/README.md | 280 ++++++++++++++
packages/react/package.json | 8 +-
packages/react/src/index.tsx | 4 +-
packages/react/tsconfig.json | 3 +-
packages/wasm/asconfig.json | 24 --
packages/wasm/package.json | 34 --
21 files changed, 535 insertions(+), 1075 deletions(-)
rename packages/wasm/assembly/index.ts => compute/blur.ts (100%)
delete mode 100644 examples/react-demo/public/direct-test.html
delete mode 100644 examples/react-demo/public/minimal-test.html
delete mode 100644 examples/react-demo/public/test-worker.js
delete mode 100644 examples/react-demo/public/test.html
delete mode 100644 examples/vanilla-demo/index.html
delete mode 100644 examples/vanilla-demo/package.json
delete mode 100644 examples/vanilla-demo/src/main.js
delete mode 100644 examples/vanilla-demo/vite.config.ts
create mode 100644 packages/core/README.md
create mode 100644 packages/react/README.md
delete mode 100644 packages/wasm/asconfig.json
delete mode 100644 packages/wasm/package.json
diff --git a/README.md b/README.md
index 30f3f7e..b69f141 100644
--- a/README.md
+++ b/README.md
@@ -500,13 +500,14 @@ computekit/
│ └── package.json
│
├── compute/ # AssemblyScript functions
+│ ├── blur.ts
│ ├── fibonacci.ts
│ ├── mandelbrot.ts
-│ └── matrix.ts
+│ ├── matrix.ts
+│ └── sum.ts
│
├── examples/
-│ ├── react-demo/ # React example app
-│ └── vanilla-demo/ # Vanilla JS example
+│ └── react-demo/ # React example app
│
└── docs/ # Documentation
```
diff --git a/packages/wasm/assembly/index.ts b/compute/blur.ts
similarity index 100%
rename from packages/wasm/assembly/index.ts
rename to compute/blur.ts
diff --git a/compute/index.ts b/compute/index.ts
index 9bfc9f1..8a25f04 100644
--- a/compute/index.ts
+++ b/compute/index.ts
@@ -15,3 +15,4 @@ export {
vectorMagnitude,
vectorNormalize,
} from './matrix';
+export { getBufferPtr, blurImage } from './blur';
diff --git a/examples/react-demo/public/compute.d.ts b/examples/react-demo/public/compute.d.ts
index 93c2d66..75e1b04 100644
--- a/examples/react-demo/public/compute.d.ts
+++ b/examples/react-demo/public/compute.d.ts
@@ -109,3 +109,15 @@ export declare function vectorMagnitude(v: Float64Array): number;
* @returns `~lib/typedarray/Float64Array`
*/
export declare function vectorNormalize(v: Float64Array): Float64Array;
+/**
+ * compute/blur/getBufferPtr
+ * @returns `usize`
+ */
+export declare function getBufferPtr(): number;
+/**
+ * compute/blur/blurImage
+ * @param width `i32`
+ * @param height `i32`
+ * @param passes `i32`
+ */
+export declare function blurImage(width: number, height: number, passes: number): void;
diff --git a/examples/react-demo/public/compute.js b/examples/react-demo/public/compute.js
index d88d0d7..b0a3f29 100644
--- a/examples/react-demo/public/compute.js
+++ b/examples/react-demo/public/compute.js
@@ -99,6 +99,10 @@ async function instantiate(module, imports = {}) {
v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
return __liftTypedArray(Float64Array, exports.vectorNormalize(v) >>> 0);
},
+ getBufferPtr() {
+ // compute/blur/getBufferPtr() => usize
+ return exports.getBufferPtr() >>> 0;
+ },
}, exports);
function __liftString(pointer) {
if (!pointer) return null;
@@ -188,6 +192,8 @@ export const {
dotProduct,
vectorMagnitude,
vectorNormalize,
+ getBufferPtr,
+ blurImage,
} = await (async url => instantiate(
await (async () => {
const isNodeOrBun = typeof process != "undefined" && process.versions != null && (process.versions.node != null || process.versions.bun != null);
diff --git a/examples/react-demo/public/direct-test.html b/examples/react-demo/public/direct-test.html
deleted file mode 100644
index b223408..0000000
--- a/examples/react-demo/public/direct-test.html
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
- ComputeKit Direct Test
-
-
-
- ComputeKit Direct Test
- Open browser DevTools (F12) to see console logs.
-
-
-
-
-
- Output:
- Click "Run Test" to begin...
-
-
-
-
diff --git a/examples/react-demo/public/minimal-test.html b/examples/react-demo/public/minimal-test.html
deleted file mode 100644
index 2600914..0000000
--- a/examples/react-demo/public/minimal-test.html
+++ /dev/null
@@ -1,154 +0,0 @@
-
-
-
- Minimal Worker Test
-
-
-
- Minimal Worker Test
-
-
-
-
diff --git a/examples/react-demo/public/test-worker.js b/examples/react-demo/public/test-worker.js
deleted file mode 100644
index 1c04a35..0000000
--- a/examples/react-demo/public/test-worker.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// Test Worker - standalone version of the generated worker code
-console.log('[Test Worker] Starting...');
-
-const functions = {
- double: (n) => n * 2,
- fibonacci: (n) => {
- if (n <= 1) return String(n);
- let a = BigInt(0);
- let b = BigInt(1);
- for (let i = 2; i <= n; i++) {
- const temp = a + b;
- a = b;
- b = temp;
- }
- return b.toString();
- },
-};
-
-console.log('[Test Worker] Functions loaded:', Object.keys(functions));
-
-self.onmessage = async function (e) {
- console.log('[Test Worker] Received message:', e.data);
- const { id, type, payload, timestamp } = e.data;
-
- if (type === 'execute') {
- const { functionName, input } = payload;
- console.log('[Test Worker] Executing:', functionName, 'with input:', input);
- const fn = functions[functionName];
-
- if (!fn) {
- self.postMessage({
- id,
- type: 'error',
- payload: { message: 'Function not found: ' + functionName },
- timestamp: Date.now(),
- });
- return;
- }
-
- const startTime = performance.now();
-
- try {
- console.log('[Test Worker] Calling function...');
- const result = await fn(input);
- console.log('[Test Worker] Function returned:', typeof result, result);
- const duration = performance.now() - startTime;
-
- self.postMessage({
- id,
- type: 'result',
- payload: { data: result, duration },
- timestamp: Date.now(),
- });
- console.log('[Test Worker] Result sent');
- } catch (err) {
- console.error('[Test Worker] Error:', err);
- self.postMessage({
- id,
- type: 'error',
- payload: {
- message: err.message || 'Unknown error',
- stack: err.stack,
- },
- timestamp: Date.now(),
- });
- }
- }
-};
-
-// Signal ready
-self.postMessage({ type: 'ready', timestamp: Date.now() });
-console.log('[Test Worker] Ready signal sent');
diff --git a/examples/react-demo/public/test.html b/examples/react-demo/public/test.html
deleted file mode 100644
index 41da4ba..0000000
--- a/examples/react-demo/public/test.html
+++ /dev/null
@@ -1,149 +0,0 @@
-
-
-
- Worker Test
-
-
-
- Worker Test
-
-
-
-
- Click a button to test...
-
-
-
-
diff --git a/examples/vanilla-demo/index.html b/examples/vanilla-demo/index.html
deleted file mode 100644
index b5e4bd3..0000000
--- a/examples/vanilla-demo/index.html
+++ /dev/null
@@ -1,350 +0,0 @@
-
-
-
-
-
- ComputeKit Vanilla Demo
-
-
-
-
-
-
- ComputeKit
Vanilla Demo
-
- Ready
-
-
-
-
ComputeKit - Vanilla JavaScript
-
No framework needed. Just import and compute.
-
-
-
📦 Basic Usage
-
Import ComputeKit, register a function, and run it asynchronously.
-
-
- import { ComputeKit }
- from
- '@computekit/core';
-
- const kit = new
- ComputeKit();
-
- kit.register('fibonacci', (n) => {
- if (n <=
- 1) return n;
- let a = 0,
- b = 1;
- for (let i
- = 2; i <= n; i++) [a, b] = [b, a + b];
- return b;
- });
-
- const result =
- await kit.run('fibonacci', 50);
- console.log(result);
-
-
-
-
-
-
-
-
- Click the button to compute...
-
-
-
-
-
⚡ Blocking vs Non-Blocking
-
Compare main thread execution (freezes UI) vs Worker execution (smooth).
-
-
-
-
🔴 Main Thread (Blocking)
-
-
-
UI will freeze
-
-
-
🟢 Web Worker (Async)
-
-
-
UI stays responsive
-
-
-
-
-
-
-
-
-
-
-
-
📊 Array Processing
-
Process large arrays without blocking the main thread.
-
-
-
-
-
-
- Click to sum a large array...
-
-
-
-
-
-
-
diff --git a/examples/vanilla-demo/package.json b/examples/vanilla-demo/package.json
deleted file mode 100644
index 66f1d86..0000000
--- a/examples/vanilla-demo/package.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "vanilla-demo",
- "version": "0.0.0",
- "private": true,
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview"
- },
- "dependencies": {
- "@computekit/core": "*"
- },
- "devDependencies": {
- "vite": "^5.0.10"
- }
-}
diff --git a/examples/vanilla-demo/src/main.js b/examples/vanilla-demo/src/main.js
deleted file mode 100644
index a7641e2..0000000
--- a/examples/vanilla-demo/src/main.js
+++ /dev/null
@@ -1,156 +0,0 @@
-/**
- * ComputeKit Vanilla JavaScript Demo
- */
-
-import { ComputeKit } from '@computekit/core';
-
-// Initialize ComputeKit
-const kit = new ComputeKit({
- maxWorkers: 4,
- debug: true,
-});
-
-// Register compute functions
-kit.register('fibonacci', (n) => {
- if (n <= 1) return BigInt(n);
- let a = 0n;
- let b = 1n;
- for (let i = 2; i <= n; i++) {
- [a, b] = [b, a + b];
- }
- return b.toString();
-});
-
-kit.register('heavyTask', (iterations) => {
- // Simulate CPU-intensive work
- let result = 0;
- for (let i = 0; i < iterations; i++) {
- result += Math.sqrt(i) * Math.sin(i);
- }
- return result;
-});
-
-kit.register('sum', (arr) => {
- let total = 0;
- for (let i = 0; i < arr.length; i++) {
- total += arr[i];
- }
- return total;
-});
-
-// Helper functions
-function setStatus(elementId, text, isLoading = false) {
- const el = document.getElementById(elementId);
- if (isLoading) {
- el.innerHTML = ' ' + text + '';
- } else {
- el.textContent = text;
- }
-}
-
-function formatNumber(num) {
- return new Intl.NumberFormat().format(num);
-}
-
-// Fibonacci Demo
-document.getElementById('btn-fib').addEventListener('click', async () => {
- const output = document.getElementById('fib-output');
- setStatus('fib-status', 'Computing...', true);
-
- const start = performance.now();
-
- try {
- const result = await kit.run('fibonacci', 50);
- const duration = performance.now() - start;
-
- output.innerHTML = `Fibonacci(50) = ${result}
-Time: ${duration.toFixed(2)}ms`;
- setStatus('fib-status', '✓ Done');
- } catch (err) {
- output.innerHTML = `Error: ${err.message}`;
- setStatus('fib-status', '✗ Error');
- }
-
- setTimeout(() => setStatus('fib-status', ''), 2000);
-});
-
-// Blocking vs Async Comparison
-const HEAVY_ITERATIONS = 50000000;
-
-function heavyTaskBlocking(iterations) {
- let result = 0;
- for (let i = 0; i < iterations; i++) {
- result += Math.sqrt(i) * Math.sin(i);
- }
- return result;
-}
-
-document.getElementById('btn-blocking').addEventListener('click', () => {
- setStatus('compare-status', 'Running (UI will freeze)...', true);
- document.getElementById('blocking-result').textContent = '...';
-
- // Small delay to show the status before freezing
- setTimeout(() => {
- const start = performance.now();
- heavyTaskBlocking(HEAVY_ITERATIONS);
- const duration = performance.now() - start;
-
- document.getElementById('blocking-result').textContent = `${duration.toFixed(0)}ms`;
- setStatus('compare-status', '');
- }, 50);
-});
-
-document.getElementById('btn-async').addEventListener('click', async () => {
- setStatus('compare-status', 'Running in Worker...', true);
- document.getElementById('async-result').textContent = '...';
-
- const start = performance.now();
- await kit.run('heavyTask', HEAVY_ITERATIONS);
- const duration = performance.now() - start;
-
- document.getElementById('async-result').textContent = `${duration.toFixed(0)}ms`;
- setStatus('compare-status', '');
-});
-
-// Array Sum Demo
-document.getElementById('btn-sum').addEventListener('click', async () => {
- const output = document.getElementById('sum-output');
- setStatus('sum-status', 'Generating array...', true);
-
- // Generate large array
- const size = 10_000_000;
- const arr = new Array(size);
- for (let i = 0; i < size; i++) {
- arr[i] = Math.floor(Math.random() * 100);
- }
-
- setStatus('sum-status', 'Computing sum...', true);
-
- const start = performance.now();
-
- try {
- const result = await kit.run('sum', arr);
- const duration = performance.now() - start;
-
- output.innerHTML = `Sum of ${formatNumber(size)} random numbers:
-${formatNumber(result)}
-Time: ${duration.toFixed(0)}ms
-Throughput: ${formatNumber(Math.round((size / duration) * 1000))} ops/sec`;
- setStatus('sum-status', '✓ Done');
- } catch (err) {
- output.innerHTML = `Error: ${err.message}`;
- setStatus('sum-status', '✗ Error');
- }
-
- setTimeout(() => setStatus('sum-status', ''), 2000);
-});
-
-// Update global status
-const globalStatus = document.getElementById('status');
-kit.on?.('task:start', () => {
- globalStatus.innerHTML = ' Working...';
-});
-
-// Log ready state
-console.log('ComputeKit Demo Ready!');
-console.log('Pool Stats:', kit.getStats());
diff --git a/examples/vanilla-demo/vite.config.ts b/examples/vanilla-demo/vite.config.ts
deleted file mode 100644
index d366faa..0000000
--- a/examples/vanilla-demo/vite.config.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { defineConfig } from 'vite';
-
-export default defineConfig({
- server: {
- port: 3001,
- headers: {
- 'Cross-Origin-Opener-Policy': 'same-origin',
- 'Cross-Origin-Embedder-Policy': 'require-corp',
- },
- },
- optimizeDeps: {
- exclude: ['@computekit/core'],
- },
-});
diff --git a/package.json b/package.json
index 6c3da92..a47d421 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,9 @@
"build:wasm": "asc compute/index.ts --outFile examples/react-demo/public/compute.wasm --bindings esm --optimize",
"build:core": "npm run build -w @computekit/core",
"build:react": "npm run build -w @computekit/react",
- "build:examples": "npm run build -w examples/react-demo && npm run build -w examples/vanilla-demo",
+ "build:examples": "npm run build -w examples/react-demo",
"build:packages": "npm run build:core && npm run build:react",
"dev": "npm run dev -w examples/react-demo",
- "dev:vanilla": "npm run dev -w examples/vanilla-demo",
"test": "vitest",
"lint": "eslint packages --ext .ts,.tsx",
"clean": "rimraf packages/*/dist examples/*/dist",
diff --git a/packages/core/README.md b/packages/core/README.md
new file mode 100644
index 0000000..e2697c6
--- /dev/null
+++ b/packages/core/README.md
@@ -0,0 +1,220 @@
+# @computekit/core
+
+The core library for ComputeKit - run heavy computations in Web Workers with WASM support.
+
+## Installation
+
+```bash
+npm install @computekit/core
+```
+
+## Quick Start
+
+```typescript
+import { ComputeKit } from '@computekit/core';
+
+// Create an instance
+const kit = new ComputeKit();
+
+// Register a compute function
+kit.register('fibonacci', (n: number) => {
+ let a = 0,
+ b = 1;
+ for (let i = 0; i < n; i++) {
+ [a, b] = [b, a + b];
+ }
+ return a;
+});
+
+// Run it (non-blocking!)
+const result = await kit.run('fibonacci', 50);
+console.log(result); // 12586269025
+```
+
+## API Reference
+
+### `new ComputeKit(options?)`
+
+Create a new ComputeKit instance.
+
+```typescript
+const kit = new ComputeKit({
+ maxWorkers: 4, // Max workers (default: CPU cores)
+ timeout: 30000, // Default timeout in ms
+ debug: false, // Enable debug logging
+ useSharedMemory: true, // Use SharedArrayBuffer when available
+});
+```
+
+### `kit.register(name, fn)`
+
+Register a function to run in workers.
+
+```typescript
+kit.register('sum', (numbers: number[]) => {
+ return numbers.reduce((a, b) => a + b, 0);
+});
+
+// Async functions work too
+kit.register('fetchAndProcess', async (url: string) => {
+ const res = await fetch(url);
+ const data = await res.json();
+ return processData(data);
+});
+```
+
+### `kit.run(name, input, options?)`
+
+Execute a registered function.
+
+```typescript
+const result = await kit.run('sum', [1, 2, 3, 4, 5]);
+console.log(result); // 15
+```
+
+**Options:**
+
+```typescript
+await kit.run('task', data, {
+ timeout: 5000, // Timeout in ms
+ priority: 10, // Priority (0-10, higher = first)
+ signal: abortController.signal, // AbortSignal for cancellation
+ onProgress: (p) => {
+ // Progress callback
+ console.log(`${p.percent}%`);
+ },
+});
+```
+
+### `kit.runWithMetadata(name, input, options?)`
+
+Execute and get execution metadata.
+
+```typescript
+const result = await kit.runWithMetadata('task', data);
+console.log(result.data); // The result
+console.log(result.duration); // Execution time in ms
+console.log(result.workerId); // Which worker ran it
+```
+
+### `kit.getStats()`
+
+Get worker pool statistics.
+
+```typescript
+const stats = kit.getStats();
+console.log(stats.activeWorkers); // Currently busy workers
+console.log(stats.queueLength); // Tasks waiting
+console.log(stats.tasksCompleted); // Total completed
+```
+
+### `kit.terminate()`
+
+Terminate all workers and clean up.
+
+```typescript
+await kit.terminate();
+```
+
+## WASM Support
+
+Load and use WebAssembly modules:
+
+```typescript
+import { loadWasmModule, loadAssemblyScript } from '@computekit/core';
+
+// Load a WASM module
+const module = await loadWasmModule('/path/to/module.wasm');
+
+// Load AssemblyScript with default imports
+const { exports } = await loadAssemblyScript('/as-module.wasm');
+const result = exports.compute(data);
+```
+
+### WASM Utilities
+
+```typescript
+import {
+ loadWasmModule,
+ loadAndInstantiate,
+ loadAssemblyScript,
+ getMemoryView,
+ copyToWasmMemory,
+ copyFromWasmMemory,
+ isWasmSupported,
+} from '@computekit/core';
+
+// Check support
+if (isWasmSupported()) {
+ // Load and instantiate with custom imports
+ const { instance } = await loadAndInstantiate({
+ source: '/module.wasm',
+ imports: { env: { log: console.log } },
+ memory: { initial: 256, maximum: 512 },
+ });
+}
+```
+
+## Events
+
+ComputeKit emits events for monitoring:
+
+```typescript
+kit.on('worker:created', (info) => console.log('Worker created:', info.id));
+kit.on('worker:terminated', (info) => console.log('Worker terminated:', info.id));
+kit.on('task:start', (taskId, name) => console.log('Task started:', name));
+kit.on('task:complete', (taskId, duration) => console.log('Done in', duration, 'ms'));
+kit.on('task:error', (taskId, error) => console.error('Task failed:', error));
+kit.on('task:progress', (taskId, progress) => console.log(progress.percent, '%'));
+```
+
+## Error Handling
+
+```typescript
+try {
+ await kit.run('task', data);
+} catch (error) {
+ if (error.message.includes('not registered')) {
+ // Function not found
+ } else if (error.message.includes('timed out')) {
+ // Timeout exceeded
+ } else if (error.message.includes('aborted')) {
+ // Cancelled via AbortSignal
+ }
+}
+```
+
+## Cancellation
+
+```typescript
+const controller = new AbortController();
+
+// Start a long task
+const promise = kit.run('heavyTask', data, {
+ signal: controller.signal,
+});
+
+// Cancel it
+controller.abort();
+
+try {
+ await promise;
+} catch (error) {
+ // error.message contains 'aborted'
+}
+```
+
+## TypeScript
+
+Full type safety:
+
+```typescript
+// Generic types flow through
+kit.register('double', (n: number) => n * 2);
+const result = await kit.run('double', 21);
+// result is typed as number
+```
+
+## License
+
+MIT
diff --git a/packages/react/README.md b/packages/react/README.md
new file mode 100644
index 0000000..232368b
--- /dev/null
+++ b/packages/react/README.md
@@ -0,0 +1,280 @@
+# @computekit/react
+
+React bindings for ComputeKit - run heavy computations in Web Workers with simple hooks.
+
+## Installation
+
+```bash
+npm install @computekit/core @computekit/react
+```
+
+## Quick Start
+
+```tsx
+import { ComputeKitProvider, useComputeKit, useCompute } from '@computekit/react';
+
+// 1. Wrap your app with the provider
+function App() {
+ return (
+
+
+
+ );
+}
+
+// 2. Register functions and use them
+function MyApp() {
+ const kit = useComputeKit();
+
+ useEffect(() => {
+ kit.register('fibonacci', (n: number) => {
+ let a = 0,
+ b = 1;
+ for (let i = 0; i < n; i++) {
+ [a, b] = [b, a + b];
+ }
+ return a;
+ });
+ }, [kit]);
+
+ return ;
+}
+
+// 3. Use the compute function
+function FibCalculator() {
+ const { data, loading, error, run } = useCompute('fibonacci');
+
+ return (
+
+
+ {loading &&
Computing...
}
+ {error &&
Error: {error.message}
}
+ {data !== null &&
Result: {data}
}
+
+ );
+}
+```
+
+## API Reference
+
+### ``
+
+Provider component that creates and manages the ComputeKit instance.
+
+```tsx
+
+ {children}
+
+```
+
+**Props:**
+
+| Prop | Type | Description |
+| ---------- | ------------------- | ------------------------------------- |
+| `options` | `ComputeKitOptions` | Configuration options |
+| `instance` | `ComputeKit` | Custom ComputeKit instance (optional) |
+| `children` | `ReactNode` | Child components |
+
+### `useComputeKit()`
+
+Get the ComputeKit instance from context.
+
+```tsx
+const kit = useComputeKit();
+
+// Register functions
+kit.register('myFunc', (input) => /* ... */);
+
+// Run directly
+const result = await kit.run('myFunc', data);
+```
+
+### `useCompute(name, options?)`
+
+Hook for running compute functions with full state management.
+
+```tsx
+const {
+ data, // Result (null until complete)
+ loading, // Boolean loading state
+ error, // Error object if failed
+ progress, // Progress info for long tasks
+ run, // Function to execute
+ reset, // Reset state
+ cancel, // Cancel ongoing computation
+} = useCompute('fibonacci');
+
+// Execute
+await run(50);
+
+// With options
+await run(50, { timeout: 5000 });
+```
+
+**Options:**
+
+| Option | Type | Description |
+| -------------- | -------------------- | -------------------------------------- |
+| `timeout` | `number` | Operation timeout in ms |
+| `autoRun` | `boolean` | Auto-run on mount |
+| `initialInput` | `unknown` | Input for autoRun |
+| `resetOnRun` | `boolean` | Reset state on new run (default: true) |
+| `onProgress` | `(progress) => void` | Progress callback |
+
+### `useComputeCallback(name, options?)`
+
+Returns a memoized async function for simple use cases.
+
+```tsx
+const calculate = useComputeCallback('sum');
+
+const handleClick = async () => {
+ const result = await calculate([1, 2, 3, 4, 5]);
+ console.log(result); // 15
+};
+```
+
+### `useComputeFunction(name, fn, options?)`
+
+Register and use a function in one hook. Useful for component-local compute functions.
+
+```tsx
+const { data, loading, run } = useComputeFunction('double', (n: number) => n * 2);
+
+// Function is registered automatically
+run(21); // data will be 42
+```
+
+### `usePoolStats(refreshInterval?)`
+
+Get worker pool statistics.
+
+```tsx
+const stats = usePoolStats(1000); // Refresh every second
+
+return (
+
+
+ Active: {stats.activeWorkers}/{stats.totalWorkers}
+
+
Queue: {stats.queueLength}
+
Completed: {stats.tasksCompleted}
+
+);
+```
+
+**Returns `PoolStats`:**
+
+| Property | Type | Description |
+| ---------------- | -------- | ---------------------- |
+| `totalWorkers` | `number` | Total worker count |
+| `activeWorkers` | `number` | Currently busy workers |
+| `idleWorkers` | `number` | Currently idle workers |
+| `queueLength` | `number` | Tasks waiting in queue |
+| `tasksCompleted` | `number` | Total completed tasks |
+| `tasksFailed` | `number` | Total failed tasks |
+
+### `useWasmSupport()`
+
+Check if WebAssembly is supported.
+
+```tsx
+const isSupported = useWasmSupport();
+
+if (!isSupported) {
+ return WebAssembly not supported
;
+}
+```
+
+## Patterns
+
+### Cancellation on Unmount
+
+The `useCompute` hook automatically cancels pending operations when the component unmounts:
+
+```tsx
+function MyComponent() {
+ const { run, loading } = useCompute('heavyTask');
+
+ useEffect(() => {
+ run(data); // Automatically cancelled if component unmounts
+ }, []);
+
+ // ...
+}
+```
+
+### Manual Cancellation
+
+```tsx
+function MyComponent() {
+ const { run, cancel, loading } = useCompute('heavyTask');
+
+ return (
+ <>
+
+
+ >
+ );
+}
+```
+
+### Progress Tracking
+
+```tsx
+function MyComponent() {
+ const { run, progress, loading } = useCompute('heavyTask', {
+ onProgress: (p) => console.log(`${p.percent}%`),
+ });
+
+ return (
+ {loading && progress &&
}
+ );
+}
+```
+
+### With AbortController
+
+```tsx
+function MyComponent() {
+ const controller = useRef(new AbortController());
+ const { run } = useCompute('task');
+
+ const handleRun = () => {
+ run(data, { signal: controller.current.signal });
+ };
+
+ const handleCancel = () => {
+ controller.current.abort();
+ controller.current = new AbortController();
+ };
+}
+```
+
+## TypeScript
+
+Full type inference is supported:
+
+```tsx
+// Types are inferred from registration
+kit.register('add', (nums: number[]) => nums.reduce((a, b) => a + b, 0));
+
+// Explicit types for hooks
+const { data, run } = useCompute('add');
+// data: number | null
+// run: (input: number[]) => Promise
+```
+
+## License
+
+MIT
diff --git a/packages/react/package.json b/packages/react/package.json
index 13b9c85..e6d2ee2 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -27,7 +27,13 @@
"@computekit/core": "*"
},
"peerDependencies": {
- "react": ">=17.0.0"
+ "react": ">=17.0.0",
+ "@types/react": ">=17.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
},
"devDependencies": {
"@types/react": "^18.2.45",
diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx
index 5ad7014..e9b06eb 100644
--- a/packages/react/src/index.tsx
+++ b/packages/react/src/index.tsx
@@ -3,7 +3,7 @@
* React hooks and utilities for ComputeKit
*/
-import {
+import React, {
useState,
useEffect,
useCallback,
@@ -60,7 +60,7 @@ export function ComputeKitProvider({
options,
instance,
children,
-}: ComputeKitProviderProps): JSX.Element {
+}: ComputeKitProviderProps): React.ReactElement {
const kit = useMemo(() => {
return instance ?? new ComputeKit(options);
}, [instance, options]);
diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json
index 54ffb39..33b100f 100644
--- a/packages/react/tsconfig.json
+++ b/packages/react/tsconfig.json
@@ -3,7 +3,8 @@
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
- "jsx": "react-jsx"
+ "jsx": "react-jsx",
+ "jsxImportSource": "react"
},
"include": [
"src/**/*"
diff --git a/packages/wasm/asconfig.json b/packages/wasm/asconfig.json
deleted file mode 100644
index aa9bd3b..0000000
--- a/packages/wasm/asconfig.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "targets": {
- "debug": {
- "outFile": "build/debug.wasm",
- "textFile": "build/debug.wat",
- "sourceMap": true,
- "debug": true
- },
- "release": {
- "outFile": "build/release.wasm",
- "textFile": "build/release.wat",
- "sourceMap": true,
- "optimizeLevel": 3,
- "shrinkLevel": 0,
- "converge": false,
- "noAssert": false
- }
- },
- "options": {
- "bindings": "esm",
- "exportRuntime": true,
- "enable": ["simd"]
- }
-}
diff --git a/packages/wasm/package.json b/packages/wasm/package.json
deleted file mode 100644
index 09df823..0000000
--- a/packages/wasm/package.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "name": "@computekit/wasm",
- "version": "0.1.0",
- "description": "WASM compute functions for ComputeKit",
- "type": "module",
- "main": "./build/release.js",
- "types": "./build/release.d.ts",
- "exports": {
- ".": {
- "types": "./build/release.d.ts",
- "import": "./build/release.js"
- },
- "./wasm": "./build/release.wasm"
- },
- "files": [
- "build",
- "assembly"
- ],
- "scripts": {
- "build": "npm run asbuild:release",
- "asbuild:debug": "asc assembly/index.ts --target debug",
- "asbuild:release": "asc assembly/index.ts --target release"
- },
- "author": "Ghassen Lassoued ",
- "license": "MIT",
- "repository": {
- "type": "git",
- "url": "https://github.com/tapava/compute-kit",
- "directory": "packages/wasm"
- },
- "devDependencies": {
- "assemblyscript": "^0.27.0"
- }
-}
From 0e8cef317a4a34f4ddb45b3fda05da8a460b9d67 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 23:19:20 +0000
Subject: [PATCH 06/37] chore: include README.md in package files for core and
react packages
---
packages/core/package.json | 3 ++-
packages/react/package.json | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/core/package.json b/packages/core/package.json
index 097b0ca..03c8201 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -20,7 +20,8 @@
},
"files": [
"dist",
- "src"
+ "src",
+ "README.md"
],
"scripts": {
"build": "tsup",
diff --git a/packages/react/package.json b/packages/react/package.json
index e6d2ee2..a61c447 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -15,7 +15,8 @@
},
"files": [
"dist",
- "src"
+ "src",
+ "README.md"
],
"scripts": {
"build": "tsup",
From 1dbde14260f79f3e4388bbac33c3511b1dff1356 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 23:28:47 +0000
Subject: [PATCH 07/37] chore: bump version to 0.1.1 for core and react
packages
---
packages/core/package.json | 2 +-
packages/react/package.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/core/package.json b/packages/core/package.json
index 03c8201..815d2fd 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@computekit/core",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "Core WASM + Worker toolkit for running heavy computations without blocking the UI",
"type": "module",
"main": "./dist/index.cjs",
diff --git a/packages/react/package.json b/packages/react/package.json
index a61c447..2b022ef 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@computekit/react",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "React bindings for ComputeKit - WASM + Worker toolkit",
"type": "module",
"main": "./dist/index.cjs",
From babfaaa16ba03073eff5baa4aa5e70f87dc7b487 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 23:53:40 +0000
Subject: [PATCH 08/37] feat: add support for remote dependencies in ComputeKit
and update README
---
README.md | 36 +++++++++++++++++++++++++++++-------
packages/core/README.md | 21 +++++++++++++++++++++
packages/core/package.json | 2 +-
packages/core/src/pool.ts | 10 ++++++++++
packages/core/src/types.ts | 2 ++
packages/react/README.md | 18 ++++++++++++++++++
packages/react/package.json | 2 +-
packages/react/src/index.tsx | 25 +++++++++++++++++++++++--
8 files changed, 105 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index b69f141..4d05237 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,8 @@
*Run heavy computations with React hooks. Use WASM for native-speed performance. Keep your UI at 60fps.*
[](https://www.npmjs.com/package/@computekit/core)
-[](https://bundlephobia.com/package/@computekit/core)
+[](https://bundlephobia.com/package/@computekit/core)
+[](https://bundlephobia.com/package/@computekit/react)
[](https://opensource.org/licenses/MIT)
[](https://www.typescriptlang.org/)
@@ -323,11 +324,29 @@ const kit = new ComputeKit(options?: ComputeKitOptions);
#### Options
-| Option | Type | Default | Description |
-| ------------ | --------- | ------------------------------- | ----------------------- |
-| `maxWorkers` | `number` | `navigator.hardwareConcurrency` | Max workers in the pool |
-| `timeout` | `number` | `30000` | Default timeout in ms |
-| `debug` | `boolean` | `false` | Enable debug logging |
+| Option | Type | Default | Description |
+| -------------------- | ---------- | ------------------------------- | ----------------------------------- |
+| `maxWorkers` | `number` | `navigator.hardwareConcurrency` | Max workers in the pool |
+| `timeout` | `number` | `30000` | Default timeout in ms |
+| `debug` | `boolean` | `false` | Enable debug logging |
+| `remoteDependencies` | `string[]` | `[]` | External scripts to load in workers |
+
+### Remote Dependencies
+
+Load external libraries inside your workers:
+
+```typescript
+const kit = new ComputeKit({
+ remoteDependencies: [
+ 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
+ ],
+});
+
+kit.register('processData', (data: number[]) => {
+ // @ts-ignore - lodash loaded via importScripts
+ return _.chunk(data, 3);
+});
+```
#### Methods
@@ -363,12 +382,15 @@ const {
loading, // Boolean loading state
error, // Error if failed
progress, // Progress info
+ status, // 'idle' | 'running' | 'success' | 'error' | 'cancelled'
run, // Function to execute
reset, // Reset state
cancel, // Cancel current operation
} = useCompute(functionName, options?);
```
+````
+
### `useComputeCallback`
Returns a memoized async function (similar to `useCallback`).
@@ -376,7 +398,7 @@ Returns a memoized async function (similar to `useCallback`).
```typescript
const calculate = useComputeCallback('sum');
const result = await calculate([1, 2, 3, 4, 5]);
-```
+````
### `usePoolStats`
diff --git a/packages/core/README.md b/packages/core/README.md
index e2697c6..4602c6c 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -43,9 +43,30 @@ const kit = new ComputeKit({
timeout: 30000, // Default timeout in ms
debug: false, // Enable debug logging
useSharedMemory: true, // Use SharedArrayBuffer when available
+ remoteDependencies: [], // External scripts to load in workers
});
```
+### Remote Dependencies
+
+Load external scripts inside workers using `remoteDependencies`:
+
+```typescript
+const kit = new ComputeKit({
+ remoteDependencies: [
+ 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
+ ],
+});
+
+// Now you can use lodash inside your compute functions
+kit.register('processData', (data: number[]) => {
+ // @ts-ignore - lodash is loaded via importScripts
+ return _.chunk(data, 3);
+});
+```
+
+**Note:** Remote scripts must be served with proper CORS headers.
+
### `kit.register(name, fn)`
Register a function to run in workers.
diff --git a/packages/core/package.json b/packages/core/package.json
index 815d2fd..f4a4ed7 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@computekit/core",
- "version": "0.1.1",
+ "version": "0.1.2",
"description": "Core WASM + Worker toolkit for running heavy computations without blocking the UI",
"type": "module",
"main": "./dist/index.cjs",
diff --git a/packages/core/src/pool.ts b/packages/core/src/pool.ts
index 6ee1282..ca0a431 100644
--- a/packages/core/src/pool.ts
+++ b/packages/core/src/pool.ts
@@ -86,6 +86,7 @@ export class WorkerPool {
debug: options.debug ?? false,
workerPath: options.workerPath ?? '',
useSharedMemory: options.useSharedMemory ?? true,
+ remoteDependencies: options.remoteDependencies ?? [],
};
this.logger = createLogger('ComputeKit:Pool', this.options.debug);
@@ -308,7 +309,16 @@ export class WorkerPool {
Array.from(this.functions.keys())
);
+ // Generate importScripts for remote dependencies
+ const remoteDeps = this.options.remoteDependencies;
+ const importScriptsCode =
+ remoteDeps.length > 0
+ ? `importScripts(${remoteDeps.map((url) => `"${url}"`).join(', ')});`
+ : '';
+
const workerCode = `
+${importScriptsCode}
+
const functions = {
${functionsCode}
};
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index adb658f..9253667 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -15,6 +15,8 @@ export interface ComputeKitOptions {
workerPath?: string;
/** Whether to use SharedArrayBuffer when available (default: true) */
useSharedMemory?: boolean;
+ /** Remote scripts to load in workers via importScripts */
+ remoteDependencies?: string[];
}
/** Options for individual compute operations */
diff --git a/packages/react/README.md b/packages/react/README.md
index 232368b..c084294 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -107,6 +107,7 @@ const {
loading, // Boolean loading state
error, // Error object if failed
progress, // Progress info for long tasks
+ status, // 'idle' | 'running' | 'success' | 'error' | 'cancelled'
run, // Function to execute
reset, // Reset state
cancel, // Cancel ongoing computation
@@ -117,8 +118,25 @@ await run(50);
// With options
await run(50, { timeout: 5000 });
+
+// React to status changes
+if (status === 'success') {
+ console.log('Completed!', data);
+} else if (status === 'error') {
+ console.error('Failed:', error);
+}
```
+**Status values:**
+
+| Status | Description |
+| ----------- | ----------------------------------- |
+| `idle` | Initial state, no computation yet |
+| `running` | Computation in progress |
+| `success` | Completed successfully |
+| `error` | Failed with an error |
+| `cancelled` | Cancelled via `cancel()` or unmount |
+
**Options:**
| Option | Type | Description |
diff --git a/packages/react/package.json b/packages/react/package.json
index 2b022ef..9753254 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@computekit/react",
- "version": "0.1.1",
+ "version": "0.1.2",
"description": "React bindings for ComputeKit - WASM + Worker toolkit",
"type": "module",
"main": "./dist/index.cjs",
diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx
index e9b06eb..85f8ffd 100644
--- a/packages/react/src/index.tsx
+++ b/packages/react/src/index.tsx
@@ -92,6 +92,11 @@ export function useComputeKit(): ComputeKit {
// useCompute Hook
// ============================================================================
+/**
+ * Status of a compute operation
+ */
+export type ComputeStatus = 'idle' | 'running' | 'success' | 'error' | 'cancelled';
+
/**
* State returned by useCompute
*/
@@ -104,6 +109,8 @@ export interface UseComputeState {
error: Error | null;
/** Progress information */
progress: ComputeProgress | null;
+ /** Current status of the computation */
+ status: ComputeStatus;
}
/**
@@ -163,27 +170,37 @@ export function useCompute(
): UseComputeReturn {
const kit = useComputeKit();
const abortControllerRef = useRef(null);
+ const cancelledRef = useRef(false);
const [state, setState] = useState>({
data: null,
loading: false,
error: null,
progress: null,
+ status: 'idle',
});
const reset = useCallback(() => {
+ cancelledRef.current = false;
setState({
data: null,
loading: false,
error: null,
progress: null,
+ status: 'idle',
});
}, []);
const cancel = useCallback(() => {
if (abortControllerRef.current) {
+ cancelledRef.current = true;
abortControllerRef.current.abort();
abortControllerRef.current = null;
+ setState((prev) => ({
+ ...prev,
+ loading: false,
+ status: 'cancelled',
+ }));
}
}, []);
@@ -191,6 +208,7 @@ export function useCompute(
async (input: TInput, runOptions?: ComputeOptions) => {
// Cancel any ongoing computation
cancel();
+ cancelledRef.current = false;
// Create new abort controller
const abortController = new AbortController();
@@ -203,9 +221,10 @@ export function useCompute(
loading: true,
error: null,
progress: null,
+ status: 'running',
}));
} else {
- setState((prev) => ({ ...prev, loading: true }));
+ setState((prev) => ({ ...prev, loading: true, status: 'running' }));
}
try {
@@ -226,15 +245,17 @@ export function useCompute(
loading: false,
error: null,
progress: null,
+ status: 'success',
});
}
} catch (err) {
- if (!abortController.signal.aborted) {
+ if (!abortController.signal.aborted && !cancelledRef.current) {
setState({
data: null,
loading: false,
error: err instanceof Error ? err : new Error(String(err)),
progress: null,
+ status: 'error',
});
}
}
From 7c503897fb3969aa694122460450912825fe7372 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Fri, 26 Dec 2025 23:57:43 +0000
Subject: [PATCH 09/37] chore: update license attribution in README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4d05237..49f52fe 100644
--- a/README.md
+++ b/README.md
@@ -562,7 +562,7 @@ npm test
## 📄 License
-MIT © [Your Name](https://github.com/your-username)
+MIT © [Ghassen Lassoued](https://github.com/tapava)
---
From b8a0be84c92f3e4bde7b286213b13bc937b34e08 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 00:18:09 +0000
Subject: [PATCH 10/37] feat: add TODO list for future enhancements and
features in ComputeKit
---
TODO.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 TODO.md
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..cc89743
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,21 @@
+# ComputeKit TODO
+
+## Features
+
+- [ ] **Progress throttling** - Add optional `progressThrottle` option to prevent state flooding when compute functions fire progress updates in tight loops. Should throttle/debounce progress callbacks to avoid choking the main thread with re-renders.
+ - Add `progressThrottle?: number` option (ms) to `ComputeOptions`
+ - Throttle `onProgress` calls in the React hook
+ - Consider both throttle (regular intervals) and debounce (wait for pause) strategies
+
+## Improvements
+
+- [ ] Add more WASM examples (Rust, C++)
+- [ ] Benchmark suite for comparing JS vs WASM performance
+- [ ] Documentation site (Docusaurus or similar)
+
+## Ideas
+
+- [ ] `useComputeMultiple` hook for managing multiple parallel tasks
+- [ ] `useComputeFile` hook for loading functions from separate files
+- [ ] Built-in caching for compute results
+- [ ] Streaming results for very large outputs
From bfbfa54f9ba3d84d8f11cfc1d2b6a87285e8ddae Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 10:38:28 +0000
Subject: [PATCH 11/37] feat: enhance WASM loading with error handling and
absolute path resolution
---
TODO.md | 5 +
examples/react-demo/public/compute.js | 240 +++++++++++++++-----------
examples/react-demo/src/wasmLoader.ts | 16 +-
3 files changed, 154 insertions(+), 107 deletions(-)
diff --git a/TODO.md b/TODO.md
index cc89743..c806272 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,6 +7,11 @@
- Throttle `onProgress` calls in the React hook
- Consider both throttle (regular intervals) and debounce (wait for pause) strategies
+- [ ] **Typed registry** - Add TypeScript support to narrow function names to only registered functions for autocomplete and type safety.
+ - Make `useCompute('functionName')` autocomplete only registered function names
+ - Type safety on input/output based on registered function signatures
+ - Consider using TypeScript's string literal types or const assertions
+
## Improvements
- [ ] Add more WASM examples (Rust, C++)
diff --git a/examples/react-demo/public/compute.js b/examples/react-demo/public/compute.js
index b0a3f29..6080d96 100644
--- a/examples/react-demo/public/compute.js
+++ b/examples/react-demo/public/compute.js
@@ -16,103 +16,117 @@ async function instantiate(module, imports = {}) {
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
- const adaptedExports = Object.setPrototypeOf({
- sum(arr) {
- // compute/sum/sum(~lib/typedarray/Int32Array) => i32
- arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
- return exports.sum(arr);
- },
- sumFloat(arr) {
- // compute/sum/sumFloat(~lib/typedarray/Float64Array) => f64
- arr = __lowerTypedArray(Float64Array, 5, 3, arr) || __notnull();
- return exports.sumFloat(arr);
- },
- average(arr) {
- // compute/sum/average(~lib/typedarray/Int32Array) => f64
- arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
- return exports.average(arr);
- },
- fibonacciSequence(n) {
- // compute/fibonacci/fibonacciSequence(i32) => ~lib/typedarray/Int64Array
- return __liftTypedArray(BigInt64Array, exports.fibonacciSequence(n) >>> 0);
- },
- isFibonacci(num) {
- // compute/fibonacci/isFibonacci(i64) => bool
- num = num || 0n;
- return exports.isFibonacci(num) != 0;
- },
- mandelbrot(width, height, zoom, panX, panY, maxIter) {
- // compute/mandelbrot/mandelbrot(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
- return __liftTypedArray(Uint32Array, exports.mandelbrot(width, height, zoom, panX, panY, maxIter) >>> 0);
- },
- julia(width, height, cRe, cIm, zoom, maxIter) {
- // compute/mandelbrot/julia(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
- return __liftTypedArray(Uint32Array, exports.julia(width, height, cRe, cIm, zoom, maxIter) >>> 0);
- },
- matrixMultiply(a, b, aRows, aCols, bCols) {
- // compute/matrix/matrixMultiply(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array, i32, i32, i32) => ~lib/typedarray/Float64Array
- a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
- b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
- try {
- return __liftTypedArray(Float64Array, exports.matrixMultiply(a, b, aRows, aCols, bCols) >>> 0);
- } finally {
- __release(a);
- }
- },
- matrixTranspose(matrix, rows, cols) {
- // compute/matrix/matrixTranspose(~lib/typedarray/Float64Array, i32, i32) => ~lib/typedarray/Float64Array
- matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
- return __liftTypedArray(Float64Array, exports.matrixTranspose(matrix, rows, cols) >>> 0);
- },
- matrixAdd(a, b) {
- // compute/matrix/matrixAdd(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
- a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
- b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
- try {
- return __liftTypedArray(Float64Array, exports.matrixAdd(a, b) >>> 0);
- } finally {
- __release(a);
- }
- },
- matrixScale(matrix, scalar) {
- // compute/matrix/matrixScale(~lib/typedarray/Float64Array, f64) => ~lib/typedarray/Float64Array
- matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
- return __liftTypedArray(Float64Array, exports.matrixScale(matrix, scalar) >>> 0);
- },
- dotProduct(a, b) {
- // compute/matrix/dotProduct(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => f64
- a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
- b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
- try {
- return exports.dotProduct(a, b);
- } finally {
- __release(a);
- }
- },
- vectorMagnitude(v) {
- // compute/matrix/vectorMagnitude(~lib/typedarray/Float64Array) => f64
- v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
- return exports.vectorMagnitude(v);
- },
- vectorNormalize(v) {
- // compute/matrix/vectorNormalize(~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
- v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
- return __liftTypedArray(Float64Array, exports.vectorNormalize(v) >>> 0);
- },
- getBufferPtr() {
- // compute/blur/getBufferPtr() => usize
- return exports.getBufferPtr() >>> 0;
+ const adaptedExports = Object.setPrototypeOf(
+ {
+ sum(arr) {
+ // compute/sum/sum(~lib/typedarray/Int32Array) => i32
+ arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
+ return exports.sum(arr);
+ },
+ sumFloat(arr) {
+ // compute/sum/sumFloat(~lib/typedarray/Float64Array) => f64
+ arr = __lowerTypedArray(Float64Array, 5, 3, arr) || __notnull();
+ return exports.sumFloat(arr);
+ },
+ average(arr) {
+ // compute/sum/average(~lib/typedarray/Int32Array) => f64
+ arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
+ return exports.average(arr);
+ },
+ fibonacciSequence(n) {
+ // compute/fibonacci/fibonacciSequence(i32) => ~lib/typedarray/Int64Array
+ return __liftTypedArray(BigInt64Array, exports.fibonacciSequence(n) >>> 0);
+ },
+ isFibonacci(num) {
+ // compute/fibonacci/isFibonacci(i64) => bool
+ num = num || 0n;
+ return exports.isFibonacci(num) != 0;
+ },
+ mandelbrot(width, height, zoom, panX, panY, maxIter) {
+ // compute/mandelbrot/mandelbrot(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
+ return __liftTypedArray(
+ Uint32Array,
+ exports.mandelbrot(width, height, zoom, panX, panY, maxIter) >>> 0
+ );
+ },
+ julia(width, height, cRe, cIm, zoom, maxIter) {
+ // compute/mandelbrot/julia(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
+ return __liftTypedArray(
+ Uint32Array,
+ exports.julia(width, height, cRe, cIm, zoom, maxIter) >>> 0
+ );
+ },
+ matrixMultiply(a, b, aRows, aCols, bCols) {
+ // compute/matrix/matrixMultiply(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array, i32, i32, i32) => ~lib/typedarray/Float64Array
+ a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
+ b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
+ try {
+ return __liftTypedArray(
+ Float64Array,
+ exports.matrixMultiply(a, b, aRows, aCols, bCols) >>> 0
+ );
+ } finally {
+ __release(a);
+ }
+ },
+ matrixTranspose(matrix, rows, cols) {
+ // compute/matrix/matrixTranspose(~lib/typedarray/Float64Array, i32, i32) => ~lib/typedarray/Float64Array
+ matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
+ return __liftTypedArray(
+ Float64Array,
+ exports.matrixTranspose(matrix, rows, cols) >>> 0
+ );
+ },
+ matrixAdd(a, b) {
+ // compute/matrix/matrixAdd(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
+ a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
+ b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
+ try {
+ return __liftTypedArray(Float64Array, exports.matrixAdd(a, b) >>> 0);
+ } finally {
+ __release(a);
+ }
+ },
+ matrixScale(matrix, scalar) {
+ // compute/matrix/matrixScale(~lib/typedarray/Float64Array, f64) => ~lib/typedarray/Float64Array
+ matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
+ return __liftTypedArray(Float64Array, exports.matrixScale(matrix, scalar) >>> 0);
+ },
+ dotProduct(a, b) {
+ // compute/matrix/dotProduct(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => f64
+ a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
+ b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
+ try {
+ return exports.dotProduct(a, b);
+ } finally {
+ __release(a);
+ }
+ },
+ vectorMagnitude(v) {
+ // compute/matrix/vectorMagnitude(~lib/typedarray/Float64Array) => f64
+ v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
+ return exports.vectorMagnitude(v);
+ },
+ vectorNormalize(v) {
+ // compute/matrix/vectorNormalize(~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
+ v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
+ return __liftTypedArray(Float64Array, exports.vectorNormalize(v) >>> 0);
+ },
+ getBufferPtr() {
+ // compute/blur/getBufferPtr() => usize
+ return exports.getBufferPtr() >>> 0;
+ },
},
- }, exports);
+ exports
+ );
function __liftString(pointer) {
if (!pointer) return null;
- const
- end = pointer + new Uint32Array(memory.buffer)[pointer - 4 >>> 2] >>> 1,
+ const end = (pointer + new Uint32Array(memory.buffer)[(pointer - 4) >>> 2]) >>> 1,
memoryU16 = new Uint16Array(memory.buffer);
- let
- start = pointer >>> 1,
- string = "";
- while (end - start > 1024) string += String.fromCharCode(...memoryU16.subarray(start, start += 1024));
+ let start = pointer >>> 1,
+ string = '';
+ while (end - start > 1024)
+ string += String.fromCharCode(...memoryU16.subarray(start, (start += 1024)));
return string + String.fromCharCode(...memoryU16.subarray(start, end));
}
function __liftTypedArray(constructor, pointer) {
@@ -125,8 +139,7 @@ async function instantiate(module, imports = {}) {
}
function __lowerTypedArray(constructor, id, align, values) {
if (values == null) return 0;
- const
- length = values.length,
+ const length = values.length,
buffer = exports.__pin(exports.__new(length << align, 1)) >>> 0,
header = exports.__new(12, id) >>> 0;
__setU32(header + 0, buffer);
@@ -148,13 +161,13 @@ async function instantiate(module, imports = {}) {
function __release(pointer) {
if (pointer) {
const refcount = refcounts.get(pointer);
- if (refcount === 1) exports.__unpin(pointer), refcounts.delete(pointer);
+ if (refcount === 1) (exports.__unpin(pointer), refcounts.delete(pointer));
else if (refcount) refcounts.set(pointer, refcount - 1);
else throw Error(`invalid refcount '${refcount}' for reference '${pointer}'`);
}
}
function __notnull() {
- throw TypeError("value must not be null");
+ throw TypeError('value must not be null');
}
let __dataview = new DataView(memory.buffer);
function __setU32(pointer, value) {
@@ -194,11 +207,28 @@ export const {
vectorNormalize,
getBufferPtr,
blurImage,
-} = await (async url => instantiate(
- await (async () => {
- const isNodeOrBun = typeof process != "undefined" && process.versions != null && (process.versions.node != null || process.versions.bun != null);
- if (isNodeOrBun) { return globalThis.WebAssembly.compile(await (await import("node:fs/promises")).readFile(url)); }
- else { return await globalThis.WebAssembly.compileStreaming(globalThis.fetch(url)); }
- })(), {
- }
-))(new URL("compute.wasm", import.meta.url));
+} = await (async (url) =>
+ instantiate(
+ await (async () => {
+ const isNodeOrBun =
+ typeof process != 'undefined' &&
+ process.versions != null &&
+ (process.versions.node != null || process.versions.bun != null);
+ if (isNodeOrBun) {
+ return globalThis.WebAssembly.compile(
+ await (await import('node:fs/promises')).readFile(url)
+ );
+ } else {
+ const response = await globalThis.fetch(url);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch WASM: ${response.status}`);
+ }
+ const contentType = response.headers.get('content-type');
+ if (contentType && contentType.includes('application/wasm')) {
+ return await globalThis.WebAssembly.compileStreaming(response);
+ }
+ return await globalThis.WebAssembly.compile(await response.arrayBuffer());
+ }
+ })(),
+ {}
+ ))(new URL('/compute.wasm', globalThis.location?.origin || import.meta.url));
diff --git a/examples/react-demo/src/wasmLoader.ts b/examples/react-demo/src/wasmLoader.ts
index 5397e37..a71f4b4 100644
--- a/examples/react-demo/src/wasmLoader.ts
+++ b/examples/react-demo/src/wasmLoader.ts
@@ -77,8 +77,20 @@ export async function loadWasm(): Promise {
}
loadingPromise = (async () => {
- const wasmUrl = new URL('/compute.wasm', import.meta.url);
- const module = await WebAssembly.compileStreaming(fetch(wasmUrl));
+ // Use absolute path from origin - works in both dev and production
+ const wasmUrl = new URL('/compute.wasm', window.location.origin).href;
+ const response = await fetch(wasmUrl);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch WASM: ${response.status} ${response.statusText}`);
+ }
+ const module = await WebAssembly.compileStreaming(
+ // Create a new Response with the correct MIME type if needed
+ response.headers.get('content-type')?.includes('application/wasm')
+ ? response
+ : new Response(await response.arrayBuffer(), {
+ headers: { 'Content-Type': 'application/wasm' },
+ })
+ );
cachedExports = await instantiate(module, {});
return cachedExports;
})();
From 0aae49c4abbad2ac23476d84a2bbb65438ed5cd0 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 10:43:52 +0000
Subject: [PATCH 12/37] feat: implement WASM loading and configuration for
Vercel deployment
---
examples/react-demo/public/compute.js | 240 +++++++++++---------------
examples/react-demo/vercel.json | 19 ++
examples/react-demo/vite.config.ts | 4 +
vercel.json | 21 +++
4 files changed, 149 insertions(+), 135 deletions(-)
create mode 100644 examples/react-demo/vercel.json
create mode 100644 vercel.json
diff --git a/examples/react-demo/public/compute.js b/examples/react-demo/public/compute.js
index 6080d96..b0a3f29 100644
--- a/examples/react-demo/public/compute.js
+++ b/examples/react-demo/public/compute.js
@@ -16,117 +16,103 @@ async function instantiate(module, imports = {}) {
};
const { exports } = await WebAssembly.instantiate(module, adaptedImports);
const memory = exports.memory || imports.env.memory;
- const adaptedExports = Object.setPrototypeOf(
- {
- sum(arr) {
- // compute/sum/sum(~lib/typedarray/Int32Array) => i32
- arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
- return exports.sum(arr);
- },
- sumFloat(arr) {
- // compute/sum/sumFloat(~lib/typedarray/Float64Array) => f64
- arr = __lowerTypedArray(Float64Array, 5, 3, arr) || __notnull();
- return exports.sumFloat(arr);
- },
- average(arr) {
- // compute/sum/average(~lib/typedarray/Int32Array) => f64
- arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
- return exports.average(arr);
- },
- fibonacciSequence(n) {
- // compute/fibonacci/fibonacciSequence(i32) => ~lib/typedarray/Int64Array
- return __liftTypedArray(BigInt64Array, exports.fibonacciSequence(n) >>> 0);
- },
- isFibonacci(num) {
- // compute/fibonacci/isFibonacci(i64) => bool
- num = num || 0n;
- return exports.isFibonacci(num) != 0;
- },
- mandelbrot(width, height, zoom, panX, panY, maxIter) {
- // compute/mandelbrot/mandelbrot(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
- return __liftTypedArray(
- Uint32Array,
- exports.mandelbrot(width, height, zoom, panX, panY, maxIter) >>> 0
- );
- },
- julia(width, height, cRe, cIm, zoom, maxIter) {
- // compute/mandelbrot/julia(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
- return __liftTypedArray(
- Uint32Array,
- exports.julia(width, height, cRe, cIm, zoom, maxIter) >>> 0
- );
- },
- matrixMultiply(a, b, aRows, aCols, bCols) {
- // compute/matrix/matrixMultiply(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array, i32, i32, i32) => ~lib/typedarray/Float64Array
- a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
- b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
- try {
- return __liftTypedArray(
- Float64Array,
- exports.matrixMultiply(a, b, aRows, aCols, bCols) >>> 0
- );
- } finally {
- __release(a);
- }
- },
- matrixTranspose(matrix, rows, cols) {
- // compute/matrix/matrixTranspose(~lib/typedarray/Float64Array, i32, i32) => ~lib/typedarray/Float64Array
- matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
- return __liftTypedArray(
- Float64Array,
- exports.matrixTranspose(matrix, rows, cols) >>> 0
- );
- },
- matrixAdd(a, b) {
- // compute/matrix/matrixAdd(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
- a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
- b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
- try {
- return __liftTypedArray(Float64Array, exports.matrixAdd(a, b) >>> 0);
- } finally {
- __release(a);
- }
- },
- matrixScale(matrix, scalar) {
- // compute/matrix/matrixScale(~lib/typedarray/Float64Array, f64) => ~lib/typedarray/Float64Array
- matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
- return __liftTypedArray(Float64Array, exports.matrixScale(matrix, scalar) >>> 0);
- },
- dotProduct(a, b) {
- // compute/matrix/dotProduct(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => f64
- a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
- b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
- try {
- return exports.dotProduct(a, b);
- } finally {
- __release(a);
- }
- },
- vectorMagnitude(v) {
- // compute/matrix/vectorMagnitude(~lib/typedarray/Float64Array) => f64
- v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
- return exports.vectorMagnitude(v);
- },
- vectorNormalize(v) {
- // compute/matrix/vectorNormalize(~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
- v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
- return __liftTypedArray(Float64Array, exports.vectorNormalize(v) >>> 0);
- },
- getBufferPtr() {
- // compute/blur/getBufferPtr() => usize
- return exports.getBufferPtr() >>> 0;
- },
+ const adaptedExports = Object.setPrototypeOf({
+ sum(arr) {
+ // compute/sum/sum(~lib/typedarray/Int32Array) => i32
+ arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
+ return exports.sum(arr);
+ },
+ sumFloat(arr) {
+ // compute/sum/sumFloat(~lib/typedarray/Float64Array) => f64
+ arr = __lowerTypedArray(Float64Array, 5, 3, arr) || __notnull();
+ return exports.sumFloat(arr);
+ },
+ average(arr) {
+ // compute/sum/average(~lib/typedarray/Int32Array) => f64
+ arr = __lowerTypedArray(Int32Array, 4, 2, arr) || __notnull();
+ return exports.average(arr);
},
- exports
- );
+ fibonacciSequence(n) {
+ // compute/fibonacci/fibonacciSequence(i32) => ~lib/typedarray/Int64Array
+ return __liftTypedArray(BigInt64Array, exports.fibonacciSequence(n) >>> 0);
+ },
+ isFibonacci(num) {
+ // compute/fibonacci/isFibonacci(i64) => bool
+ num = num || 0n;
+ return exports.isFibonacci(num) != 0;
+ },
+ mandelbrot(width, height, zoom, panX, panY, maxIter) {
+ // compute/mandelbrot/mandelbrot(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
+ return __liftTypedArray(Uint32Array, exports.mandelbrot(width, height, zoom, panX, panY, maxIter) >>> 0);
+ },
+ julia(width, height, cRe, cIm, zoom, maxIter) {
+ // compute/mandelbrot/julia(i32, i32, f64, f64, f64, i32) => ~lib/typedarray/Uint32Array
+ return __liftTypedArray(Uint32Array, exports.julia(width, height, cRe, cIm, zoom, maxIter) >>> 0);
+ },
+ matrixMultiply(a, b, aRows, aCols, bCols) {
+ // compute/matrix/matrixMultiply(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array, i32, i32, i32) => ~lib/typedarray/Float64Array
+ a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
+ b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
+ try {
+ return __liftTypedArray(Float64Array, exports.matrixMultiply(a, b, aRows, aCols, bCols) >>> 0);
+ } finally {
+ __release(a);
+ }
+ },
+ matrixTranspose(matrix, rows, cols) {
+ // compute/matrix/matrixTranspose(~lib/typedarray/Float64Array, i32, i32) => ~lib/typedarray/Float64Array
+ matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
+ return __liftTypedArray(Float64Array, exports.matrixTranspose(matrix, rows, cols) >>> 0);
+ },
+ matrixAdd(a, b) {
+ // compute/matrix/matrixAdd(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
+ a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
+ b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
+ try {
+ return __liftTypedArray(Float64Array, exports.matrixAdd(a, b) >>> 0);
+ } finally {
+ __release(a);
+ }
+ },
+ matrixScale(matrix, scalar) {
+ // compute/matrix/matrixScale(~lib/typedarray/Float64Array, f64) => ~lib/typedarray/Float64Array
+ matrix = __lowerTypedArray(Float64Array, 5, 3, matrix) || __notnull();
+ return __liftTypedArray(Float64Array, exports.matrixScale(matrix, scalar) >>> 0);
+ },
+ dotProduct(a, b) {
+ // compute/matrix/dotProduct(~lib/typedarray/Float64Array, ~lib/typedarray/Float64Array) => f64
+ a = __retain(__lowerTypedArray(Float64Array, 5, 3, a) || __notnull());
+ b = __lowerTypedArray(Float64Array, 5, 3, b) || __notnull();
+ try {
+ return exports.dotProduct(a, b);
+ } finally {
+ __release(a);
+ }
+ },
+ vectorMagnitude(v) {
+ // compute/matrix/vectorMagnitude(~lib/typedarray/Float64Array) => f64
+ v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
+ return exports.vectorMagnitude(v);
+ },
+ vectorNormalize(v) {
+ // compute/matrix/vectorNormalize(~lib/typedarray/Float64Array) => ~lib/typedarray/Float64Array
+ v = __lowerTypedArray(Float64Array, 5, 3, v) || __notnull();
+ return __liftTypedArray(Float64Array, exports.vectorNormalize(v) >>> 0);
+ },
+ getBufferPtr() {
+ // compute/blur/getBufferPtr() => usize
+ return exports.getBufferPtr() >>> 0;
+ },
+ }, exports);
function __liftString(pointer) {
if (!pointer) return null;
- const end = (pointer + new Uint32Array(memory.buffer)[(pointer - 4) >>> 2]) >>> 1,
+ const
+ end = pointer + new Uint32Array(memory.buffer)[pointer - 4 >>> 2] >>> 1,
memoryU16 = new Uint16Array(memory.buffer);
- let start = pointer >>> 1,
- string = '';
- while (end - start > 1024)
- string += String.fromCharCode(...memoryU16.subarray(start, (start += 1024)));
+ let
+ start = pointer >>> 1,
+ string = "";
+ while (end - start > 1024) string += String.fromCharCode(...memoryU16.subarray(start, start += 1024));
return string + String.fromCharCode(...memoryU16.subarray(start, end));
}
function __liftTypedArray(constructor, pointer) {
@@ -139,7 +125,8 @@ async function instantiate(module, imports = {}) {
}
function __lowerTypedArray(constructor, id, align, values) {
if (values == null) return 0;
- const length = values.length,
+ const
+ length = values.length,
buffer = exports.__pin(exports.__new(length << align, 1)) >>> 0,
header = exports.__new(12, id) >>> 0;
__setU32(header + 0, buffer);
@@ -161,13 +148,13 @@ async function instantiate(module, imports = {}) {
function __release(pointer) {
if (pointer) {
const refcount = refcounts.get(pointer);
- if (refcount === 1) (exports.__unpin(pointer), refcounts.delete(pointer));
+ if (refcount === 1) exports.__unpin(pointer), refcounts.delete(pointer);
else if (refcount) refcounts.set(pointer, refcount - 1);
else throw Error(`invalid refcount '${refcount}' for reference '${pointer}'`);
}
}
function __notnull() {
- throw TypeError('value must not be null');
+ throw TypeError("value must not be null");
}
let __dataview = new DataView(memory.buffer);
function __setU32(pointer, value) {
@@ -207,28 +194,11 @@ export const {
vectorNormalize,
getBufferPtr,
blurImage,
-} = await (async (url) =>
- instantiate(
- await (async () => {
- const isNodeOrBun =
- typeof process != 'undefined' &&
- process.versions != null &&
- (process.versions.node != null || process.versions.bun != null);
- if (isNodeOrBun) {
- return globalThis.WebAssembly.compile(
- await (await import('node:fs/promises')).readFile(url)
- );
- } else {
- const response = await globalThis.fetch(url);
- if (!response.ok) {
- throw new Error(`Failed to fetch WASM: ${response.status}`);
- }
- const contentType = response.headers.get('content-type');
- if (contentType && contentType.includes('application/wasm')) {
- return await globalThis.WebAssembly.compileStreaming(response);
- }
- return await globalThis.WebAssembly.compile(await response.arrayBuffer());
- }
- })(),
- {}
- ))(new URL('/compute.wasm', globalThis.location?.origin || import.meta.url));
+} = await (async url => instantiate(
+ await (async () => {
+ const isNodeOrBun = typeof process != "undefined" && process.versions != null && (process.versions.node != null || process.versions.bun != null);
+ if (isNodeOrBun) { return globalThis.WebAssembly.compile(await (await import("node:fs/promises")).readFile(url)); }
+ else { return await globalThis.WebAssembly.compileStreaming(globalThis.fetch(url)); }
+ })(), {
+ }
+))(new URL("compute.wasm", import.meta.url));
diff --git a/examples/react-demo/vercel.json b/examples/react-demo/vercel.json
new file mode 100644
index 0000000..a4067a0
--- /dev/null
+++ b/examples/react-demo/vercel.json
@@ -0,0 +1,19 @@
+{
+ "headers": [
+ {
+ "source": "/(.*).wasm",
+ "headers": [
+ {
+ "key": "Content-Type",
+ "value": "application/wasm"
+ }
+ ]
+ }
+ ],
+ "rewrites": [
+ {
+ "source": "/((?!assets|compute\\.wasm|compute\\.js|compute\\.d\\.ts).*)",
+ "destination": "/index.html"
+ }
+ ]
+}
diff --git a/examples/react-demo/vite.config.ts b/examples/react-demo/vite.config.ts
index dac4d68..e049025 100644
--- a/examples/react-demo/vite.config.ts
+++ b/examples/react-demo/vite.config.ts
@@ -11,6 +11,10 @@ export default defineConfig({
// 'Cross-Origin-Embedder-Policy': 'require-corp',
// },
},
+ build: {
+ // Ensure static assets from public folder are copied
+ copyPublicDir: true,
+ },
optimizeDeps: {
exclude: ['@computekit/core', '@computekit/react'],
},
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..8037e1d
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,21 @@
+{
+ "buildCommand": "npm run build",
+ "outputDirectory": "examples/react-demo/dist",
+ "installCommand": "npm install",
+ "framework": "vite",
+ "headers": [
+ {
+ "source": "/(.*).wasm",
+ "headers": [
+ {
+ "key": "Content-Type",
+ "value": "application/wasm"
+ },
+ {
+ "key": "Cache-Control",
+ "value": "public, max-age=31536000, immutable"
+ }
+ ]
+ }
+ ]
+}
From f7a6340c89d18ff8122246690fef38ed9b82a52d Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 10:49:56 +0000
Subject: [PATCH 13/37] fix: update vercel.json with version 2 and fix regex
pattern
---
vercel.json | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/vercel.json b/vercel.json
index 8037e1d..8abe898 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,19 +1,15 @@
{
+ "version": 2,
"buildCommand": "npm run build",
"outputDirectory": "examples/react-demo/dist",
"installCommand": "npm install",
- "framework": "vite",
"headers": [
{
- "source": "/(.*).wasm",
+ "source": "/(.*)\\.wasm",
"headers": [
{
"key": "Content-Type",
"value": "application/wasm"
- },
- {
- "key": "Cache-Control",
- "value": "public, max-age=31536000, immutable"
}
]
}
From e6aaa7a3bf601fa808ab41bdc8fc23a23e901bd8 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 10:56:10 +0000
Subject: [PATCH 14/37] fix: include compute.wasm in git for Vercel deployment
---
.gitignore | 2 ++
examples/react-demo/public/compute.wasm | Bin 0 -> 9703 bytes
2 files changed, 2 insertions(+)
create mode 100644 examples/react-demo/public/compute.wasm
diff --git a/.gitignore b/.gitignore
index e355f9b..c7007ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@ dist/
build/
*.wasm
*.wasm.map
+# But include the public WASM file for deployment
+!examples/react-demo/public/compute.wasm
# IDE
.idea/
diff --git a/examples/react-demo/public/compute.wasm b/examples/react-demo/public/compute.wasm
new file mode 100644
index 0000000000000000000000000000000000000000..f2f5f92f579d700abe24aeb8dacfcddc26a102bc
GIT binary patch
literal 9703
zcmb_iUub04c|YgeduQ&<8ENj?>)N~P*yr9E$D26Lwl5Bbf^>v;lXdIZ!D(KlS!s5y
zt66C^JCg09NP2KuQ`}-oOG7AlR_owE8weq=Nio<^(z-MW#xxLG3i}WjQu1JE`qahl
z?|06rKE#3$Y;P^79dz)4;b3QH
z8xy`&jBat1la-yFt*tG;TW&9e7{wB~jVZOY#ZN5T=ISl65OJ1k)effAlnlD7n^EW7
zT7Mv>g5N&XP*Tb;tf*=fh11PyETobOEY|9kM2J{xaU>8ya92ILyB3HjkU~~w8#~ep
zePuOMAo>_9n5W
zUDf&dja9bpuFcOcuJwA|#eq1g=jZ!_!SejM&gE`UuUzb2T`3v2zSIu}-#2OCRW@%dup%WM6Mo!;^vcEzb!{#HZY
zQ~%}Msk178f{n1^_ruva(bQ`O$E!7bTyL;Dl|-#DlP6VZx7JhP$jL8)oZwKfZUb~I^jNb)
zX%mTs-CvM^MsLP0W2wUh6$s_oRLoq
z>xYG*G)>*A5~G!@;NJ0@gslK7X$M;WiIU+Cj}RG#3TUot`--yDj-LR!z*OU`6Av?g
zHlEp4XR$f3;bvqXv^PHb=%c{k@vjE`UQWGHh
z*CkOWY($2_ECjZpi71YV(2h(<>?tC)hXWJj04t}(EKn}VwF@SK+-^lN7uszTJiksP
zO+e%VzW|5`OsVJBA5#KWqyIjT5wO546bYvONfsC!gy)|}Q#Q;Ue1Iv>LgJ`^qvIe<
zDm;=al$*xsFr))JQl+sMj&6l?sGU3`T@9#UJ1`C5M#08d
zXTK`h4gK*d_nrhH|8q2jqmATtWxZm7@q}U(#+{0xSOvufR4fdc0v3u@RBW_QvHw$&
z7np#`lv5cLMs--2DmbE6ViQ1oj5!VOTg$RFd4Vk-1ZyM(B!8?cacXdeaAo$x7yYJb+eG7-K4R1c!RP
z0j-aA{CQv_?$AcP0s2M~oulv=1Qtd=wGNPC4$8h`zLcB*#9k!<)M(++kcW5-8~Bz<
zRsWSlyqtkWF!CaFfcUJi;!6<{U^pwZ-GxJfjIhIR`3B=AbU+hiEWv#4fVLu)n1f7y
z4%(2^#%RR?<`Af*3)F&57f#&?uYXI;h`*gn{wRsrZ`btu0Yadf^{iH!`=*0iaSHa^$OvsXcN@Xg3)Y4;wv~1Q
zM_DN$Ws_hUNK{V)T_+{13>oZwX^%Q9^GV5R%ee!T3!{iy!byP55MZT^9=ZXd0u35@z&6({dMAnu1Njj^U?kqGzmF8^4um7SDQ}5J!N|0&Ls6Ah1-SdK
z81O3%8~azuWGY=qpz(y8Dbkm;A0paI8WL>AA}3u~8RS;Nv0(D{LH40l9~i)*a#?^=
z2SWePO7CRgfkzWh95`6dn3;r;&tW_SaW}UBjgtEv49a|B5pedKGc^j}|E7F?2Nm@k
z&YqwG$+Z4)$bc{GubH`kDvA-R<{QdBNTgb#B{8aI?QW7P7_Lg{0J_BkTH?EN@5t@B
zUy9u~>~5U5Wdl(hrvK!W+lZ|z7(Z+41I%eCYNRlvZkyyl$3;^H5gdz1{L{dG$|;C}
zyumu5!8)M~Svrdn?Sw-#P>6oJ>S+KJm6~9anurQbL?%M0inI+4(h}kcqVoV6`68f%
zxVhckg>5C9S-=?n}_gmo#MDC`rkp3
zJV68biYAvTDs^9>+eeI$2@nTEk*OeTIunKVa}(uR^Ac^7-gEJX}FAN7_jZvU24ZFq!0kW(kN-g%&sx=4~AP+h8nbd3Sxuy7w}|eCYD(6
z#DXr4$l-|!L5cC$h#MOAsprx1nN!k@z4^h8-YBF)2?l3h@s(|`?n*H9{XvKo)zhwe
zNy4ZFqdo)k2+p`-APHHP=x3zdHexQq$95*?!mp<|ZZ6zHTRaOh5sahqN3Y$eWcc3x
ziuM6OjAq4X&WNvo;Omi$wv2p26n~v?wrF3UNyWHXuE1y*6~iCQj5c1M8BVnKu4KU#
z7^ZmQ58U6+XNFlxK!`;~iV=w+!vqv?B`e9^H@i2Ue+zWSy_uyGS-6KS3guuy{Kqqk
zQ^Eu(vg?Rlj+FE7&+tH)QL+C0853UFE0*FcP(zk8%7rWBb?BYkRK_bHheziFNGV1=
zNOEE%)(107;TXpc-Woy?aMOa<^6W9MrLfl>_vL)aYh}!9bSqv*;J!7?N(Mc+We@D8
zC@gzk?(V+O
zFSYF#*?7?J>x#f!sNag@w95lMSF7wIo%2eg25E&M*~CvWnxp|bs3g&WDl5G#G*yO-
zkTmkFhG@VVNxEAqBd}ayutRM&$m;OxlrgNHo0`#irXLjwEsU?^S)=IWSskrBw}10<
z(X&llWlG+$TpBEs1}o&m3W#AaDL5%ODaMmNim}M@7h`fnW)RqNM7J6*fQ)Mamjo=y
zD2b_>%8k09AQ1#+BKCq*8U0rvc%IfU3;+TEkT$RZ6S|?pYid%Dq$(y+vtHI%!My^OE`VMP{DtG^tm?e?c
zUlTJZnFg%vSFGG@OZ!sY?$+thFPDwodONhQ*X_^^f2V8=>+OpDfwte%T>Znc@jcy+
z^_*y-I$CLKeEnvMF7PVuQg7o8y}AMr1u(X?zz2SGB)Ki6Iq`)6tN4Yx6$@-_8@RAEG9n2LQ3Z_K
zBAJ@#@V+vh@|S7<97=7n6dlI)-Pqo3ANT5{2MtiVNziqro!<)*=rs&Vy9w$X8&h|-
zPcgjWbBc5a;7D&h!6vU6c;Yu~*guZ#+oIsBw66j{qZ}Ge@fw=gzO%z%s0E?V?AOHi
zl~Q;%Pi&!$NrNr$cXv5!HSYsx
zYQm+k6lxs-VuCTU`+ce5oa~@4eGT{DeP|pOC&uVhfeykX(ZLt(2dcLU=upQb%|nxeRVaBp%r9u
z=HKL+sb4Yio(YF(1vLW;2w|F>YF;xnHxsjUug*Lf`2$7DQ&63#z8Nmo=bDwUk;
z8X#FE*{VZwT;G&P*7O~EN_42FFpm7ccnb26QI9jJcu&cd#4nKSjpEv2YCe#;j~N9m
z01y|W;mk-9$9d7_HIJbSS-G$%m#5`x`U@ZL+eJl@+?SRNqMCR~D*h5%@HiQs-SU0B
zA6jJ`o2)Y6XRy`eI2{VHbAQ!Ppkma)fVV5hss9u5P@*G}d&f|TRPsGj$&=%09Re_r^FD|e2;b`AhL#+M_G_h|xp-o59-J5}uoZW>
zTGF1pDvW&xf-?scUnV{%|KG{OtIE;}n7pyQs5Q&n1N)50tWlI6KZQ|xaO|b${TL;r
zmlBgjWfLQ+;-=BVWe;K#Ih2{naqu3{WZdNocS1DXH5I2aPvXnjdUTNY_oNEE4KtkH
zMt8Rr!ugs=j=5~lbPA2g9-^3x@I?!lMs=B2K6x>8NSs^+&z%
zsUpyUM?k!Nj$6p&Qge7vrte5xuqgB461igPminM5WGOiYe({(Q{CW;i93`XRf{791
zcogNCM~t}zrw)DmDsr>u{E+B>63-b2!BHTY;3{=>3t>kc3E1(YF6;=fPh#zL
z>^EcGQbLIP@jfe7#ir&%3u$rFeV&sAti06cT6YGCh*mq+6DA#`)?`^m8oH&I&
zeY`s2MR87Si1T=L(f>SlUKaL--}!P)pW@Dwcw6l3i8Zl^Jp-J!hI0&BYk2Wo(z^f}
z_RX3L$0xa_
zj5zBC_5qQXwLZapM-QA;(Eo{c^naoCLtOX3fpw=qzXyKKmmIy)a67))7;`k1VgS@G
z;rt~hwMVf$$?y9`L;ns>`Yhg01K}L27o7~$RUf?$cE1Q|E&+{IyhzQyGUZR58soly
i_cC}iSiR
Date: Sat, 27 Dec 2025 10:57:14 +0000
Subject: [PATCH 15/37] refactor: build WASM during deployment instead of
committing it
---
.gitignore | 2 --
examples/react-demo/public/compute.wasm | Bin 9703 -> 0 bytes
2 files changed, 2 deletions(-)
delete mode 100644 examples/react-demo/public/compute.wasm
diff --git a/.gitignore b/.gitignore
index c7007ef..e355f9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,8 +7,6 @@ dist/
build/
*.wasm
*.wasm.map
-# But include the public WASM file for deployment
-!examples/react-demo/public/compute.wasm
# IDE
.idea/
diff --git a/examples/react-demo/public/compute.wasm b/examples/react-demo/public/compute.wasm
deleted file mode 100644
index f2f5f92f579d700abe24aeb8dacfcddc26a102bc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 9703
zcmb_iUub04c|YgeduQ&<8ENj?>)N~P*yr9E$D26Lwl5Bbf^>v;lXdIZ!D(KlS!s5y
zt66C^JCg09NP2KuQ`}-oOG7AlR_owE8weq=Nio<^(z-MW#xxLG3i}WjQu1JE`qahl
z?|06rKE#3$Y;P^79dz)4;b3QH
z8xy`&jBat1la-yFt*tG;TW&9e7{wB~jVZOY#ZN5T=ISl65OJ1k)effAlnlD7n^EW7
zT7Mv>g5N&XP*Tb;tf*=fh11PyETobOEY|9kM2J{xaU>8ya92ILyB3HjkU~~w8#~ep
zePuOMAo>_9n5W
zUDf&dja9bpuFcOcuJwA|#eq1g=jZ!_!SejM&gE`UuUzb2T`3v2zSIu}-#2OCRW@%dup%WM6Mo!;^vcEzb!{#HZY
zQ~%}Msk178f{n1^_ruva(bQ`O$E!7bTyL;Dl|-#DlP6VZx7JhP$jL8)oZwKfZUb~I^jNb)
zX%mTs-CvM^MsLP0W2wUh6$s_oRLoq
z>xYG*G)>*A5~G!@;NJ0@gslK7X$M;WiIU+Cj}RG#3TUot`--yDj-LR!z*OU`6Av?g
zHlEp4XR$f3;bvqXv^PHb=%c{k@vjE`UQWGHh
z*CkOWY($2_ECjZpi71YV(2h(<>?tC)hXWJj04t}(EKn}VwF@SK+-^lN7uszTJiksP
zO+e%VzW|5`OsVJBA5#KWqyIjT5wO546bYvONfsC!gy)|}Q#Q;Ue1Iv>LgJ`^qvIe<
zDm;=al$*xsFr))JQl+sMj&6l?sGU3`T@9#UJ1`C5M#08d
zXTK`h4gK*d_nrhH|8q2jqmATtWxZm7@q}U(#+{0xSOvufR4fdc0v3u@RBW_QvHw$&
z7np#`lv5cLMs--2DmbE6ViQ1oj5!VOTg$RFd4Vk-1ZyM(B!8?cacXdeaAo$x7yYJb+eG7-K4R1c!RP
z0j-aA{CQv_?$AcP0s2M~oulv=1Qtd=wGNPC4$8h`zLcB*#9k!<)M(++kcW5-8~Bz<
zRsWSlyqtkWF!CaFfcUJi;!6<{U^pwZ-GxJfjIhIR`3B=AbU+hiEWv#4fVLu)n1f7y
z4%(2^#%RR?<`Af*3)F&57f#&?uYXI;h`*gn{wRsrZ`btu0Yadf^{iH!`=*0iaSHa^$OvsXcN@Xg3)Y4;wv~1Q
zM_DN$Ws_hUNK{V)T_+{13>oZwX^%Q9^GV5R%ee!T3!{iy!byP55MZT^9=ZXd0u35@z&6({dMAnu1Njj^U?kqGzmF8^4um7SDQ}5J!N|0&Ls6Ah1-SdK
z81O3%8~azuWGY=qpz(y8Dbkm;A0paI8WL>AA}3u~8RS;Nv0(D{LH40l9~i)*a#?^=
z2SWePO7CRgfkzWh95`6dn3;r;&tW_SaW}UBjgtEv49a|B5pedKGc^j}|E7F?2Nm@k
z&YqwG$+Z4)$bc{GubH`kDvA-R<{QdBNTgb#B{8aI?QW7P7_Lg{0J_BkTH?EN@5t@B
zUy9u~>~5U5Wdl(hrvK!W+lZ|z7(Z+41I%eCYNRlvZkyyl$3;^H5gdz1{L{dG$|;C}
zyumu5!8)M~Svrdn?Sw-#P>6oJ>S+KJm6~9anurQbL?%M0inI+4(h}kcqVoV6`68f%
zxVhckg>5C9S-=?n}_gmo#MDC`rkp3
zJV68biYAvTDs^9>+eeI$2@nTEk*OeTIunKVa}(uR^Ac^7-gEJX}FAN7_jZvU24ZFq!0kW(kN-g%&sx=4~AP+h8nbd3Sxuy7w}|eCYD(6
z#DXr4$l-|!L5cC$h#MOAsprx1nN!k@z4^h8-YBF)2?l3h@s(|`?n*H9{XvKo)zhwe
zNy4ZFqdo)k2+p`-APHHP=x3zdHexQq$95*?!mp<|ZZ6zHTRaOh5sahqN3Y$eWcc3x
ziuM6OjAq4X&WNvo;Omi$wv2p26n~v?wrF3UNyWHXuE1y*6~iCQj5c1M8BVnKu4KU#
z7^ZmQ58U6+XNFlxK!`;~iV=w+!vqv?B`e9^H@i2Ue+zWSy_uyGS-6KS3guuy{Kqqk
zQ^Eu(vg?Rlj+FE7&+tH)QL+C0853UFE0*FcP(zk8%7rWBb?BYkRK_bHheziFNGV1=
zNOEE%)(107;TXpc-Woy?aMOa<^6W9MrLfl>_vL)aYh}!9bSqv*;J!7?N(Mc+We@D8
zC@gzk?(V+O
zFSYF#*?7?J>x#f!sNag@w95lMSF7wIo%2eg25E&M*~CvWnxp|bs3g&WDl5G#G*yO-
zkTmkFhG@VVNxEAqBd}ayutRM&$m;OxlrgNHo0`#irXLjwEsU?^S)=IWSskrBw}10<
z(X&llWlG+$TpBEs1}o&m3W#AaDL5%ODaMmNim}M@7h`fnW)RqNM7J6*fQ)Mamjo=y
zD2b_>%8k09AQ1#+BKCq*8U0rvc%IfU3;+TEkT$RZ6S|?pYid%Dq$(y+vtHI%!My^OE`VMP{DtG^tm?e?c
zUlTJZnFg%vSFGG@OZ!sY?$+thFPDwodONhQ*X_^^f2V8=>+OpDfwte%T>Znc@jcy+
z^_*y-I$CLKeEnvMF7PVuQg7o8y}AMr1u(X?zz2SGB)Ki6Iq`)6tN4Yx6$@-_8@RAEG9n2LQ3Z_K
zBAJ@#@V+vh@|S7<97=7n6dlI)-Pqo3ANT5{2MtiVNziqro!<)*=rs&Vy9w$X8&h|-
zPcgjWbBc5a;7D&h!6vU6c;Yu~*guZ#+oIsBw66j{qZ}Ge@fw=gzO%z%s0E?V?AOHi
zl~Q;%Pi&!$NrNr$cXv5!HSYsx
zYQm+k6lxs-VuCTU`+ce5oa~@4eGT{DeP|pOC&uVhfeykX(ZLt(2dcLU=upQb%|nxeRVaBp%r9u
z=HKL+sb4Yio(YF(1vLW;2w|F>YF;xnHxsjUug*Lf`2$7DQ&63#z8Nmo=bDwUk;
z8X#FE*{VZwT;G&P*7O~EN_42FFpm7ccnb26QI9jJcu&cd#4nKSjpEv2YCe#;j~N9m
z01y|W;mk-9$9d7_HIJbSS-G$%m#5`x`U@ZL+eJl@+?SRNqMCR~D*h5%@HiQs-SU0B
zA6jJ`o2)Y6XRy`eI2{VHbAQ!Ppkma)fVV5hss9u5P@*G}d&f|TRPsGj$&=%09Re_r^FD|e2;b`AhL#+M_G_h|xp-o59-J5}uoZW>
zTGF1pDvW&xf-?scUnV{%|KG{OtIE;}n7pyQs5Q&n1N)50tWlI6KZQ|xaO|b${TL;r
zmlBgjWfLQ+;-=BVWe;K#Ih2{naqu3{WZdNocS1DXH5I2aPvXnjdUTNY_oNEE4KtkH
zMt8Rr!ugs=j=5~lbPA2g9-^3x@I?!lMs=B2K6x>8NSs^+&z%
zsUpyUM?k!Nj$6p&Qge7vrte5xuqgB461igPminM5WGOiYe({(Q{CW;i93`XRf{791
zcogNCM~t}zrw)DmDsr>u{E+B>63-b2!BHTY;3{=>3t>kc3E1(YF6;=fPh#zL
z>^EcGQbLIP@jfe7#ir&%3u$rFeV&sAti06cT6YGCh*mq+6DA#`)?`^m8oH&I&
zeY`s2MR87Si1T=L(f>SlUKaL--}!P)pW@Dwcw6l3i8Zl^Jp-J!hI0&BYk2Wo(z^f}
z_RX3L$0xa_
zj5zBC_5qQXwLZapM-QA;(Eo{c^naoCLtOX3fpw=qzXyKKmmIy)a67))7;`k1VgS@G
z;rt~hwMVf$$?y9`L;ns>`Yhg01K}L27o7~$RUf?$cE1Q|E&+{IyhzQyGUZR58soly
i_cC}iSiR
Date: Sat, 27 Dec 2025 11:02:16 +0000
Subject: [PATCH 16/37] trigger redeploy
From 84603d91acae8e142ca5ea49d8ebb5b8c557a9e8 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 11:13:25 +0000
Subject: [PATCH 17/37] feat: add @computekit/react-query package for TanStack
Query integration
---
package-lock.json | 67 ++++++-
package.json | 5 +-
packages/react-query/README.md | 156 ++++++++++++++++
packages/react-query/package.json | 63 +++++++
packages/react-query/src/index.tsx | 268 ++++++++++++++++++++++++++++
packages/react-query/tsconfig.json | 18 ++
packages/react-query/tsup.config.ts | 15 ++
7 files changed, 583 insertions(+), 9 deletions(-)
create mode 100644 packages/react-query/README.md
create mode 100644 packages/react-query/package.json
create mode 100644 packages/react-query/src/index.tsx
create mode 100644 packages/react-query/tsconfig.json
create mode 100644 packages/react-query/tsup.config.ts
diff --git a/package-lock.json b/package-lock.json
index f0ecfcf..2d7160f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,6 +48,7 @@
},
"examples/vanilla-demo": {
"version": "0.0.0",
+ "extraneous": true,
"dependencies": {
"@computekit/core": "*"
},
@@ -418,8 +419,8 @@
"resolved": "packages/react",
"link": true
},
- "node_modules/@computekit/wasm": {
- "resolved": "packages/wasm",
+ "node_modules/@computekit/react-query": {
+ "resolved": "packages/react-query",
"link": true
},
"node_modules/@csstools/color-helpers": {
@@ -1583,6 +1584,34 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.90.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz",
+ "integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.90.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz",
+ "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.90.12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2212,6 +2241,7 @@
"integrity": "sha512-YtY5k3PiV3SyUQ6gRlR2OCn8dcVRwkpiG/k2T5buoL2ymH/Z/YbaYWbk/f9mO2HTgEtGWjPiAQrIuvA7G/63Gg==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"binaryen": "116.0.0-nightly.20240114",
"long": "^5.2.4"
@@ -5079,10 +5109,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/vanilla-demo": {
- "resolved": "examples/vanilla-demo",
- "link": true
- },
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -5973,7 +5999,7 @@
},
"packages/core": {
"name": "@computekit/core",
- "version": "0.1.0",
+ "version": "0.1.2",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.10.0",
@@ -5992,12 +6018,37 @@
},
"packages/react": {
"name": "@computekit/react",
+ "version": "0.1.2",
+ "license": "MIT",
+ "dependencies": {
+ "@computekit/core": "*"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.45",
+ "react": "^18.2.0",
+ "tsup": "^8.0.1",
+ "typescript": "^5.3.3",
+ "vitest": "^1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=17.0.0",
+ "react": ">=17.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "packages/react-query": {
+ "name": "@computekit/react-query",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@computekit/core": "*"
},
"devDependencies": {
+ "@tanstack/react-query": "^5.17.0",
"@types/react": "^18.2.45",
"react": "^18.2.0",
"tsup": "^8.0.1",
@@ -6005,12 +6056,14 @@
"vitest": "^1.1.0"
},
"peerDependencies": {
+ "@tanstack/react-query": ">=4.0.0",
"react": ">=17.0.0"
}
},
"packages/wasm": {
"name": "@computekit/wasm",
"version": "0.1.0",
+ "extraneous": true,
"devDependencies": {
"assemblyscript": "^0.27.0"
}
diff --git a/package.json b/package.json
index a47d421..5f71518 100644
--- a/package.json
+++ b/package.json
@@ -9,12 +9,13 @@
"examples/*"
],
"scripts": {
- "build": "npm run build:wasm && npm run build:core && npm run build:react && npm run build:examples",
+ "build": "npm run build:wasm && npm run build:core && npm run build:react && npm run build:react-query && npm run build:examples",
"build:wasm": "asc compute/index.ts --outFile examples/react-demo/public/compute.wasm --bindings esm --optimize",
"build:core": "npm run build -w @computekit/core",
"build:react": "npm run build -w @computekit/react",
+ "build:react-query": "npm run build -w @computekit/react-query",
"build:examples": "npm run build -w examples/react-demo",
- "build:packages": "npm run build:core && npm run build:react",
+ "build:packages": "npm run build:core && npm run build:react && npm run build:react-query",
"dev": "npm run dev -w examples/react-demo",
"test": "vitest",
"lint": "eslint packages --ext .ts,.tsx",
diff --git a/packages/react-query/README.md b/packages/react-query/README.md
new file mode 100644
index 0000000..ebcd7d0
--- /dev/null
+++ b/packages/react-query/README.md
@@ -0,0 +1,156 @@
+# @computekit/react-query
+
+TanStack Query integration for [ComputeKit](https://github.com/tapava/compute-kit) - run heavy computations in Web Workers with automatic caching, background refetching, and all the goodies from React Query.
+
+## Why?
+
+If you're already using TanStack Query (React Query), this package lets you use ComputeKit as a "fetcher" while React Query handles:
+
+- ✅ Caching & deduplication
+- ✅ Background refetching
+- ✅ Stale-while-revalidate
+- ✅ Retry logic
+- ✅ DevTools support
+- ✅ Optimistic updates (via mutations)
+
+## Installation
+
+```bash
+npm install @computekit/react-query @computekit/core @tanstack/react-query
+```
+
+## Quick Start
+
+### 1. Setup Providers
+
+```tsx
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ComputeKitProvider } from '@computekit/react-query';
+
+const queryClient = new QueryClient();
+
+function App() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+### 2. Register Compute Functions
+
+```tsx
+import { useComputeKit } from '@computekit/react-query';
+
+function Setup() {
+ const kit = useComputeKit();
+
+ useEffect(() => {
+ kit.register('fibonacci', (n: number) => {
+ let a = 0,
+ b = 1;
+ for (let i = 2; i <= n; i++) [a, b] = [b, a + b];
+ return b;
+ });
+ }, [kit]);
+
+ return ;
+}
+```
+
+### 3. Use with React Query
+
+```tsx
+import { useComputeQuery } from '@computekit/react-query';
+
+function Fibonacci({ n }: { n: number }) {
+ const { data, isLoading, error } = useComputeQuery('fibonacci', n);
+
+ if (isLoading) return Computing...
;
+ if (error) return Error: {error.message}
;
+ return (
+
+ fib({n}) = {data}
+
+ );
+}
+```
+
+## API
+
+### `useComputeQuery(name, input, options?)`
+
+Execute a compute function with React Query's `useQuery`.
+
+```tsx
+const { data, isLoading, error, refetch } = useComputeQuery('functionName', inputData, {
+ // React Query options
+ staleTime: 1000 * 60 * 5, // 5 minutes
+ retry: 3,
+ enabled: shouldRun,
+
+ // ComputeKit options
+ computeOptions: {
+ priority: 'high',
+ timeout: 5000,
+ },
+});
+```
+
+### `useComputeMutation(name, options?)`
+
+Execute a compute function manually with React Query's `useMutation`.
+
+```tsx
+function ImageProcessor() {
+ const { mutate, data, isPending } = useComputeMutation('blur');
+
+ return (
+
+ );
+}
+```
+
+### `createComputeHooks(kit)`
+
+Create hooks without using the context provider - useful for multiple instances or custom setups.
+
+```tsx
+import { ComputeKit } from '@computekit/core';
+import { createComputeHooks } from '@computekit/react-query';
+
+const kit = new ComputeKit();
+kit.register('fibonacci', (n) => /* ... */);
+
+const { useQuery, useMutation } = createComputeHooks(kit);
+
+// Now use these hooks directly
+function MyComponent() {
+ const { data } = useQuery('fibonacci', 50);
+ return {data}
;
+}
+```
+
+## Comparison with @computekit/react
+
+| Feature | `@computekit/react` | `@computekit/react-query` |
+| ------------------------- | ------------------- | ------------------------- |
+| Built-in state management | ✅ Yes | ❌ Uses React Query |
+| Caching | ❌ Manual | ✅ Automatic |
+| Background refetch | ❌ No | ✅ Yes |
+| DevTools | ❌ No | ✅ React Query DevTools |
+| Progress tracking | ✅ Yes | ❌ Not yet |
+| Bundle size | Smaller | Requires React Query |
+
+**Use `@computekit/react`** if you want a simple, standalone solution.
+
+**Use `@computekit/react-query`** if you're already using TanStack Query and want consistent patterns across your app.
+
+## License
+
+MIT
diff --git a/packages/react-query/package.json b/packages/react-query/package.json
new file mode 100644
index 0000000..ca9d55d
--- /dev/null
+++ b/packages/react-query/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@computekit/react-query",
+ "version": "0.1.0",
+ "description": "TanStack Query integration for ComputeKit - lightweight async compute with caching",
+ "type": "module",
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ }
+ },
+ "files": [
+ "dist",
+ "src",
+ "README.md"
+ ],
+ "scripts": {
+ "build": "tsup",
+ "dev": "tsup --watch",
+ "test": "vitest",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@computekit/core": "*"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0",
+ "@tanstack/react-query": ">=4.0.0"
+ },
+ "devDependencies": {
+ "@tanstack/react-query": "^5.17.0",
+ "@types/react": "^18.2.45",
+ "react": "^18.2.0",
+ "tsup": "^8.0.1",
+ "typescript": "^5.3.3",
+ "vitest": "^1.1.0"
+ },
+ "keywords": [
+ "react",
+ "react-query",
+ "tanstack-query",
+ "wasm",
+ "webassembly",
+ "workers",
+ "compute",
+ "async"
+ ],
+ "author": "Ghassen Lassoued ",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/tapava/compute-kit.git",
+ "directory": "packages/react-query"
+ },
+ "homepage": "https://github.com/tapava/compute-kit#readme",
+ "bugs": {
+ "url": "https://github.com/tapava/compute-kit/issues"
+ }
+}
diff --git a/packages/react-query/src/index.tsx b/packages/react-query/src/index.tsx
new file mode 100644
index 0000000..13c75c6
--- /dev/null
+++ b/packages/react-query/src/index.tsx
@@ -0,0 +1,268 @@
+/**
+ * ComputeKit React Query Integration
+ * Lightweight TanStack Query bindings for ComputeKit
+ */
+
+import { useMemo, createContext, useContext, type ReactNode } from 'react';
+import {
+ useQuery,
+ useMutation,
+ type UseQueryOptions,
+ type UseMutationOptions,
+ type QueryKey,
+} from '@tanstack/react-query';
+import {
+ ComputeKit,
+ type ComputeKitOptions,
+ type ComputeOptions,
+} from '@computekit/core';
+
+// ============================================================================
+// Context
+// ============================================================================
+
+const ComputeKitContext = createContext(null);
+
+export interface ComputeKitProviderProps {
+ /** ComputeKit options */
+ options?: ComputeKitOptions;
+ /** Custom ComputeKit instance (if you want to share with @computekit/react) */
+ instance?: ComputeKit;
+ /** Children */
+ children: ReactNode;
+}
+
+/**
+ * Provider component for ComputeKit with React Query
+ *
+ * @example
+ * ```tsx
+ * import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+ * import { ComputeKitProvider } from '@computekit/react-query';
+ *
+ * const queryClient = new QueryClient();
+ *
+ * function App() {
+ * return (
+ *
+ *
+ *
+ *
+ *
+ * );
+ * }
+ * ```
+ */
+export function ComputeKitProvider({
+ options,
+ instance,
+ children,
+}: ComputeKitProviderProps): React.ReactElement {
+ const kit = useMemo(() => {
+ return instance ?? new ComputeKit(options);
+ }, [instance, options]);
+
+ return {children};
+}
+
+/**
+ * Get the ComputeKit instance from context
+ */
+export function useComputeKit(): ComputeKit {
+ const kit = useContext(ComputeKitContext);
+ if (!kit) {
+ throw new Error('useComputeKit must be used within a ComputeKitProvider');
+ }
+ return kit;
+}
+
+// ============================================================================
+// Query Hook
+// ============================================================================
+
+export interface UseComputeQueryOptions extends Omit<
+ UseQueryOptions,
+ 'queryKey' | 'queryFn'
+> {
+ /** ComputeKit run options */
+ computeOptions?: ComputeOptions;
+}
+
+/**
+ * Execute a registered compute function with React Query
+ *
+ * This hook integrates ComputeKit with TanStack Query, giving you:
+ * - Automatic caching
+ * - Background refetching
+ * - Stale-while-revalidate
+ * - Retry logic
+ * - DevTools support
+ *
+ * @example
+ * ```tsx
+ * // First, register the function
+ * kit.register('fibonacci', (n: number) => {
+ * let a = 0, b = 1;
+ * for (let i = 2; i <= n; i++) [a, b] = [b, a + b];
+ * return b;
+ * });
+ *
+ * // Then use it in your component
+ * function Fibonacci({ n }: { n: number }) {
+ * const { data, isLoading, error } = useComputeQuery('fibonacci', n);
+ *
+ * if (isLoading) return Computing...
;
+ * if (error) return Error: {error.message}
;
+ * return Result: {data}
;
+ * }
+ * ```
+ */
+export function useComputeQuery(
+ /** Name of the registered compute function */
+ name: string,
+ /** Input to pass to the function */
+ input: TInput,
+ /** React Query and ComputeKit options */
+ options?: UseComputeQueryOptions
+) {
+ const kit = useComputeKit();
+ const { computeOptions, ...queryOptions } = options ?? {};
+
+ return useQuery({
+ queryKey: ['compute', name, input] as const,
+ queryFn: async () => {
+ const result = await kit.run(name, input, computeOptions);
+ return result;
+ },
+ ...queryOptions,
+ });
+}
+
+// ============================================================================
+// Mutation Hook
+// ============================================================================
+
+export interface UseComputeMutationOptions extends Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> {
+ /** ComputeKit run options */
+ computeOptions?: ComputeOptions;
+}
+
+/**
+ * Execute a registered compute function as a mutation
+ *
+ * Use this when you want to trigger computation manually (e.g., on button click)
+ * rather than automatically on mount/input change.
+ *
+ * @example
+ * ```tsx
+ * function ImageProcessor() {
+ * const { mutate, data, isPending, error } = useComputeMutation('blur');
+ *
+ * return (
+ *
+ *
+ * {data &&

}
+ *
+ * );
+ * }
+ * ```
+ */
+export function useComputeMutation(
+ /** Name of the registered compute function */
+ name: string,
+ /** React Query and ComputeKit options */
+ options?: UseComputeMutationOptions
+) {
+ const kit = useComputeKit();
+ const { computeOptions, ...mutationOptions } = options ?? {};
+
+ return useMutation({
+ mutationFn: async (input: TInput) => {
+ const result = await kit.run(name, input, computeOptions);
+ return result;
+ },
+ ...mutationOptions,
+ });
+}
+
+// ============================================================================
+// Factory for standalone usage (without context)
+// ============================================================================
+
+/**
+ * Create compute query/mutation hooks bound to a specific ComputeKit instance
+ *
+ * Use this if you don't want to use the context provider, or need multiple
+ * ComputeKit instances.
+ *
+ * @example
+ * ```tsx
+ * import { ComputeKit } from '@computekit/core';
+ * import { createComputeHooks } from '@computekit/react-query';
+ *
+ * const kit = new ComputeKit();
+ * kit.register('fibonacci', (n: number) => { ... });
+ *
+ * const { useQuery, useMutation } = createComputeHooks(kit);
+ *
+ * function MyComponent() {
+ * const { data } = useQuery('fibonacci', 50);
+ * return {data}
;
+ * }
+ * ```
+ */
+export function createComputeHooks(kit: ComputeKit) {
+ return {
+ /**
+ * Query hook bound to this ComputeKit instance
+ */
+ useQuery: (
+ name: string,
+ input: TInput,
+ options?: Omit, 'computeOptions'> & {
+ computeOptions?: ComputeOptions;
+ }
+ ) => {
+ const { computeOptions, ...queryOptions } = options ?? {};
+
+ return useQuery({
+ queryKey: ['compute', name, input] as const,
+ queryFn: async () => kit.run(name, input, computeOptions),
+ ...queryOptions,
+ });
+ },
+
+ /**
+ * Mutation hook bound to this ComputeKit instance
+ */
+ useMutation: (
+ name: string,
+ options?: Omit, 'computeOptions'> & {
+ computeOptions?: ComputeOptions;
+ }
+ ) => {
+ const { computeOptions, ...mutationOptions } = options ?? {};
+
+ return useMutation({
+ mutationFn: async (input: TInput) =>
+ kit.run(name, input, computeOptions),
+ ...mutationOptions,
+ });
+ },
+
+ /** The ComputeKit instance */
+ kit,
+ };
+}
+
+// ============================================================================
+// Exports
+// ============================================================================
+
+export type { ComputeKitOptions, ComputeOptions } from '@computekit/core';
+export { ComputeKit } from '@computekit/core';
diff --git a/packages/react-query/tsconfig.json b/packages/react-query/tsconfig.json
new file mode 100644
index 0000000..33b100f
--- /dev/null
+++ b/packages/react-query/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "jsx": "react-jsx",
+ "jsxImportSource": "react"
+ },
+ "include": [
+ "src/**/*"
+ ],
+ "exclude": [
+ "node_modules",
+ "dist",
+ "**/*.test.ts",
+ "**/*.test.tsx"
+ ]
+}
\ No newline at end of file
diff --git a/packages/react-query/tsup.config.ts b/packages/react-query/tsup.config.ts
new file mode 100644
index 0000000..b0752cc
--- /dev/null
+++ b/packages/react-query/tsup.config.ts
@@ -0,0 +1,15 @@
+import { defineConfig } from 'tsup';
+
+export default defineConfig({
+ entry: ['src/index.tsx'],
+ format: ['esm', 'cjs'],
+ dts: true,
+ clean: true,
+ sourcemap: true,
+ minify: false,
+ splitting: false,
+ treeshake: true,
+ target: 'es2022',
+ outDir: 'dist',
+ external: ['react', '@computekit/core', '@tanstack/react-query'],
+});
From db0195e8ea488e107d1a97f375dc626701d9db2d Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 11:14:36 +0000
Subject: [PATCH 18/37] ci: add @computekit/react-query to publish workflow
---
.github/workflows/publish.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 2fa05bf..7999013 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -35,3 +35,9 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
continue-on-error: true
+
+ - name: Publish @computekit/react-query
+ run: cd packages/react-query && npm publish --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ continue-on-error: true
From 5927ca1c993d727a7e869e7610a72905cbf3fe07 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 13:46:20 +0000
Subject: [PATCH 19/37] doc: Add comprehensive documentation for ComputeKit
---
.github/workflows/docs.yml | 45 +++++
README.md | 9 +-
docs/.gitignore | 6 +
docs/Gemfile | 6 +
docs/_config.yml | 67 +++++++
docs/api-reference.md | 332 ++++++++++++++++++++++++++++++
docs/api.md | 86 ++++----
docs/assets/logo.svg | 40 ++++
docs/examples.md | 402 +++++++++++++++++++++++++++++++++++++
docs/getting-started.md | 204 +++++++++++++++++++
docs/index.md | 181 +++++++++++++++++
docs/react-hooks.md | 310 ++++++++++++++++++++++++++++
docs/react-query.md | 139 +++++++++++++
docs/wasm.md | 357 ++++++++++++++++++++++++++++++++
14 files changed, 2142 insertions(+), 42 deletions(-)
create mode 100644 .github/workflows/docs.yml
create mode 100644 docs/.gitignore
create mode 100644 docs/Gemfile
create mode 100644 docs/_config.yml
create mode 100644 docs/api-reference.md
create mode 100644 docs/assets/logo.svg
create mode 100644 docs/examples.md
create mode 100644 docs/getting-started.md
create mode 100644 docs/index.md
create mode 100644 docs/react-hooks.md
create mode 100644 docs/react-query.md
create mode 100644 docs/wasm.md
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..ed54dde
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,45 @@
+name: Deploy Documentation to GitHub Pages
+
+on:
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: 'pages'
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Pages
+ uses: actions/configure-pages@v4
+
+ - name: Build with Jekyll
+ uses: actions/jekyll-build-pages@v1
+ with:
+ source: ./docs
+ destination: ./_site
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ permissions:
+ pages: write
+ id-token: write
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/README.md b/README.md
index 49f52fe..83e17fa 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
[](https://opensource.org/licenses/MIT)
[](https://www.typescriptlang.org/)
-[Getting Started](#-getting-started) • [Examples](#-examples) • [API](#-api) • [React Hooks](#-react-hooks) • [WASM](#-webassembly-support)
+[📚 Documentation](https://tapava.github.io/compute-kit) • [Getting Started](#-getting-started) • [Examples](#-examples) • [API](#-api) • [React Hooks](#-react-hooks) • [WASM](#-webassembly-support)
@@ -542,8 +542,8 @@ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md)
```bash
# Clone the repo
-git clone https://github.com/your-username/computekit.git
-cd computekit
+git clone https://github.com/tapava/compute-kit.git
+cd compute-kit
# Install dependencies
npm install
@@ -571,6 +571,7 @@ MIT © [Ghassen Lassoued](https://github.com/tapava)
Built with ❤️ for the web platform
- ⭐ Star on GitHub
+ 📚 Read the Docs •
+ ⭐ Star on GitHub
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..e913d3b
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,6 @@
+# Ignore Jekyll build output
+_site/
+.sass-cache/
+.jekyll-cache/
+.jekyll-metadata
+vendor/
diff --git a/docs/Gemfile b/docs/Gemfile
new file mode 100644
index 0000000..23ecc60
--- /dev/null
+++ b/docs/Gemfile
@@ -0,0 +1,6 @@
+source "https://rubygems.org"
+
+gem "jekyll", "~> 4.3"
+gem "just-the-docs", "~> 0.8"
+gem "jekyll-seo-tag"
+gem "jekyll-include-cache"
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000..42447c1
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,67 @@
+title: ComputeKit
+description: The React-first toolkit for WASM and Web Workers
+url: 'https://tapava.github.io'
+baseurl: '/compute-kit'
+
+remote_theme: just-the-docs/just-the-docs@v0.8.2
+
+color_scheme: dark
+
+logo: '/assets/logo.svg'
+
+aux_links:
+ GitHub: https://github.com/tapava/compute-kit
+ npm: https://www.npmjs.com/package/@computekit/core
+
+aux_links_new_tab: true
+
+heading_anchors: true
+
+search_enabled: true
+search:
+ heading_level: 2
+ previews: 3
+ preview_words_before: 5
+ preview_words_after: 10
+ tokenizer_separator: /[\s/]+/
+ rel_url: true
+ button: false
+
+nav_enabled: true
+nav_sort: case_insensitive
+
+back_to_top: true
+back_to_top_text: 'Back to top'
+
+footer_content: 'Copyright © 2024-2025 Ghassen Lassoued. Distributed under the MIT license.'
+
+ga_tracking:
+ga_tracking_anonymize_ip: true
+
+callouts:
+ warning:
+ title: Warning
+ color: yellow
+ note:
+ title: Note
+ color: blue
+ tip:
+ title: Tip
+ color: green
+
+plugins:
+ - jekyll-seo-tag
+ - jekyll-include-cache
+
+kramdown:
+ syntax_highlighter_opts:
+ block:
+ line_numbers: false
+
+compress_html:
+ clippings: all
+ comments: all
+ endings: all
+ startings: []
+ blanklines: false
+ profile: false
diff --git a/docs/api-reference.md b/docs/api-reference.md
new file mode 100644
index 0000000..55d7df9
--- /dev/null
+++ b/docs/api-reference.md
@@ -0,0 +1,332 @@
+---
+layout: default
+title: API Reference
+nav_order: 4
+description: 'Complete API reference for ComputeKit'
+permalink: /api-reference
+---
+
+# API Reference
+
+{: .no_toc }
+
+Complete API documentation for the ComputeKit core library.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## ComputeKit Class
+
+The main entry point for using ComputeKit.
+
+### Constructor
+
+```typescript
+new ComputeKit(options?: ComputeKitOptions)
+```
+
+### ComputeKitOptions
+
+| Property | Type | Default | Description |
+| -------------------- | ---------- | -------------------------------------- | ------------------------------------- |
+| `maxWorkers` | `number` | `navigator.hardwareConcurrency \|\| 4` | Maximum number of workers in the pool |
+| `timeout` | `number` | `30000` | Default timeout for operations (ms) |
+| `debug` | `boolean` | `false` | Enable debug logging |
+| `workerPath` | `string` | `''` | Custom path to worker script |
+| `useSharedMemory` | `boolean` | `true` | Use SharedArrayBuffer when available |
+| `remoteDependencies` | `string[]` | `[]` | External scripts to load in workers |
+
+---
+
+## Methods
+
+### initialize()
+
+Manually initialize the worker pool. Called automatically on first `run()`.
+
+```typescript
+const kit = new ComputeKit();
+await kit.initialize(); // Optional: eager initialization
+```
+
+### register()
+
+Register a compute function.
+
+```typescript
+register(
+ name: string,
+ fn: (input: TInput, context: ComputeContext) => TOutput | Promise
+): this
+```
+
+**Parameters:**
+
+- `name` - Unique identifier for the function
+- `fn` - The function to execute (runs in a Web Worker)
+
+**Returns:** `this` (for chaining)
+
+```typescript
+kit.register('double', (n: number) => n * 2);
+
+kit.register('asyncTask', async (data, { reportProgress }) => {
+ // Report progress during long operations
+ reportProgress({ percent: 50 });
+ return await processData(data);
+});
+
+// Chaining
+kit.register('add', (a, b) => a + b).register('multiply', (a, b) => a * b);
+```
+
+### run()
+
+Execute a registered function.
+
+```typescript
+run(
+ name: string,
+ input: TInput,
+ options?: ComputeOptions
+): Promise
+```
+
+**Parameters:**
+
+- `name` - Name of the registered function
+- `input` - Input data (will be serialized)
+- `options` - Optional execution options
+
+```typescript
+const result = await kit.run('double', 21);
+console.log(result); // 42
+```
+
+### runWithMetadata()
+
+Execute a function and receive metadata about the execution.
+
+```typescript
+runWithMetadata(
+ name: string,
+ input: TInput,
+ options?: ComputeOptions
+): Promise>
+```
+
+```typescript
+const result = await kit.runWithMetadata('heavy', data);
+console.log(`Took ${result.duration}ms on worker ${result.workerId}`);
+```
+
+### getStats()
+
+Get current worker pool statistics.
+
+```typescript
+const stats = kit.getStats();
+console.log(`Active workers: ${stats.activeWorkers}`);
+console.log(`Queue length: ${stats.queueLength}`);
+```
+
+### isWasmSupported()
+
+Check if WebAssembly is supported in the current environment.
+
+```typescript
+if (kit.isWasmSupported()) {
+ // Load WASM module
+}
+```
+
+### terminate()
+
+Terminate all workers and clean up resources.
+
+```typescript
+await kit.terminate();
+```
+
+---
+
+## ComputeOptions
+
+Options for individual compute operations.
+
+| Property | Type | Description |
+| ------------ | ------------------------------------- | ------------------------------------- |
+| `timeout` | `number` | Operation timeout in ms |
+| `transfer` | `ArrayBuffer[]` | ArrayBuffers to transfer (not copy) |
+| `priority` | `number` | Priority level (0-10, higher = first) |
+| `signal` | `AbortSignal` | Abort signal for cancellation |
+| `onProgress` | `(progress: ComputeProgress) => void` | Progress callback |
+
+```typescript
+const controller = new AbortController();
+
+await kit.run('task', data, {
+ timeout: 5000,
+ priority: 10,
+ signal: controller.signal,
+ onProgress: (p) => console.log(`${p.percent}%`),
+});
+
+// Cancel the operation
+controller.abort();
+```
+
+---
+
+## ComputeProgress
+
+Progress information for long-running tasks.
+
+| Property | Type | Description |
+| ------------------------ | ---------- | --------------------------- |
+| `percent` | `number` | Progress percentage (0-100) |
+| `phase` | `string?` | Current phase name |
+| `estimatedTimeRemaining` | `number?` | Estimated ms remaining |
+| `data` | `unknown?` | Additional custom data |
+
+---
+
+## ComputeResult
+
+Result wrapper with execution metadata.
+
+| Property | Type | Description |
+| ---------- | --------- | ------------------------------------ |
+| `data` | `T` | The computed result |
+| `duration` | `number` | Execution time in ms |
+| `cached` | `boolean` | Whether result was cached |
+| `workerId` | `string` | ID of the worker that processed this |
+
+---
+
+## PoolStats
+
+Worker pool statistics.
+
+| Property | Type | Description |
+| --------------------- | -------------- | -------------------------- |
+| `workers` | `WorkerInfo[]` | Info about each worker |
+| `totalWorkers` | `number` | Total worker count |
+| `activeWorkers` | `number` | Currently busy workers |
+| `idleWorkers` | `number` | Currently idle workers |
+| `queueLength` | `number` | Tasks waiting in queue |
+| `tasksCompleted` | `number` | Total completed tasks |
+| `tasksFailed` | `number` | Total failed tasks |
+| `averageTaskDuration` | `number` | Average task duration (ms) |
+
+---
+
+## Event Handling
+
+ComputeKit extends EventEmitter and emits events:
+
+```typescript
+kit.on('worker:created', (info) => {
+ console.log('New worker:', info.id);
+});
+
+kit.on('worker:terminated', (info) => {
+ console.log('Worker terminated:', info.id);
+});
+
+kit.on('task:start', (taskId, name) => {
+ console.log(`Starting ${name}`);
+});
+
+kit.on('task:complete', (taskId, duration) => {
+ console.log(`Done in ${duration}ms`);
+});
+
+kit.on('task:error', (taskId, error) => {
+ console.error('Task failed:', error);
+});
+
+kit.on('task:progress', (taskId, progress) => {
+ console.log(`${progress.percent}%`);
+});
+```
+
+---
+
+## Utility Functions
+
+### isWasmSupported()
+
+Check if WebAssembly is available.
+
+```typescript
+import { isWasmSupported } from '@computekit/core';
+
+if (isWasmSupported()) {
+ // Use WASM
+}
+```
+
+### isSharedArrayBufferAvailable()
+
+Check if SharedArrayBuffer is available.
+
+```typescript
+import { isSharedArrayBufferAvailable } from '@computekit/core';
+
+if (isSharedArrayBufferAvailable()) {
+ // Use shared memory
+}
+```
+
+### getHardwareConcurrency()
+
+Get the number of logical CPU cores.
+
+```typescript
+import { getHardwareConcurrency } from '@computekit/core';
+
+const cores = getHardwareConcurrency();
+console.log(`${cores} CPU cores available`);
+```
+
+### findTransferables()
+
+Detect transferable objects in data for efficient worker communication.
+
+```typescript
+import { findTransferables } from '@computekit/core';
+
+const data = { buffer: new ArrayBuffer(1024), values: [1, 2, 3] };
+const transferables = findTransferables(data);
+// [ArrayBuffer(1024)]
+```
+
+---
+
+## Error Handling
+
+ComputeKit throws errors in these cases:
+
+```typescript
+try {
+ await kit.run('unknown', data);
+} catch (error) {
+ if (error.message.includes('not registered')) {
+ // Function not registered
+ } else if (error.message.includes('timed out')) {
+ // Timeout
+ } else if (error.message.includes('aborted')) {
+ // Cancelled via AbortSignal
+ } else {
+ // Worker error
+ }
+}
+```
diff --git a/docs/api.md b/docs/api.md
index a9814da..ab2a40c 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1,3 +1,11 @@
+---
+layout: default
+title: Core API (Detailed)
+nav_order: 8
+description: 'Detailed API reference for @computekit/core'
+permalink: /core-api
+---
+
# @computekit/core API Reference
Complete API documentation for the ComputeKit core library.
@@ -24,13 +32,13 @@ new ComputeKit(options?: ComputeKitOptions)
#### ComputeKitOptions
-| Property | Type | Default | Description |
-|----------|------|---------|-------------|
-| `maxWorkers` | `number` | `navigator.hardwareConcurrency \|\| 4` | Maximum number of workers in the pool |
-| `timeout` | `number` | `30000` | Default timeout for operations (ms) |
-| `debug` | `boolean` | `false` | Enable debug logging |
-| `workerPath` | `string` | `''` | Custom path to worker script |
-| `useSharedMemory` | `boolean` | `true` | Use SharedArrayBuffer when available |
+| Property | Type | Default | Description |
+| ----------------- | --------- | -------------------------------------- | ------------------------------------- |
+| `maxWorkers` | `number` | `navigator.hardwareConcurrency \|\| 4` | Maximum number of workers in the pool |
+| `timeout` | `number` | `30000` | Default timeout for operations (ms) |
+| `debug` | `boolean` | `false` | Enable debug logging |
+| `workerPath` | `string` | `''` | Custom path to worker script |
+| `useSharedMemory` | `boolean` | `true` | Use SharedArrayBuffer when available |
### Methods
@@ -56,6 +64,7 @@ kit.register('asyncTask', async (data) => {
```
**Parameters:**
+
- `name` - Unique identifier for the function
- `fn` - The function to execute (runs in a Web Worker)
@@ -71,6 +80,7 @@ console.log(result); // 42
```
**Parameters:**
+
- `name` - Name of the registered function
- `input` - Input data (will be serialized)
- `options` - Optional execution options
@@ -115,13 +125,13 @@ await kit.terminate();
Options for individual compute operations.
-| Property | Type | Description |
-|----------|------|-------------|
-| `timeout` | `number` | Operation timeout in ms |
-| `transfer` | `ArrayBuffer[]` | ArrayBuffers to transfer (not copy) |
-| `priority` | `number` | Priority level (0-10, higher = first) |
-| `signal` | `AbortSignal` | Abort signal for cancellation |
-| `onProgress` | `(progress: ComputeProgress) => void` | Progress callback |
+| Property | Type | Description |
+| ------------ | ------------------------------------- | ------------------------------------- |
+| `timeout` | `number` | Operation timeout in ms |
+| `transfer` | `ArrayBuffer[]` | ArrayBuffers to transfer (not copy) |
+| `priority` | `number` | Priority level (0-10, higher = first) |
+| `signal` | `AbortSignal` | Abort signal for cancellation |
+| `onProgress` | `(progress: ComputeProgress) => void` | Progress callback |
```typescript
const controller = new AbortController();
@@ -140,12 +150,12 @@ await kit.run('task', data, {
Progress information for long-running tasks.
-| Property | Type | Description |
-|----------|------|-------------|
-| `percent` | `number` | Progress percentage (0-100) |
-| `phase` | `string?` | Current phase name |
-| `estimatedTimeRemaining` | `number?` | Estimated ms remaining |
-| `data` | `unknown?` | Additional data |
+| Property | Type | Description |
+| ------------------------ | ---------- | --------------------------- |
+| `percent` | `number` | Progress percentage (0-100) |
+| `phase` | `string?` | Current phase name |
+| `estimatedTimeRemaining` | `number?` | Estimated ms remaining |
+| `data` | `unknown?` | Additional data |
---
@@ -153,12 +163,12 @@ Progress information for long-running tasks.
Result wrapper with execution metadata.
-| Property | Type | Description |
-|----------|------|-------------|
-| `data` | `T` | The computed result |
-| `duration` | `number` | Execution time in ms |
-| `cached` | `boolean` | Whether result was cached |
-| `workerId` | `string` | ID of the worker that processed this |
+| Property | Type | Description |
+| ---------- | --------- | ------------------------------------ |
+| `data` | `T` | The computed result |
+| `duration` | `number` | Execution time in ms |
+| `cached` | `boolean` | Whether result was cached |
+| `workerId` | `string` | ID of the worker that processed this |
---
@@ -166,16 +176,16 @@ Result wrapper with execution metadata.
Worker pool statistics.
-| Property | Type | Description |
-|----------|------|-------------|
-| `workers` | `WorkerInfo[]` | Info about each worker |
-| `totalWorkers` | `number` | Total worker count |
-| `activeWorkers` | `number` | Currently busy workers |
-| `idleWorkers` | `number` | Currently idle workers |
-| `queueLength` | `number` | Tasks waiting in queue |
-| `tasksCompleted` | `number` | Total completed tasks |
-| `tasksFailed` | `number` | Total failed tasks |
-| `averageTaskDuration` | `number` | Average task duration (ms) |
+| Property | Type | Description |
+| --------------------- | -------------- | -------------------------- |
+| `workers` | `WorkerInfo[]` | Info about each worker |
+| `totalWorkers` | `number` | Total worker count |
+| `activeWorkers` | `number` | Currently busy workers |
+| `idleWorkers` | `number` | Currently idle workers |
+| `queueLength` | `number` | Tasks waiting in queue |
+| `tasksCompleted` | `number` | Total completed tasks |
+| `tasksFailed` | `number` | Total failed tasks |
+| `averageTaskDuration` | `number` | Average task duration (ms) |
---
@@ -192,7 +202,7 @@ Load a WASM module from various sources.
const module = await loadWasmModule('/path/to/module.wasm');
// From ArrayBuffer
-const bytes = await fetch('/module.wasm').then(r => r.arrayBuffer());
+const bytes = await fetch('/module.wasm').then((r) => r.arrayBuffer());
const module = await loadWasmModule(bytes);
// From base64
@@ -207,7 +217,7 @@ Load and instantiate a WASM module.
const { module, instance } = await loadAndInstantiate({
source: '/module.wasm',
imports: {
- env: { log: console.log }
+ env: { log: console.log },
},
memory: {
initial: 256,
diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg
new file mode 100644
index 0000000..44ecb90
--- /dev/null
+++ b/docs/assets/logo.svg
@@ -0,0 +1,40 @@
+
diff --git a/docs/examples.md b/docs/examples.md
new file mode 100644
index 0000000..cf22ebd
--- /dev/null
+++ b/docs/examples.md
@@ -0,0 +1,402 @@
+---
+layout: default
+title: Examples
+nav_order: 6
+description: 'Real-world examples using ComputeKit'
+permalink: /examples
+---
+
+# Examples
+
+{: .no_toc }
+
+Real-world examples demonstrating ComputeKit's capabilities.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## Basic Examples
+
+### Fibonacci Sequence
+
+Calculate large Fibonacci numbers without freezing the UI:
+
+```typescript
+import { ComputeKit } from '@computekit/core';
+
+const kit = new ComputeKit();
+
+kit.register('fibonacci', (n: number) => {
+ if (n <= 1) return n;
+ let a = 0n,
+ b = 1n;
+ for (let i = 2; i <= n; i++) {
+ [a, b] = [b, a + b];
+ }
+ return b.toString();
+});
+
+// Calculate fib(1000) without blocking
+const result = await kit.run('fibonacci', 1000);
+console.log(result); // Very large number!
+```
+
+### Sum Large Array
+
+Process millions of items without blocking:
+
+```typescript
+kit.register('sum', (arr: number[]) => {
+ return arr.reduce((a, b) => a + b, 0);
+});
+
+const bigArray = Array.from({ length: 10_000_000 }, () => Math.random());
+const sum = await kit.run('sum', bigArray);
+console.log(sum);
+```
+
+---
+
+## Image Processing
+
+### Grayscale Conversion
+
+```typescript
+kit.register('grayscale', (imageData: number[]) => {
+ const result = new Uint8ClampedArray(imageData.length);
+
+ for (let i = 0; i < imageData.length; i += 4) {
+ const avg = (imageData[i] + imageData[i + 1] + imageData[i + 2]) / 3;
+ result[i] = avg; // R
+ result[i + 1] = avg; // G
+ result[i + 2] = avg; // B
+ result[i + 3] = imageData[i + 3]; // A (preserve alpha)
+ }
+
+ return Array.from(result);
+});
+
+// Usage with Canvas
+const canvas = document.querySelector('canvas');
+const ctx = canvas.getContext('2d');
+const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+const grayscaleData = await kit.run('grayscale', Array.from(imageData.data));
+const newImageData = new ImageData(
+ new Uint8ClampedArray(grayscaleData),
+ canvas.width,
+ canvas.height
+);
+ctx.putImageData(newImageData, 0, 0);
+```
+
+### Image Blur with Progress
+
+```typescript
+kit.register('blur', async (input, { reportProgress }) => {
+ const { data, width, height, radius } = input;
+ const result = new Uint8ClampedArray(data.length);
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ let r = 0,
+ g = 0,
+ b = 0,
+ count = 0;
+
+ for (let dy = -radius; dy <= radius; dy++) {
+ for (let dx = -radius; dx <= radius; dx++) {
+ const nx = x + dx;
+ const ny = y + dy;
+
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
+ const i = (ny * width + nx) * 4;
+ r += data[i];
+ g += data[i + 1];
+ b += data[i + 2];
+ count++;
+ }
+ }
+ }
+
+ const i = (y * width + x) * 4;
+ result[i] = r / count;
+ result[i + 1] = g / count;
+ result[i + 2] = b / count;
+ result[i + 3] = data[i + 3];
+ }
+
+ // Report progress every row
+ if (y % 10 === 0) {
+ reportProgress({ percent: (y / height) * 100 });
+ }
+ }
+
+ return Array.from(result);
+});
+```
+
+---
+
+## Data Processing
+
+### CSV Parsing
+
+```typescript
+kit.register('parseCSV', (csv: string) => {
+ const lines = csv.split('\n');
+ const headers = lines[0].split(',').map((h) => h.trim());
+
+ return lines.slice(1).map((line) => {
+ const values = line.split(',');
+ return headers.reduce(
+ (obj, header, i) => {
+ obj[header] = values[i]?.trim();
+ return obj;
+ },
+ {} as Record
+ );
+ });
+});
+
+const csvData = await fetch('/large-data.csv').then((r) => r.text());
+const parsed = await kit.run('parseCSV', csvData);
+```
+
+### JSON Processing with Progress
+
+```typescript
+kit.register('processRecords', async (records, { reportProgress }) => {
+ const total = records.length;
+ const results = [];
+
+ for (let i = 0; i < total; i++) {
+ // Expensive processing
+ const processed = {
+ ...records[i],
+ score: calculateScore(records[i]),
+ category: categorize(records[i]),
+ };
+ results.push(processed);
+
+ // Report every 1000 records
+ if (i % 1000 === 0) {
+ reportProgress({
+ percent: (i / total) * 100,
+ phase: 'Processing',
+ data: { processed: i, total },
+ });
+ }
+ }
+
+ return results;
+});
+```
+
+---
+
+## Mathematical Computations
+
+### Mandelbrot Set
+
+```typescript
+kit.register(
+ 'mandelbrot',
+ (config: {
+ width: number;
+ height: number;
+ xMin: number;
+ xMax: number;
+ yMin: number;
+ yMax: number;
+ maxIterations: number;
+ }) => {
+ const { width, height, xMin, xMax, yMin, yMax, maxIterations } = config;
+ const data = new Uint8Array(width * height * 4);
+
+ for (let py = 0; py < height; py++) {
+ for (let px = 0; px < width; px++) {
+ const x0 = xMin + (px / width) * (xMax - xMin);
+ const y0 = yMin + (py / height) * (yMax - yMin);
+
+ let x = 0,
+ y = 0;
+ let iteration = 0;
+
+ while (x * x + y * y <= 4 && iteration < maxIterations) {
+ const xTemp = x * x - y * y + x0;
+ y = 2 * x * y + y0;
+ x = xTemp;
+ iteration++;
+ }
+
+ const i = (py * width + px) * 4;
+ const color = iteration === maxIterations ? 0 : (iteration / maxIterations) * 255;
+
+ data[i] = color;
+ data[i + 1] = color * 0.5;
+ data[i + 2] = color * 2;
+ data[i + 3] = 255;
+ }
+ }
+
+ return Array.from(data);
+ }
+);
+```
+
+### Matrix Multiplication
+
+```typescript
+kit.register('matrixMultiply', (input: { a: number[][]; b: number[][] }) => {
+ const { a, b } = input;
+ const rows = a.length;
+ const cols = b[0].length;
+ const n = b.length;
+
+ const result: number[][] = Array(rows)
+ .fill(null)
+ .map(() => Array(cols).fill(0));
+
+ for (let i = 0; i < rows; i++) {
+ for (let j = 0; j < cols; j++) {
+ for (let k = 0; k < n; k++) {
+ result[i][j] += a[i][k] * b[k][j];
+ }
+ }
+ }
+
+ return result;
+});
+```
+
+---
+
+## React Examples
+
+### Debounced Search with Compute
+
+```tsx
+import { useCompute } from '@computekit/react';
+import { useState, useMemo } from 'react';
+import { useDebouncedValue } from './hooks';
+
+function SearchComponent() {
+ const [query, setQuery] = useState('');
+ const debouncedQuery = useDebouncedValue(query, 300);
+
+ const { data: results, loading } = useCompute<{ query: string; items: Item[] }, Item[]>(
+ 'fuzzySearch',
+ {
+ runOnMount: false,
+ }
+ );
+
+ // Register the fuzzy search function
+ useEffect(() => {
+ kit.register('fuzzySearch', ({ query, items }) => {
+ return items
+ .filter(
+ (item) =>
+ item.name.toLowerCase().includes(query.toLowerCase()) ||
+ item.description.toLowerCase().includes(query.toLowerCase())
+ )
+ .sort((a, b) => {
+ // Score by relevance
+ const aScore = getMatchScore(a, query);
+ const bScore = getMatchScore(b, query);
+ return bScore - aScore;
+ });
+ });
+ }, []);
+
+ return (
+
+ setQuery(e.target.value)}
+ placeholder="Search..."
+ />
+ {loading && }
+ {results?.map((item) => (
+
+ ))}
+
+ );
+}
+```
+
+### Real-time Data Visualization
+
+```tsx
+function DataVisualization() {
+ const { data, loading, run } = useCompute('processData');
+ const stats = usePoolStats(500);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ const newData = generateRandomData(10000);
+ run(newData);
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, [run]);
+
+ return (
+
+
+ Workers: {stats.activeWorkers}/{stats.totalWorkers}
+
+ {loading &&
}
+ {data &&
}
+
+ );
+}
+```
+
+---
+
+## Using External Libraries
+
+Load external libraries inside workers:
+
+```typescript
+const kit = new ComputeKit({
+ remoteDependencies: [
+ 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
+ 'https://cdnjs.cloudflare.com/ajax/libs/mathjs/11.8.0/math.min.js',
+ ],
+});
+
+kit.register('advancedMath', (expression: string) => {
+ // @ts-ignore - math.js loaded via importScripts
+ return math.evaluate(expression);
+});
+
+kit.register('processData', (data: number[]) => {
+ // @ts-ignore - lodash loaded via importScripts
+ return _.chain(data)
+ .filter((n) => n > 0)
+ .map((n) => n * 2)
+ .sortBy()
+ .value();
+});
+
+const result = await kit.run('advancedMath', 'sqrt(16) + sin(pi/2)');
+console.log(result); // 5
+```
+
+---
+
+## Live Demo
+
+Try the interactive demo:
+
+[View Live Demo]({{ site.baseurl }}/demo.html){: .btn .btn-primary }
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..5840c06
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,204 @@
+---
+layout: default
+title: Getting Started
+nav_order: 2
+description: 'Get started with ComputeKit in minutes'
+permalink: /getting-started
+---
+
+# Getting Started
+
+{: .no_toc }
+
+Get up and running with ComputeKit in just a few minutes.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## Installation
+
+### Core Package
+
+```bash
+npm install @computekit/core
+```
+
+### With React Bindings
+
+```bash
+npm install @computekit/core @computekit/react
+```
+
+### With React Query Integration
+
+```bash
+npm install @computekit/core @computekit/react-query @tanstack/react-query
+```
+
+---
+
+## Basic Usage
+
+### Vanilla JavaScript/TypeScript
+
+```typescript
+import { ComputeKit } from '@computekit/core';
+
+// Create an instance
+const kit = new ComputeKit();
+
+// Register compute functions
+kit.register('fibonacci', (n: number) => {
+ if (n <= 1) return n;
+ let a = 0,
+ b = 1;
+ for (let i = 2; i <= n; i++) {
+ [a, b] = [b, a + b];
+ }
+ return b;
+});
+
+kit.register('sum', (arr: number[]) => {
+ return arr.reduce((a, b) => a + b, 0);
+});
+
+// Run computations (non-blocking!)
+const fib = await kit.run('fibonacci', 50);
+console.log(fib); // 12586269025
+
+const total = await kit.run('sum', [1, 2, 3, 4, 5]);
+console.log(total); // 15
+```
+
+### React
+
+```tsx
+import { ComputeKitProvider, useComputeKit, useCompute } from '@computekit/react';
+import { useEffect } from 'react';
+
+// Wrap your app
+function App() {
+ return (
+
+
+
+ );
+}
+
+// Register functions once
+function MyApp() {
+ const kit = useComputeKit();
+
+ useEffect(() => {
+ kit.register('fibonacci', (n: number) => {
+ if (n <= 1) return n;
+ let a = 0,
+ b = 1;
+ for (let i = 2; i <= n; i++) {
+ [a, b] = [b, a + b];
+ }
+ return b;
+ });
+ }, [kit]);
+
+ return ;
+}
+
+// Use in components
+function Calculator() {
+ const { data, loading, error, run } = useCompute('fibonacci');
+
+ return (
+
+
+ {data &&
Result: {data}
}
+ {error &&
Error: {error.message}
}
+
+ );
+}
+```
+
+---
+
+## Configuration Options
+
+```typescript
+const kit = new ComputeKit({
+ // Maximum number of workers in the pool
+ maxWorkers: navigator.hardwareConcurrency || 4,
+
+ // Default timeout for operations (ms)
+ timeout: 30000,
+
+ // Enable debug logging
+ debug: false,
+
+ // Custom worker script path
+ workerPath: '',
+
+ // Use SharedArrayBuffer when available
+ useSharedMemory: true,
+
+ // External scripts to load in workers
+ remoteDependencies: [
+ 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
+ ],
+});
+```
+
+---
+
+## Vite/Webpack Configuration
+
+For SharedArrayBuffer support, you need to add COOP/COEP headers:
+
+### Vite
+
+```typescript
+// vite.config.ts
+export default {
+ server: {
+ headers: {
+ 'Cross-Origin-Opener-Policy': 'same-origin',
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
+ },
+ },
+};
+```
+
+### Webpack (Next.js)
+
+```javascript
+// next.config.js
+module.exports = {
+ async headers() {
+ return [
+ {
+ source: '/(.*)',
+ headers: [
+ { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
+ { key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
+ ],
+ },
+ ];
+ },
+};
+```
+
+---
+
+## Next Steps
+
+- Learn about [React Hooks]({{ site.baseurl }}/react-hooks) for the full React experience
+- Check the [API Reference]({{ site.baseurl }}/api-reference) for all available methods
+- Explore [WASM integration]({{ site.baseurl }}/wasm) for maximum performance
+- See [Examples]({{ site.baseurl }}/examples) for real-world use cases
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..1ebe461
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,181 @@
+---
+layout: home
+title: Home
+nav_order: 1
+description: 'ComputeKit - The React-first toolkit for WASM and Web Workers'
+permalink: /
+---
+
+
+

+
+
+# ComputeKit
+
+{: .fs-9 }
+
+The React-first toolkit for WASM and Web Workers
+{: .fs-6 .fw-300 }
+
+Run heavy computations with React hooks. Use WASM for native-speed performance. Keep your UI at 60fps.
+{: .fs-5 .fw-300 }
+
+[Get Started](#getting-started){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
+[View on GitHub](https://github.com/tapava/compute-kit){: .btn .fs-5 .mb-4 .mb-md-0 }
+
+---
+
+## ✨ Features
+
+| Feature | Description |
+| :----------------------- | :----------------------------------------------------------------------------- |
+| ⚛️ **React-first** | Purpose-built hooks like `useCompute` with loading, error, and progress states |
+| 🦀 **WASM integration** | Load and call AssemblyScript/Rust WASM modules with zero boilerplate |
+| 🚀 **Non-blocking** | Everything runs in Web Workers, keeping your UI at 60fps |
+| 🔧 **Zero config** | No manual worker files, postMessage handlers, or WASM glue code |
+| 📦 **Tiny** | Core library is ~3KB gzipped |
+| 🎯 **TypeScript** | Full type safety for your compute functions and WASM bindings |
+| 🔄 **Worker pool** | Automatic load balancing across CPU cores |
+| 📊 **Progress tracking** | Built-in progress reporting for long-running tasks |
+
+---
+
+## 🤔 Why ComputeKit?
+
+You _can_ use Web Workers and WASM without a library. But here's the reality:
+
+| Task | Without ComputeKit | With ComputeKit |
+| ----------------- | ------------------------------------------------------------------- | ---------------------------------- |
+| Web Worker setup | Create separate `.js` files, handle `postMessage`, manage callbacks | `kit.register('fn', myFunc)` |
+| WASM loading | Fetch, instantiate, memory management, glue code | `await loadWasmModule('/my.wasm')` |
+| React integration | Manual state, effects, cleanup, abort handling | `useCompute()` hook |
+| Worker pooling | Build your own pool, queue, and load balancer | Built-in |
+| TypeScript | Tricky worker typing, no WASM types | Full type inference |
+| Error handling | Try-catch across message boundaries | Automatic with React error states |
+
+**ComputeKit's unique value:** The only library that combines **React hooks + WASM + Worker pool** into one cohesive, type-safe developer experience.
+
+---
+
+## 🎯 When to use ComputeKit
+
+| ✅ Use ComputeKit | ❌ Don't use ComputeKit |
+| ---------------------------------- | ---------------------------- |
+| Image/video processing | Simple DOM updates |
+| Data transformations (100K+ items) | Small array operations |
+| Mathematical computations | API calls (use native fetch) |
+| Parsing large files | String formatting |
+| Cryptographic operations | UI state management |
+| Real-time data analysis | Small form validations |
+
+---
+
+## 📦 Installation
+
+```bash
+# npm
+npm install @computekit/core
+
+# With React bindings
+npm install @computekit/core @computekit/react
+
+# pnpm
+pnpm add @computekit/core @computekit/react
+
+# yarn
+yarn add @computekit/core @computekit/react
+```
+
+---
+
+## Getting Started
+
+### Basic Usage (Vanilla JS)
+
+```typescript
+import { ComputeKit } from '@computekit/core';
+
+// 1. Create a ComputeKit instance
+const kit = new ComputeKit();
+
+// 2. Register a compute function
+kit.register('fibonacci', (n: number) => {
+ if (n <= 1) return n;
+ let a = 0,
+ b = 1;
+ for (let i = 2; i <= n; i++) {
+ [a, b] = [b, a + b];
+ }
+ return b;
+});
+
+// 3. Run it (non-blocking!)
+const result = await kit.run('fibonacci', 50);
+console.log(result); // 12586269025 — UI never froze!
+```
+
+### React Usage
+
+```tsx
+import { ComputeKitProvider, useComputeKit, useCompute } from '@computekit/react';
+import { useEffect } from 'react';
+
+// 1. Wrap your app with the provider
+function App() {
+ return (
+
+
+
+ );
+}
+
+// 2. Register functions at the app level
+function AppContent() {
+ const kit = useComputeKit();
+
+ useEffect(() => {
+ kit.register('fibonacci', (n: number) => {
+ if (n <= 1) return n;
+ let a = 0,
+ b = 1;
+ for (let i = 2; i <= n; i++) {
+ [a, b] = [b, a + b];
+ }
+ return b;
+ });
+ }, [kit]);
+
+ return ;
+}
+
+// 3. Use the hook in any component
+function Calculator() {
+ const { data, loading, error, run } = useCompute('fibonacci');
+
+ return (
+
+
+ {data &&
Result: {data}
}
+ {error &&
Error: {error.message}
}
+
+ );
+}
+```
+
+---
+
+## Quick Links
+
+- [Getting Started Guide]({{ site.baseurl }}/getting-started)
+- [React Hooks Reference]({{ site.baseurl }}/react-hooks)
+- [API Reference]({{ site.baseurl }}/api-reference)
+- [WASM Guide]({{ site.baseurl }}/wasm)
+- [Examples]({{ site.baseurl }}/examples)
+
+---
+
+## 📄 License
+
+MIT © [Ghassen Lassoued](https://github.com/tapava)
diff --git a/docs/react-hooks.md b/docs/react-hooks.md
new file mode 100644
index 0000000..722328d
--- /dev/null
+++ b/docs/react-hooks.md
@@ -0,0 +1,310 @@
+---
+layout: default
+title: React Hooks
+nav_order: 3
+description: 'React hooks for ComputeKit'
+permalink: /react-hooks
+---
+
+# React Hooks
+
+{: .no_toc }
+
+ComputeKit provides purpose-built React hooks for seamless integration.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## ComputeKitProvider
+
+Wrap your application with the provider to enable all hooks.
+
+```tsx
+import { ComputeKitProvider } from '@computekit/react';
+
+function App() {
+ return (
+
+
+
+ );
+}
+```
+
+### Provider Options
+
+| Option | Type | Default | Description |
+| -------------------- | ---------- | ------------------------------- | ---------------------------- |
+| `maxWorkers` | `number` | `navigator.hardwareConcurrency` | Max workers in the pool |
+| `timeout` | `number` | `30000` | Default timeout in ms |
+| `debug` | `boolean` | `false` | Enable debug logging |
+| `remoteDependencies` | `string[]` | `[]` | External scripts for workers |
+
+---
+
+## useComputeKit
+
+Access the ComputeKit instance directly.
+
+```tsx
+import { useComputeKit } from '@computekit/react';
+
+function MyComponent() {
+ const kit = useComputeKit();
+
+ useEffect(() => {
+ // Register functions
+ kit.register('myFunction', (data) => {
+ // Heavy computation
+ return result;
+ });
+ }, [kit]);
+
+ return ...
;
+}
+```
+
+---
+
+## useCompute
+
+The primary hook for running compute functions.
+
+```tsx
+import { useCompute } from '@computekit/react';
+
+function Calculator() {
+ const {
+ data, // Result data (TOutput | undefined)
+ loading, // Boolean loading state
+ error, // Error if failed (Error | null)
+ progress, // Progress info (ComputeProgress | undefined)
+ status, // 'idle' | 'running' | 'success' | 'error' | 'cancelled'
+ run, // Function to execute
+ reset, // Reset state to idle
+ cancel, // Cancel current operation
+ } = useCompute('functionName');
+
+ return (
+
+
+
+ {progress &&
}
+ {data &&
Result: {data}
}
+ {error &&
{error.message}
}
+
+ );
+}
+```
+
+### Options
+
+```tsx
+const { run } = useCompute('functionName', {
+ // Initial input to run on mount
+ initialInput: undefined,
+
+ // Run immediately on mount
+ runOnMount: false,
+
+ // Timeout for this specific function
+ timeout: 5000,
+
+ // Progress callback
+ onProgress: (progress) => {
+ console.log(`${progress.percent}% complete`);
+ },
+
+ // Success callback
+ onSuccess: (data) => {
+ console.log('Completed:', data);
+ },
+
+ // Error callback
+ onError: (error) => {
+ console.error('Failed:', error);
+ },
+});
+```
+
+---
+
+## useComputeCallback
+
+Returns a memoized async function, similar to `useCallback`.
+
+```tsx
+import { useComputeCallback } from '@computekit/react';
+
+function MyComponent() {
+ const calculate = useComputeCallback('sum');
+
+ const handleClick = async () => {
+ const result = await calculate([1, 2, 3, 4, 5]);
+ console.log(result); // 15
+ };
+
+ return ;
+}
+```
+
+---
+
+## useComputeFunction
+
+Register and use a function in a single hook.
+
+```tsx
+import { useComputeFunction } from '@computekit/react';
+
+function MyComponent() {
+ const { data, loading, run } = useComputeFunction('double', (n: number) => n * 2);
+
+ return (
+
+ );
+}
+```
+
+---
+
+## usePoolStats
+
+Monitor worker pool performance in real-time.
+
+```tsx
+import { usePoolStats } from '@computekit/react';
+
+function PoolMonitor() {
+ // Refresh every 1000ms
+ const stats = usePoolStats(1000);
+
+ return (
+
+
Total Workers: {stats.totalWorkers}
+
Active: {stats.activeWorkers}
+
Idle: {stats.idleWorkers}
+
Queue: {stats.queueLength}
+
Completed: {stats.tasksCompleted}
+
Failed: {stats.tasksFailed}
+
Avg Duration: {stats.averageTaskDuration.toFixed(2)}ms
+
+ );
+}
+```
+
+---
+
+## Progress Reporting
+
+Track progress for long-running operations:
+
+```tsx
+// Register function with progress reporting
+kit.register('longTask', async (data, { reportProgress }) => {
+ const total = data.items.length;
+ const results = [];
+
+ for (let i = 0; i < total; i++) {
+ results.push(await process(data.items[i]));
+
+ // Report progress
+ reportProgress({
+ percent: ((i + 1) / total) * 100,
+ phase: 'Processing',
+ data: { current: i + 1, total },
+ });
+ }
+
+ return results;
+});
+
+// Use with progress tracking
+function LongTaskComponent() {
+ const { progress, loading, run } = useCompute('longTask');
+
+ return (
+
+
+
+ {loading && progress && (
+
+
+
+ {progress.phase}: {progress.percent.toFixed(0)}%
+
+
+ )}
+
+ );
+}
+```
+
+---
+
+## Cancellation
+
+Cancel running operations using AbortController:
+
+```tsx
+function CancellableTask() {
+ const { data, loading, run, cancel } = useCompute('longTask');
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+---
+
+## TypeScript Support
+
+Full type inference for inputs and outputs:
+
+```tsx
+// Define your types
+interface ImageInput {
+ data: number[];
+ width: number;
+ height: number;
+}
+
+interface ImageOutput {
+ data: number[];
+ processingTime: number;
+}
+
+// Types are inferred in the hook
+const { data, run } = useCompute('processImage');
+
+// data is ImageOutput | undefined
+// run expects ImageInput
+run({ data: [...], width: 256, height: 256 });
+```
+
+---
+
+## Next Steps
+
+- Check the [API Reference]({{ site.baseurl }}/api-reference) for the complete API
+- Learn about [WASM integration]({{ site.baseurl }}/wasm) for native-speed performance
diff --git a/docs/react-query.md b/docs/react-query.md
new file mode 100644
index 0000000..dd434f1
--- /dev/null
+++ b/docs/react-query.md
@@ -0,0 +1,139 @@
+---
+layout: default
+title: React Query
+nav_order: 7
+description: 'React Query integration for ComputeKit'
+permalink: /react-query
+---
+
+# React Query Integration
+
+{: .no_toc }
+
+Seamlessly integrate ComputeKit with TanStack React Query.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## Installation
+
+```bash
+npm install @computekit/core @computekit/react-query @tanstack/react-query
+```
+
+---
+
+## Setup
+
+Wrap your app with both providers:
+
+```tsx
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ComputeKitProvider } from '@computekit/react';
+
+const queryClient = new QueryClient();
+
+function App() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+---
+
+## useComputeQuery
+
+Use ComputeKit functions with React Query's caching and refetching.
+
+```tsx
+import { useComputeQuery } from '@computekit/react-query';
+
+function DataProcessor() {
+ const { data, isLoading, error, refetch } = useComputeQuery(
+ ['processData', dataId], // Query key
+ 'heavyProcess', // Function name
+ inputData, // Input
+ {
+ staleTime: 5 * 60 * 1000, // 5 minutes
+ cacheTime: 30 * 60 * 1000, // 30 minutes
+ }
+ );
+
+ return (
+
+ {isLoading && }
+ {data && }
+
+
+ );
+}
+```
+
+---
+
+## useComputeMutation
+
+For on-demand computations:
+
+```tsx
+import { useComputeMutation } from '@computekit/react-query';
+
+function ImageEditor() {
+ const mutation = useComputeMutation('processImage', {
+ onSuccess: (data) => {
+ console.log('Processed:', data);
+ },
+ onError: (error) => {
+ console.error('Failed:', error);
+ },
+ });
+
+ return (
+
+
+
+ );
+}
+```
+
+---
+
+## Benefits
+
+| Feature | Description |
+| ---------------------- | ------------------------------------------------------ |
+| **Caching** | Results are cached and reused automatically |
+| **Background Updates** | Stale data is refreshed in the background |
+| **Deduplication** | Identical queries are deduplicated |
+| **DevTools** | Use React Query DevTools to inspect compute operations |
+| **Suspense** | Works with React Suspense for loading states |
+
+---
+
+## Example: Cached Computation
+
+```tsx
+function ExpensiveCalculation({ params }) {
+ const { data } = useComputeQuery(['calculate', params], 'expensiveCalc', params, {
+ staleTime: Infinity, // Never refetch automatically
+ cacheTime: 60 * 60 * 1000, // Keep in cache for 1 hour
+ });
+
+ // If the same params are used again, the cached result is returned instantly
+ return ;
+}
+```
diff --git a/docs/wasm.md b/docs/wasm.md
new file mode 100644
index 0000000..b62e1e4
--- /dev/null
+++ b/docs/wasm.md
@@ -0,0 +1,357 @@
+---
+layout: default
+title: WebAssembly
+nav_order: 5
+description: 'WASM integration with ComputeKit'
+permalink: /wasm
+---
+
+# WebAssembly Integration
+
+{: .no_toc }
+
+Use WASM for native-speed performance in your compute functions.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## Overview
+
+ComputeKit provides seamless WebAssembly integration, allowing you to:
+
+- Load WASM modules from URLs, ArrayBuffers, or base64 strings
+- Use AssemblyScript for easy TypeScript-to-WASM compilation
+- Combine WASM with Web Workers for maximum performance
+- Manage WASM memory efficiently
+
+---
+
+## WASM Utilities
+
+### loadWasmModule()
+
+Load a WASM module from various sources.
+
+```typescript
+import { loadWasmModule } from '@computekit/core';
+
+// From URL
+const module = await loadWasmModule('/path/to/module.wasm');
+
+// From ArrayBuffer
+const bytes = await fetch('/module.wasm').then((r) => r.arrayBuffer());
+const module = await loadWasmModule(bytes);
+
+// From base64
+const module = await loadWasmModule('data:application/wasm;base64,...');
+```
+
+### loadAndInstantiate()
+
+Load and instantiate a WASM module with custom imports.
+
+```typescript
+import { loadAndInstantiate } from '@computekit/core';
+
+const { module, instance } = await loadAndInstantiate({
+ source: '/module.wasm',
+ imports: {
+ env: {
+ log: (value: number) => console.log(value),
+ abort: () => {
+ throw new Error('WASM abort');
+ },
+ },
+ },
+ memory: {
+ initial: 256, // 256 pages = 16MB
+ maximum: 512, // 512 pages = 32MB
+ shared: true, // Use SharedArrayBuffer
+ },
+});
+
+// Call exported functions
+const result = instance.exports.compute(42);
+```
+
+### loadAssemblyScript()
+
+Load an AssemblyScript-compiled WASM module with default imports.
+
+```typescript
+import { loadAssemblyScript } from '@computekit/core';
+
+const { exports } = await loadAssemblyScript('/as-module.wasm');
+
+// Call exported functions directly
+const sum = exports.computeSum(new Int32Array([1, 2, 3, 4, 5]));
+```
+
+---
+
+## Memory Utilities
+
+### getMemoryView()
+
+Create a typed array view into WASM memory.
+
+```typescript
+import { getMemoryView } from '@computekit/core';
+
+const view = getMemoryView(memory, Float64Array, 0, 100);
+// Now you can read/write to WASM memory through `view`
+```
+
+### copyToWasmMemory()
+
+Copy data to WASM memory.
+
+```typescript
+import { copyToWasmMemory } from '@computekit/core';
+
+const data = new Uint8Array([1, 2, 3, 4]);
+copyToWasmMemory(wasmMemory, data, 0);
+```
+
+### copyFromWasmMemory()
+
+Copy data from WASM memory.
+
+```typescript
+import { copyFromWasmMemory } from '@computekit/core';
+
+const result = copyFromWasmMemory(wasmMemory, 0, 4);
+// Uint8Array([1, 2, 3, 4])
+```
+
+---
+
+## Cache Management
+
+### clearWasmCache()
+
+Clear the WASM module cache.
+
+```typescript
+import { clearWasmCache } from '@computekit/core';
+
+clearWasmCache();
+```
+
+### getWasmCacheStats()
+
+Get WASM cache statistics.
+
+```typescript
+import { getWasmCacheStats } from '@computekit/core';
+
+const stats = getWasmCacheStats();
+console.log(`Cached modules: ${stats.modules}`);
+console.log(`Cached instances: ${stats.instances}`);
+```
+
+---
+
+## AssemblyScript Guide
+
+### 1. Write AssemblyScript
+
+Create an AssemblyScript file with your compute functions:
+
+```typescript
+// compute/sum.ts
+export function sum(arr: Int32Array): i32 {
+ let total: i32 = 0;
+ for (let i = 0; i < arr.length; i++) {
+ total += unchecked(arr[i]);
+ }
+ return total;
+}
+
+export function fibonacci(n: i32): i64 {
+ if (n <= 1) return n as i64;
+ let a: i64 = 0;
+ let b: i64 = 1;
+ for (let i: i32 = 2; i <= n; i++) {
+ let temp = a + b;
+ a = b;
+ b = temp;
+ }
+ return b;
+}
+```
+
+### 2. Install AssemblyScript
+
+```bash
+npm install --save-dev assemblyscript
+npx asinit .
+```
+
+### 3. Compile to WASM
+
+```bash
+npx asc compute/sum.ts -o public/sum.wasm --optimize
+```
+
+Or add a script to `package.json`:
+
+```json
+{
+ "scripts": {
+ "build:wasm": "asc compute/sum.ts -o public/sum.wasm --optimize"
+ }
+}
+```
+
+### 4. Use in ComputeKit
+
+```typescript
+import { ComputeKit, loadAssemblyScript } from '@computekit/core';
+
+const kit = new ComputeKit();
+
+kit.register('wasmSum', async (data: number[]) => {
+ const { exports } = await loadAssemblyScript('/sum.wasm');
+ const arr = new Int32Array(data);
+ return exports.sum(arr);
+});
+
+const result = await kit.run('wasmSum', [1, 2, 3, 4, 5]);
+console.log(result); // 15
+```
+
+---
+
+## React + WASM Example
+
+Combine `useCompute` with WASM for the ultimate performance:
+
+```tsx
+import { ComputeKitProvider, useComputeKit, useCompute } from '@computekit/react';
+import { loadAssemblyScript } from '@computekit/core';
+import { useEffect, useRef } from 'react';
+
+function App() {
+ return (
+
+
+
+ );
+}
+
+function ImageProcessor() {
+ const kit = useComputeKit();
+ const canvasRef = useRef(null);
+
+ useEffect(() => {
+ // Register WASM-powered blur function
+ kit.register(
+ 'blurImage',
+ async (input: {
+ data: number[];
+ width: number;
+ height: number;
+ passes: number;
+ }) => {
+ const { exports, memory } = await loadAssemblyScript('/blur.wasm');
+ const { data, width, height, passes } = input;
+
+ // Copy input to WASM memory
+ const ptr = exports.getBufferPtr();
+ const wasmMem = new Uint8ClampedArray(memory.buffer, ptr, data.length);
+ wasmMem.set(data);
+
+ // Run WASM blur
+ exports.blurImage(width, height, passes);
+
+ // Return result
+ return Array.from(new Uint8ClampedArray(memory.buffer, ptr, data.length));
+ }
+ );
+ }, [kit]);
+
+ const { data, loading, run } = useCompute<
+ { data: number[]; width: number; height: number; passes: number },
+ number[]
+ >('blurImage');
+
+ const handleBlur = () => {
+ const canvas = canvasRef.current!;
+ const ctx = canvas.getContext('2d')!;
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+ run({
+ data: Array.from(imageData.data),
+ width: canvas.width,
+ height: canvas.height,
+ passes: 10,
+ });
+ };
+
+ useEffect(() => {
+ if (data && canvasRef.current) {
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext('2d')!;
+ const imageData = new ImageData(
+ new Uint8ClampedArray(data),
+ canvas.width,
+ canvas.height
+ );
+ ctx.putImageData(imageData, 0, 0);
+ }
+ }, [data]);
+
+ return (
+
+
+
+
+ );
+}
+```
+
+---
+
+## Performance Tips
+
+{: .note }
+WASM functions can be 10-100x faster than JavaScript for numeric computations.
+
+1. **Minimize memory copies** - Use typed arrays and transfer data efficiently
+2. **Batch operations** - Process large chunks of data at once
+3. **Use SIMD** - AssemblyScript supports SIMD for parallel math operations
+4. **Pre-allocate memory** - Avoid growing WASM memory during computation
+
+```typescript
+// ❌ Slow: Many small WASM calls
+for (const pixel of pixels) {
+ wasm.processPixel(pixel);
+}
+
+// ✅ Fast: One batched WASM call
+wasm.processAllPixels(pixelBuffer, width, height);
+```
+
+---
+
+## Browser Support
+
+| Browser | WASM | SharedArrayBuffer |
+| ----------- | ---- | ----------------- |
+| Chrome 57+ | ✅ | ✅ (with headers) |
+| Firefox 52+ | ✅ | ✅ (with headers) |
+| Safari 11+ | ✅ | ✅ (Safari 15.2+) |
+| Edge 16+ | ✅ | ✅ (with headers) |
+
+{: .warning }
+SharedArrayBuffer requires Cross-Origin Isolation headers. See the [Getting Started]({{ site.baseurl }}/getting-started#vitepwebpack-configuration) guide for configuration.
From f703f2cf5061ad2371af1f0c80a65bb6e7012ae2 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 13:47:34 +0000
Subject: [PATCH 20/37] fix: ensure pages setup is correctly enabled in
workflow
---
.github/workflows/docs.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index ed54dde..4544052 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -23,6 +23,8 @@ jobs:
- name: Setup Pages
uses: actions/configure-pages@v4
+ with:
+ enablement: true
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
From 5a58ae8c51b48e206ebcbb03266186c2a39b9eb6 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 13:50:59 +0000
Subject: [PATCH 21/37] fix: remove unnecessary enablement configuration in
Setup Pages step
---
.github/workflows/docs.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 4544052..ed54dde 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -23,8 +23,6 @@ jobs:
- name: Setup Pages
uses: actions/configure-pages@v4
- with:
- enablement: true
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
From bc6cf594f4a857d79c0172cbf0185f9feaaa4959 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 13:57:17 +0000
Subject: [PATCH 22/37] feat: update theme colors and add custom styles for
ComputeKit
---
docs/_config.yml | 2 +-
docs/_sass/custom/custom.scss | 438 ++++++++++++++++++++++++++++++++++
docs/assets/logo.svg | 48 ++--
3 files changed, 463 insertions(+), 25 deletions(-)
create mode 100644 docs/_sass/custom/custom.scss
diff --git a/docs/_config.yml b/docs/_config.yml
index 42447c1..62f77d1 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -5,7 +5,7 @@ baseurl: '/compute-kit'
remote_theme: just-the-docs/just-the-docs@v0.8.2
-color_scheme: dark
+color_scheme: light
logo: '/assets/logo.svg'
diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss
new file mode 100644
index 0000000..99c4573
--- /dev/null
+++ b/docs/_sass/custom/custom.scss
@@ -0,0 +1,438 @@
+// Modern ComputeKit Theme - Inspired by TanStack
+// Clean, professional, and readable
+
+// Import Inter font for modern typography
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
+
+// Root variables
+:root {
+ // Colors - Modern purple/blue accent like TanStack
+ --color-primary: #7c3aed;
+ --color-primary-light: #a78bfa;
+ --color-primary-dark: #5b21b6;
+ --color-accent: #06b6d4;
+
+ // Background
+ --color-bg: #ffffff;
+ --color-bg-secondary: #f8fafc;
+ --color-bg-tertiary: #f1f5f9;
+
+ // Text
+ --color-text: #1e293b;
+ --color-text-secondary: #64748b;
+ --color-text-muted: #94a3b8;
+
+ // Borders
+ --color-border: #e2e8f0;
+ --color-border-light: #f1f5f9;
+
+ // Code
+ --color-code-bg: #f8fafc;
+ --color-code-border: #e2e8f0;
+
+ // Shadows
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
+
+ // Fonts
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
+}
+
+// Base typography
+body {
+ font-family: var(--font-body) !important;
+ font-size: 16px;
+ line-height: 1.7;
+ color: var(--color-text);
+ background-color: var(--color-bg);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+// Headings
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.site-title {
+ font-family: var(--font-body) !important;
+ font-weight: 700;
+ color: var(--color-text);
+ letter-spacing: -0.02em;
+}
+
+h1 {
+ font-size: 2.5rem !important;
+ margin-bottom: 1rem;
+}
+
+h2 {
+ font-size: 1.75rem !important;
+ margin-top: 2.5rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid var(--color-border);
+}
+
+h3 {
+ font-size: 1.35rem !important;
+ margin-top: 2rem;
+}
+
+// Sidebar styles
+.side-bar {
+ background-color: var(--color-bg-secondary) !important;
+ border-right: 1px solid var(--color-border);
+}
+
+.site-title {
+ font-size: 1.25rem !important;
+ font-weight: 700 !important;
+ color: var(--color-primary) !important;
+}
+
+.site-logo {
+ max-height: 2.5rem;
+}
+
+// Navigation
+.nav-list {
+ font-size: 0.9rem;
+
+ .nav-list-item {
+ margin: 0;
+
+ .nav-list-link {
+ padding: 0.5rem 1rem;
+ border-radius: 0.5rem;
+ margin: 0.125rem 0.5rem;
+ color: var(--color-text-secondary);
+ font-weight: 500;
+ transition: all 0.15s ease;
+
+ &:hover {
+ background-color: var(--color-bg-tertiary);
+ color: var(--color-primary);
+ }
+
+ &.active {
+ background-color: rgba(124, 58, 237, 0.1);
+ color: var(--color-primary);
+ font-weight: 600;
+ }
+ }
+ }
+}
+
+// Main content
+.main {
+ background-color: var(--color-bg);
+}
+
+.main-content {
+ max-width: 52rem;
+ padding: 2rem 3rem;
+
+ p {
+ margin-bottom: 1.25rem;
+ color: var(--color-text);
+ }
+
+ a {
+ color: var(--color-primary);
+ text-decoration: none;
+ font-weight: 500;
+
+ &:hover {
+ text-decoration: underline;
+ color: var(--color-primary-dark);
+ }
+ }
+}
+
+// Code blocks
+code {
+ font-family: var(--font-mono) !important;
+ font-size: 0.875em;
+ background-color: var(--color-code-bg);
+ padding: 0.2em 0.4em;
+ border-radius: 0.375rem;
+ color: var(--color-primary-dark);
+}
+
+pre {
+ background-color: #1e293b !important;
+ border: none !important;
+ border-radius: 0.75rem !important;
+ padding: 1.25rem !important;
+ margin: 1.5rem 0 !important;
+ box-shadow: var(--shadow-md);
+
+ code {
+ background-color: transparent !important;
+ color: #e2e8f0 !important;
+ padding: 0;
+ font-size: 0.875rem;
+ }
+}
+
+// Syntax highlighting (dark theme for code blocks)
+.highlight {
+ background-color: #1e293b !important;
+ border-radius: 0.75rem;
+
+ .c,
+ .c1,
+ .cm {
+ color: #64748b;
+ } // Comments
+ .k,
+ .kd,
+ .kn,
+ .kp,
+ .kr {
+ color: #c084fc;
+ } // Keywords
+ .s,
+ .s1,
+ .s2,
+ .sb,
+ .sc,
+ .sd,
+ .se,
+ .sh,
+ .si,
+ .sx,
+ .sr,
+ .ss {
+ color: #86efac;
+ } // Strings
+ .n,
+ .na,
+ .nb,
+ .nc,
+ .nd,
+ .ne,
+ .nf,
+ .ni,
+ .nl,
+ .nn,
+ .no,
+ .nt,
+ .nv {
+ color: #7dd3fc;
+ } // Names
+ .m,
+ .mf,
+ .mh,
+ .mi,
+ .mo {
+ color: #fbbf24;
+ } // Numbers
+ .o,
+ .ow {
+ color: #f472b6;
+ } // Operators
+ .p {
+ color: #e2e8f0;
+ } // Punctuation
+}
+
+// Tables
+table {
+ border-collapse: collapse;
+ width: 100%;
+ margin: 1.5rem 0;
+ font-size: 0.9rem;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: var(--shadow-sm);
+ border: 1px solid var(--color-border);
+}
+
+th {
+ background-color: var(--color-bg-tertiary);
+ font-weight: 600;
+ text-align: left;
+ padding: 0.75rem 1rem;
+ border-bottom: 2px solid var(--color-border);
+}
+
+td {
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid var(--color-border-light);
+}
+
+tr:last-child td {
+ border-bottom: none;
+}
+
+tr:hover {
+ background-color: var(--color-bg-secondary);
+}
+
+// Buttons
+.btn {
+ font-family: var(--font-body);
+ font-weight: 600;
+ font-size: 0.9rem;
+ padding: 0.625rem 1.25rem;
+ border-radius: 0.5rem;
+ transition: all 0.15s ease;
+ text-decoration: none !important;
+}
+
+.btn-primary {
+ background-color: var(--color-primary) !important;
+ color: white !important;
+ border: none;
+ box-shadow: var(--shadow-sm);
+
+ &:hover {
+ background-color: var(--color-primary-dark) !important;
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-md);
+ }
+}
+
+// Callouts
+.warning,
+.note,
+.tip {
+ border-radius: 0.75rem;
+ padding: 1rem 1.25rem;
+ margin: 1.5rem 0;
+ border-left: 4px solid;
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.note {
+ background-color: #eff6ff;
+ border-left-color: #3b82f6;
+}
+
+.warning {
+ background-color: #fef3c7;
+ border-left-color: #f59e0b;
+}
+
+.tip {
+ background-color: #ecfdf5;
+ border-left-color: #10b981;
+}
+
+// Search
+.search-input {
+ font-family: var(--font-body);
+ border-radius: 0.5rem;
+ border: 1px solid var(--color-border);
+ padding: 0.625rem 1rem;
+ font-size: 0.9rem;
+
+ &:focus {
+ border-color: var(--color-primary);
+ box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
+ outline: none;
+ }
+}
+
+// TOC
+.toc {
+ background-color: var(--color-bg-secondary);
+ border-radius: 0.75rem;
+ padding: 1rem 1.25rem;
+ border: 1px solid var(--color-border);
+}
+
+// Footer
+.site-footer {
+ border-top: 1px solid var(--color-border);
+ color: var(--color-text-muted);
+ font-size: 0.875rem;
+}
+
+// Aux links (GitHub, npm)
+.aux-nav {
+ .aux-nav-list-item {
+ a {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--color-text-secondary);
+
+ &:hover {
+ color: var(--color-primary);
+ }
+ }
+ }
+}
+
+// Home page hero adjustments
+.fs-9 {
+ font-size: 3rem !important;
+ font-weight: 800 !important;
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.fs-6 {
+ font-size: 1.35rem !important;
+ color: var(--color-text-secondary) !important;
+}
+
+// Lists
+ul,
+ol {
+ margin-bottom: 1.25rem;
+
+ li {
+ margin-bottom: 0.5rem;
+
+ &::marker {
+ color: var(--color-primary);
+ }
+ }
+}
+
+// Blockquotes
+blockquote {
+ border-left: 3px solid var(--color-primary);
+ padding-left: 1rem;
+ color: var(--color-text-secondary);
+ font-style: italic;
+}
+
+// Back to top button
+.back-to-top {
+ background-color: var(--color-primary);
+ color: white;
+ border-radius: 9999px;
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+
+ &:hover {
+ background-color: var(--color-primary-dark);
+ }
+}
+
+// Responsive
+@media (max-width: 800px) {
+ .main-content {
+ padding: 1.5rem;
+ }
+
+ h1 {
+ font-size: 2rem !important;
+ }
+
+ .fs-9 {
+ font-size: 2.25rem !important;
+ }
+}
diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg
index 44ecb90..da53943 100644
--- a/docs/assets/logo.svg
+++ b/docs/assets/logo.svg
@@ -1,40 +1,40 @@
From e8d1da83e30f40a78fed6dc4232ab0d7368c753b Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 14:01:09 +0000
Subject: [PATCH 23/37] feat: enhance search component with improved styling
and functionality
---
docs/_sass/custom/custom.scss | 112 +++++++++++++++++++++++++++++++++-
1 file changed, 109 insertions(+), 3 deletions(-)
diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss
index 99c4573..939121b 100644
--- a/docs/_sass/custom/custom.scss
+++ b/docs/_sass/custom/custom.scss
@@ -327,12 +327,32 @@ tr:hover {
}
// Search
+.search {
+ position: relative;
+ margin-bottom: 1rem;
+ padding: 0 1rem;
+}
+
+.search-input-wrap {
+ display: flex;
+ align-items: center;
+ position: relative;
+}
+
.search-input {
font-family: var(--font-body);
- border-radius: 0.5rem;
- border: 1px solid var(--color-border);
- padding: 0.625rem 1rem;
+ width: 100%;
+ padding: 0.625rem 1rem 0.625rem 2.5rem;
font-size: 0.9rem;
+ background-color: var(--color-bg);
+ border: 1px solid var(--color-border);
+ border-radius: 0.5rem;
+ color: var(--color-text);
+ transition: all 0.15s ease;
+
+ &::placeholder {
+ color: var(--color-text-muted);
+ }
&:focus {
border-color: var(--color-primary);
@@ -341,6 +361,92 @@ tr:hover {
}
}
+.search-label {
+ position: absolute;
+ left: 0.75rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--color-text-muted);
+ pointer-events: none;
+
+ .search-icon {
+ width: 1rem;
+ height: 1rem;
+ }
+}
+
+.search-results {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ margin-top: 0.5rem;
+ background-color: var(--color-bg);
+ border: 1px solid var(--color-border);
+ border-radius: 0.5rem;
+ box-shadow: var(--shadow-md);
+ max-height: 20rem;
+ overflow-y: auto;
+ z-index: 100;
+}
+
+.search-results-list {
+ list-style: none;
+ padding: 0.5rem;
+ margin: 0;
+}
+
+.search-results-list-item {
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.375rem;
+ cursor: pointer;
+
+ &:hover,
+ &.active {
+ background-color: var(--color-bg-tertiary);
+ }
+}
+
+.search-result-title {
+ font-weight: 600;
+ color: var(--color-text);
+ font-size: 0.9rem;
+}
+
+.search-result-doc {
+ font-size: 0.8rem;
+ color: var(--color-text-secondary);
+ margin-top: 0.125rem;
+
+ .search-result-doc-title {
+ font-weight: 500;
+ }
+}
+
+.search-result-previews {
+ font-size: 0.8rem;
+ color: var(--color-text-muted);
+ margin-top: 0.25rem;
+}
+
+.search-result-preview + .search-result-preview {
+ margin-top: 0.125rem;
+}
+
+.search-result-highlight {
+ background-color: rgba(124, 58, 237, 0.15);
+ color: var(--color-primary-dark);
+ padding: 0.125em 0.25em;
+ border-radius: 0.25rem;
+}
+
+.search-no-result {
+ padding: 1rem;
+ text-align: center;
+ color: var(--color-text-muted);
+ font-size: 0.9rem;
+}
+
// TOC
.toc {
background-color: var(--color-bg-secondary);
From 4f98e33af179da915018486a8146da2bba22e9d8 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 14:04:31 +0000
Subject: [PATCH 24/37] fix: update code block styling for improved readability
and dark theme support
---
docs/_sass/custom/custom.scss | 124 ++++++++++++++++++++++++++--------
1 file changed, 96 insertions(+), 28 deletions(-)
diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss
index 939121b..952f62a 100644
--- a/docs/_sass/custom/custom.scss
+++ b/docs/_sass/custom/custom.scss
@@ -163,38 +163,56 @@ code {
}
pre {
- background-color: #1e293b !important;
+ background-color: #0d1117 !important;
border: none !important;
border-radius: 0.75rem !important;
padding: 1.25rem !important;
margin: 1.5rem 0 !important;
box-shadow: var(--shadow-md);
+ overflow-x: auto;
code {
background-color: transparent !important;
- color: #e2e8f0 !important;
+ color: #e6edf3 !important;
padding: 0;
font-size: 0.875rem;
+ line-height: 1.6;
}
}
-// Syntax highlighting (dark theme for code blocks)
+// Syntax highlighting - GitHub Dark theme (high contrast)
.highlight {
- background-color: #1e293b !important;
+ background-color: #0d1117 !important;
border-radius: 0.75rem;
+ // Plain text
+ .p,
+ .w {
+ color: #e6edf3;
+ }
+
+ // Comments
.c,
.c1,
- .cm {
- color: #64748b;
- } // Comments
+ .cm,
+ .cs,
+ .cp {
+ color: #8b949e;
+ font-style: italic;
+ }
+
+ // Keywords (import, from, const, function, async, await, return)
.k,
.kd,
.kn,
.kp,
- .kr {
- color: #c084fc;
- } // Keywords
+ .kr,
+ .kc,
+ .kt {
+ color: #ff7b72;
+ }
+
+ // Strings
.s,
.s1,
.s2,
@@ -207,37 +225,87 @@ pre {
.sx,
.sr,
.ss {
- color: #86efac;
- } // Strings
+ color: #a5d6ff;
+ }
+
+ // Function names and method calls
+ .nf,
+ .fm {
+ color: #d2a8ff;
+ }
+
+ // Class names and types
+ .nc,
+ .nn,
+ .nx {
+ color: #ffa657;
+ }
+
+ // Variables and identifiers
.n,
.na,
.nb,
- .nc,
- .nd,
- .ne,
- .nf,
.ni,
.nl,
- .nn,
.no,
- .nt,
- .nv {
- color: #7dd3fc;
- } // Names
+ .nv,
+ .ne,
+ .nd {
+ color: #e6edf3;
+ }
+
+ // HTML/JSX tags
+ .nt {
+ color: #7ee787;
+ }
+
+ // Numbers
.m,
.mf,
.mh,
.mi,
- .mo {
- color: #fbbf24;
- } // Numbers
+ .mo,
+ .il {
+ color: #79c0ff;
+ }
+
+ // Operators
.o,
.ow {
- color: #f472b6;
- } // Operators
+ color: #ff7b72;
+ }
+
+ // Punctuation (braces, parentheses, etc)
.p {
- color: #e2e8f0;
- } // Punctuation
+ color: #e6edf3;
+ }
+
+ // Property names
+ .py,
+ .na {
+ color: #79c0ff;
+ }
+
+ // Special - imports and exports
+ .kn {
+ color: #ff7b72;
+ }
+
+ // Decorators
+ .nd {
+ color: #d2a8ff;
+ }
+
+ // Built-in functions
+ .nb {
+ color: #ffa657;
+ }
+
+ // Error
+ .err {
+ color: #ffa198;
+ background-color: transparent;
+ }
}
// Tables
From 4e22a11c82a32136adf1eb61dff8b7ecfa454ed2 Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sat, 27 Dec 2025 14:11:17 +0000
Subject: [PATCH 25/37] feat: replace static table of contents with collapsible
details in documentation
---
docs/api-reference.md | 13 +++++++------
docs/examples.md | 13 +++++++------
docs/getting-started.md | 13 +++++++------
docs/react-hooks.md | 13 +++++++------
docs/react-query.md | 13 +++++++------
docs/wasm.md | 13 +++++++------
6 files changed, 42 insertions(+), 36 deletions(-)
diff --git a/docs/api-reference.md b/docs/api-reference.md
index 55d7df9..5635d46 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -13,12 +13,13 @@ permalink: /api-reference
Complete API documentation for the ComputeKit core library.
{: .fs-6 .fw-300 }
-## Table of contents
-
-{: .no_toc .text-delta }
-
-1. TOC
- {:toc}
+
+
+ Table of contents
+ {: .text-delta }
+- TOC
+{:toc}
+
---
diff --git a/docs/examples.md b/docs/examples.md
index cf22ebd..803e0db 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -13,12 +13,13 @@ permalink: /examples
Real-world examples demonstrating ComputeKit's capabilities.
{: .fs-6 .fw-300 }
-## Table of contents
-
-{: .no_toc .text-delta }
-
-1. TOC
- {:toc}
+
+
+ Table of contents
+ {: .text-delta }
+- TOC
+{:toc}
+
---
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 5840c06..b247596 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -13,12 +13,13 @@ permalink: /getting-started
Get up and running with ComputeKit in just a few minutes.
{: .fs-6 .fw-300 }
-## Table of contents
-
-{: .no_toc .text-delta }
-
-1. TOC
- {:toc}
+
+
+ Table of contents
+ {: .text-delta }
+- TOC
+{:toc}
+
---
diff --git a/docs/react-hooks.md b/docs/react-hooks.md
index 722328d..5fea34b 100644
--- a/docs/react-hooks.md
+++ b/docs/react-hooks.md
@@ -13,12 +13,13 @@ permalink: /react-hooks
ComputeKit provides purpose-built React hooks for seamless integration.
{: .fs-6 .fw-300 }
-## Table of contents
-
-{: .no_toc .text-delta }
-
-1. TOC
- {:toc}
+
+
+ Table of contents
+ {: .text-delta }
+- TOC
+{:toc}
+
---
diff --git a/docs/react-query.md b/docs/react-query.md
index dd434f1..98fab94 100644
--- a/docs/react-query.md
+++ b/docs/react-query.md
@@ -13,12 +13,13 @@ permalink: /react-query
Seamlessly integrate ComputeKit with TanStack React Query.
{: .fs-6 .fw-300 }
-## Table of contents
-
-{: .no_toc .text-delta }
-
-1. TOC
- {:toc}
+
+
+ Table of contents
+ {: .text-delta }
+- TOC
+{:toc}
+
---
diff --git a/docs/wasm.md b/docs/wasm.md
index b62e1e4..357509b 100644
--- a/docs/wasm.md
+++ b/docs/wasm.md
@@ -13,12 +13,13 @@ permalink: /wasm
Use WASM for native-speed performance in your compute functions.
{: .fs-6 .fw-300 }
-## Table of contents
-
-{: .no_toc .text-delta }
-
-1. TOC
- {:toc}
+
+
+ Table of contents
+ {: .text-delta }
+- TOC
+{:toc}
+
---
From 7ea8a3b1d81bf7a42703ffd0f41de71af6e6d3ce Mon Sep 17 00:00:00 2001
From: Ghassen-Lassoued
Date: Sun, 28 Dec 2025 11:15:30 +0000
Subject: [PATCH 26/37] Add comprehensive documentation for debugging,
performance, and multi-stage pipelines in ComputeKit
- Introduced a detailed guide on debugging worker code, including error handling, Chrome DevTools integration, and common issues.
- Added performance optimization strategies focusing on data transfer methods, including structured cloning, transferable objects, and SharedArrayBuffer.
- Documented the use of multi-stage pipelines for complex workflows, including examples of stage configuration, input/output transformation, and error handling.
- Enhanced the user experience with progress tracking, metrics, and execution reports for pipelines.
---
docs/debugging.md | 461 +++++++++++
docs/getting-started.md | 2 +
docs/index.md | 42 +-
docs/performance.md | 458 +++++++++++
docs/pipeline.md | 542 ++++++++++++
docs/react-hooks.md | 23 +
packages/core/src/index.ts | 14 +
packages/core/src/pool.ts | 10 +-
packages/core/src/types.ts | 222 +++++
packages/core/src/utils.ts | 74 ++
packages/core/src/worker/runtime.ts | 9 +-
packages/react/src/index.tsx | 1175 +++++++++++++++++++++++++++
12 files changed, 3012 insertions(+), 20 deletions(-)
create mode 100644 docs/debugging.md
create mode 100644 docs/performance.md
create mode 100644 docs/pipeline.md
diff --git a/docs/debugging.md b/docs/debugging.md
new file mode 100644
index 0000000..302157a
--- /dev/null
+++ b/docs/debugging.md
@@ -0,0 +1,461 @@
+---
+layout: default
+title: Debugging
+nav_order: 8
+---
+
+# Debugging ComputeKit
+
+{: .no_toc }
+
+Learn how to debug worker code, understand errors, and troubleshoot issues in ComputeKit.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## Overview
+
+Debugging Web Workers can be tricky because they run in a separate thread with their own global scope. ComputeKit provides several tools to make debugging easier:
+
+- Enhanced error messages with context
+- Debug mode with verbose logging
+- Console forwarding from workers
+- Chrome DevTools integration
+- Validation mode for main-thread debugging
+
+---
+
+## Enable Debug Mode
+
+The simplest way to start debugging is to enable debug mode:
+
+```typescript
+import { ComputeKit } from '@computekit/core';
+
+const kit = new ComputeKit({
+ debug: true, // Enable verbose logging
+});
+```
+
+With React:
+
+```tsx
+
+
+
+```
+
+Debug mode logs:
+
+- Worker creation and termination
+- Function registration
+- Task execution (start, complete, error)
+- Message passing between main thread and workers
+- Payload sizes for data transfer
+
+---
+
+## Understanding Error Messages
+
+When a compute function throws an error, ComputeKit captures and enriches it with context:
+
+```typescript
+kit.register('riskyFunction', (input: number) => {
+ if (input < 0) {
+ throw new Error('Input must be non-negative');
+ }
+ return Math.sqrt(input);
+});
+
+try {
+ await kit.run('riskyFunction', -5);
+} catch (error) {
+ console.error(error);
+ // ComputeError: Input must be non-negative
+ // Function: riskyFunction
+ // Worker: worker-abc123
+ // Duration: 2ms
+ // Stack: ...original stack trace...
+}
+```
+
+### Error Properties
+
+All errors from ComputeKit include:
+
+| Property | Description |
+| -------------- | ---------------------------------------- |
+| `message` | The original error message |
+| `functionName` | Name of the compute function that failed |
+| `workerId` | ID of the worker that processed the task |
+| `duration` | Time elapsed before the error occurred |
+| `stack` | Full stack trace from the worker |
+| `inputSize` | Size of the input data (in debug mode) |
+
+---
+
+## Chrome DevTools Integration
+
+### Debugging Workers Directly
+
+Chrome DevTools supports debugging Web Workers:
+
+1. Open DevTools (F12)
+2. Go to **Sources** tab
+3. In the left panel, find **Threads** section
+4. Click on a worker thread to debug it
+
+### Setting Breakpoints
+
+To set breakpoints in your compute functions:
+
+1. Enable source maps in your bundler (see below)
+2. In DevTools Sources, find your worker code
+3. Set breakpoints as normal
+4. Use the worker thread selector to switch contexts
+
+### Console Output
+
+Worker `console.log()` calls appear in the main DevTools console, prefixed with the worker context. Enable **Verbose** log level to see all worker output:
+
+```typescript
+kit.register('debugMe', (input: number[]) => {
+ console.log('Processing', input.length, 'items'); // Visible in DevTools
+ console.time('processing');
+
+ const result = input.map((x) => x * 2);
+
+ console.timeEnd('processing');
+ console.log('Result:', result.slice(0, 5), '...');
+
+ return result;
+});
+```
+
+---
+
+## Source Maps
+
+For the best debugging experience, configure your bundler to generate source maps for workers.
+
+### Vite
+
+```typescript
+// vite.config.ts
+export default defineConfig({
+ build: {
+ sourcemap: true,
+ },
+ worker: {
+ format: 'es',
+ rollupOptions: {
+ output: {
+ sourcemap: true,
+ },
+ },
+ },
+});
+```
+
+### Webpack
+
+```javascript
+// webpack.config.js
+module.exports = {
+ devtool: 'source-map',
+ module: {
+ rules: [
+ {
+ test: /\.worker\.ts$/,
+ use: {
+ loader: 'worker-loader',
+ options: {
+ inline: 'fallback',
+ },
+ },
+ },
+ ],
+ },
+};
+```
+
+### esbuild
+
+```typescript
+import * as esbuild from 'esbuild';
+
+await esbuild.build({
+ entryPoints: ['src/index.ts'],
+ bundle: true,
+ sourcemap: true,
+ outfile: 'dist/bundle.js',
+});
+```
+
+---
+
+## Validation Mode (Main Thread Execution)
+
+For difficult bugs, run your compute function on the main thread to use standard debugging tools:
+
+```typescript
+import { ComputeKit } from '@computekit/core';
+
+const kit = new ComputeKit({
+ // Force main thread execution (bypasses workers)
+ forceMainThread: true,
+});
+
+// Now you can set breakpoints directly in your compute functions
+kit.register('myFunction', (input) => {
+ debugger; // This will pause execution in DevTools
+ return processData(input);
+});
+```
+
+{: .warning }
+
+> **forceMainThread** should only be used for debugging. It defeats the purpose of using workers and will block the UI thread.
+
+---
+
+## Common Issues
+
+### "Function not found in worker"
+
+This error occurs when a compute function isn't registered before calling `run()`:
+
+```typescript
+// ❌ Wrong - function not registered
+await kit.run('myFunction', data);
+
+// ✅ Correct - register first
+kit.register('myFunction', (data) => processData(data));
+await kit.run('myFunction', data);
+```
+
+**In React**, ensure registration happens before the component that uses the function mounts:
+
+```tsx
+// ❌ Wrong - might race with child components
+function App() {
+ const kit = useComputeKit();
+
+ useEffect(() => {
+ kit.register('myFunction', fn);
+ }, [kit]);
+
+ return ;
+}
+
+// ✅ Correct - use a registration component or context
+function App() {
+ return (
+
+
+
+
+ );
+}
+
+function RegisterFunctions() {
+ const kit = useComputeKit();
+
+ useEffect(() => {
+ kit.register('myFunction', fn);
+ }, [kit]);
+
+ return null;
+}
+```
+
+### "DataCloneError: Failed to execute 'postMessage'"
+
+This error occurs when trying to transfer non-cloneable data:
+
+```typescript
+// ❌ Wrong - functions can't be cloned
+kit.register('bad', () => {
+ return {
+ data: [1, 2, 3],
+ callback: () => console.log('hi'), // Can't clone functions!
+ };
+});
+
+// ✅ Correct - return only cloneable data
+kit.register('good', () => {
+ return {
+ data: [1, 2, 3],
+ callbackName: 'logHi', // Return a reference instead
+ };
+});
+```
+
+**Non-cloneable types include:**
+
+- Functions
+- DOM nodes
+- Error objects (use `{ message, stack }` instead)
+- Symbols
+- WeakMap/WeakSet
+
+### Timeout Errors
+
+If tasks are timing out unexpectedly:
+
+```typescript
+// Increase timeout for long-running operations
+const result = await kit.run('heavyComputation', data, {
+ timeout: 120000, // 2 minutes
+});
+
+// Or set a global default
+const kit = new ComputeKit({
+ timeout: 60000, // 1 minute default
+});
+```
+
+### Worker Creation Failures
+
+If workers fail to create, check:
+
+1. **Content Security Policy** - Ensure your CSP allows `worker-src 'self' blob:`
+2. **Cross-origin issues** - Workers must be same-origin
+3. **Memory limits** - Each worker uses ~2-5MB of memory
+
+---
+
+## Logging Events
+
+Subscribe to ComputeKit events for detailed logging:
+
+```typescript
+const kit = new ComputeKit();
+
+kit.on('worker:created', ({ info }) => {
+ console.log(`Worker ${info.id} created`);
+});
+
+kit.on('worker:error', ({ error, info }) => {
+ console.error(`Worker ${info.id} error:`, error);
+});
+
+kit.on('task:start', ({ taskId, functionName }) => {
+ console.log(`Task ${taskId} started: ${functionName}`);
+});
+
+kit.on('task:complete', ({ taskId, duration }) => {
+ console.log(`Task ${taskId} completed in ${duration}ms`);
+});
+
+kit.on('task:error', ({ taskId, error }) => {
+ console.error(`Task ${taskId} failed:`, error);
+});
+
+kit.on('task:progress', ({ taskId, progress }) => {
+ console.log(`Task ${taskId}: ${progress.percent}%`);
+});
+```
+
+---
+
+## Pool Statistics
+
+Monitor worker pool health:
+
+```typescript
+const stats = kit.getStats();
+
+console.log(stats);
+// {
+// workers: [
+// { id: 'w1', state: 'idle', tasksCompleted: 42, errors: 0 },
+// { id: 'w2', state: 'busy', tasksCompleted: 38, errors: 1 },
+// ],
+// totalWorkers: 2,
+// activeWorkers: 1,
+// idleWorkers: 1,
+// queueLength: 0,
+// tasksCompleted: 80,
+// tasksFailed: 1,
+// averageTaskDuration: 145,
+// }
+```
+
+In React, use the `usePoolStats` hook:
+
+```tsx
+function PoolMonitor() {
+ const stats = usePoolStats();
+
+ return (
+
+
+ Workers: {stats.activeWorkers}/{stats.totalWorkers} active
+
+
Queue: {stats.queueLength} pending
+
Completed: {stats.tasksCompleted}
+
Failed: {stats.tasksFailed}
+
+ );
+}
+```
+
+---
+
+## Debugging Pipelines
+
+For multi-stage pipelines, use the built-in debugging features:
+
+```tsx
+const pipeline = usePipeline(stages);
+
+// Get a detailed report
+const report = pipeline.getReport();
+console.log(report.summary);
+console.log(report.timeline);
+console.log(report.insights);
+
+// Access metrics
+console.log(pipeline.metrics);
+// {
+// totalStages: 4,
+// completedStages: 3,
+// failedStages: 1,
+// slowestStage: { id: 'process', duration: 2340 },
+// timeline: [...]
+// }
+
+// Check individual stage status
+pipeline.stages.forEach((stage) => {
+ console.log(`${stage.name}: ${stage.status}`);
+ if (stage.error) {
+ console.error(` Error: ${stage.error.message}`);
+ }
+ if (stage.duration) {
+ console.log(` Duration: ${stage.duration}ms`);
+ }
+});
+```
+
+---
+
+## Getting Help
+
+If you're stuck:
+
+1. Enable `debug: true` and check the console
+2. Check the [Common Issues](#common-issues) section
+3. Use `forceMainThread: true` to debug on the main thread
+4. [Open an issue](https://github.com/pzaino/compute-kit/issues) with:
+ - ComputeKit version
+ - Browser and version
+ - Minimal reproduction code
+ - Console output with debug mode enabled
diff --git a/docs/getting-started.md b/docs/getting-started.md
index b247596..da68b63 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -200,6 +200,8 @@ module.exports = {
## Next Steps
- Learn about [React Hooks]({{ site.baseurl }}/react-hooks) for the full React experience
+- Read the [Debugging Guide]({{ site.baseurl }}/debugging) for troubleshooting tips
+- Understand [Performance & Data Transfer]({{ site.baseurl }}/performance) to optimize payloads
- Check the [API Reference]({{ site.baseurl }}/api-reference) for all available methods
- Explore [WASM integration]({{ site.baseurl }}/wasm) for maximum performance
- See [Examples]({{ site.baseurl }}/examples) for real-world use cases
diff --git a/docs/index.md b/docs/index.md
index 1ebe461..de3e94f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -27,16 +27,17 @@ Run heavy computations with React hooks. Use WASM for native-speed performance.
## ✨ Features
-| Feature | Description |
-| :----------------------- | :----------------------------------------------------------------------------- |
-| ⚛️ **React-first** | Purpose-built hooks like `useCompute` with loading, error, and progress states |
-| 🦀 **WASM integration** | Load and call AssemblyScript/Rust WASM modules with zero boilerplate |
-| 🚀 **Non-blocking** | Everything runs in Web Workers, keeping your UI at 60fps |
-| 🔧 **Zero config** | No manual worker files, postMessage handlers, or WASM glue code |
-| 📦 **Tiny** | Core library is ~3KB gzipped |
-| 🎯 **TypeScript** | Full type safety for your compute functions and WASM bindings |
-| 🔄 **Worker pool** | Automatic load balancing across CPU cores |
-| 📊 **Progress tracking** | Built-in progress reporting for long-running tasks |
+| Feature | Description |
+| :--------------------------- | :----------------------------------------------------------------------------- |
+| ⚛️ **React-first** | Purpose-built hooks like `useCompute` with loading, error, and progress states |
+| 🦀 **WASM integration** | Load and call AssemblyScript/Rust WASM modules with zero boilerplate |
+| 🚀 **Non-blocking** | Everything runs in Web Workers, keeping your UI at 60fps |
+| 🔧 **Zero config** | No manual worker files, postMessage handlers, or WASM glue code |
+| 📦 **Tiny** | Core library is ~3KB gzipped |
+| 🎯 **TypeScript** | Full type safety for your compute functions and WASM bindings |
+| 🔄 **Worker pool** | Automatic load balancing across CPU cores |
+| 📊 **Progress tracking** | Built-in progress reporting for long-running tasks |
+| 🔗 **Multi-stage pipelines** | Chain compute operations with `usePipeline` for complex workflows |
---
@@ -44,14 +45,15 @@ Run heavy computations with React hooks. Use WASM for native-speed performance.
You _can_ use Web Workers and WASM without a library. But here's the reality:
-| Task | Without ComputeKit | With ComputeKit |
-| ----------------- | ------------------------------------------------------------------- | ---------------------------------- |
-| Web Worker setup | Create separate `.js` files, handle `postMessage`, manage callbacks | `kit.register('fn', myFunc)` |
-| WASM loading | Fetch, instantiate, memory management, glue code | `await loadWasmModule('/my.wasm')` |
-| React integration | Manual state, effects, cleanup, abort handling | `useCompute()` hook |
-| Worker pooling | Build your own pool, queue, and load balancer | Built-in |
-| TypeScript | Tricky worker typing, no WASM types | Full type inference |
-| Error handling | Try-catch across message boundaries | Automatic with React error states |
+| Task | Without ComputeKit | With ComputeKit |
+| --------------------- | ------------------------------------------------------------------- | ---------------------------------- |
+| Web Worker setup | Create separate `.js` files, handle `postMessage`, manage callbacks | `kit.register('fn', myFunc)` |
+| WASM loading | Fetch, instantiate, memory management, glue code | `await loadWasmModule('/my.wasm')` |
+| React integration | Manual state, effects, cleanup, abort handling | `useCompute()` hook |
+| Worker pooling | Build your own pool, queue, and load balancer | Built-in |
+| Multi-stage workflows | Manual chaining, error handling per stage, retry logic | `usePipeline()` hook |
+| TypeScript | Tricky worker typing, no WASM types | Full type inference |
+| Error handling | Try-catch across message boundaries | Automatic with React error states |
**ComputeKit's unique value:** The only library that combines **React hooks + WASM + Worker pool** into one cohesive, type-safe developer experience.
@@ -67,6 +69,7 @@ You _can_ use Web Workers and WASM without a library. But here's the reality:
| Parsing large files | String formatting |
| Cryptographic operations | UI state management |
| Real-time data analysis | Small form validations |
+| Multi-file processing pipelines | Simple single-step tasks |
---
@@ -170,6 +173,9 @@ function Calculator() {
- [Getting Started Guide]({{ site.baseurl }}/getting-started)
- [React Hooks Reference]({{ site.baseurl }}/react-hooks)
+- [Multi-Stage Pipelines]({{ site.baseurl }}/pipeline)
+- [Debugging Guide]({{ site.baseurl }}/debugging)
+- [Performance & Data Transfer]({{ site.baseurl }}/performance)
- [API Reference]({{ site.baseurl }}/api-reference)
- [WASM Guide]({{ site.baseurl }}/wasm)
- [Examples]({{ site.baseurl }}/examples)
diff --git a/docs/performance.md b/docs/performance.md
new file mode 100644
index 0000000..3c90dfc
--- /dev/null
+++ b/docs/performance.md
@@ -0,0 +1,458 @@
+---
+layout: default
+title: Performance & Data Transfer
+nav_order: 9
+---
+
+# Performance & Data Transfer
+
+{: .no_toc }
+
+Understand data transfer costs, optimize payloads, and get the best performance from ComputeKit.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## Overview
+
+When using Web Workers, data must be transferred between the main thread and worker threads. Understanding how this works is crucial for optimal performance.
+
+**Key concepts:**
+
+- **Structured Cloning** - Default method; copies data (slow for large payloads)
+- **Transferable Objects** - Zero-copy transfer; ownership moves (fast, but original becomes unusable)
+- **SharedArrayBuffer** - Shared memory; no transfer needed (fastest, but requires setup)
+
+---
+
+## How Data Transfer Works
+
+### Structured Cloning (Default)
+
+When you pass data to a compute function, JavaScript uses the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to copy the data:
+
+```typescript
+kit.register('processImages', (images: ImageData[]) => {
+ // images is a COPY of the original data
+ return images.map((img) => applyFilter(img));
+});
+
+// This copies ALL data to the worker, then copies the result back
+const result = await kit.run('processImages', largeImageArray);
+```
+
+**Performance characteristics:**
+| Payload Size | Clone Time (approx) |
+|--------------|---------------------|
+| 1 KB | < 1ms |
+| 100 KB | 1-5ms |
+| 1 MB | 10-50ms |
+| 10 MB | 100-500ms |
+| 100 MB | 1-5 seconds |
+
+{: .warning }
+
+> For payloads over **1 MB**, consider using Transferables or SharedArrayBuffer to avoid cloning overhead.
+
+### What Can Be Cloned?
+
+✅ **Cloneable types:**
+
+- Primitives (strings, numbers, booleans, null, undefined)
+- Arrays and typed arrays (Uint8Array, Float32Array, etc.)
+- Plain objects
+- Map, Set
+- Date, RegExp
+- Blob, File, FileList
+- ImageData
+- ArrayBuffer
+
+❌ **NOT cloneable:**
+
+- Functions
+- DOM nodes
+- Error objects (clone `{ message, stack }` instead)
+- Symbols
+- WeakMap, WeakSet
+
+---
+
+## Transferable Objects
+
+Transferables use zero-copy transfer by moving ownership of memory:
+
+```typescript
+// Input: ArrayBuffer that will be transferred (not copied)
+const buffer = new ArrayBuffer(10_000_000); // 10MB
+
+kit.register('processBuffer', (data: ArrayBuffer) => {
+ const view = new Uint8Array(data);
+ // Process the data...
+ return view.buffer; // Return the same buffer (transferred back)
+});
+
+// Use the transfer option
+const result = await kit.run('processBuffer', buffer, {
+ transfer: [buffer], // Transfer ownership to worker
+});
+
+// ⚠️ buffer is now "neutered" - unusable on main thread
+console.log(buffer.byteLength); // 0
+```
+
+### Transferable Types
+
+- `ArrayBuffer`
+- `MessagePort`
+- `ImageBitmap`
+- `OffscreenCanvas`
+- `ReadableStream`, `WritableStream`, `TransformStream`
+
+### Automatic Transfer Detection
+
+ComputeKit automatically detects and transfers ArrayBuffers in your return values:
+
+```typescript
+kit.register('createBuffer', () => {
+ const buffer = new ArrayBuffer(1000000);
+ const view = new Float32Array(buffer);
+ // Fill with data...
+ return buffer; // Automatically transferred back
+});
+
+// Result is transferred, not cloned
+const result = await kit.run('createBuffer', null);
+```
+
+---
+
+## SharedArrayBuffer
+
+For the highest performance, use SharedArrayBuffer to share memory directly:
+
+```typescript
+import { ComputeKit } from '@computekit/core';
+
+const kit = new ComputeKit({
+ useSharedMemory: true, // Enable SharedArrayBuffer support
+});
+
+// Create shared memory
+const shared = new SharedArrayBuffer(10_000_000); // 10MB
+const view = new Float32Array(shared);
+
+kit.register('processShared', (sharedBuffer: SharedArrayBuffer) => {
+ const view = new Float32Array(sharedBuffer);
+ // Modify in place - changes visible to main thread!
+ for (let i = 0; i < view.length; i++) {
+ view[i] = view[i] * 2;
+ }
+ return { processed: view.length };
+});
+
+// No data transfer - just passes a reference
+await kit.run('processShared', shared);
+
+// Main thread sees the changes immediately
+console.log(view[0]); // Modified value
+```
+
+### SharedArrayBuffer Requirements
+
+{: .important }
+
+> SharedArrayBuffer requires specific HTTP headers for security:
+
+```
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
+```
+
+**Vite configuration:**
+
+```typescript
+// vite.config.ts
+export default defineConfig({
+ server: {
+ headers: {
+ 'Cross-Origin-Opener-Policy': 'same-origin',
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
+ },
+ },
+});
+```
+
+**Express configuration:**
+
+```javascript
+app.use((req, res, next) => {
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
+ next();
+});
+```
+
+---
+
+## Measuring Payload Size
+
+### Debug Mode Size Reporting
+
+Enable debug mode to see payload sizes in the console:
+
+```typescript
+const kit = new ComputeKit({ debug: true });
+
+kit.register('process', (data) => transform(data));
+
+await kit.run('process', largeData);
+// Console: [ComputeKit] Task xyz: input=4.2MB, output=3.8MB, duration=145ms
+```
+
+### Manual Size Estimation
+
+```typescript
+/**
+ * Estimate the size of a value for structured cloning
+ */
+function estimateSize(value: unknown): number {
+ if (value === null || value === undefined) return 0;
+ if (typeof value === 'boolean') return 4;
+ if (typeof value === 'number') return 8;
+ if (typeof value === 'string') return value.length * 2;
+
+ if (value instanceof ArrayBuffer) return value.byteLength;
+ if (ArrayBuffer.isView(value)) return value.byteLength;
+
+ if (Array.isArray(value)) {
+ return value.reduce((sum, item) => sum + estimateSize(item), 0);
+ }
+
+ if (typeof value === 'object') {
+ return Object.entries(value).reduce(
+ (sum, [key, val]) => sum + key.length * 2 + estimateSize(val),
+ 0
+ );
+ }
+
+ return 0;
+}
+
+// Usage
+const size = estimateSize(myData);
+console.log(`Payload: ${(size / 1024 / 1024).toFixed(2)} MB`);
+```
+
+---
+
+## Optimization Strategies
+
+### 1. Minimize Data Transfer
+
+Only send what's needed:
+
+```typescript
+// ❌ Sending entire objects
+kit.register('processUsers', (users: User[]) => {
+ return users.map((u) => ({ id: u.id, score: calculateScore(u.age) }));
+});
+await kit.run('processUsers', fullUserObjects); // Transfers everything
+
+// ✅ Send only required fields
+kit.register('processAges', (ages: number[]) => {
+ return ages.map((age) => calculateScore(age));
+});
+const ages = users.map((u) => u.age);
+await kit.run('processAges', ages); // Much smaller payload
+```
+
+### 2. Use Typed Arrays
+
+Typed arrays are more efficient than regular arrays:
+
+```typescript
+// ❌ Regular array of numbers
+const numbers = [1.5, 2.3, 3.7, ...]; // Each number is a JS object
+
+// ✅ Typed array
+const numbers = new Float32Array([1.5, 2.3, 3.7, ...]); // Compact binary
+```
+
+**Size comparison for 1 million numbers:**
+| Type | Approx Size |
+|------|-------------|
+| `number[]` | ~8-16 MB |
+| `Float64Array` | 8 MB |
+| `Float32Array` | 4 MB |
+| `Int16Array` | 2 MB |
+| `Uint8Array` | 1 MB |
+
+### 3. Batch Operations
+
+Reduce transfer overhead by batching:
+
+```typescript
+// ❌ Many small transfers
+for (const item of items) {
+ await kit.run('process', item); // Transfer overhead for each call
+}
+
+// ✅ One large transfer
+await kit.run('processBatch', items); // Single transfer
+```
+
+### 4. Use Transferables for Large ArrayBuffers
+
+```typescript
+// Processing a large image
+const imageBuffer = await fetchImageAsArrayBuffer(url);
+
+// Transfer instead of clone
+const result = await kit.run('processImage', imageBuffer, {
+ transfer: [imageBuffer],
+});
+```
+
+### 5. Return Minimal Results
+
+```typescript
+// ❌ Returning large intermediate data
+kit.register('analyze', (data: number[]) => {
+ const allResults = heavyComputation(data);
+ return allResults; // Might be huge
+});
+
+// ✅ Return only what's needed
+kit.register('analyze', (data: number[]) => {
+ const allResults = heavyComputation(data);
+ return {
+ summary: summarize(allResults),
+ count: allResults.length,
+ // Don't return allResults unless needed
+ };
+});
+```
+
+---
+
+## Performance Benchmarking
+
+### Built-in Timing
+
+Every compute result includes timing information:
+
+```typescript
+const result = await kit.run('myFunction', data);
+// Returns: { data: ..., duration: 145, workerId: 'w1', cached: false }
+```
+
+### Comparing Strategies
+
+```typescript
+async function benchmark() {
+ const largeArray = new Float32Array(1_000_000);
+
+ // Strategy 1: Structured clone
+ console.time('clone');
+ await kit.run('process', largeArray.slice()); // Copy
+ console.timeEnd('clone');
+
+ // Strategy 2: Transfer
+ console.time('transfer');
+ const toTransfer = largeArray.slice();
+ await kit.run('process', toTransfer.buffer, {
+ transfer: [toTransfer.buffer],
+ });
+ console.timeEnd('transfer');
+
+ // Strategy 3: Shared memory
+ console.time('shared');
+ const shared = new SharedArrayBuffer(largeArray.byteLength);
+ new Float32Array(shared).set(largeArray);
+ await kit.run('processShared', shared);
+ console.timeEnd('shared');
+}
+```
+
+**Typical results (10MB payload):**
+| Strategy | Transfer Time | Processing Time |
+|----------|---------------|-----------------|
+| Clone | ~50-100ms | + computation |
+| Transfer | ~1-5ms | + computation |
+| Shared | ~0ms | + computation |
+
+---
+
+## When to Use Each Strategy
+
+| Scenario | Recommended Approach |
+| -------------------------------- | --------------------------- |
+| Small data (< 100KB) | Structured clone (default) |
+| Large ArrayBuffers | Transferables |
+| Multiple operations on same data | SharedArrayBuffer |
+| Read-only shared data | SharedArrayBuffer |
+| Data needed after transfer | Structured clone |
+| Maximum performance | SharedArrayBuffer + Atomics |
+
+---
+
+## Avoiding Common Pitfalls
+
+### Pitfall 1: Accidental Large Clones
+
+```typescript
+// ❌ Accidentally including large data
+const context = {
+ config: { threshold: 0.5 },
+ cache: hugeCache, // Oops! This gets cloned
+};
+await kit.run('process', context);
+
+// ✅ Send only what's needed
+await kit.run('process', { threshold: 0.5 });
+```
+
+### Pitfall 2: Using Transferred Data
+
+```typescript
+const buffer = new ArrayBuffer(1000);
+await kit.run('process', buffer, { transfer: [buffer] });
+
+// ❌ Error! Buffer is neutered
+console.log(buffer.byteLength); // 0
+
+// ✅ Copy first if you need the original
+const copy = buffer.slice();
+await kit.run('process', buffer, { transfer: [buffer] });
+console.log(copy.byteLength); // 1000
+```
+
+### Pitfall 3: Circular References
+
+```typescript
+// ❌ Circular reference - can't be cloned
+const obj: any = { name: 'test' };
+obj.self = obj;
+await kit.run('process', obj); // Error!
+
+// ✅ Remove circular references first
+const { self, ...safeObj } = obj;
+await kit.run('process', safeObj);
+```
+
+---
+
+## Summary
+
+1. **Under 100KB**: Don't worry about it, structured cloning is fine
+2. **100KB - 1MB**: Consider typed arrays and minimal payloads
+3. **Over 1MB**: Use Transferables for ArrayBuffers
+4. **Repeated operations**: Use SharedArrayBuffer
+5. **Always measure**: Enable `debug: true` to see actual payload sizes
diff --git a/docs/pipeline.md b/docs/pipeline.md
new file mode 100644
index 0000000..91ab6c0
--- /dev/null
+++ b/docs/pipeline.md
@@ -0,0 +1,542 @@
+---
+layout: default
+title: Multi-Stage Pipelines
+nav_order: 5
+---
+
+# Multi-Stage Pipelines
+
+{: .no_toc }
+
+Build complex, debuggable multi-stage compute workflows with automatic progress tracking, error handling, and execution reports.
+{: .fs-6 .fw-300 }
+
+## Table of contents
+
+{: .no_toc .text-delta }
+
+1. TOC
+ {:toc}
+
+---
+
+## Overview
+
+ComputeKit's pipeline system enables you to chain multiple compute operations together, with each stage's output becoming the next stage's input. This is perfect for workflows like:
+
+- **File Processing**: Download → Parse → Transform → Compress
+- **Data Pipelines**: Fetch → Validate → Process → Aggregate
+- **Image Processing**: Load → Resize → Filter → Encode
+- **ML Workflows**: Preprocess → Inference → Postprocess
+
+## Quick Start
+
+```tsx
+import { usePipeline } from '@computekit/react';
+
+function FileProcessor() {
+ const pipeline = usePipeline([
+ { id: 'download', name: 'Download Files', functionName: 'downloadFiles' },
+ { id: 'process', name: 'Process Files', functionName: 'processFiles' },
+ { id: 'compress', name: 'Compress Output', functionName: 'compressFiles' },
+ ]);
+
+ return (
+
+
+
+
+
+ {pipeline.currentStage &&
Current: {pipeline.currentStage.name}
}
+
+ );
+}
+```
+
+## The `usePipeline` Hook
+
+### Basic Usage
+
+```tsx
+const pipeline = usePipeline(stages, options);
+```
+
+### Stage Configuration
+
+Each stage is defined with a `StageConfig` object:
+
+```tsx
+interface StageConfig {
+ // Required
+ id: string; // Unique identifier
+ name: string; // Display name
+ functionName: string; // Registered compute function name
+
+ // Optional
+ transformInput?: (input, previousResults) => any; // Transform before execution
+ transformOutput?: (output) => any; // Transform after execution
+ shouldSkip?: (input, previousResults) => boolean; // Conditionally skip
+ maxRetries?: number; // Retry attempts on failure (default: 0)
+ retryDelay?: number; // Delay between retries in ms (default: 1000)
+ options?: ComputeOptions; // Per-stage compute options
+}
+```
+
+### Pipeline State
+
+The hook returns comprehensive state for debugging and UI:
+
+```tsx
+const {
+ // Status
+ status, // 'idle' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'
+ isRunning, // boolean
+ isComplete, // boolean
+ isFailed, // boolean
+
+ // Progress
+ progress, // Overall progress (0-100)
+ currentStage, // Current StageInfo or null
+ currentStageIndex, // Index of current stage (-1 if not running)
+ stages, // Array of all StageInfo objects
+
+ // Data
+ input, // Original input
+ output, // Final output (when complete)
+ stageResults, // Array of each stage's output
+ error, // Error if failed
+
+ // Timing
+ startedAt, // Start timestamp
+ completedAt, // End timestamp
+ totalDuration, // Total duration in ms
+
+ // Metrics (for debugging)
+ metrics, // PipelineMetrics object
+
+ // Actions
+ run, // (input) => Promise
+ cancel, // () => void
+ reset, // () => void
+ pause, // () => void
+ resume, // () => void
+ retry, // () => Promise
+ getReport, // () => PipelineReport
+
+ // Helpers
+ isStageComplete, // (stageId) => boolean
+ getStage, // (stageId) => StageInfo | undefined
+} = usePipeline(stages, options);
+```
+
+## Examples
+
+### Complete Pipeline UI
+
+```tsx
+function DataPipeline() {
+ const pipeline = usePipeline([
+ { id: 'fetch', name: 'Fetch Data', functionName: 'fetchData' },
+ { id: 'validate', name: 'Validate', functionName: 'validateData' },
+ { id: 'transform', name: 'Transform', functionName: 'transformData' },
+ { id: 'save', name: 'Save Results', functionName: 'saveData' },
+ ]);
+
+ return (
+
+ {/* Controls */}
+
+
+
+ {pipeline.isRunning && (
+ <>
+
+
+ >
+ )}
+
+ {pipeline.status === 'paused' && (
+
+ )}
+
+ {pipeline.isFailed && }
+
+
+ {/* Overall Progress */}
+
+
+
{pipeline.progress.toFixed(0)}%
+
+
+ {/* Stage List */}
+
+ {pipeline.stages.map((stage, index) => (
+
+
+ {stage.status === 'completed' && '✓'}
+ {stage.status === 'running' && '⟳'}
+ {stage.status === 'failed' && '✗'}
+ {stage.status === 'pending' && '○'}
+ {stage.status === 'skipped' && '–'}
+
+
+
+ {stage.name}
+ {stage.duration && (
+ {stage.duration.toFixed(0)}ms
+ )}
+ {stage.error && {stage.error.message}}
+
+
+ {stage.status === 'running' && stage.progress && (
+
{stage.progress.toFixed(0)}%
+ )}
+
+ ))}
+
+
+ {/* Error Display */}
+ {pipeline.error && (
+
+
Pipeline Failed
+
{pipeline.error.message}
+
+
+ )}
+
+ {/* Success + Report */}
+ {pipeline.isComplete && (
+
+
Pipeline Complete!
+
{pipeline.getReport().summary}
+
+ )}
+
+ );
+}
+```
+
+### Conditional Stage Skipping
+
+```tsx
+const pipeline = usePipeline([
+ {
+ id: 'fetch',
+ name: 'Fetch Data',
+ functionName: 'fetchData',
+ },
+ {
+ id: 'cache-check',
+ name: 'Check Cache',
+ functionName: 'checkCache',
+ },
+ {
+ id: 'process',
+ name: 'Process Data',
+ functionName: 'processData',
+ // Skip processing if cache hit
+ shouldSkip: (input, previousResults) => {
+ const cacheResult = previousResults[1];
+ return cacheResult?.cacheHit === true;
+ },
+ },
+ {
+ id: 'save',
+ name: 'Save Results',
+ functionName: 'saveResults',
+ },
+]);
+```
+
+### Input/Output Transformation
+
+```tsx
+const pipeline = usePipeline([
+ {
+ id: 'fetch',
+ name: 'Fetch Users',
+ functionName: 'fetchUsers',
+ },
+ {
+ id: 'enrich',
+ name: 'Enrich Data',
+ functionName: 'enrichUsers',
+ // Extract just the users array from previous result
+ transformInput: (input, previousResults) => {
+ const fetchResult = previousResults[0];
+ return fetchResult.users;
+ },
+ // Wrap the result
+ transformOutput: (output) => ({
+ enrichedUsers: output,
+ timestamp: Date.now(),
+ }),
+ },
+]);
+```
+
+### With Retries
+
+```tsx
+const pipeline = usePipeline([
+ {
+ id: 'upload',
+ name: 'Upload Files',
+ functionName: 'uploadFiles',
+ maxRetries: 3,
+ retryDelay: 2000, // Wait 2s between retries
+ },
+ {
+ id: 'process',
+ name: 'Process on Server',
+ functionName: 'serverProcess',
+ maxRetries: 2,
+ retryDelay: 5000,
+ },
+]);
+```
+
+## Parallel Batch Processing
+
+For processing multiple items in parallel within a single stage, use `useParallelBatch`:
+
+```tsx
+import { useParallelBatch } from '@computekit/react';
+
+function ImageProcessor() {
+ const batch = useParallelBatch('processImage', {
+ concurrency: 4, // Process 4 images at a time
+ });
+
+ const handleProcess = async () => {
+ const result = await batch.run(imageUrls);
+
+ console.log(`Processed ${result.successful.length} images`);
+ console.log(`Failed: ${result.failed.length}`);
+ console.log(`Success rate: ${(result.successRate * 100).toFixed(0)}%`);
+ };
+
+ return (
+
+
+
+ {batch.loading && (
+
+ Processing: {batch.completedCount}/{batch.totalCount}(
+ {batch.progress.toFixed(0)}%)
+
+ )}
+
+ {batch.result && (
+
+
✓ {batch.result.successful.length} succeeded
+
✗ {batch.result.failed.length} failed
+
Duration: {batch.result.totalDuration.toFixed(0)}ms
+
+ )}
+
+ );
+}
+```
+
+## Debugging & Reports
+
+### Pipeline Metrics
+
+Access detailed metrics for debugging:
+
+```tsx
+const { metrics } = pipeline;
+
+console.log(metrics);
+// {
+// totalStages: 4,
+// completedStages: 4,
+// failedStages: 0,
+// skippedStages: 0,
+// totalRetries: 1,
+// slowestStage: { id: 'process', name: 'Process Files', duration: 2340 },
+// fastestStage: { id: 'fetch', name: 'Fetch Data', duration: 120 },
+// averageStageDuration: 890,
+// timeline: [...] // Detailed event timeline
+// }
+```
+
+### Execution Report
+
+Generate a human-readable report:
+
+```tsx
+const report = pipeline.getReport();
+
+console.log(report.summary);
+// Pipeline Status: COMPLETED
+// Stages: 4/4 completed
+// Success Rate: 100%
+// Total Duration: 3.56s
+
+console.log(report.stageDetails);
+// [
+// { name: 'Fetch Data', status: 'completed', duration: '120ms' },
+// { name: 'Validate', status: 'completed', duration: '45ms' },
+// { name: 'Process', status: 'completed', duration: '2.34s' },
+// { name: 'Save', status: 'completed', duration: '1.05s' },
+// ]
+
+console.log(report.timeline);
+// [
+// '[10:30:01] Fetch Data: started',
+// '[10:30:01] Fetch Data: completed (120ms)',
+// '[10:30:01] Validate: started',
+// ...
+// ]
+
+console.log(report.insights);
+// [
+// 'Slowest stage: Process Files (2.34s)',
+// 'Fastest stage: Validate (45ms)',
+// 'Average stage duration: 890ms',
+// ]
+```
+
+### Timeline Visualization
+
+Build a timeline UI from the metrics:
+
+```tsx
+function PipelineTimeline({ metrics }: { metrics: PipelineMetrics }) {
+ const startTime = metrics.timeline[0]?.timestamp ?? 0;
+
+ return (
+
+ {metrics.timeline.map((event, i) => (
+
+
+
+ {event.stageName}: {event.event}
+ {event.duration && ` (${event.duration.toFixed(0)}ms)`}
+
+
+ ))}
+
+ );
+}
+```
+
+## Pipeline Options
+
+```tsx
+const pipeline = usePipeline(stages, {
+ // Stop pipeline on first stage failure (default: true)
+ stopOnError: true,
+
+ // Global timeout for entire pipeline
+ timeout: 60000,
+
+ // Track detailed timeline (default: true)
+ trackTimeline: true,
+
+ // Auto-run on mount
+ autoRun: false,
+ initialInput: undefined,
+
+ // Callbacks
+ onStateChange: (state) => console.log('State:', state.status),
+ onStageStart: (stage) => console.log('Starting:', stage.name),
+ onStageComplete: (stage) => console.log('Completed:', stage.name),
+ onStageError: (stage, error) => console.error('Failed:', stage.name, error),
+ onStageRetry: (stage, attempt) => console.log('Retry:', stage.name, attempt),
+});
+```
+
+## Combining Pipeline with Parallel Batch
+
+For the user's original use case (multi-file download → process → compress):
+
+```tsx
+function MultiFileProcessor() {
+ const kit = useComputeKit();
+
+ // Register functions that handle batches
+ useEffect(() => {
+ kit.register('downloadBatch', async (urls: string[]) => {
+ // Download all files in parallel
+ return Promise.all(urls.map((url) => fetch(url).then((r) => r.arrayBuffer())));
+ });
+
+ kit.register('processBatch', async (files: ArrayBuffer[]) => {
+ // Process all files in parallel
+ return Promise.all(files.map((file) => processFile(file)));
+ });
+
+ kit.register('compressBatch', async (files: ProcessedFile[]) => {
+ // Compress all files
+ return Promise.all(files.map((file) => compress(file)));
+ });
+ }, [kit]);
+
+ const pipeline = usePipeline([
+ { id: 'download', name: 'Download Files', functionName: 'downloadBatch' },
+ { id: 'process', name: 'Process Files', functionName: 'processBatch' },
+ { id: 'compress', name: 'Compress Files', functionName: 'compressBatch' },
+ ]);
+
+ return (
+
+
+
+
+
+ {pipeline.isComplete && (
+
+ Processed {pipeline.output?.length} files in{' '}
+ {(pipeline.totalDuration! / 1000).toFixed(2)}s
+
+ )}
+
+ );
+}
+```
+
+## Type Safety
+
+The pipeline is fully typed:
+
+```tsx
+interface InputData {
+ urls: string[];
+ options: ProcessOptions;
+}
+
+interface OutputData {
+ files: ProcessedFile[];
+ stats: ProcessingStats;
+}
+
+const pipeline = usePipeline([
+ // ... stages
+]);
+
+// pipeline.input is InputData | null
+// pipeline.output is OutputData | null
+// TypeScript will enforce types throughout
+```
diff --git a/docs/react-hooks.md b/docs/react-hooks.md
index 5fea34b..03e3ea9 100644
--- a/docs/react-hooks.md
+++ b/docs/react-hooks.md
@@ -305,7 +305,30 @@ run({ data: [...], width: 256, height: 256 });
---
+## usePipeline & useParallelBatch
+
+For complex multi-stage workflows and parallel batch processing, see the dedicated [Multi-Stage Pipelines]({{ site.baseurl }}/pipeline) guide.
+
+Quick preview:
+
+```tsx
+// Multi-stage pipeline
+const pipeline = usePipeline([
+ { id: 'download', name: 'Download', functionName: 'downloadFiles' },
+ { id: 'process', name: 'Process', functionName: 'processFiles' },
+ { id: 'compress', name: 'Compress', functionName: 'compressFiles' },
+]);
+
+// Parallel batch processing
+const batch = useParallelBatch('processFile', {
+ concurrency: 4,
+});
+```
+
+---
+
## Next Steps
+- Check the [Multi-Stage Pipelines]({{ site.baseurl }}/pipeline) for complex workflows
- Check the [API Reference]({{ site.baseurl }}/api-reference) for the complete API
- Learn about [WASM integration]({{ site.baseurl }}/wasm) for native-speed performance
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index bee103d..cce2b7c 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -205,6 +205,20 @@ export type {
WorkerInfo,
WasmModuleConfig,
ComputeKitEvents,
+ // Pipeline types
+ StageStatus,
+ StageInfo,
+ StageConfig,
+ PipelineMode,
+ PipelineStatus,
+ PipelineState,
+ PipelineMetrics,
+ PipelineOptions,
+ PipelineEvents,
+ // Parallel batch types
+ ParallelBatchConfig,
+ BatchItemResult,
+ ParallelBatchResult,
} from './types';
// Re-export utilities
diff --git a/packages/core/src/pool.ts b/packages/core/src/pool.ts
index ca0a431..30880ec 100644
--- a/packages/core/src/pool.ts
+++ b/packages/core/src/pool.ts
@@ -24,6 +24,8 @@ import {
findTransferables,
getHardwareConcurrency,
createLogger,
+ estimatePayloadSize,
+ formatBytes,
type Deferred,
type Logger,
} from './utils';
@@ -427,8 +429,14 @@ self.postMessage({ type: 'ready' });
this.stats.tasksCompleted++;
this.stats.totalDuration += resultPayload.duration;
+ // Log with payload sizes in debug mode
+ const inputSize = estimatePayloadSize(task.input);
+ const outputSize = resultPayload.outputSize ?? 0;
this.logger.debug(
- `Task ${id} completed in ${resultPayload.duration.toFixed(2)}ms`
+ `Task ${id} (${task.functionName}): ` +
+ `input=${formatBytes(inputSize)}, ` +
+ `output=${formatBytes(outputSize)}, ` +
+ `duration=${resultPayload.duration.toFixed(2)}ms`
);
task.deferred.resolve(resultPayload.data);
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 9253667..dcd8eef 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -55,6 +55,10 @@ export interface ComputeResult {
cached: boolean;
/** Worker ID that processed this */
workerId: string;
+ /** Size of input data in bytes (available in debug mode) */
+ inputSize?: number;
+ /** Size of output data in bytes (available in debug mode) */
+ outputSize?: number;
}
/** Function definition for registration */
@@ -111,6 +115,8 @@ export interface ResultPayload {
data: T;
duration: number;
transfer?: ArrayBuffer[];
+ /** Size of the output data in bytes */
+ outputSize?: number;
}
/** Error message payload */
@@ -118,6 +124,10 @@ export interface ErrorPayload {
message: string;
stack?: string;
code?: string;
+ /** Name of the function that failed */
+ functionName?: string;
+ /** Duration before the error occurred in ms */
+ duration?: number;
}
/** Progress message payload */
@@ -213,6 +223,174 @@ export type ComputeFn = (
/** Registry of compute functions */
export type ComputeRegistry = Map;
+// ============================================================================
+// Pipeline Types - Multi-stage Processing
+// ============================================================================
+
+/** Status of a pipeline stage */
+export type StageStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
+
+/** Detailed information about a single pipeline stage */
+export interface StageInfo {
+ /** Unique identifier for the stage */
+ id: string;
+ /** Display name for the stage */
+ name: string;
+ /** Name of the registered compute function to execute */
+ functionName: string;
+ /** Current status of this stage */
+ status: StageStatus;
+ /** Input data for this stage (set when stage starts) */
+ input?: TInput;
+ /** Output data from this stage (set when stage completes) */
+ output?: TOutput;
+ /** Error if stage failed */
+ error?: Error;
+ /** Start timestamp (ms since epoch) */
+ startedAt?: number;
+ /** End timestamp (ms since epoch) */
+ completedAt?: number;
+ /** Duration in milliseconds */
+ duration?: number;
+ /** Progress within this stage (0-100) */
+ progress?: number;
+ /** Number of retry attempts */
+ retryCount: number;
+ /** Compute options specific to this stage */
+ options?: ComputeOptions;
+}
+
+/** Pipeline execution mode */
+export type PipelineMode =
+ | 'sequential' // Each stage runs after previous completes
+ | 'parallel'; // All items in a stage run in parallel, then next stage
+
+/** Configuration for a pipeline stage */
+export interface StageConfig {
+ /** Unique identifier for the stage */
+ id: string;
+ /** Display name for the stage */
+ name: string;
+ /** Name of the registered compute function */
+ functionName: string;
+ /** Transform input before passing to compute function */
+ transformInput?: (input: TInput, previousResults: unknown[]) => unknown;
+ /** Transform output after compute function returns */
+ transformOutput?: (output: unknown) => TOutput;
+ /** Whether to skip this stage based on previous results */
+ shouldSkip?: (input: TInput, previousResults: unknown[]) => boolean;
+ /** Maximum retry attempts on failure (default: 0) */
+ maxRetries?: number;
+ /** Delay between retries in ms (default: 1000) */
+ retryDelay?: number;
+ /** Compute options for this stage */
+ options?: ComputeOptions;
+}
+
+/** Overall pipeline status */
+export type PipelineStatus =
+ | 'idle' // Not started
+ | 'running' // Currently executing
+ | 'paused' // Paused mid-execution
+ | 'completed' // All stages completed successfully
+ | 'failed' // A stage failed (and wasn't recovered)
+ | 'cancelled'; // User cancelled
+
+/** Comprehensive pipeline state for debugging */
+export interface PipelineState {
+ /** Overall pipeline status */
+ status: PipelineStatus;
+ /** All stage information */
+ stages: StageInfo[];
+ /** Index of currently executing stage (-1 if not running) */
+ currentStageIndex: number;
+ /** Current stage info (convenience) */
+ currentStage: StageInfo | null;
+ /** Overall progress percentage (0-100) */
+ progress: number;
+ /** Final output from the last stage */
+ output: TOutput | null;
+ /** Initial input that started the pipeline */
+ input: TInput | null;
+ /** Error that caused pipeline failure */
+ error: Error | null;
+ /** Pipeline start timestamp */
+ startedAt: number | null;
+ /** Pipeline completion timestamp */
+ completedAt: number | null;
+ /** Total duration in milliseconds */
+ totalDuration: number | null;
+ /** Results from each completed stage */
+ stageResults: unknown[];
+ /** Execution metrics for debugging */
+ metrics: PipelineMetrics;
+}
+
+/** Metrics for pipeline debugging and reporting */
+export interface PipelineMetrics {
+ /** Total stages in pipeline */
+ totalStages: number;
+ /** Number of completed stages */
+ completedStages: number;
+ /** Number of failed stages */
+ failedStages: number;
+ /** Number of skipped stages */
+ skippedStages: number;
+ /** Total retry attempts across all stages */
+ totalRetries: number;
+ /** Slowest stage info */
+ slowestStage: { id: string; name: string; duration: number } | null;
+ /** Fastest stage info */
+ fastestStage: { id: string; name: string; duration: number } | null;
+ /** Average stage duration */
+ averageStageDuration: number;
+ /** Timestamp of each stage transition for timeline view */
+ timeline: Array<{
+ stageId: string;
+ stageName: string;
+ event: 'started' | 'completed' | 'failed' | 'skipped' | 'retry';
+ timestamp: number;
+ duration?: number;
+ error?: string;
+ }>;
+}
+
+/** Pipeline configuration options */
+export interface PipelineOptions {
+ /** Execution mode (default: 'sequential') */
+ mode?: PipelineMode;
+ /** Stop pipeline on first stage failure (default: true) */
+ stopOnError?: boolean;
+ /** Global timeout for entire pipeline in ms */
+ timeout?: number;
+ /** Enable detailed timeline tracking (default: true) */
+ trackTimeline?: boolean;
+ /** Called when pipeline state changes */
+ onStateChange?: (state: PipelineState) => void;
+ /** Called when a stage starts */
+ onStageStart?: (stage: StageInfo) => void;
+ /** Called when a stage completes */
+ onStageComplete?: (stage: StageInfo) => void;
+ /** Called when a stage fails */
+ onStageError?: (stage: StageInfo, error: Error) => void;
+ /** Called when a stage is retried */
+ onStageRetry?: (stage: StageInfo, attempt: number) => void;
+}
+
+/** Events emitted by Pipeline */
+export interface PipelineEvents {
+ 'pipeline:start': { input: unknown };
+ 'pipeline:complete': { output: unknown; duration: number };
+ 'pipeline:error': { error: Error; stageId: string };
+ 'pipeline:cancel': { stageId: string };
+ 'stage:start': StageInfo;
+ 'stage:progress': { stageId: string; progress: number };
+ 'stage:complete': StageInfo;
+ 'stage:error': { stage: StageInfo; error: Error };
+ 'stage:skip': StageInfo;
+ 'stage:retry': { stage: StageInfo; attempt: number };
+}
+
/** Transferable types */
export type Transferable = ArrayBuffer | MessagePort | ImageBitmap | OffscreenCanvas;
@@ -229,3 +407,47 @@ export type Serializable =
| ArrayBufferView
| Map
| Set;
+
+// ============================================================================
+// Parallel Batch Types - For parallel processing within stages
+// ============================================================================
+
+/** Configuration for parallel batch processing */
+export interface ParallelBatchConfig {
+ /** Items to process in parallel */
+ items: TItem[];
+ /** Name of the registered compute function */
+ functionName: string;
+ /** Maximum concurrent executions (default: all) */
+ concurrency?: number;
+ /** Compute options for batch items */
+ options?: ComputeOptions;
+}
+
+/** Result of a single item in parallel batch */
+export interface BatchItemResult {
+ /** Index of the item in original array */
+ index: number;
+ /** Whether this item succeeded */
+ success: boolean;
+ /** Result if successful */
+ data?: TOutput;
+ /** Error if failed */
+ error?: Error;
+ /** Duration in ms */
+ duration: number;
+}
+
+/** Aggregate result of parallel batch processing */
+export interface ParallelBatchResult {
+ /** All individual results */
+ results: BatchItemResult[];
+ /** Successfully processed items */
+ successful: TOutput[];
+ /** Failed items with their errors */
+ failed: Array<{ index: number; error: Error }>;
+ /** Total duration */
+ totalDuration: number;
+ /** Success rate (0-1) */
+ successRate: number;
+}
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
index 9acc22b..d0b0d6b 100644
--- a/packages/core/src/utils.ts
+++ b/packages/core/src/utils.ts
@@ -279,6 +279,80 @@ export function serializeFunction(fn: Function): string {
return fn.toString();
}
+/**
+ * Estimate the byte size of a value for structured cloning.
+ * This is an approximation useful for debugging and performance monitoring.
+ */
+export function estimatePayloadSize(value: unknown): number {
+ if (value === null || value === undefined) return 0;
+ if (typeof value === 'boolean') return 4;
+ if (typeof value === 'number') return 8;
+ if (typeof value === 'string') return value.length * 2; // UTF-16
+
+ if (value instanceof ArrayBuffer) return value.byteLength;
+ if (ArrayBuffer.isView(value)) return value.byteLength;
+ if (value instanceof Blob) return value.size;
+
+ const seen = new WeakSet