@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.
- package/dist/client/SignalingClient.d.ts +1 -0
- package/dist/client/SignalingClient.d.ts.map +1 -1
- package/dist/executor/WorkerPool.d.ts +22 -1
- package/dist/executor/WorkerPool.d.ts.map +1 -1
- package/dist/executor/__tests__/throttle.test.d.ts +2 -0
- package/dist/executor/__tests__/throttle.test.d.ts.map +1 -0
- package/dist/executor/throttle.d.ts +34 -0
- package/dist/executor/throttle.d.ts.map +1 -0
- package/dist/index.js +187 -76
- package/dist/worker.js +93 -45
- package/package.json +3 -3
- package/dist/worker/main.worker.js +0 -29
|
@@ -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;
|
|
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
|
|
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;
|
|
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 @@
|
|
|
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"
|
|
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((
|
|
65
|
+
run(e, t, n, r, i, a) {
|
|
66
|
+
return new Promise((o, s) => {
|
|
61
67
|
if (!this.worker) {
|
|
62
|
-
|
|
68
|
+
s(/* @__PURE__ */ Error("Worker not available"));
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
|
-
let
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
},
|
|
118
|
-
let t
|
|
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
|
-
|
|
175
|
+
this.send({
|
|
121
176
|
type: "pong",
|
|
122
|
-
cpuLoad: .
|
|
123
|
-
})
|
|
177
|
+
cpuLoad: this.workerPool.lastCpuLoad
|
|
178
|
+
});
|
|
124
179
|
return;
|
|
125
180
|
}
|
|
126
|
-
t.type === "task"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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(
|
|
168
|
-
i || (i = crypto.randomUUID(), localStorage.setItem(
|
|
169
|
-
let a = new
|
|
170
|
-
window[
|
|
171
|
-
},
|
|
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
|
-
|
|
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(),
|
|
289
|
+
a(), n.remove(), d(t);
|
|
179
290
|
});
|
|
180
291
|
};
|
|
181
292
|
//#endregion
|
|
182
|
-
export {
|
|
293
|
+
export { f as initFlaxiaNode };
|
package/dist/worker.js
CHANGED
|
@@ -1,54 +1,102 @@
|
|
|
1
|
-
//#region src/
|
|
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:
|
|
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
|
-
|
|
46
|
+
n ||= new t(o?.maxCpuLoad), await n.waitForSlot();
|
|
47
|
+
let e = setInterval(() => {
|
|
11
48
|
self.postMessage({
|
|
12
|
-
id:
|
|
13
|
-
type: "
|
|
14
|
-
|
|
49
|
+
id: r,
|
|
50
|
+
type: "heartbeat",
|
|
51
|
+
cpuLoad: n?.lastMeasuredLoad ?? 0
|
|
15
52
|
});
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
51
|
-
id:
|
|
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.
|
|
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/
|
|
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.
|
|
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
|
-
};
|