@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/index.cjs +820 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +270 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +800 -0
- package/dist/index.js.map +1 -0
- package/dist/types-AaH5nWxG.d.cts +143 -0
- package/dist/types-AaH5nWxG.d.ts +143 -0
- package/dist/worker.cjs +148 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +23 -0
- package/dist/worker.d.ts +23 -0
- package/dist/worker.js +143 -0
- package/dist/worker.js.map +1 -0
- package/package.json +61 -0
- package/src/cli.ts +246 -0
- package/src/index.test.ts +93 -0
- package/src/index.ts +232 -0
- package/src/pool.ts +591 -0
- package/src/types.ts +229 -0
- package/src/utils.ts +305 -0
- package/src/wasm.ts +205 -0
- package/src/worker/index.ts +11 -0
- package/src/worker/runtime.ts +149 -0
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
|
+
});
|