@computekit/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/worker.js ADDED
@@ -0,0 +1,143 @@
1
+ // src/utils.ts
2
+ function generateId() {
3
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
4
+ }
5
+ function findTransferables(data) {
6
+ const transferables = [];
7
+ const seen = /* @__PURE__ */ new WeakSet();
8
+ function traverse(obj) {
9
+ if (obj === null || typeof obj !== "object") return;
10
+ if (seen.has(obj)) return;
11
+ seen.add(obj);
12
+ if (obj instanceof ArrayBuffer) {
13
+ transferables.push(obj);
14
+ return;
15
+ }
16
+ if (ArrayBuffer.isView(obj)) {
17
+ transferables.push(obj.buffer);
18
+ return;
19
+ }
20
+ if (obj instanceof MessagePort) {
21
+ transferables.push(obj);
22
+ return;
23
+ }
24
+ if (typeof ImageBitmap !== "undefined" && obj instanceof ImageBitmap) {
25
+ transferables.push(obj);
26
+ return;
27
+ }
28
+ if (typeof OffscreenCanvas !== "undefined" && obj instanceof OffscreenCanvas) {
29
+ transferables.push(obj);
30
+ return;
31
+ }
32
+ if (Array.isArray(obj)) {
33
+ obj.forEach(traverse);
34
+ return;
35
+ }
36
+ if (obj instanceof Map) {
37
+ obj.forEach((value, key) => {
38
+ traverse(key);
39
+ traverse(value);
40
+ });
41
+ return;
42
+ }
43
+ if (obj instanceof Set) {
44
+ obj.forEach(traverse);
45
+ return;
46
+ }
47
+ Object.values(obj).forEach(traverse);
48
+ }
49
+ traverse(data);
50
+ return transferables;
51
+ }
52
+
53
+ // src/worker/runtime.ts
54
+ var functionRegistry = /* @__PURE__ */ new Map();
55
+ var currentTaskId = null;
56
+ function reportProgress(progress) {
57
+ if (!currentTaskId) {
58
+ console.warn("reportProgress called outside of compute function");
59
+ return;
60
+ }
61
+ const message = {
62
+ id: generateId(),
63
+ type: "progress",
64
+ payload: {
65
+ taskId: currentTaskId,
66
+ progress: {
67
+ percent: progress.percent ?? 0,
68
+ phase: progress.phase,
69
+ estimatedTimeRemaining: progress.estimatedTimeRemaining,
70
+ data: progress.data
71
+ }
72
+ },
73
+ timestamp: Date.now()
74
+ };
75
+ self.postMessage(message);
76
+ }
77
+ function registerFunction(name, fn) {
78
+ functionRegistry.set(name, fn);
79
+ }
80
+ async function executeFunction(functionName, input) {
81
+ const fn = functionRegistry.get(functionName);
82
+ if (!fn) {
83
+ throw new Error(`Function "${functionName}" not found in worker`);
84
+ }
85
+ return fn(input);
86
+ }
87
+ async function handleMessage(event) {
88
+ const { id, type, payload } = event.data;
89
+ if (type === "execute") {
90
+ const { functionName, input } = payload;
91
+ const startTime = performance.now();
92
+ currentTaskId = id;
93
+ try {
94
+ const result = await executeFunction(functionName, input);
95
+ const duration = performance.now() - startTime;
96
+ const transfer = findTransferables(result);
97
+ const response = {
98
+ id,
99
+ type: "result",
100
+ payload: {
101
+ data: result,
102
+ duration
103
+ },
104
+ timestamp: Date.now()
105
+ };
106
+ self.postMessage(response, transfer);
107
+ } catch (err) {
108
+ const error = err instanceof Error ? err : new Error(String(err));
109
+ const response = {
110
+ id,
111
+ type: "error",
112
+ payload: {
113
+ message: error.message,
114
+ stack: error.stack
115
+ },
116
+ timestamp: Date.now()
117
+ };
118
+ self.postMessage(response);
119
+ } finally {
120
+ currentTaskId = null;
121
+ }
122
+ } else if (type === "init") {
123
+ const response = {
124
+ id,
125
+ type: "ready",
126
+ timestamp: Date.now()
127
+ };
128
+ self.postMessage(response);
129
+ }
130
+ }
131
+ function initWorkerRuntime() {
132
+ self.onmessage = handleMessage;
133
+ const readyMessage = {
134
+ id: generateId(),
135
+ type: "ready",
136
+ timestamp: Date.now()
137
+ };
138
+ self.postMessage(readyMessage);
139
+ }
140
+
141
+ export { functionRegistry, initWorkerRuntime, registerFunction, reportProgress };
142
+ //# sourceMappingURL=worker.js.map
143
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/worker/runtime.ts"],"names":[],"mappings":"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","file":"worker.js","sourcesContent":["/**\n * ComputeKit Utilities\n * Helper functions for the WASM + Worker toolkit\n */\n\n/**\n * Generate a unique ID\n */\nexport function generateId(): string {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * Check if running in a Web Worker context\n */\nexport function isWorkerContext(): boolean {\n return (\n typeof self !== 'undefined' &&\n typeof Window === 'undefined' &&\n typeof self.postMessage === 'function'\n );\n}\n\n/**\n * Check if running in a browser context\n */\nexport function isBrowserContext(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n\n/**\n * Check if SharedArrayBuffer is available\n */\nexport function isSharedArrayBufferAvailable(): boolean {\n try {\n return typeof SharedArrayBuffer !== 'undefined';\n } catch {\n return false;\n }\n}\n\n/**\n * Check if WASM is supported\n */\nexport function isWasmSupported(): boolean {\n try {\n if (typeof WebAssembly === 'object') {\n const module = new WebAssembly.Module(\n Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)\n );\n return module instanceof WebAssembly.Module;\n }\n } catch {\n // WASM not supported\n }\n return false;\n}\n\n/**\n * Get the number of logical processors\n */\nexport function getHardwareConcurrency(): number {\n if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) {\n return navigator.hardwareConcurrency;\n }\n return 4; // Reasonable default\n}\n\n/**\n * Create a deferred promise\n */\nexport interface Deferred<T> {\n promise: Promise<T>;\n resolve: (value: T | PromiseLike<T>) => void;\n reject: (reason?: unknown) => void;\n}\n\nexport function createDeferred<T>(): Deferred<T> {\n let resolve!: (value: T | PromiseLike<T>) => void;\n let reject!: (reason?: unknown) => void;\n\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n return { promise, resolve, reject };\n}\n\n/**\n * Create a timeout promise\n */\nexport function createTimeout(ms: number, message?: string): Promise<never> {\n return new Promise((_, reject) => {\n setTimeout(() => {\n reject(new Error(message || `Operation timed out after ${ms}ms`));\n }, ms);\n });\n}\n\n/**\n * Race a promise against a timeout\n */\nexport async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n message?: string\n): Promise<T> {\n return Promise.race([promise, createTimeout(ms, message)]);\n}\n\n/**\n * Detect transferable objects in data\n */\nexport function findTransferables(data: unknown): Transferable[] {\n const transferables: Transferable[] = [];\n const seen = new WeakSet();\n\n function traverse(obj: unknown): void {\n if (obj === null || typeof obj !== 'object') return;\n if (seen.has(obj as object)) return;\n seen.add(obj as object);\n\n if (obj instanceof ArrayBuffer) {\n transferables.push(obj);\n return;\n }\n\n if (ArrayBuffer.isView(obj)) {\n transferables.push(obj.buffer);\n return;\n }\n\n if (obj instanceof MessagePort) {\n transferables.push(obj);\n return;\n }\n\n if (typeof ImageBitmap !== 'undefined' && obj instanceof ImageBitmap) {\n transferables.push(obj);\n return;\n }\n\n if (typeof OffscreenCanvas !== 'undefined' && obj instanceof OffscreenCanvas) {\n transferables.push(obj);\n return;\n }\n\n if (Array.isArray(obj)) {\n obj.forEach(traverse);\n return;\n }\n\n if (obj instanceof Map) {\n obj.forEach((value, key) => {\n traverse(key);\n traverse(value);\n });\n return;\n }\n\n if (obj instanceof Set) {\n obj.forEach(traverse);\n return;\n }\n\n Object.values(obj).forEach(traverse);\n }\n\n traverse(data);\n return transferables;\n}\n\n/**\n * Clone data, detaching transferables\n */\nexport function cloneForTransfer<T>(data: T): { data: T; transfer: Transferable[] } {\n const transfer = findTransferables(data);\n return { data, transfer };\n}\n\n/**\n * Create a typed event emitter\n */\nexport type EventHandler<T = unknown> = (data: T) => void;\n\nexport class EventEmitter<TEvents extends Record<string, unknown>> {\n private handlers = new Map<keyof TEvents, Set<EventHandler>>();\n\n on<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): () => void {\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Set());\n }\n this.handlers.get(event)!.add(handler as EventHandler);\n\n // Return unsubscribe function\n return () => this.off(event, handler);\n }\n\n off<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void {\n this.handlers.get(event)?.delete(handler as EventHandler);\n }\n\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n this.handlers.get(event)?.forEach((handler) => {\n try {\n handler(data);\n } catch (err) {\n console.error(`Error in event handler for ${String(event)}:`, err);\n }\n });\n }\n\n removeAllListeners(event?: keyof TEvents): void {\n if (event) {\n this.handlers.delete(event);\n } else {\n this.handlers.clear();\n }\n }\n}\n\n/**\n * Simple LRU cache\n */\nexport class LRUCache<K, V> {\n private cache = new Map<K, V>();\n private maxSize: number;\n\n constructor(maxSize: number = 100) {\n this.maxSize = maxSize;\n }\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value !== undefined) {\n // Move to end (most recently used)\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n } else if (this.cache.size >= this.maxSize) {\n // Delete oldest (first) entry\n const firstKey = this.cache.keys().next().value;\n if (firstKey !== undefined) {\n this.cache.delete(firstKey);\n }\n }\n this.cache.set(key, value);\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n delete(key: K): boolean {\n return this.cache.delete(key);\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n\n/**\n * Serialize function to string for worker\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function serializeFunction(fn: Function): string {\n return fn.toString();\n}\n\n/**\n * Logger utility\n */\nexport interface Logger {\n debug(...args: unknown[]): void;\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nexport function createLogger(prefix: string, enabled: boolean = false): Logger {\n const noop = () => {};\n const log = (level: string) =>\n enabled ? (...args: unknown[]) => console.log(`[${prefix}:${level}]`, ...args) : noop;\n\n return {\n debug: log('debug'),\n info: log('info'),\n warn: enabled\n ? (...args: unknown[]) => console.warn(`[${prefix}:warn]`, ...args)\n : noop,\n error: (...args: unknown[]) => console.error(`[${prefix}:error]`, ...args),\n };\n}\n","/**\n * ComputeKit Worker Runtime\n * Code that runs inside Web Workers\n */\n\nimport type {\n WorkerMessage,\n ExecutePayload,\n ResultPayload,\n ErrorPayload,\n ComputeProgress,\n} from '../types';\n\nimport { generateId, findTransferables } from '../utils';\n\n/** Registry of compute functions available in the worker */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nconst functionRegistry = new Map<string, Function>();\n\n/** Current task context for progress reporting */\nlet currentTaskId: string | null = null;\n\n/**\n * Report progress from within a compute function\n */\nexport function reportProgress(progress: Partial<ComputeProgress>): void {\n if (!currentTaskId) {\n console.warn('reportProgress called outside of compute function');\n return;\n }\n\n const message: WorkerMessage = {\n id: generateId(),\n type: 'progress',\n payload: {\n taskId: currentTaskId,\n progress: {\n percent: progress.percent ?? 0,\n phase: progress.phase,\n estimatedTimeRemaining: progress.estimatedTimeRemaining,\n data: progress.data,\n },\n },\n timestamp: Date.now(),\n };\n\n self.postMessage(message);\n}\n\n/**\n * Register a compute function in the worker\n */\nexport function registerFunction(\n name: string,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n fn: Function\n): void {\n functionRegistry.set(name, fn);\n}\n\n/**\n * Execute a registered function\n */\nasync function executeFunction(functionName: string, input: unknown): Promise<unknown> {\n const fn = functionRegistry.get(functionName);\n\n if (!fn) {\n throw new Error(`Function \"${functionName}\" not found in worker`);\n }\n\n return fn(input);\n}\n\n/**\n * Handle incoming messages from the main thread\n */\nasync function handleMessage(event: MessageEvent<WorkerMessage>): Promise<void> {\n const { id, type, payload } = event.data;\n\n if (type === 'execute') {\n const { functionName, input } = payload as ExecutePayload;\n const startTime = performance.now();\n\n // Set current task for progress reporting\n currentTaskId = id;\n\n try {\n const result = await executeFunction(functionName, input);\n const duration = performance.now() - startTime;\n\n // Find transferable objects in result\n const transfer = findTransferables(result);\n\n const response: WorkerMessage<ResultPayload> = {\n id,\n type: 'result',\n payload: {\n data: result,\n duration,\n },\n timestamp: Date.now(),\n };\n\n self.postMessage(response, transfer as Transferable[]);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n\n const response: WorkerMessage<ErrorPayload> = {\n id,\n type: 'error',\n payload: {\n message: error.message,\n stack: error.stack,\n },\n timestamp: Date.now(),\n };\n\n self.postMessage(response);\n } finally {\n currentTaskId = null;\n }\n } else if (type === 'init') {\n // Handle initialization if needed\n const response: WorkerMessage = {\n id,\n type: 'ready',\n timestamp: Date.now(),\n };\n self.postMessage(response);\n }\n}\n\n/**\n * Initialize the worker runtime\n */\nexport function initWorkerRuntime(): void {\n self.onmessage = handleMessage;\n\n // Signal that worker is ready\n const readyMessage: WorkerMessage = {\n id: generateId(),\n type: 'ready',\n timestamp: Date.now(),\n };\n self.postMessage(readyMessage);\n}\n\n// Export for use in worker entry point\nexport { functionRegistry };\n"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@computekit/core",
3
+ "version": "0.1.0",
4
+ "description": "Core WASM + Worker toolkit for running heavy computations without blocking the UI",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./worker": {
16
+ "types": "./dist/worker.d.ts",
17
+ "import": "./dist/worker.js",
18
+ "require": "./dist/worker.cjs"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "test": "vitest",
29
+ "typecheck": "tsc --noEmit"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.10.0",
33
+ "tsup": "^8.0.1",
34
+ "typescript": "^5.3.3",
35
+ "vitest": "^1.1.0"
36
+ },
37
+ "peerDependencies": {
38
+ "assemblyscript": "^0.27.0"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "assemblyscript": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "keywords": [
46
+ "wasm",
47
+ "webassembly",
48
+ "workers",
49
+ "compute",
50
+ "performance",
51
+ "async"
52
+ ],
53
+ "author": "Ghassen Lassoued <https://github.com/tapava>",
54
+ "license": "MIT",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "https://github.com/tapava/compute-kit",
58
+ "directory": "packages/core"
59
+ },
60
+ "sideEffects": false
61
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,246 @@
1
+ /**
2
+ * ComputeKit CLI
3
+ * Build tool for compiling AssemblyScript compute functions
4
+ */
5
+
6
+ import { execSync } from 'child_process';
7
+ import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
8
+ import { join, basename, dirname, relative } from 'path';
9
+
10
+ interface BuildOptions {
11
+ /** Input directory containing .ts files */
12
+ input: string;
13
+ /** Output directory for compiled WASM */
14
+ output: string;
15
+ /** Enable debug mode */
16
+ debug?: boolean;
17
+ /** Optimization level (0-3) */
18
+ optimize?: number;
19
+ /** Generate source maps */
20
+ sourceMap?: boolean;
21
+ /** Memory configuration */
22
+ memory?: {
23
+ initial?: number;
24
+ maximum?: number;
25
+ };
26
+ }
27
+
28
+ interface ComputeFunction {
29
+ name: string;
30
+ path: string;
31
+ wasmPath: string;
32
+ }
33
+
34
+ /**
35
+ * Find all compute function files
36
+ */
37
+ function findComputeFunctions(dir: string): string[] {
38
+ const files: string[] = [];
39
+
40
+ function traverse(currentDir: string): void {
41
+ if (!existsSync(currentDir)) return;
42
+
43
+ const entries = readdirSync(currentDir, { withFileTypes: true });
44
+ for (const entry of entries) {
45
+ const fullPath = join(currentDir, entry.name);
46
+ if (entry.isDirectory()) {
47
+ traverse(fullPath);
48
+ } else if (entry.isFile() && entry.name.endsWith('.ts')) {
49
+ files.push(fullPath);
50
+ }
51
+ }
52
+ }
53
+
54
+ traverse(dir);
55
+ return files;
56
+ }
57
+
58
+ /**
59
+ * Compile a single AssemblyScript file to WASM
60
+ */
61
+ function compileToWasm(
62
+ inputPath: string,
63
+ outputPath: string,
64
+ options: BuildOptions
65
+ ): void {
66
+ const outputDir = dirname(outputPath);
67
+ if (!existsSync(outputDir)) {
68
+ mkdirSync(outputDir, { recursive: true });
69
+ }
70
+
71
+ const optimizeLevel = options.optimize ?? 3;
72
+ const args = [
73
+ 'npx',
74
+ 'asc',
75
+ inputPath,
76
+ '-o',
77
+ outputPath,
78
+ '--runtime',
79
+ 'stub',
80
+ `--optimize`,
81
+ `-O${optimizeLevel}`,
82
+ '--exportRuntime',
83
+ ];
84
+
85
+ if (options.debug) {
86
+ args.push('--debug');
87
+ }
88
+
89
+ if (options.sourceMap) {
90
+ args.push('--sourceMap');
91
+ }
92
+
93
+ if (options.memory?.initial) {
94
+ args.push('--initialMemory', String(options.memory.initial));
95
+ }
96
+
97
+ if (options.memory?.maximum) {
98
+ args.push('--maximumMemory', String(options.memory.maximum));
99
+ }
100
+
101
+ const command = args.join(' ');
102
+ console.log(`Compiling: ${inputPath}`);
103
+
104
+ try {
105
+ execSync(command, { stdio: 'inherit' });
106
+ console.log(` → ${outputPath}`);
107
+ } catch (error) {
108
+ console.error(`Failed to compile ${inputPath}`);
109
+ throw error;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Generate the compute registry file
115
+ */
116
+ function generateRegistry(functions: ComputeFunction[], outputDir: string): void {
117
+ const imports = functions
118
+ .map((fn) => {
119
+ const relativePath = relative(outputDir, fn.wasmPath).replace(/\\/g, '/');
120
+ return ` '${fn.name}': () => import('./${relativePath}?url'),`;
121
+ })
122
+ .join('\n');
123
+
124
+ const registry = `/**
125
+ * ComputeKit - Auto-generated WASM Registry
126
+ * Generated at: ${new Date().toISOString()}
127
+ */
128
+
129
+ export const wasmModules: Record<string, () => Promise<{ default: string }>> = {
130
+ ${imports}
131
+ };
132
+
133
+ export const moduleNames = ${JSON.stringify(functions.map((fn) => fn.name))};
134
+ `;
135
+
136
+ const registryPath = join(outputDir, 'registry.ts');
137
+ writeFileSync(registryPath, registry);
138
+ console.log(`Generated registry: ${registryPath}`);
139
+ }
140
+
141
+ /**
142
+ * Build all compute functions
143
+ */
144
+ export async function build(options: BuildOptions): Promise<void> {
145
+ console.log('\n🔧 ComputeKit Build\n');
146
+
147
+ const { input, output } = options;
148
+
149
+ // Find all .ts files
150
+ const files = findComputeFunctions(input);
151
+ if (files.length === 0) {
152
+ console.log('No compute functions found.');
153
+ return;
154
+ }
155
+
156
+ console.log(`Found ${files.length} compute function(s):\n`);
157
+
158
+ // Ensure output directory exists
159
+ if (!existsSync(output)) {
160
+ mkdirSync(output, { recursive: true });
161
+ }
162
+
163
+ const functions: ComputeFunction[] = [];
164
+
165
+ // Compile each file
166
+ for (const file of files) {
167
+ const name = basename(file, '.ts');
168
+ const wasmPath = join(output, `${name}.wasm`);
169
+
170
+ compileToWasm(file, wasmPath, options);
171
+
172
+ functions.push({
173
+ name,
174
+ path: file,
175
+ wasmPath,
176
+ });
177
+ }
178
+
179
+ // Generate registry
180
+ generateRegistry(functions, output);
181
+
182
+ console.log(`\n✅ Build complete! ${functions.length} module(s) compiled.\n`);
183
+ }
184
+
185
+ /**
186
+ * Watch mode for development
187
+ */
188
+ export function watch(options: BuildOptions): void {
189
+ console.log('\n👀 Watching for changes...\n');
190
+
191
+ // Initial build
192
+ build(options);
193
+
194
+ // Watch for changes (simplified - in production use chokidar)
195
+ const checkInterval = setInterval(() => {
196
+ // In a real implementation, use file watchers
197
+ }, 1000);
198
+
199
+ process.on('SIGINT', () => {
200
+ clearInterval(checkInterval);
201
+ process.exit(0);
202
+ });
203
+ }
204
+
205
+ // CLI entry point
206
+ if (typeof process !== 'undefined' && process.argv) {
207
+ const args = process.argv.slice(2);
208
+
209
+ if (args.includes('--help') || args.includes('-h')) {
210
+ console.log(`
211
+ ComputeKit Build Tool
212
+
213
+ Usage:
214
+ computekit build [options]
215
+ computekit watch [options]
216
+
217
+ Options:
218
+ --input, -i Input directory (default: ./compute)
219
+ --output, -o Output directory (default: ./compute/wasm)
220
+ --debug Enable debug mode
221
+ --optimize Optimization level 0-3 (default: 3)
222
+ --sourceMap Generate source maps
223
+ --help, -h Show this help
224
+ `);
225
+ process.exit(0);
226
+ }
227
+
228
+ const getArg = (name: string, short?: string): string | undefined => {
229
+ const idx = args.findIndex((a) => a === `--${name}` || a === `-${short}`);
230
+ return idx !== -1 ? args[idx + 1] : undefined;
231
+ };
232
+
233
+ const options: BuildOptions = {
234
+ input: getArg('input', 'i') ?? './compute',
235
+ output: getArg('output', 'o') ?? './compute/wasm',
236
+ debug: args.includes('--debug'),
237
+ optimize: parseInt(getArg('optimize') ?? '3'),
238
+ sourceMap: args.includes('--sourceMap'),
239
+ };
240
+
241
+ if (args.includes('watch')) {
242
+ watch(options);
243
+ } else {
244
+ build(options).catch(console.error);
245
+ }
246
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * ComputeKit Core Tests
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { ComputeKit, isWasmSupported, getHardwareConcurrency } from '../src';
7
+
8
+ describe('ComputeKit', () => {
9
+ let kit: ComputeKit;
10
+
11
+ beforeEach(() => {
12
+ kit = new ComputeKit({ debug: false });
13
+ });
14
+
15
+ afterEach(async () => {
16
+ await kit.terminate();
17
+ });
18
+
19
+ describe('constructor', () => {
20
+ it('should create instance with default options', () => {
21
+ expect(kit).toBeInstanceOf(ComputeKit);
22
+ });
23
+
24
+ it('should accept custom options', () => {
25
+ const customKit = new ComputeKit({
26
+ maxWorkers: 2,
27
+ timeout: 5000,
28
+ debug: true,
29
+ });
30
+ expect(customKit).toBeInstanceOf(ComputeKit);
31
+ });
32
+ });
33
+
34
+ describe('register', () => {
35
+ it('should register a function', () => {
36
+ kit.register('test', (n: number) => n * 2);
37
+ // Should not throw
38
+ });
39
+
40
+ it('should allow chaining', () => {
41
+ const result = kit
42
+ .register('fn1', () => 1)
43
+ .register('fn2', () => 2);
44
+ expect(result).toBe(kit);
45
+ });
46
+ });
47
+
48
+ describe('run', () => {
49
+ it('should execute a registered function', async () => {
50
+ kit.register('double', (n: number) => n * 2);
51
+ // Note: In the mock environment, we get 'mock result' back
52
+ const result = await kit.run('double', 5);
53
+ expect(result).toBeDefined();
54
+ });
55
+
56
+ it('should reject for unregistered function', async () => {
57
+ await expect(kit.run('unknown', {})).rejects.toThrow();
58
+ });
59
+ });
60
+
61
+ describe('getStats', () => {
62
+ it('should return pool statistics', () => {
63
+ const stats = kit.getStats();
64
+ expect(stats).toHaveProperty('totalWorkers');
65
+ expect(stats).toHaveProperty('activeWorkers');
66
+ expect(stats).toHaveProperty('queueLength');
67
+ expect(stats).toHaveProperty('tasksCompleted');
68
+ });
69
+ });
70
+
71
+ describe('isWasmSupported', () => {
72
+ it('should return a boolean', () => {
73
+ const result = kit.isWasmSupported();
74
+ expect(typeof result).toBe('boolean');
75
+ });
76
+ });
77
+ });
78
+
79
+ describe('Utility Functions', () => {
80
+ describe('isWasmSupported', () => {
81
+ it('should return a boolean', () => {
82
+ const result = isWasmSupported();
83
+ expect(typeof result).toBe('boolean');
84
+ });
85
+ });
86
+
87
+ describe('getHardwareConcurrency', () => {
88
+ it('should return a positive number', () => {
89
+ const result = getHardwareConcurrency();
90
+ expect(result).toBeGreaterThan(0);
91
+ });
92
+ });
93
+ });