@b9g/platform 0.1.11 → 0.1.13
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/README.md +1 -1
- package/package.json +22 -38
- package/src/config.d.ts +15 -163
- package/src/config.js +18 -630
- package/src/globals.d.ts +119 -0
- package/src/index.d.ts +294 -25
- package/src/index.js +466 -126
- package/src/runtime.d.ts +423 -22
- package/src/runtime.js +693 -250
- package/src/shovel-config.d.ts +10 -0
- package/chunk-P57PW2II.js +0 -11
- package/src/cookie-store.d.ts +0 -80
- package/src/cookie-store.js +0 -233
- package/src/single-threaded.d.ts +0 -59
- package/src/single-threaded.js +0 -114
- package/src/worker-pool.d.ts +0 -93
- package/src/worker-pool.js +0 -390
package/src/worker-pool.js
DELETED
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
/// <reference types="./worker-pool.d.ts" />
|
|
2
|
-
import {
|
|
3
|
-
__require
|
|
4
|
-
} from "../chunk-P57PW2II.js";
|
|
5
|
-
|
|
6
|
-
// src/worker-pool.ts
|
|
7
|
-
import * as Path from "path";
|
|
8
|
-
import { existsSync } from "fs";
|
|
9
|
-
import { getLogger } from "@logtape/logtape";
|
|
10
|
-
var logger = getLogger(["worker"]);
|
|
11
|
-
function resolveWorkerScript(entrypoint) {
|
|
12
|
-
if (entrypoint) {
|
|
13
|
-
const entryDir = Path.dirname(entrypoint);
|
|
14
|
-
const bundledWorker = Path.join(entryDir, "worker.js");
|
|
15
|
-
try {
|
|
16
|
-
if (typeof Bun !== "undefined") {
|
|
17
|
-
const file = Bun.file(bundledWorker);
|
|
18
|
-
if (file.size > 0) {
|
|
19
|
-
logger.info("Using bundled worker", { bundledWorker });
|
|
20
|
-
return bundledWorker;
|
|
21
|
-
}
|
|
22
|
-
} else if (typeof __require !== "undefined") {
|
|
23
|
-
if (existsSync(bundledWorker)) {
|
|
24
|
-
logger.info("Using bundled worker", { bundledWorker });
|
|
25
|
-
return bundledWorker;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
} catch (err) {
|
|
29
|
-
if (err.code !== "ENOENT") {
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const workerURL = import.meta.resolve("@b9g/platform/runtime.js");
|
|
36
|
-
let workerScript;
|
|
37
|
-
if (workerURL.startsWith("file://")) {
|
|
38
|
-
workerScript = workerURL.slice(7);
|
|
39
|
-
} else {
|
|
40
|
-
workerScript = workerURL;
|
|
41
|
-
}
|
|
42
|
-
logger.info("Using worker runtime script", { workerScript });
|
|
43
|
-
return workerScript;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
const bundledPath = entrypoint ? Path.join(Path.dirname(entrypoint), "runtime.js") : "runtime.js";
|
|
46
|
-
throw new Error(
|
|
47
|
-
`Could not resolve runtime.js. Checked bundled path: ${bundledPath} and package: @b9g/platform/runtime.js. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function createWebWorker(workerScript) {
|
|
52
|
-
if (typeof Worker !== "undefined") {
|
|
53
|
-
return new Worker(workerScript, { type: "module" });
|
|
54
|
-
}
|
|
55
|
-
const isNodeJs = typeof process !== "undefined" && process.versions?.node;
|
|
56
|
-
if (isNodeJs) {
|
|
57
|
-
try {
|
|
58
|
-
const { Worker: NodeWebWorker } = await import("@b9g/node-webworker");
|
|
59
|
-
logger.info("Using @b9g/node-webworker shim for Node.js", {});
|
|
60
|
-
return new NodeWebWorker(workerScript, {
|
|
61
|
-
type: "module"
|
|
62
|
-
});
|
|
63
|
-
} catch (shimError) {
|
|
64
|
-
logger.error(
|
|
65
|
-
"MISSING WEB STANDARD: Node.js lacks native Web Worker support",
|
|
66
|
-
{
|
|
67
|
-
canonicalIssue: "https://github.com/nodejs/node/issues/43583",
|
|
68
|
-
message: "This is a basic web standard from 2009 - help push for implementation!"
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
throw new Error(`\u274C Web Worker not available on Node.js
|
|
72
|
-
|
|
73
|
-
\u{1F517} Node.js doesn't implement the Web Worker standard yet.
|
|
74
|
-
CANONICAL ISSUE: https://github.com/nodejs/node/issues/43583
|
|
75
|
-
|
|
76
|
-
\u{1F5F3}\uFE0F Please \u{1F44D} react and comment to show demand for this basic web standard!
|
|
77
|
-
|
|
78
|
-
\u{1F4A1} Immediate workaround:
|
|
79
|
-
npm install @b9g/node-webworker
|
|
80
|
-
|
|
81
|
-
This installs our minimal, reliable Web Worker shim for Node.js.
|
|
82
|
-
|
|
83
|
-
\u{1F4DA} Learn more: https://developer.mozilla.org/en-US/docs/Web/API/Worker`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const runtime = typeof Bun !== "undefined" ? "Bun" : typeof Deno !== "undefined" ? "Deno" : "Unknown";
|
|
87
|
-
throw new Error(`\u274C Web Worker not available on ${runtime}
|
|
88
|
-
|
|
89
|
-
This runtime should support Web Workers but the API is not available.
|
|
90
|
-
Please check your runtime version and configuration.
|
|
91
|
-
|
|
92
|
-
\u{1F4DA} Web Worker standard: https://developer.mozilla.org/en-US/docs/Web/API/Worker`);
|
|
93
|
-
}
|
|
94
|
-
var ServiceWorkerPool = class {
|
|
95
|
-
#workers;
|
|
96
|
-
#currentWorker;
|
|
97
|
-
#requestID;
|
|
98
|
-
#pendingRequests;
|
|
99
|
-
#pendingWorkerInit;
|
|
100
|
-
#options;
|
|
101
|
-
#appEntrypoint;
|
|
102
|
-
#cacheStorage;
|
|
103
|
-
// CustomCacheStorage for cache coordination
|
|
104
|
-
#config;
|
|
105
|
-
// ShovelConfig from config.ts
|
|
106
|
-
constructor(options = {}, appEntrypoint, cacheStorage, config) {
|
|
107
|
-
this.#workers = [];
|
|
108
|
-
this.#currentWorker = 0;
|
|
109
|
-
this.#requestID = 0;
|
|
110
|
-
this.#pendingRequests = /* @__PURE__ */ new Map();
|
|
111
|
-
this.#pendingWorkerInit = /* @__PURE__ */ new Map();
|
|
112
|
-
this.#appEntrypoint = appEntrypoint;
|
|
113
|
-
this.#cacheStorage = cacheStorage;
|
|
114
|
-
this.#config = config || {};
|
|
115
|
-
this.#options = {
|
|
116
|
-
workerCount: 1,
|
|
117
|
-
requestTimeout: 3e4,
|
|
118
|
-
...options
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Initialize workers (must be called after construction)
|
|
123
|
-
*/
|
|
124
|
-
async init() {
|
|
125
|
-
await this.#initWorkers();
|
|
126
|
-
}
|
|
127
|
-
async #initWorkers() {
|
|
128
|
-
for (let i = 0; i < this.#options.workerCount; i++) {
|
|
129
|
-
await this.#createWorker();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
async #createWorker() {
|
|
133
|
-
const workerScript = resolveWorkerScript(this.#appEntrypoint);
|
|
134
|
-
const worker = await createWebWorker(workerScript);
|
|
135
|
-
const workerReadyPromise = new Promise((resolve) => {
|
|
136
|
-
this.#pendingWorkerInit.set(worker, {
|
|
137
|
-
workerReady: resolve
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
worker.addEventListener("message", (event) => {
|
|
141
|
-
this.#handleWorkerMessage(worker, event.data || event);
|
|
142
|
-
});
|
|
143
|
-
worker.addEventListener("error", (event) => {
|
|
144
|
-
logger.error("Worker error", {
|
|
145
|
-
message: event.message || event.error?.message,
|
|
146
|
-
filename: event.filename,
|
|
147
|
-
lineno: event.lineno,
|
|
148
|
-
colno: event.colno,
|
|
149
|
-
error: event.error,
|
|
150
|
-
stack: event.error?.stack
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
this.#workers.push(worker);
|
|
154
|
-
logger.info("Waiting for worker-ready signal");
|
|
155
|
-
await workerReadyPromise;
|
|
156
|
-
logger.info("Received worker-ready signal");
|
|
157
|
-
const initializedPromise = new Promise((resolve) => {
|
|
158
|
-
const pending = this.#pendingWorkerInit.get(worker) || {};
|
|
159
|
-
pending.initialized = resolve;
|
|
160
|
-
this.#pendingWorkerInit.set(worker, pending);
|
|
161
|
-
});
|
|
162
|
-
if (!this.#appEntrypoint) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
"ServiceWorkerPool requires an entrypoint to derive baseDir"
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
const baseDir = Path.dirname(this.#appEntrypoint);
|
|
168
|
-
const initMessage = {
|
|
169
|
-
type: "init",
|
|
170
|
-
config: this.#config,
|
|
171
|
-
baseDir
|
|
172
|
-
};
|
|
173
|
-
logger.info("Sending init message", { config: this.#config, baseDir });
|
|
174
|
-
worker.postMessage(initMessage);
|
|
175
|
-
logger.info("Sent init message, waiting for initialized response");
|
|
176
|
-
await initializedPromise;
|
|
177
|
-
logger.info("Received initialized response");
|
|
178
|
-
this.#pendingWorkerInit.delete(worker);
|
|
179
|
-
return worker;
|
|
180
|
-
}
|
|
181
|
-
#handleWorkerMessage(worker, message) {
|
|
182
|
-
logger.debug("Worker message received", { type: message.type });
|
|
183
|
-
const pending = this.#pendingWorkerInit.get(worker);
|
|
184
|
-
if (message.type === "worker-ready" && pending?.workerReady) {
|
|
185
|
-
pending.workerReady();
|
|
186
|
-
} else if (message.type === "initialized" && pending?.initialized) {
|
|
187
|
-
pending.initialized();
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
switch (message.type) {
|
|
191
|
-
case "response":
|
|
192
|
-
this.#handleResponse(message);
|
|
193
|
-
break;
|
|
194
|
-
case "error":
|
|
195
|
-
this.#handleError(message);
|
|
196
|
-
break;
|
|
197
|
-
case "ready":
|
|
198
|
-
case "worker-ready":
|
|
199
|
-
this.#handleReady(message);
|
|
200
|
-
break;
|
|
201
|
-
case "initialized":
|
|
202
|
-
break;
|
|
203
|
-
default:
|
|
204
|
-
if (message.type?.startsWith("cache:")) {
|
|
205
|
-
logger.debug("Cache message detected", {
|
|
206
|
-
type: message.type,
|
|
207
|
-
hasStorage: !!this.#cacheStorage
|
|
208
|
-
});
|
|
209
|
-
if (this.#cacheStorage) {
|
|
210
|
-
const handleMessage = this.#cacheStorage.handleMessage;
|
|
211
|
-
logger.debug("handleMessage check", {
|
|
212
|
-
hasMethod: typeof handleMessage === "function"
|
|
213
|
-
});
|
|
214
|
-
if (typeof handleMessage === "function") {
|
|
215
|
-
logger.debug("Calling handleMessage");
|
|
216
|
-
void handleMessage.call(this.#cacheStorage, worker, message);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
#handleResponse(message) {
|
|
224
|
-
const pending = this.#pendingRequests.get(message.requestID);
|
|
225
|
-
if (pending) {
|
|
226
|
-
const response = new Response(message.response.body, {
|
|
227
|
-
status: message.response.status,
|
|
228
|
-
statusText: message.response.statusText,
|
|
229
|
-
headers: message.response.headers
|
|
230
|
-
});
|
|
231
|
-
pending.resolve(response);
|
|
232
|
-
this.#pendingRequests.delete(message.requestID);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
#handleError(message) {
|
|
236
|
-
logger.error("Worker error message received", {
|
|
237
|
-
error: message.error,
|
|
238
|
-
stack: message.stack,
|
|
239
|
-
requestID: message.requestID
|
|
240
|
-
});
|
|
241
|
-
if (message.requestID) {
|
|
242
|
-
const pending = this.#pendingRequests.get(message.requestID);
|
|
243
|
-
if (pending) {
|
|
244
|
-
pending.reject(new Error(message.error));
|
|
245
|
-
this.#pendingRequests.delete(message.requestID);
|
|
246
|
-
}
|
|
247
|
-
} else {
|
|
248
|
-
logger.error("Worker error: {error}", { error: message.error });
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
#handleReady(message) {
|
|
252
|
-
if (message.type === "ready") {
|
|
253
|
-
logger.info("ServiceWorker ready", { entrypoint: message.entrypoint });
|
|
254
|
-
} else if (message.type === "worker-ready") {
|
|
255
|
-
logger.info("Worker initialized", {});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Handle HTTP request using round-robin worker selection
|
|
260
|
-
*/
|
|
261
|
-
async handleRequest(request) {
|
|
262
|
-
const worker = this.#workers[this.#currentWorker];
|
|
263
|
-
logger.info("Dispatching to worker", {
|
|
264
|
-
workerIndex: this.#currentWorker + 1,
|
|
265
|
-
totalWorkers: this.#workers.length
|
|
266
|
-
});
|
|
267
|
-
this.#currentWorker = (this.#currentWorker + 1) % this.#workers.length;
|
|
268
|
-
const requestID = ++this.#requestID;
|
|
269
|
-
return new Promise((resolve, reject) => {
|
|
270
|
-
this.#pendingRequests.set(requestID, { resolve, reject });
|
|
271
|
-
this.#sendRequest(worker, request, requestID).catch(reject);
|
|
272
|
-
setTimeout(() => {
|
|
273
|
-
if (this.#pendingRequests.has(requestID)) {
|
|
274
|
-
this.#pendingRequests.delete(requestID);
|
|
275
|
-
reject(new Error("Request timeout"));
|
|
276
|
-
}
|
|
277
|
-
}, this.#options.requestTimeout);
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Send request to worker (async helper to avoid async promise executor)
|
|
282
|
-
*/
|
|
283
|
-
async #sendRequest(worker, request, requestID) {
|
|
284
|
-
let body = null;
|
|
285
|
-
if (request.body) {
|
|
286
|
-
body = await request.arrayBuffer();
|
|
287
|
-
}
|
|
288
|
-
const workerRequest = {
|
|
289
|
-
type: "request",
|
|
290
|
-
request: {
|
|
291
|
-
url: request.url,
|
|
292
|
-
method: request.method,
|
|
293
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
294
|
-
body
|
|
295
|
-
},
|
|
296
|
-
requestID
|
|
297
|
-
};
|
|
298
|
-
if (body) {
|
|
299
|
-
worker.postMessage(workerRequest, [body]);
|
|
300
|
-
} else {
|
|
301
|
-
worker.postMessage(workerRequest);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Reload ServiceWorker with new entrypoint (hot reload)
|
|
306
|
-
* The entrypoint path contains a content hash for cache busting
|
|
307
|
-
*/
|
|
308
|
-
async reloadWorkers(entrypoint) {
|
|
309
|
-
logger.info("Reloading ServiceWorker", { entrypoint });
|
|
310
|
-
this.#appEntrypoint = entrypoint;
|
|
311
|
-
const loadPromises = this.#workers.map((worker) => {
|
|
312
|
-
return new Promise((resolve, reject) => {
|
|
313
|
-
let timeoutId;
|
|
314
|
-
const cleanup = () => {
|
|
315
|
-
worker.removeEventListener("message", handleReady);
|
|
316
|
-
worker.removeEventListener("error", handleError);
|
|
317
|
-
if (timeoutId) {
|
|
318
|
-
clearTimeout(timeoutId);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
const handleReady = (event) => {
|
|
322
|
-
const message = event.data || event;
|
|
323
|
-
if (message.type === "ready" && message.entrypoint === entrypoint) {
|
|
324
|
-
cleanup();
|
|
325
|
-
resolve();
|
|
326
|
-
} else if (message.type === "error") {
|
|
327
|
-
cleanup();
|
|
328
|
-
reject(
|
|
329
|
-
new Error(
|
|
330
|
-
`Worker failed to load ServiceWorker: ${message.error}`
|
|
331
|
-
)
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
const handleError = (error) => {
|
|
336
|
-
cleanup();
|
|
337
|
-
const errorMsg = error?.error?.message || error?.message || JSON.stringify(error);
|
|
338
|
-
reject(new Error(`Worker failed to load ServiceWorker: ${errorMsg}`));
|
|
339
|
-
};
|
|
340
|
-
timeoutId = setTimeout(() => {
|
|
341
|
-
cleanup();
|
|
342
|
-
reject(
|
|
343
|
-
new Error(
|
|
344
|
-
`Worker failed to load ServiceWorker within 30000ms (entrypoint ${entrypoint})`
|
|
345
|
-
)
|
|
346
|
-
);
|
|
347
|
-
}, 3e4);
|
|
348
|
-
logger.info("Sending load message", {
|
|
349
|
-
entrypoint
|
|
350
|
-
});
|
|
351
|
-
worker.addEventListener("message", handleReady);
|
|
352
|
-
worker.addEventListener("error", handleError);
|
|
353
|
-
const loadMessage = {
|
|
354
|
-
type: "load",
|
|
355
|
-
entrypoint
|
|
356
|
-
};
|
|
357
|
-
logger.debug("[WorkerPool] Sending load message", {
|
|
358
|
-
entrypoint
|
|
359
|
-
});
|
|
360
|
-
worker.postMessage(loadMessage);
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
await Promise.all(loadPromises);
|
|
364
|
-
logger.info("All workers reloaded", { entrypoint });
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Graceful shutdown of all workers
|
|
368
|
-
*/
|
|
369
|
-
async terminate() {
|
|
370
|
-
const terminatePromises = this.#workers.map((worker) => worker.terminate());
|
|
371
|
-
await Promise.allSettled(terminatePromises);
|
|
372
|
-
this.#workers = [];
|
|
373
|
-
this.#pendingRequests.clear();
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Get the number of active workers
|
|
377
|
-
*/
|
|
378
|
-
get workerCount() {
|
|
379
|
-
return this.#workers.length;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Check if the pool is ready to handle requests
|
|
383
|
-
*/
|
|
384
|
-
get ready() {
|
|
385
|
-
return this.#workers.length > 0;
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
export {
|
|
389
|
-
ServiceWorkerPool
|
|
390
|
-
};
|