@flaxia/node 0.1.4 → 0.2.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.
@@ -4,6 +4,7 @@ export interface TaskMessage {
4
4
  taskId: string;
5
5
  workload: WorkloadType;
6
6
  payload: unknown;
7
+ timeoutMs?: number;
7
8
  }
8
9
  export declare const initFlaxiaNode: (config: NodeConfig) => void;
9
10
  //# sourceMappingURL=SignalingClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SignalingClient.d.ts","sourceRoot":"","sources":["../../src/client/SignalingClient.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AA4GD,eAAO,MAAM,cAAc,GAAI,QAAQ,UAAU,SAehD,CAAC"}
1
+ {"version":3,"file":"SignalingClient.d.ts","sourceRoot":"","sources":["../../src/client/SignalingClient.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8ND,eAAO,MAAM,cAAc,GAAI,QAAQ,UAAU,SAehD,CAAC"}
@@ -1,12 +1,33 @@
1
1
  import type { WorkloadType } from "@flaxia/sdk";
2
+ /**
3
+ * Executes tasks against a single Web Worker.
4
+ *
5
+ * Design notes:
6
+ * - Tasks are serialized (1 node = 1 task at a time) so a worker restart on
7
+ * timeout never destroys another task's execution.
8
+ * - Timeouts are a hard deadline from task start; heartbeats and token
9
+ * messages do NOT extend them, so a stuck task cannot hold a slot forever.
10
+ * - terminate()/suspend() settles all pending promises and clears timers so a
11
+ * stale timeout can never kill a freshly resumed worker.
12
+ */
2
13
  export declare class WorkerPool {
3
14
  private worker;
4
15
  private defaultTimeoutMs;
5
16
  private workerUrl;
17
+ private queue;
18
+ private active;
19
+ private _lastCpuLoad;
6
20
  constructor(workerUrl?: string, timeoutMs?: number);
7
21
  private initWorker;
8
- run(id: string, workload: WorkloadType, payload: unknown, timeoutMs?: number, onToken?: (token: string) => void): Promise<unknown>;
22
+ run(id: string, workload: WorkloadType, payload: unknown, timeoutMs?: number, onToken?: (token: string) => void, config?: {
23
+ maxCpuLoad?: number;
24
+ }): Promise<unknown>;
25
+ private execute;
26
+ private handleWorkerError;
27
+ private dequeue;
9
28
  private cleanupWorker;
10
29
  terminate(): void;
30
+ resume(): void;
31
+ get lastCpuLoad(): number;
11
32
  }
12
33
  //# sourceMappingURL=WorkerPool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkerPool.d.ts","sourceRoot":"","sources":["../../src/executor/WorkerPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,SAAS;IAMlD,OAAO,CAAC,UAAU;IAKlB,GAAG,CACD,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC,OAAO,CAAC;IAmDnB,OAAO,CAAC,aAAa;IAQrB,SAAS;CAMV"}
1
+ {"version":3,"file":"WorkerPool.d.ts","sourceRoot":"","sources":["../../src/executor/WorkerPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,YAAY,CAAK;gBAEb,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,SAAS;IAMlD,OAAO,CAAC,UAAU;IAYlB,GAAG,CACD,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAC/B,OAAO,CAAC,OAAO,CAAC;IAenB,OAAO,CAAC,OAAO;IA2Ef,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,aAAa;IAQrB,SAAS;IAaT,MAAM;IAMN,IAAI,WAAW,IAAI,MAAM,CAExB;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=throttle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.test.d.ts","sourceRoot":"","sources":["../../../src/executor/__tests__/throttle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Estimates current CPU contention and throttles work accordingly.
3
+ *
4
+ * Runs inside the compute Worker (where the actual workload executes) rather
5
+ * than on the main thread, so the measurement reflects the thread that does
6
+ * the work. Load is estimated with a calibrated busy-loop: a fixed-iteration
7
+ * loop takes longer to finish when the system is busy, and the ratio to the
8
+ * idle baseline is the estimated load.
9
+ *
10
+ * There is no direct API for CPU usage in the browser, so this is a heuristic
11
+ * used to (a) avoid starting work while the machine is already loaded and
12
+ * (b) space out task starts so average CPU stays near `maxLoad`.
13
+ */
14
+ export declare class CpuThrottle {
15
+ private baselineMs;
16
+ private lastLoad;
17
+ private readonly maxLoad;
18
+ private readonly loopIterations;
19
+ constructor(maxLoad?: number, loopIterations?: number);
20
+ private busyLoop;
21
+ private sample;
22
+ calibrate(): Promise<void>;
23
+ getCurrentLoad(): Promise<number>;
24
+ shouldPause(): Promise<boolean>;
25
+ /**
26
+ * Waits until the system is calm enough to run a task.
27
+ * Bounded by `maxWaitMs` so a persistently busy machine cannot stall
28
+ * the queue forever.
29
+ */
30
+ waitForSlot(maxWaitMs?: number): Promise<void>;
31
+ get lastMeasuredLoad(): number;
32
+ get maxLoadValue(): number;
33
+ }
34
+ //# sourceMappingURL=throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/executor/throttle.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,OAAO,SAAO,EAAE,cAAc,SAAY;IAKtD,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,MAAM;IAKR,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAM1B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAWjC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAIrC;;;;OAIG;IACG,WAAW,CAAC,SAAS,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnD,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF"}
package/dist/index.js CHANGED
@@ -51,55 +51,99 @@ var e = class {
51
51
  localStorage.setItem(t, "true"), localStorage.setItem(n, String(Date.now() + r));
52
52
  }, o = class {
53
53
  constructor(e, t = 3e5) {
54
- this.worker = null, this.workerUrl = e || "/worker.js", this.defaultTimeoutMs = t, this.initWorker();
54
+ this.worker = null, this.queue = [], this.active = null, this._lastCpuLoad = 0, this.workerUrl = e || "/worker.js", this.defaultTimeoutMs = t, this.initWorker();
55
55
  }
56
56
  initWorker() {
57
- typeof Worker > "u" || (this.worker = new Worker(this.workerUrl, { type: "module" }));
57
+ if (typeof Worker > "u") return;
58
+ let e = new Worker(this.workerUrl, { type: "module" });
59
+ e.onerror = (e) => {
60
+ this.handleWorkerError(Error(e.message || "Worker error"));
61
+ }, e.onmessageerror = () => {
62
+ this.handleWorkerError(/* @__PURE__ */ Error("Worker message error"));
63
+ }, this.worker = e;
58
64
  }
59
- run(e, t, n, r, i) {
60
- return new Promise((a, o) => {
65
+ run(e, t, n, r, i, a) {
66
+ return new Promise((o, s) => {
61
67
  if (!this.worker) {
62
- o(/* @__PURE__ */ Error("Worker not available"));
68
+ s(/* @__PURE__ */ Error("Worker not available"));
63
69
  return;
64
70
  }
65
- let s = r ?? this.defaultTimeoutMs, c = setTimeout(() => {
66
- this.cleanupWorker(), o(/* @__PURE__ */ Error("TIMEOUT"));
67
- }, s), l = (t) => {
68
- let { id: n, type: r, result: u, error: d, token: f } = t.data;
69
- if (n === e) {
70
- if (r === "heartbeat") {
71
- clearTimeout(c), c = setTimeout(() => {
72
- this.cleanupWorker(), o(/* @__PURE__ */ Error("TIMEOUT"));
73
- }, s);
74
- return;
75
- }
76
- if (r === "token") {
77
- i?.(f), clearTimeout(c), c = setTimeout(() => {
78
- this.cleanupWorker(), o(/* @__PURE__ */ Error("TIMEOUT"));
79
- }, s);
80
- return;
81
- }
82
- clearTimeout(c), this.worker?.removeEventListener("message", l), r === "done" ? a(u) : r === "error" && o(Error(d));
71
+ let c = () => this.execute(e, t, n, r, i, a, o, s);
72
+ this.active ? this.queue.push(c) : c();
73
+ });
74
+ }
75
+ execute(e, t, n, r, i, a, o, s) {
76
+ let c = this.worker;
77
+ if (!c) {
78
+ s(/* @__PURE__ */ Error("Worker not available")), this.dequeue();
79
+ return;
80
+ }
81
+ let l = r ?? this.defaultTimeoutMs, u = !1, d = (e) => {
82
+ if (u) return;
83
+ u = !0;
84
+ let t = this.active;
85
+ t && (clearTimeout(t.timer), t.removeListener()), this.active = null, e(), this.dequeue();
86
+ }, f = (t) => {
87
+ let { id: n, type: r, result: a, error: c, token: l, cpuLoad: u } = t.data;
88
+ if (n === e) {
89
+ if (r === "token") {
90
+ i?.(l);
91
+ return;
83
92
  }
84
- };
85
- this.worker.addEventListener("message", l), this.worker.postMessage({
93
+ if (r === "heartbeat") {
94
+ typeof u == "number" && Number.isFinite(u) && (this._lastCpuLoad = Math.min(1, Math.max(0, u)));
95
+ return;
96
+ }
97
+ d(r === "done" ? () => o(a) : r === "error" ? () => s(Error(c)) : () => s(/* @__PURE__ */ Error(`Unknown message type: ${String(r)}`)));
98
+ }
99
+ }, p = () => c.removeEventListener("message", f), m = setTimeout(() => {
100
+ this.cleanupWorker(), d(() => s(/* @__PURE__ */ Error("TIMEOUT")));
101
+ }, l);
102
+ this.active = {
103
+ id: e,
104
+ resolve: o,
105
+ reject: s,
106
+ timer: m,
107
+ worker: c,
108
+ removeListener: p
109
+ }, c.addEventListener("message", f);
110
+ try {
111
+ c.postMessage({
86
112
  id: e,
87
113
  workload: t,
88
- payload: n
114
+ payload: n,
115
+ timeoutMs: l,
116
+ config: a
89
117
  });
90
- });
118
+ } catch (e) {
119
+ d(() => s(e instanceof Error ? e : Error(String(e))));
120
+ }
121
+ }
122
+ handleWorkerError(e) {
123
+ let t = this.active;
124
+ this.cleanupWorker(), t && (clearTimeout(t.timer), this.active = null, t.reject(e), this.dequeue());
125
+ }
126
+ dequeue() {
127
+ let e = this.queue.shift();
128
+ e && e();
91
129
  }
92
130
  cleanupWorker() {
93
131
  this.worker &&= (this.worker.terminate(), null), this.initWorker();
94
132
  }
95
133
  terminate() {
96
- this.worker &&= (this.worker.terminate(), null);
134
+ this.queue = [], this.active &&= (clearTimeout(this.active.timer), this.active.reject(/* @__PURE__ */ Error("TERMINATED")), null), this.worker &&= (this.worker.terminate(), null);
135
+ }
136
+ resume() {
137
+ this.worker || this.initWorker();
138
+ }
139
+ get lastCpuLoad() {
140
+ return this._lastCpuLoad;
97
141
  }
98
- }, s = class {
142
+ }, s = "flaxia_node_id", c = "flaxia_node_token", l = class {
99
143
  constructor(e, t, n) {
100
- this.config = e, this.workerPool = t, this.nodeId = n, this.ws = null, this.reconnectAttempts = 0, this.MAX_RECONNECT_DELAY = 3e4, this.destroyed = !1;
144
+ this.config = e, this.workerPool = t, this.nodeId = n, this.ws = null, this.reconnectAttempts = 0, this.MAX_RECONNECT_DELAY = 3e4, this.destroyed = !1, this.suspended = !1, this.visibilityHandler = null;
101
145
  }
102
- connect() {
146
+ async connect() {
103
147
  if (this.destroyed) return;
104
148
  if (this.ws) {
105
149
  let e = this.ws;
@@ -109,74 +153,141 @@ var e = class {
109
153
  } catch {}
110
154
  this.ws = null;
111
155
  }
112
- let e = this.config.capabilities ?? ["ai-inference", "image-process"], t = new URL(`${this.config.orchestratorUrl.replace("http", "ws")}/crowd/signal`);
113
- t.searchParams.set("nodeId", this.nodeId), t.searchParams.set("capabilities", e.join(","));
114
- let n = new WebSocket(t.toString());
115
- this.ws = n, n.onopen = () => {
156
+ this.setupVisibilityHandler();
157
+ let e = await this.obtainToken();
158
+ if (!e) {
159
+ this.scheduleReconnect();
160
+ return;
161
+ }
162
+ let t = this.config.orchestratorUrl.replace(/\/+$/, "").replace(/^http/, "ws"), n = new URL(`${t}/crowd/signal`);
163
+ n.searchParams.set("token", e.token);
164
+ let r = new WebSocket(n.toString());
165
+ this.ws = r, r.onopen = () => {
116
166
  this.reconnectAttempts = 0;
117
- }, n.onmessage = async (e) => {
118
- let t = JSON.parse(e.data);
167
+ }, r.onmessage = async (e) => {
168
+ let t;
169
+ try {
170
+ t = JSON.parse(e.data);
171
+ } catch {
172
+ return;
173
+ }
119
174
  if (t.type === "ping") {
120
- n.send(JSON.stringify({
175
+ this.send({
121
176
  type: "pong",
122
- cpuLoad: .1
123
- }));
177
+ cpuLoad: this.workerPool.lastCpuLoad
178
+ });
124
179
  return;
125
180
  }
126
- t.type === "task" && this.handleTask(t);
127
- }, n.onclose = () => {
128
- if (n.onclose = null, this.ws !== n || (this.ws = null, this.destroyed)) return;
129
- let e = Math.min(1e3 * 2 ** this.reconnectAttempts, this.MAX_RECONNECT_DELAY);
130
- setTimeout(() => {
131
- this.reconnectAttempts++, this.connect();
132
- }, e);
181
+ if (t.type === "task") try {
182
+ await this.handleTask(t);
183
+ } catch (e) {
184
+ this.send({
185
+ type: "error",
186
+ taskId: String(t.taskId ?? ""),
187
+ error: e instanceof Error ? e.message : String(e)
188
+ });
189
+ }
190
+ }, r.onclose = () => {
191
+ r.onclose = null, this.ws === r && (this.ws = null, !this.destroyed && this.scheduleReconnect());
133
192
  };
134
193
  }
194
+ scheduleReconnect() {
195
+ let e = Math.min(1e3 * 2 ** this.reconnectAttempts, this.MAX_RECONNECT_DELAY);
196
+ setTimeout(() => {
197
+ this.destroyed || this.suspended || (this.reconnectAttempts++, this.connect());
198
+ }, e);
199
+ }
135
200
  disconnect() {
136
- this.destroyed = !0;
201
+ this.destroyed = !0, this.suspended = !1, this.removeVisibilityHandler();
137
202
  try {
138
203
  this.ws?.close();
139
204
  } catch {}
140
205
  this.ws = null;
141
206
  }
142
- async handleTask(e) {
207
+ suspend() {
208
+ if (!(this.suspended || this.destroyed)) {
209
+ if (this.suspended = !0, this.ws) {
210
+ let e = this.ws;
211
+ e.onclose = null;
212
+ try {
213
+ e.close();
214
+ } catch {}
215
+ this.ws = null;
216
+ }
217
+ this.workerPool.terminate();
218
+ }
219
+ }
220
+ resume() {
221
+ !this.suspended || this.destroyed || (this.suspended = !1, this.workerPool.resume(), this.connect());
222
+ }
223
+ async obtainToken() {
143
224
  try {
144
- let t = await this.workerPool.run(e.taskId, e.workload, e.payload, void 0, (t) => {
145
- this.ws?.send(JSON.stringify({
146
- type: "progress",
147
- taskId: e.taskId,
148
- token: t
149
- }));
225
+ let e = localStorage.getItem(c);
226
+ if (e) try {
227
+ let t = JSON.parse(e);
228
+ if (t.token && t.nodeId && t.expiresAt > Date.now() + 6e4) return t;
229
+ } catch {}
230
+ let t = this.config.orchestratorUrl.replace(/\/+$/, ""), n = await fetch(`${t}/crowd/nodes/register`, {
231
+ method: "POST",
232
+ headers: { "Content-Type": "application/json" },
233
+ body: JSON.stringify({
234
+ siteId: this.config.siteId,
235
+ nodeId: this.nodeId,
236
+ capabilities: this.config.capabilities ?? ["ai-inference", "image-process"]
237
+ })
150
238
  });
151
- this.ws?.send(JSON.stringify({
152
- type: "result",
153
- taskId: e.taskId,
154
- payload: t
155
- }));
156
- } catch (t) {
157
- this.ws?.send(JSON.stringify({
158
- type: "error",
159
- taskId: e.taskId,
160
- error: t instanceof Error ? t.message : String(t)
161
- }));
239
+ if (!n.ok) return null;
240
+ let r = await n.json();
241
+ if (!r.token) return null;
242
+ try {
243
+ localStorage.setItem(c, JSON.stringify(r));
244
+ } catch {}
245
+ return r;
246
+ } catch {
247
+ return null;
162
248
  }
163
249
  }
164
- }, c = "__flaxia_node_signal_client", l = (e) => {
165
- let t = window[c];
250
+ send(e) {
251
+ this.ws && this.ws.readyState === WebSocket.OPEN && this.ws.send(JSON.stringify(e));
252
+ }
253
+ async handleTask(e) {
254
+ let t = e.timeoutMs ? e.timeoutMs + 3e4 : void 0, n = await this.workerPool.run(e.taskId, e.workload, e.payload, t, (t) => {
255
+ this.send({
256
+ type: "progress",
257
+ taskId: e.taskId,
258
+ token: t
259
+ });
260
+ }, { maxCpuLoad: this.config.maxCpuLoad });
261
+ this.send({
262
+ type: "result",
263
+ taskId: e.taskId,
264
+ payload: n
265
+ });
266
+ }
267
+ setupVisibilityHandler() {
268
+ this.removeVisibilityHandler(), this.visibilityHandler = () => {
269
+ document.hidden ? this.suspend() : this.suspended && this.resume();
270
+ }, document.addEventListener("visibilitychange", this.visibilityHandler);
271
+ }
272
+ removeVisibilityHandler() {
273
+ this.visibilityHandler &&= (document.removeEventListener("visibilitychange", this.visibilityHandler), null);
274
+ }
275
+ }, u = "__flaxia_node_signal_client", d = (e) => {
276
+ let t = window[u];
166
277
  t && t.disconnect();
167
- let n = new URL("./worker.js", "" + import.meta.url).href, r = new o(n), i = localStorage.getItem("flaxia_node_id");
168
- i || (i = crypto.randomUUID(), localStorage.setItem("flaxia_node_id", i));
169
- let a = new s(e, r, i);
170
- window[c] = a, a.connect();
171
- }, u = (t) => {
278
+ let n = new URL("./worker.js", "" + import.meta.url).href, r = new o(n), i = localStorage.getItem(s);
279
+ i || (i = crypto.randomUUID(), localStorage.setItem(s, i));
280
+ let a = new l(e, r, i);
281
+ window[u] = a, a.connect();
282
+ }, f = (t) => {
172
283
  if (i()) {
173
- l(t);
284
+ d(t);
174
285
  return;
175
286
  }
176
287
  let n = document.createElement("div");
177
288
  n.id = "flaxia-consent-container", document.body.appendChild(n), new e(n, t.consent, () => {
178
- a(), n.remove(), l(t);
289
+ a(), n.remove(), d(t);
179
290
  });
180
291
  };
181
292
  //#endregion
182
- export { u as initFlaxiaNode };
293
+ export { f as initFlaxiaNode };
package/dist/worker.js CHANGED
@@ -1,54 +1,102 @@
1
- //#region src/worker/main.worker.ts
1
+ //#region src/executor/throttle.ts
2
+ function e(e) {
3
+ return new Promise((t) => setTimeout(t, e));
4
+ }
5
+ var t = class {
6
+ constructor(e = .15, t = 2e6) {
7
+ this.baselineMs = null, this.lastLoad = 0, this.maxLoad = Math.max(.05, Math.min(.3, e)), this.loopIterations = t;
8
+ }
9
+ busyLoop() {
10
+ let e = this.loopIterations, t = performance.now(), n = 0;
11
+ for (; n < e;) n++;
12
+ return performance.now() - t;
13
+ }
14
+ sample() {
15
+ return [
16
+ this.busyLoop(),
17
+ this.busyLoop(),
18
+ this.busyLoop()
19
+ ].sort((e, t) => e - t)[1];
20
+ }
21
+ async calibrate() {
22
+ this.busyLoop(), this.baselineMs = this.sample(), this.lastLoad = 0;
23
+ }
24
+ async getCurrentLoad() {
25
+ if (this.baselineMs === null) return await this.calibrate(), 0;
26
+ let e = this.sample(), t = Math.min(1, Math.max(0, e / this.baselineMs - 1));
27
+ return this.lastLoad = t, t;
28
+ }
29
+ async shouldPause() {
30
+ return await this.getCurrentLoad() > this.maxLoad;
31
+ }
32
+ async waitForSlot(t = 3e4) {
33
+ let n = performance.now();
34
+ for (; await this.shouldPause() && !(performance.now() - n > t);) await e(500);
35
+ }
36
+ get lastMeasuredLoad() {
37
+ return this.lastLoad;
38
+ }
39
+ get maxLoadValue() {
40
+ return this.maxLoad;
41
+ }
42
+ }, n = null;
2
43
  self.onmessage = async (e) => {
3
- let { id: t, workload: n, payload: r } = e.data, i = setInterval(() => {
4
- self.postMessage({
5
- id: t,
6
- type: "heartbeat"
7
- });
8
- }, 1e4);
44
+ let { id: r, workload: i, payload: a, config: o } = e.data;
9
45
  try {
10
- let e, a = (e) => {
46
+ n ||= new t(o?.maxCpuLoad), await n.waitForSlot();
47
+ let e = setInterval(() => {
11
48
  self.postMessage({
12
- id: t,
13
- type: "token",
14
- token: e
49
+ id: r,
50
+ type: "heartbeat",
51
+ cpuLoad: n?.lastMeasuredLoad ?? 0
15
52
  });
16
- };
17
- switch (n) {
18
- case "ai-inference":
19
- let { handleAiInference: t } = await import("./assets/ai-inference.js");
20
- e = await t(r, a);
21
- break;
22
- case "image-process":
23
- let { handleImageProcess: i } = await import("./assets/image-process.js");
24
- e = await i(r);
25
- break;
26
- case "container":
27
- let { handleContainer: o } = await import("./assets/container.js");
28
- e = await o(r);
29
- break;
30
- case "vector-embed":
31
- let { handleVectorEmbed: s } = await import("./assets/vector-embed.js");
32
- e = await s(r);
33
- break;
34
- case "vector-store":
35
- let { handleVectorStore: c } = await import("./assets/vector-store.js");
36
- e = await c(r);
37
- break;
38
- case "vector-query":
39
- let { handleVectorQuery: l } = await import("./assets/vector-query.js");
40
- e = await l(r);
41
- break;
42
- default: throw Error(`Unknown workload type: ${n}`);
53
+ }, 1e4);
54
+ try {
55
+ let e, t = (e) => {
56
+ self.postMessage({
57
+ id: r,
58
+ type: "token",
59
+ token: e
60
+ });
61
+ };
62
+ switch (i) {
63
+ case "ai-inference":
64
+ let { handleAiInference: n } = await import("./assets/ai-inference.js");
65
+ e = await n(a, t);
66
+ break;
67
+ case "image-process":
68
+ let { handleImageProcess: r } = await import("./assets/image-process.js");
69
+ e = await r(a);
70
+ break;
71
+ case "container":
72
+ let { handleContainer: o } = await import("./assets/container.js");
73
+ e = await o(a);
74
+ break;
75
+ case "vector-embed":
76
+ let { handleVectorEmbed: s } = await import("./assets/vector-embed.js");
77
+ e = await s(a);
78
+ break;
79
+ case "vector-store":
80
+ let { handleVectorStore: c } = await import("./assets/vector-store.js");
81
+ e = await c(a);
82
+ break;
83
+ case "vector-query":
84
+ let { handleVectorQuery: l } = await import("./assets/vector-query.js");
85
+ e = await l(a);
86
+ break;
87
+ default: throw Error(`Unknown workload type: ${i}`);
88
+ }
89
+ self.postMessage({
90
+ id: r,
91
+ type: "done",
92
+ result: e
93
+ });
94
+ } finally {
95
+ clearInterval(e);
43
96
  }
44
- clearInterval(i), self.postMessage({
45
- id: t,
46
- type: "done",
47
- result: e
48
- });
49
97
  } catch (e) {
50
- clearInterval(i), self.postMessage({
51
- id: t,
98
+ self.postMessage({
99
+ id: r,
52
100
  type: "error",
53
101
  error: e instanceof Error ? e.message : String(e)
54
102
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flaxia/node",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "https://github.com/RemydreScarlet/flaxia-crowd.git"
15
+ "url": "https://github.com/TeamFlaxia/flaxia-crowd.git"
16
16
  },
17
17
  "scripts": {
18
18
  "build": "rm -f tsconfig.tsbuildinfo && vite build && tsc -b",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@bjorn3/browser_wasi_shim": "^0.4.2",
24
- "@flaxia/sdk": "^0.1.0",
24
+ "@flaxia/sdk": "^0.2.0",
25
25
  "@huggingface/transformers": "^4.2.0",
26
26
  "rollup": "^4.61.0"
27
27
  },
@@ -1,29 +0,0 @@
1
- import type { WorkloadType } from '@flaxia/sdk';
2
-
3
- self.onmessage = async (e: MessageEvent) => {
4
- const { id, workload, payload } = e.data;
5
-
6
- try {
7
- let result;
8
- switch (workload as WorkloadType) {
9
- case 'ai-inference':
10
- const { handleAiInference } = await import('../workloads/ai-inference');
11
- result = await handleAiInference(payload);
12
- break;
13
- case 'image-process':
14
- const { handleImageProcess } = await import('../workloads/image-process');
15
- result = await handleImageProcess(payload);
16
- break;
17
- case 'container':
18
- const { handleContainer } = await import('../workloads/container');
19
- result = await handleContainer(payload);
20
- break;
21
- default:
22
- throw new Error(`Unknown workload type: ${workload}`);
23
- }
24
-
25
- self.postMessage({ id, type: 'done', result });
26
- } catch (err) {
27
- self.postMessage({ id, type: 'error', error: err instanceof Error ? err.message : String(err) });
28
- }
29
- };