@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/index.cjs
ADDED
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils.ts
|
|
4
|
+
function generateId() {
|
|
5
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
6
|
+
}
|
|
7
|
+
function isSharedArrayBufferAvailable() {
|
|
8
|
+
try {
|
|
9
|
+
return typeof SharedArrayBuffer !== "undefined";
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function isWasmSupported() {
|
|
15
|
+
try {
|
|
16
|
+
if (typeof WebAssembly === "object") {
|
|
17
|
+
const module = new WebAssembly.Module(
|
|
18
|
+
Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0)
|
|
19
|
+
);
|
|
20
|
+
return module instanceof WebAssembly.Module;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
function getHardwareConcurrency() {
|
|
27
|
+
if (typeof navigator !== "undefined" && navigator.hardwareConcurrency) {
|
|
28
|
+
return navigator.hardwareConcurrency;
|
|
29
|
+
}
|
|
30
|
+
return 4;
|
|
31
|
+
}
|
|
32
|
+
function createDeferred() {
|
|
33
|
+
let resolve;
|
|
34
|
+
let reject;
|
|
35
|
+
const promise = new Promise((res, rej) => {
|
|
36
|
+
resolve = res;
|
|
37
|
+
reject = rej;
|
|
38
|
+
});
|
|
39
|
+
return { promise, resolve, reject };
|
|
40
|
+
}
|
|
41
|
+
function createTimeout(ms, message) {
|
|
42
|
+
return new Promise((_, reject) => {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
reject(new Error(message || `Operation timed out after ${ms}ms`));
|
|
45
|
+
}, ms);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function withTimeout(promise, ms, message) {
|
|
49
|
+
return Promise.race([promise, createTimeout(ms, message)]);
|
|
50
|
+
}
|
|
51
|
+
function findTransferables(data) {
|
|
52
|
+
const transferables = [];
|
|
53
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
54
|
+
function traverse(obj) {
|
|
55
|
+
if (obj === null || typeof obj !== "object") return;
|
|
56
|
+
if (seen.has(obj)) return;
|
|
57
|
+
seen.add(obj);
|
|
58
|
+
if (obj instanceof ArrayBuffer) {
|
|
59
|
+
transferables.push(obj);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (ArrayBuffer.isView(obj)) {
|
|
63
|
+
transferables.push(obj.buffer);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (obj instanceof MessagePort) {
|
|
67
|
+
transferables.push(obj);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof ImageBitmap !== "undefined" && obj instanceof ImageBitmap) {
|
|
71
|
+
transferables.push(obj);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (typeof OffscreenCanvas !== "undefined" && obj instanceof OffscreenCanvas) {
|
|
75
|
+
transferables.push(obj);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(obj)) {
|
|
79
|
+
obj.forEach(traverse);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (obj instanceof Map) {
|
|
83
|
+
obj.forEach((value, key) => {
|
|
84
|
+
traverse(key);
|
|
85
|
+
traverse(value);
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (obj instanceof Set) {
|
|
90
|
+
obj.forEach(traverse);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
Object.values(obj).forEach(traverse);
|
|
94
|
+
}
|
|
95
|
+
traverse(data);
|
|
96
|
+
return transferables;
|
|
97
|
+
}
|
|
98
|
+
var EventEmitter = class {
|
|
99
|
+
handlers = /* @__PURE__ */ new Map();
|
|
100
|
+
on(event, handler) {
|
|
101
|
+
if (!this.handlers.has(event)) {
|
|
102
|
+
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
103
|
+
}
|
|
104
|
+
this.handlers.get(event).add(handler);
|
|
105
|
+
return () => this.off(event, handler);
|
|
106
|
+
}
|
|
107
|
+
off(event, handler) {
|
|
108
|
+
this.handlers.get(event)?.delete(handler);
|
|
109
|
+
}
|
|
110
|
+
emit(event, data) {
|
|
111
|
+
this.handlers.get(event)?.forEach((handler) => {
|
|
112
|
+
try {
|
|
113
|
+
handler(data);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(`Error in event handler for ${String(event)}:`, err);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
removeAllListeners(event) {
|
|
120
|
+
if (event) {
|
|
121
|
+
this.handlers.delete(event);
|
|
122
|
+
} else {
|
|
123
|
+
this.handlers.clear();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var LRUCache = class {
|
|
128
|
+
cache = /* @__PURE__ */ new Map();
|
|
129
|
+
maxSize;
|
|
130
|
+
constructor(maxSize = 100) {
|
|
131
|
+
this.maxSize = maxSize;
|
|
132
|
+
}
|
|
133
|
+
get(key) {
|
|
134
|
+
const value = this.cache.get(key);
|
|
135
|
+
if (value !== void 0) {
|
|
136
|
+
this.cache.delete(key);
|
|
137
|
+
this.cache.set(key, value);
|
|
138
|
+
}
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
set(key, value) {
|
|
142
|
+
if (this.cache.has(key)) {
|
|
143
|
+
this.cache.delete(key);
|
|
144
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
145
|
+
const firstKey = this.cache.keys().next().value;
|
|
146
|
+
if (firstKey !== void 0) {
|
|
147
|
+
this.cache.delete(firstKey);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.cache.set(key, value);
|
|
151
|
+
}
|
|
152
|
+
has(key) {
|
|
153
|
+
return this.cache.has(key);
|
|
154
|
+
}
|
|
155
|
+
delete(key) {
|
|
156
|
+
return this.cache.delete(key);
|
|
157
|
+
}
|
|
158
|
+
clear() {
|
|
159
|
+
this.cache.clear();
|
|
160
|
+
}
|
|
161
|
+
get size() {
|
|
162
|
+
return this.cache.size;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
function createLogger(prefix, enabled = false) {
|
|
166
|
+
const noop = () => {
|
|
167
|
+
};
|
|
168
|
+
const log = (level) => enabled ? (...args) => console.log(`[${prefix}:${level}]`, ...args) : noop;
|
|
169
|
+
return {
|
|
170
|
+
debug: log("debug"),
|
|
171
|
+
info: log("info"),
|
|
172
|
+
warn: enabled ? (...args) => console.warn(`[${prefix}:warn]`, ...args) : noop,
|
|
173
|
+
error: (...args) => console.error(`[${prefix}:error]`, ...args)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/pool.ts
|
|
178
|
+
var WorkerPool = class {
|
|
179
|
+
workers = /* @__PURE__ */ new Map();
|
|
180
|
+
taskQueue = [];
|
|
181
|
+
pendingTasks = /* @__PURE__ */ new Map();
|
|
182
|
+
functions = /* @__PURE__ */ new Map();
|
|
183
|
+
workerUrl = null;
|
|
184
|
+
options;
|
|
185
|
+
logger;
|
|
186
|
+
initialized = false;
|
|
187
|
+
stats = {
|
|
188
|
+
tasksCompleted: 0,
|
|
189
|
+
tasksFailed: 0,
|
|
190
|
+
totalDuration: 0
|
|
191
|
+
};
|
|
192
|
+
constructor(options = {}) {
|
|
193
|
+
this.options = {
|
|
194
|
+
maxWorkers: options.maxWorkers ?? getHardwareConcurrency(),
|
|
195
|
+
timeout: options.timeout ?? 3e4,
|
|
196
|
+
debug: options.debug ?? false,
|
|
197
|
+
workerPath: options.workerPath ?? "",
|
|
198
|
+
useSharedMemory: options.useSharedMemory ?? true
|
|
199
|
+
};
|
|
200
|
+
this.logger = createLogger("ComputeKit:Pool", this.options.debug);
|
|
201
|
+
this.logger.info("WorkerPool created with options:", this.options);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Initialize the worker pool
|
|
205
|
+
*/
|
|
206
|
+
async initialize() {
|
|
207
|
+
if (this.initialized) return;
|
|
208
|
+
this.logger.info("Initializing worker pool...");
|
|
209
|
+
this.logger.info("Registered functions:", Array.from(this.functions.keys()));
|
|
210
|
+
this.workerUrl = this.createWorkerBlob();
|
|
211
|
+
const workerCount = Math.min(2, this.options.maxWorkers);
|
|
212
|
+
for (let i = 0; i < workerCount; i++) {
|
|
213
|
+
await this.createWorker();
|
|
214
|
+
}
|
|
215
|
+
this.initialized = true;
|
|
216
|
+
this.logger.info(`Worker pool initialized with ${workerCount} workers`);
|
|
217
|
+
}
|
|
218
|
+
pendingRecreate = null;
|
|
219
|
+
/**
|
|
220
|
+
* Register a compute function
|
|
221
|
+
*/
|
|
222
|
+
register(name, fn) {
|
|
223
|
+
this.logger.debug(`Registering function: ${name}`);
|
|
224
|
+
this.functions.set(name, {
|
|
225
|
+
fn,
|
|
226
|
+
serialized: fn.toString()
|
|
227
|
+
});
|
|
228
|
+
if (this.initialized) {
|
|
229
|
+
this.pendingRecreate = this.recreateWorkers();
|
|
230
|
+
} else {
|
|
231
|
+
if (this.workerUrl) {
|
|
232
|
+
URL.revokeObjectURL(this.workerUrl);
|
|
233
|
+
this.workerUrl = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Recreate workers with updated function registry
|
|
239
|
+
*/
|
|
240
|
+
async recreateWorkers() {
|
|
241
|
+
this.logger.debug("Recreating workers with updated functions...");
|
|
242
|
+
if (this.workerUrl) {
|
|
243
|
+
URL.revokeObjectURL(this.workerUrl);
|
|
244
|
+
}
|
|
245
|
+
this.workerUrl = this.createWorkerBlob();
|
|
246
|
+
const idleWorkers = Array.from(this.workers.entries()).filter(
|
|
247
|
+
([_, w]) => w.state === "idle"
|
|
248
|
+
);
|
|
249
|
+
for (const [id, poolWorker] of idleWorkers) {
|
|
250
|
+
poolWorker.worker.terminate();
|
|
251
|
+
this.workers.delete(id);
|
|
252
|
+
}
|
|
253
|
+
const workerCount = Math.max(
|
|
254
|
+
1,
|
|
255
|
+
Math.min(2, this.options.maxWorkers) - this.workers.size
|
|
256
|
+
);
|
|
257
|
+
for (let i = 0; i < workerCount; i++) {
|
|
258
|
+
await this.createWorker();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Execute a compute function
|
|
263
|
+
*/
|
|
264
|
+
async execute(name, input, options) {
|
|
265
|
+
if (this.pendingRecreate) {
|
|
266
|
+
await this.pendingRecreate;
|
|
267
|
+
this.pendingRecreate = null;
|
|
268
|
+
}
|
|
269
|
+
if (!this.initialized) {
|
|
270
|
+
await this.initialize();
|
|
271
|
+
}
|
|
272
|
+
const fn = this.functions.get(name);
|
|
273
|
+
if (!fn) {
|
|
274
|
+
throw new Error(`Function "${name}" not registered`);
|
|
275
|
+
}
|
|
276
|
+
const taskId = generateId();
|
|
277
|
+
const timeout = options?.timeout ?? this.options.timeout;
|
|
278
|
+
this.logger.debug(`Executing task ${taskId} for function "${name}"`);
|
|
279
|
+
if (options?.signal?.aborted) {
|
|
280
|
+
throw new Error("Operation aborted");
|
|
281
|
+
}
|
|
282
|
+
const deferred = createDeferred();
|
|
283
|
+
const task = {
|
|
284
|
+
id: taskId,
|
|
285
|
+
functionName: name,
|
|
286
|
+
input,
|
|
287
|
+
options,
|
|
288
|
+
deferred,
|
|
289
|
+
priority: options?.priority ?? 5,
|
|
290
|
+
createdAt: Date.now(),
|
|
291
|
+
onProgress: options?.onProgress
|
|
292
|
+
};
|
|
293
|
+
if (options?.signal) {
|
|
294
|
+
options.signal.addEventListener("abort", () => {
|
|
295
|
+
this.cancelTask(taskId);
|
|
296
|
+
deferred.reject(new Error("Operation aborted"));
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
this.enqueue(task);
|
|
300
|
+
this.processQueue();
|
|
301
|
+
return withTimeout(deferred.promise, timeout, `Task "${name}" timed out`);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get pool statistics
|
|
305
|
+
*/
|
|
306
|
+
getStats() {
|
|
307
|
+
const workers = Array.from(this.workers.values()).map(
|
|
308
|
+
(w) => ({
|
|
309
|
+
id: w.id,
|
|
310
|
+
state: w.state,
|
|
311
|
+
currentTask: w.currentTask,
|
|
312
|
+
tasksCompleted: w.tasksCompleted,
|
|
313
|
+
errors: w.errors,
|
|
314
|
+
createdAt: w.createdAt,
|
|
315
|
+
lastActiveAt: w.lastActiveAt
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
return {
|
|
319
|
+
workers,
|
|
320
|
+
totalWorkers: this.workers.size,
|
|
321
|
+
activeWorkers: workers.filter((w) => w.state === "busy").length,
|
|
322
|
+
idleWorkers: workers.filter((w) => w.state === "idle").length,
|
|
323
|
+
queueLength: this.taskQueue.length,
|
|
324
|
+
tasksCompleted: this.stats.tasksCompleted,
|
|
325
|
+
tasksFailed: this.stats.tasksFailed,
|
|
326
|
+
averageTaskDuration: this.stats.tasksCompleted > 0 ? this.stats.totalDuration / this.stats.tasksCompleted : 0
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Terminate all workers and clean up
|
|
331
|
+
*/
|
|
332
|
+
async terminate() {
|
|
333
|
+
this.logger.info("Terminating worker pool...");
|
|
334
|
+
for (const task of this.pendingTasks.values()) {
|
|
335
|
+
task.deferred.reject(new Error("Worker pool terminated"));
|
|
336
|
+
}
|
|
337
|
+
this.pendingTasks.clear();
|
|
338
|
+
this.taskQueue = [];
|
|
339
|
+
for (const poolWorker of this.workers.values()) {
|
|
340
|
+
poolWorker.worker.terminate();
|
|
341
|
+
poolWorker.state = "terminated";
|
|
342
|
+
}
|
|
343
|
+
this.workers.clear();
|
|
344
|
+
if (this.workerUrl) {
|
|
345
|
+
URL.revokeObjectURL(this.workerUrl);
|
|
346
|
+
this.workerUrl = null;
|
|
347
|
+
}
|
|
348
|
+
this.initialized = false;
|
|
349
|
+
this.logger.info("Worker pool terminated");
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Create the worker blob URL
|
|
353
|
+
*/
|
|
354
|
+
createWorkerBlob() {
|
|
355
|
+
const functionsCode = Array.from(this.functions.entries()).map(([name, { serialized }]) => `"${name}": ${serialized}`).join(",\n");
|
|
356
|
+
this.logger.debug(
|
|
357
|
+
"Creating worker blob with functions:",
|
|
358
|
+
Array.from(this.functions.keys())
|
|
359
|
+
);
|
|
360
|
+
const workerCode = `
|
|
361
|
+
const functions = {
|
|
362
|
+
${functionsCode}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
self.onmessage = function(e) {
|
|
366
|
+
const msg = e.data;
|
|
367
|
+
if (msg.type === 'execute') {
|
|
368
|
+
const fn = functions[msg.payload.functionName];
|
|
369
|
+
if (!fn) {
|
|
370
|
+
self.postMessage({ id: msg.id, type: 'error', payload: { message: 'Function not found: ' + msg.payload.functionName } });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const start = performance.now();
|
|
375
|
+
Promise.resolve(fn(msg.payload.input)).then(function(result) {
|
|
376
|
+
self.postMessage({ id: msg.id, type: 'result', payload: { data: result, duration: performance.now() - start } });
|
|
377
|
+
}).catch(function(err) {
|
|
378
|
+
self.postMessage({ id: msg.id, type: 'error', payload: { message: err.message || String(err) } });
|
|
379
|
+
});
|
|
380
|
+
} catch (err) {
|
|
381
|
+
self.postMessage({ id: msg.id, type: 'error', payload: { message: err.message || String(err) } });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
self.postMessage({ type: 'ready' });
|
|
386
|
+
`;
|
|
387
|
+
const blob = new Blob([workerCode], { type: "application/javascript" });
|
|
388
|
+
return URL.createObjectURL(blob);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Create a new worker
|
|
392
|
+
*/
|
|
393
|
+
async createWorker() {
|
|
394
|
+
if (!this.workerUrl) {
|
|
395
|
+
this.workerUrl = this.createWorkerBlob();
|
|
396
|
+
}
|
|
397
|
+
const id = generateId();
|
|
398
|
+
const worker = new Worker(this.workerUrl);
|
|
399
|
+
let resolveReady;
|
|
400
|
+
const readyPromise = new Promise((resolve) => {
|
|
401
|
+
resolveReady = resolve;
|
|
402
|
+
});
|
|
403
|
+
const poolWorker = {
|
|
404
|
+
id,
|
|
405
|
+
worker,
|
|
406
|
+
state: "idle",
|
|
407
|
+
tasksCompleted: 0,
|
|
408
|
+
errors: 0,
|
|
409
|
+
createdAt: Date.now(),
|
|
410
|
+
lastActiveAt: Date.now(),
|
|
411
|
+
ready: false,
|
|
412
|
+
readyPromise
|
|
413
|
+
};
|
|
414
|
+
worker.onmessage = (e) => {
|
|
415
|
+
if (e.data.type === "ready") {
|
|
416
|
+
poolWorker.ready = true;
|
|
417
|
+
resolveReady();
|
|
418
|
+
}
|
|
419
|
+
this.handleWorkerMessage(poolWorker, e.data);
|
|
420
|
+
};
|
|
421
|
+
worker.onerror = (e) => {
|
|
422
|
+
this.handleWorkerError(poolWorker, e);
|
|
423
|
+
};
|
|
424
|
+
this.workers.set(id, poolWorker);
|
|
425
|
+
this.logger.debug(`Created worker ${id}`);
|
|
426
|
+
await readyPromise;
|
|
427
|
+
this.logger.debug(`Worker ${id} is ready`);
|
|
428
|
+
return poolWorker;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Handle messages from workers
|
|
432
|
+
*/
|
|
433
|
+
handleWorkerMessage(poolWorker, message) {
|
|
434
|
+
this.logger.debug("Received message from worker:", message);
|
|
435
|
+
const { id, type, payload } = message;
|
|
436
|
+
switch (type) {
|
|
437
|
+
case "ready":
|
|
438
|
+
this.logger.debug(`Worker ${poolWorker.id} ready`);
|
|
439
|
+
break;
|
|
440
|
+
case "result": {
|
|
441
|
+
const task = this.pendingTasks.get(id);
|
|
442
|
+
if (task) {
|
|
443
|
+
const resultPayload = payload;
|
|
444
|
+
this.pendingTasks.delete(id);
|
|
445
|
+
poolWorker.state = "idle";
|
|
446
|
+
poolWorker.currentTask = void 0;
|
|
447
|
+
poolWorker.tasksCompleted++;
|
|
448
|
+
poolWorker.lastActiveAt = Date.now();
|
|
449
|
+
this.stats.tasksCompleted++;
|
|
450
|
+
this.stats.totalDuration += resultPayload.duration;
|
|
451
|
+
this.logger.debug(
|
|
452
|
+
`Task ${id} completed in ${resultPayload.duration.toFixed(2)}ms`
|
|
453
|
+
);
|
|
454
|
+
task.deferred.resolve(resultPayload.data);
|
|
455
|
+
this.processQueue();
|
|
456
|
+
}
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
case "error": {
|
|
460
|
+
const task = this.pendingTasks.get(id);
|
|
461
|
+
if (task) {
|
|
462
|
+
const errorPayload = payload;
|
|
463
|
+
this.pendingTasks.delete(id);
|
|
464
|
+
poolWorker.state = "idle";
|
|
465
|
+
poolWorker.currentTask = void 0;
|
|
466
|
+
poolWorker.errors++;
|
|
467
|
+
poolWorker.lastActiveAt = Date.now();
|
|
468
|
+
this.stats.tasksFailed++;
|
|
469
|
+
const error = new Error(errorPayload.message);
|
|
470
|
+
if (errorPayload.stack) {
|
|
471
|
+
error.stack = errorPayload.stack;
|
|
472
|
+
}
|
|
473
|
+
this.logger.error(`Task ${id} failed:`, errorPayload.message);
|
|
474
|
+
task.deferred.reject(error);
|
|
475
|
+
this.processQueue();
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "progress": {
|
|
480
|
+
const progressPayload = payload;
|
|
481
|
+
const task = this.pendingTasks.get(progressPayload.taskId);
|
|
482
|
+
if (task?.onProgress) {
|
|
483
|
+
task.onProgress(progressPayload.progress);
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Handle worker errors
|
|
491
|
+
*/
|
|
492
|
+
handleWorkerError(poolWorker, error) {
|
|
493
|
+
this.logger.error(`Worker ${poolWorker.id} error:`, error.message);
|
|
494
|
+
poolWorker.state = "error";
|
|
495
|
+
poolWorker.errors++;
|
|
496
|
+
if (poolWorker.currentTask) {
|
|
497
|
+
const task = this.pendingTasks.get(poolWorker.currentTask);
|
|
498
|
+
if (task) {
|
|
499
|
+
this.pendingTasks.delete(poolWorker.currentTask);
|
|
500
|
+
task.deferred.reject(new Error(`Worker error: ${error.message}`));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
poolWorker.worker.terminate();
|
|
504
|
+
this.workers.delete(poolWorker.id);
|
|
505
|
+
this.createWorker().then(() => this.processQueue());
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Add task to queue (priority-based)
|
|
509
|
+
*/
|
|
510
|
+
enqueue(task) {
|
|
511
|
+
let inserted = false;
|
|
512
|
+
for (let i = 0; i < this.taskQueue.length; i++) {
|
|
513
|
+
if (task.priority > this.taskQueue[i].priority) {
|
|
514
|
+
this.taskQueue.splice(i, 0, task);
|
|
515
|
+
inserted = true;
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (!inserted) {
|
|
520
|
+
this.taskQueue.push(task);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Process queued tasks
|
|
525
|
+
*/
|
|
526
|
+
async processQueue() {
|
|
527
|
+
if (this.taskQueue.length === 0) return;
|
|
528
|
+
let idleWorker;
|
|
529
|
+
for (const worker of this.workers.values()) {
|
|
530
|
+
if (worker.state === "idle") {
|
|
531
|
+
idleWorker = worker;
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (!idleWorker && this.workers.size < this.options.maxWorkers) {
|
|
536
|
+
idleWorker = await this.createWorker();
|
|
537
|
+
}
|
|
538
|
+
if (!idleWorker) return;
|
|
539
|
+
const task = this.taskQueue.shift();
|
|
540
|
+
if (!task) return;
|
|
541
|
+
this.executeOnWorker(idleWorker, task);
|
|
542
|
+
if (this.taskQueue.length > 0) {
|
|
543
|
+
this.processQueue();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Execute task on a specific worker
|
|
548
|
+
*/
|
|
549
|
+
executeOnWorker(poolWorker, task) {
|
|
550
|
+
this.logger.debug(
|
|
551
|
+
`executeOnWorker: Starting task ${task.id} (${task.functionName}) on worker ${poolWorker.id}`
|
|
552
|
+
);
|
|
553
|
+
poolWorker.state = "busy";
|
|
554
|
+
poolWorker.currentTask = task.id;
|
|
555
|
+
poolWorker.lastActiveAt = Date.now();
|
|
556
|
+
this.pendingTasks.set(task.id, task);
|
|
557
|
+
const message = {
|
|
558
|
+
id: task.id,
|
|
559
|
+
type: "execute",
|
|
560
|
+
payload: {
|
|
561
|
+
functionName: task.functionName,
|
|
562
|
+
input: task.input
|
|
563
|
+
// Don't send options - they may contain non-cloneable objects like AbortSignal
|
|
564
|
+
},
|
|
565
|
+
timestamp: Date.now()
|
|
566
|
+
};
|
|
567
|
+
const transfer = findTransferables(task.input);
|
|
568
|
+
this.logger.debug(`Posting message to worker:`, message);
|
|
569
|
+
poolWorker.worker.postMessage(message, transfer);
|
|
570
|
+
this.logger.debug(`Message posted to worker ${poolWorker.id}`);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Cancel a pending task
|
|
574
|
+
*/
|
|
575
|
+
cancelTask(taskId) {
|
|
576
|
+
const queueIndex = this.taskQueue.findIndex((t) => t.id === taskId);
|
|
577
|
+
if (queueIndex !== -1) {
|
|
578
|
+
this.taskQueue.splice(queueIndex, 1);
|
|
579
|
+
}
|
|
580
|
+
this.pendingTasks.delete(taskId);
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// src/wasm.ts
|
|
585
|
+
var logger = createLogger("ComputeKit:WASM");
|
|
586
|
+
var moduleCache = new LRUCache(10);
|
|
587
|
+
var instanceCache = new LRUCache(10);
|
|
588
|
+
async function loadWasmModule(source) {
|
|
589
|
+
if (typeof source === "string") {
|
|
590
|
+
const cached = moduleCache.get(source);
|
|
591
|
+
if (cached) {
|
|
592
|
+
logger.debug("Using cached WASM module:", source);
|
|
593
|
+
return cached;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
let bytes;
|
|
597
|
+
if (typeof source === "string") {
|
|
598
|
+
if (source.startsWith("data:")) {
|
|
599
|
+
const base64 = source.split(",")[1];
|
|
600
|
+
bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
601
|
+
} else {
|
|
602
|
+
logger.debug("Fetching WASM from:", source);
|
|
603
|
+
const response = await fetch(source);
|
|
604
|
+
if (!response.ok) {
|
|
605
|
+
throw new Error(`Failed to fetch WASM: ${response.statusText}`);
|
|
606
|
+
}
|
|
607
|
+
if (WebAssembly.compileStreaming) {
|
|
608
|
+
const module2 = await WebAssembly.compileStreaming(response);
|
|
609
|
+
moduleCache.set(source, module2);
|
|
610
|
+
return module2;
|
|
611
|
+
}
|
|
612
|
+
bytes = await response.arrayBuffer();
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
bytes = source;
|
|
616
|
+
}
|
|
617
|
+
const module = await WebAssembly.compile(bytes);
|
|
618
|
+
if (typeof source === "string") {
|
|
619
|
+
moduleCache.set(source, module);
|
|
620
|
+
}
|
|
621
|
+
return module;
|
|
622
|
+
}
|
|
623
|
+
async function instantiateWasm(module, imports = {}) {
|
|
624
|
+
return WebAssembly.instantiate(module, imports);
|
|
625
|
+
}
|
|
626
|
+
async function loadAndInstantiate(config) {
|
|
627
|
+
const { source, imports = {}, memory } = config;
|
|
628
|
+
const wasmImports = { ...imports };
|
|
629
|
+
if (memory) {
|
|
630
|
+
wasmImports.env = {
|
|
631
|
+
...wasmImports.env,
|
|
632
|
+
memory: new WebAssembly.Memory({
|
|
633
|
+
initial: memory.initial,
|
|
634
|
+
maximum: memory.maximum,
|
|
635
|
+
shared: memory.shared
|
|
636
|
+
})
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
const module = await loadWasmModule(source);
|
|
640
|
+
const instance = await instantiateWasm(module, wasmImports);
|
|
641
|
+
return { module, instance };
|
|
642
|
+
}
|
|
643
|
+
async function loadAssemblyScript(source, imports = {}) {
|
|
644
|
+
const defaultImports = {
|
|
645
|
+
env: {
|
|
646
|
+
abort: (_message, fileName, line, column) => {
|
|
647
|
+
console.error(`AssemblyScript abort at ${fileName}:${line}:${column}`);
|
|
648
|
+
},
|
|
649
|
+
seed: () => Date.now(),
|
|
650
|
+
...imports.env || {}
|
|
651
|
+
},
|
|
652
|
+
...imports
|
|
653
|
+
};
|
|
654
|
+
const { module, instance } = await loadAndInstantiate({
|
|
655
|
+
source,
|
|
656
|
+
imports: defaultImports
|
|
657
|
+
});
|
|
658
|
+
return {
|
|
659
|
+
module,
|
|
660
|
+
instance,
|
|
661
|
+
exports: instance.exports
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function wrapWasmExports(instance) {
|
|
665
|
+
return instance.exports;
|
|
666
|
+
}
|
|
667
|
+
function getMemoryView(memory, ArrayType, offset = 0, length) {
|
|
668
|
+
return new ArrayType(memory.buffer, offset, length);
|
|
669
|
+
}
|
|
670
|
+
function copyToWasmMemory(memory, data, offset) {
|
|
671
|
+
const view = new Uint8Array(memory.buffer);
|
|
672
|
+
const source = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
673
|
+
view.set(source, offset);
|
|
674
|
+
}
|
|
675
|
+
function copyFromWasmMemory(memory, offset, length) {
|
|
676
|
+
const view = new Uint8Array(memory.buffer, offset, length);
|
|
677
|
+
return new Uint8Array(view);
|
|
678
|
+
}
|
|
679
|
+
function clearWasmCache() {
|
|
680
|
+
moduleCache.clear();
|
|
681
|
+
instanceCache.clear();
|
|
682
|
+
}
|
|
683
|
+
function getWasmCacheStats() {
|
|
684
|
+
return {
|
|
685
|
+
modules: moduleCache.size,
|
|
686
|
+
instances: instanceCache.size
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/index.ts
|
|
691
|
+
var logger2 = createLogger("ComputeKit");
|
|
692
|
+
var ComputeKit = class extends EventEmitter {
|
|
693
|
+
pool;
|
|
694
|
+
constructor(options = {}) {
|
|
695
|
+
super();
|
|
696
|
+
this.pool = new WorkerPool(options);
|
|
697
|
+
logger2.debug("ComputeKit initialized", options);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Initialize ComputeKit
|
|
701
|
+
* Called automatically on first run, but can be called manually for eager initialization
|
|
702
|
+
*/
|
|
703
|
+
async initialize() {
|
|
704
|
+
await this.pool.initialize();
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Register a compute function
|
|
708
|
+
*
|
|
709
|
+
* @param name - Unique name for the function
|
|
710
|
+
* @param fn - The function to execute (will run in a Web Worker)
|
|
711
|
+
*
|
|
712
|
+
* @example
|
|
713
|
+
* ```ts
|
|
714
|
+
* kit.register('sum', (arr: number[]) => arr.reduce((a, b) => a + b, 0));
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
register(name, fn) {
|
|
718
|
+
this.pool.register(name, fn);
|
|
719
|
+
return this;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Execute a registered compute function
|
|
723
|
+
*
|
|
724
|
+
* @param name - Name of the registered function
|
|
725
|
+
* @param input - Input data for the function
|
|
726
|
+
* @param options - Execution options
|
|
727
|
+
* @returns Promise resolving to the function result
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* ```ts
|
|
731
|
+
* const sum = await kit.run('sum', [1, 2, 3, 4, 5]);
|
|
732
|
+
* ```
|
|
733
|
+
*/
|
|
734
|
+
async run(name, input, options) {
|
|
735
|
+
return this.pool.execute(name, input, options);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Execute a registered compute function with full result metadata
|
|
739
|
+
*
|
|
740
|
+
* @param name - Name of the registered function
|
|
741
|
+
* @param input - Input data for the function
|
|
742
|
+
* @param options - Execution options
|
|
743
|
+
* @returns Promise resolving to ComputeResult with metadata
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```ts
|
|
747
|
+
* const result = await kit.runWithMetadata('sum', data);
|
|
748
|
+
* console.log(`Took ${result.duration}ms`);
|
|
749
|
+
* ```
|
|
750
|
+
*/
|
|
751
|
+
async runWithMetadata(name, input, options) {
|
|
752
|
+
const startTime = performance.now();
|
|
753
|
+
const data = await this.pool.execute(name, input, options);
|
|
754
|
+
const duration = performance.now() - startTime;
|
|
755
|
+
return {
|
|
756
|
+
data,
|
|
757
|
+
duration,
|
|
758
|
+
cached: false,
|
|
759
|
+
workerId: "unknown"
|
|
760
|
+
// Would need pool changes to track this
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Get pool statistics
|
|
765
|
+
*/
|
|
766
|
+
getStats() {
|
|
767
|
+
return this.pool.getStats();
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Check if WebAssembly is supported
|
|
771
|
+
*/
|
|
772
|
+
isWasmSupported() {
|
|
773
|
+
return isWasmSupported();
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Terminate the worker pool and clean up resources
|
|
777
|
+
*/
|
|
778
|
+
async terminate() {
|
|
779
|
+
await this.pool.terminate();
|
|
780
|
+
this.removeAllListeners();
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
function createComputeKit(options) {
|
|
784
|
+
return new ComputeKit(options);
|
|
785
|
+
}
|
|
786
|
+
var defaultInstance = null;
|
|
787
|
+
function getDefaultInstance() {
|
|
788
|
+
if (!defaultInstance) {
|
|
789
|
+
defaultInstance = new ComputeKit();
|
|
790
|
+
}
|
|
791
|
+
return defaultInstance;
|
|
792
|
+
}
|
|
793
|
+
function register(name, fn) {
|
|
794
|
+
getDefaultInstance().register(name, fn);
|
|
795
|
+
}
|
|
796
|
+
async function run(name, input, options) {
|
|
797
|
+
return getDefaultInstance().run(name, input, options);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
exports.ComputeKit = ComputeKit;
|
|
801
|
+
exports.WorkerPool = WorkerPool;
|
|
802
|
+
exports.clearWasmCache = clearWasmCache;
|
|
803
|
+
exports.copyFromWasmMemory = copyFromWasmMemory;
|
|
804
|
+
exports.copyToWasmMemory = copyToWasmMemory;
|
|
805
|
+
exports.createComputeKit = createComputeKit;
|
|
806
|
+
exports.findTransferables = findTransferables;
|
|
807
|
+
exports.getDefaultInstance = getDefaultInstance;
|
|
808
|
+
exports.getHardwareConcurrency = getHardwareConcurrency;
|
|
809
|
+
exports.getMemoryView = getMemoryView;
|
|
810
|
+
exports.getWasmCacheStats = getWasmCacheStats;
|
|
811
|
+
exports.isSharedArrayBufferAvailable = isSharedArrayBufferAvailable;
|
|
812
|
+
exports.isWasmSupported = isWasmSupported;
|
|
813
|
+
exports.loadAndInstantiate = loadAndInstantiate;
|
|
814
|
+
exports.loadAssemblyScript = loadAssemblyScript;
|
|
815
|
+
exports.loadWasmModule = loadWasmModule;
|
|
816
|
+
exports.register = register;
|
|
817
|
+
exports.run = run;
|
|
818
|
+
exports.wrapWasmExports = wrapWasmExports;
|
|
819
|
+
//# sourceMappingURL=index.cjs.map
|
|
820
|
+
//# sourceMappingURL=index.cjs.map
|