@b9g/platform 0.1.12 → 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/package.json +24 -16
- package/src/config.d.ts +24 -0
- package/src/config.js +29 -0
- package/src/globals.d.ts +119 -0
- package/src/index.d.ts +105 -41
- package/src/index.js +174 -219
- package/src/runtime.d.ts +315 -62
- package/src/runtime.js +424 -129
- package/chunk-P57PW2II.js +0 -11
- package/src/worker.d.ts +0 -39
- package/src/worker.js +0 -285
package/src/index.js
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
/// <reference types="./index.d.ts" />
|
|
2
|
-
import {
|
|
3
|
-
__require
|
|
4
|
-
} from "../chunk-P57PW2II.js";
|
|
5
|
-
|
|
6
2
|
// src/index.ts
|
|
7
|
-
import * as Path from "path";
|
|
8
|
-
import { existsSync } from "fs";
|
|
9
|
-
import { fileURLToPath } from "url";
|
|
10
|
-
import { CustomCacheStorage } from "@b9g/cache";
|
|
11
|
-
import { MemoryCache } from "@b9g/cache/memory";
|
|
12
3
|
import { getLogger } from "@logtape/logtape";
|
|
13
4
|
import {
|
|
14
5
|
ServiceWorkerGlobals,
|
|
15
6
|
ShovelServiceWorkerRegistration,
|
|
7
|
+
ShovelFetchEvent,
|
|
16
8
|
CustomLoggerStorage
|
|
17
9
|
} from "./runtime.js";
|
|
18
|
-
|
|
10
|
+
import { validateConfig, ConfigValidationError } from "./config.js";
|
|
11
|
+
import {
|
|
12
|
+
CustomDatabaseStorage,
|
|
13
|
+
createDatabaseFactory
|
|
14
|
+
} from "./runtime.js";
|
|
15
|
+
var logger = getLogger(["shovel", "platform"]);
|
|
19
16
|
function detectRuntime() {
|
|
20
17
|
if (typeof Bun !== "undefined" || process.versions?.bun) {
|
|
21
18
|
return "bun";
|
|
@@ -66,27 +63,20 @@ function resolvePlatform(options) {
|
|
|
66
63
|
async function createPlatform(platformName, options = {}) {
|
|
67
64
|
switch (platformName) {
|
|
68
65
|
case "node": {
|
|
69
|
-
const
|
|
70
|
-
const NodePlatform = await import(modulePath).then((m) => m.default);
|
|
66
|
+
const { default: NodePlatform } = await import("@b9g/platform-node");
|
|
71
67
|
return new NodePlatform(options);
|
|
72
68
|
}
|
|
73
69
|
case "bun": {
|
|
74
|
-
const
|
|
75
|
-
const BunPlatform = await import(modulePath).then((m) => m.default);
|
|
70
|
+
const { default: BunPlatform } = await import("@b9g/platform-bun");
|
|
76
71
|
return new BunPlatform(options);
|
|
77
72
|
}
|
|
78
|
-
case "cloudflare":
|
|
79
|
-
|
|
80
|
-
case "cf": {
|
|
81
|
-
const modulePath = import.meta.resolve("@b9g/platform-cloudflare");
|
|
82
|
-
const CloudflarePlatform = await import(modulePath).then(
|
|
83
|
-
(m) => m.default
|
|
84
|
-
);
|
|
73
|
+
case "cloudflare": {
|
|
74
|
+
const { default: CloudflarePlatform } = await import("@b9g/platform-cloudflare");
|
|
85
75
|
return new CloudflarePlatform(options);
|
|
86
76
|
}
|
|
87
77
|
default:
|
|
88
78
|
throw new Error(
|
|
89
|
-
`Unknown platform: ${platformName}.
|
|
79
|
+
`Unknown platform: ${platformName}. Valid platforms: node, bun, cloudflare`
|
|
90
80
|
);
|
|
91
81
|
}
|
|
92
82
|
}
|
|
@@ -95,15 +85,6 @@ var BasePlatform = class {
|
|
|
95
85
|
constructor(config = {}) {
|
|
96
86
|
this.config = config;
|
|
97
87
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Create cache storage
|
|
100
|
-
* Returns empty CacheStorage - applications create caches on-demand via caches.open()
|
|
101
|
-
*/
|
|
102
|
-
async createCaches() {
|
|
103
|
-
return new CustomCacheStorage(
|
|
104
|
-
(name) => new MemoryCache(name)
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
88
|
};
|
|
108
89
|
var DefaultPlatformRegistry = class {
|
|
109
90
|
#platforms;
|
|
@@ -173,16 +154,17 @@ var SingleThreadedRuntime = class {
|
|
|
173
154
|
registration: this.#registration,
|
|
174
155
|
caches: options.caches,
|
|
175
156
|
directories: options.directories,
|
|
157
|
+
databases: options.databases,
|
|
176
158
|
loggers: options.loggers
|
|
177
159
|
});
|
|
178
|
-
logger.
|
|
160
|
+
logger.debug("SingleThreadedRuntime created");
|
|
179
161
|
}
|
|
180
162
|
/**
|
|
181
163
|
* Initialize the runtime (install ServiceWorker globals)
|
|
182
164
|
*/
|
|
183
165
|
async init() {
|
|
184
166
|
this.#scope.install();
|
|
185
|
-
logger.
|
|
167
|
+
logger.debug("SingleThreadedRuntime initialized - globals installed");
|
|
186
168
|
}
|
|
187
169
|
/**
|
|
188
170
|
* Load (or reload) a ServiceWorker entrypoint
|
|
@@ -191,13 +173,13 @@ var SingleThreadedRuntime = class {
|
|
|
191
173
|
async load(entrypoint) {
|
|
192
174
|
const isReload = this.#entrypoint !== void 0;
|
|
193
175
|
if (isReload) {
|
|
194
|
-
logger.
|
|
176
|
+
logger.debug("Reloading ServiceWorker", {
|
|
195
177
|
oldEntrypoint: this.#entrypoint,
|
|
196
178
|
newEntrypoint: entrypoint
|
|
197
179
|
});
|
|
198
180
|
this.#registration._serviceWorker._setState("parsed");
|
|
199
181
|
} else {
|
|
200
|
-
logger.
|
|
182
|
+
logger.debug("Loading ServiceWorker entrypoint", { entrypoint });
|
|
201
183
|
}
|
|
202
184
|
this.#entrypoint = entrypoint;
|
|
203
185
|
this.#ready = false;
|
|
@@ -205,7 +187,7 @@ var SingleThreadedRuntime = class {
|
|
|
205
187
|
await this.#registration.install();
|
|
206
188
|
await this.#registration.activate();
|
|
207
189
|
this.#ready = true;
|
|
208
|
-
logger.
|
|
190
|
+
logger.debug("ServiceWorker loaded and activated", { entrypoint });
|
|
209
191
|
}
|
|
210
192
|
/**
|
|
211
193
|
* Handle an HTTP request
|
|
@@ -217,14 +199,15 @@ var SingleThreadedRuntime = class {
|
|
|
217
199
|
"SingleThreadedRuntime not ready - ServiceWorker not loaded"
|
|
218
200
|
);
|
|
219
201
|
}
|
|
220
|
-
|
|
202
|
+
const event = new ShovelFetchEvent(request);
|
|
203
|
+
return this.#registration.handleRequest(event);
|
|
221
204
|
}
|
|
222
205
|
/**
|
|
223
206
|
* Graceful shutdown
|
|
224
207
|
*/
|
|
225
208
|
async terminate() {
|
|
226
209
|
this.#ready = false;
|
|
227
|
-
logger.
|
|
210
|
+
logger.debug("SingleThreadedRuntime terminated");
|
|
228
211
|
}
|
|
229
212
|
/**
|
|
230
213
|
* Get the number of workers (always 1 for single-threaded)
|
|
@@ -239,46 +222,6 @@ var SingleThreadedRuntime = class {
|
|
|
239
222
|
return this.#ready;
|
|
240
223
|
}
|
|
241
224
|
};
|
|
242
|
-
function resolveWorkerScript(entrypoint) {
|
|
243
|
-
if (entrypoint) {
|
|
244
|
-
const entryDir = Path.dirname(entrypoint);
|
|
245
|
-
const bundledWorker = Path.join(entryDir, "worker.js");
|
|
246
|
-
try {
|
|
247
|
-
if (typeof Bun !== "undefined") {
|
|
248
|
-
const file = Bun.file(bundledWorker);
|
|
249
|
-
if (file.size > 0) {
|
|
250
|
-
logger.info("Using bundled worker", { bundledWorker });
|
|
251
|
-
return bundledWorker;
|
|
252
|
-
}
|
|
253
|
-
} else if (typeof __require !== "undefined") {
|
|
254
|
-
if (existsSync(bundledWorker)) {
|
|
255
|
-
logger.info("Using bundled worker", { bundledWorker });
|
|
256
|
-
return bundledWorker;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
} catch (err) {
|
|
260
|
-
if (err.code !== "ENOENT") {
|
|
261
|
-
throw err;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
const workerURL = import.meta.resolve("@b9g/platform/worker.js");
|
|
267
|
-
let workerScript;
|
|
268
|
-
if (workerURL.startsWith("file://")) {
|
|
269
|
-
workerScript = fileURLToPath(workerURL);
|
|
270
|
-
} else {
|
|
271
|
-
workerScript = workerURL;
|
|
272
|
-
}
|
|
273
|
-
logger.info("Using worker entry script", { workerScript });
|
|
274
|
-
return workerScript;
|
|
275
|
-
} catch (error) {
|
|
276
|
-
const bundledPath = entrypoint ? Path.join(Path.dirname(entrypoint), "worker.js") : "worker.js";
|
|
277
|
-
throw new Error(
|
|
278
|
-
`Could not resolve worker.js. Checked bundled path: ${bundledPath} and package: @b9g/platform/worker.js. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
225
|
async function createWebWorker(workerScript) {
|
|
283
226
|
if (typeof Worker !== "undefined") {
|
|
284
227
|
return new Worker(workerScript, { type: "module" });
|
|
@@ -327,22 +270,21 @@ var ServiceWorkerPool = class {
|
|
|
327
270
|
#currentWorker;
|
|
328
271
|
#requestID;
|
|
329
272
|
#pendingRequests;
|
|
330
|
-
#
|
|
273
|
+
#pendingWorkerReady;
|
|
331
274
|
#options;
|
|
332
275
|
#appEntrypoint;
|
|
333
276
|
#cacheStorage;
|
|
334
|
-
//
|
|
335
|
-
#
|
|
336
|
-
|
|
337
|
-
constructor(options = {}, appEntrypoint, cacheStorage, config) {
|
|
277
|
+
// Waiters for when workers become available (used during reload)
|
|
278
|
+
#workerAvailableWaiters;
|
|
279
|
+
constructor(options = {}, appEntrypoint, cacheStorage) {
|
|
338
280
|
this.#workers = [];
|
|
339
281
|
this.#currentWorker = 0;
|
|
340
282
|
this.#requestID = 0;
|
|
341
283
|
this.#pendingRequests = /* @__PURE__ */ new Map();
|
|
342
|
-
this.#
|
|
284
|
+
this.#pendingWorkerReady = /* @__PURE__ */ new Map();
|
|
285
|
+
this.#workerAvailableWaiters = [];
|
|
343
286
|
this.#appEntrypoint = appEntrypoint;
|
|
344
287
|
this.#cacheStorage = cacheStorage;
|
|
345
|
-
this.#config = config || {};
|
|
346
288
|
this.#options = {
|
|
347
289
|
workerCount: 1,
|
|
348
290
|
requestTimeout: 3e4,
|
|
@@ -353,27 +295,35 @@ var ServiceWorkerPool = class {
|
|
|
353
295
|
* Initialize workers (must be called after construction)
|
|
354
296
|
*/
|
|
355
297
|
async init() {
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
async #initWorkers() {
|
|
298
|
+
const promises = [];
|
|
359
299
|
for (let i = 0; i < this.#options.workerCount; i++) {
|
|
360
|
-
|
|
300
|
+
promises.push(this.#createWorker(this.#appEntrypoint));
|
|
361
301
|
}
|
|
302
|
+
await Promise.all(promises);
|
|
362
303
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Create a worker from the unified bundle
|
|
306
|
+
* The bundle self-initializes and sends "ready" when done
|
|
307
|
+
*/
|
|
308
|
+
async #createWorker(entrypoint) {
|
|
309
|
+
const worker = await createWebWorker(entrypoint);
|
|
310
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
311
|
+
const timeoutId = setTimeout(() => {
|
|
312
|
+
this.#pendingWorkerReady.delete(worker);
|
|
313
|
+
reject(
|
|
314
|
+
new Error(
|
|
315
|
+
`Worker failed to become ready within 30000ms (${entrypoint})`
|
|
316
|
+
)
|
|
317
|
+
);
|
|
372
318
|
}, 3e4);
|
|
373
|
-
this.#
|
|
374
|
-
|
|
375
|
-
clearTimeout(
|
|
319
|
+
this.#pendingWorkerReady.set(worker, {
|
|
320
|
+
resolve: () => {
|
|
321
|
+
clearTimeout(timeoutId);
|
|
376
322
|
resolve();
|
|
323
|
+
},
|
|
324
|
+
reject: (error) => {
|
|
325
|
+
clearTimeout(timeoutId);
|
|
326
|
+
reject(error);
|
|
377
327
|
}
|
|
378
328
|
});
|
|
379
329
|
});
|
|
@@ -382,74 +332,49 @@ var ServiceWorkerPool = class {
|
|
|
382
332
|
});
|
|
383
333
|
worker.addEventListener("error", (event) => {
|
|
384
334
|
const errorMessage = event.message || event.error?.message || "Unknown worker error";
|
|
385
|
-
const error = new Error(`Worker
|
|
335
|
+
const error = new Error(`Worker error: ${errorMessage}`);
|
|
386
336
|
logger.error("Worker error: {error}", {
|
|
387
337
|
error: event.error || errorMessage,
|
|
388
338
|
filename: event.filename,
|
|
389
339
|
lineno: event.lineno,
|
|
390
340
|
colno: event.colno
|
|
391
341
|
});
|
|
392
|
-
|
|
393
|
-
|
|
342
|
+
const pending = this.#pendingWorkerReady.get(worker);
|
|
343
|
+
if (pending) {
|
|
344
|
+
this.#pendingWorkerReady.delete(worker);
|
|
345
|
+
pending.reject(error);
|
|
346
|
+
}
|
|
394
347
|
});
|
|
348
|
+
logger.debug("Waiting for worker ready signal", { entrypoint });
|
|
349
|
+
await readyPromise;
|
|
350
|
+
this.#pendingWorkerReady.delete(worker);
|
|
351
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
395
352
|
this.#workers.push(worker);
|
|
396
|
-
logger.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
reject(
|
|
402
|
-
new Error("Worker failed to send initialized signal within 30000ms")
|
|
403
|
-
);
|
|
404
|
-
}, 3e4);
|
|
405
|
-
const pending = this.#pendingWorkerInit.get(worker) || {};
|
|
406
|
-
pending.initialized = () => {
|
|
407
|
-
clearTimeout(timeoutId);
|
|
408
|
-
resolve();
|
|
409
|
-
};
|
|
410
|
-
this.#pendingWorkerInit.set(worker, pending);
|
|
411
|
-
});
|
|
412
|
-
if (!this.#appEntrypoint) {
|
|
413
|
-
throw new Error(
|
|
414
|
-
"ServiceWorkerPool requires an entrypoint to derive baseDir"
|
|
415
|
-
);
|
|
353
|
+
logger.debug("Worker ready", { entrypoint });
|
|
354
|
+
const waiters = this.#workerAvailableWaiters;
|
|
355
|
+
this.#workerAvailableWaiters = [];
|
|
356
|
+
for (const waiter of waiters) {
|
|
357
|
+
waiter.resolve();
|
|
416
358
|
}
|
|
417
|
-
const baseDir = Path.dirname(this.#appEntrypoint);
|
|
418
|
-
const initMessage = {
|
|
419
|
-
type: "init",
|
|
420
|
-
config: this.#config,
|
|
421
|
-
baseDir
|
|
422
|
-
};
|
|
423
|
-
logger.info("Sending init message", { config: this.#config, baseDir });
|
|
424
|
-
worker.postMessage(initMessage);
|
|
425
|
-
logger.info("Sent init message, waiting for initialized response");
|
|
426
|
-
await initializedPromise;
|
|
427
|
-
logger.info("Received initialized response");
|
|
428
|
-
this.#pendingWorkerInit.delete(worker);
|
|
429
359
|
return worker;
|
|
430
360
|
}
|
|
431
361
|
#handleWorkerMessage(worker, message) {
|
|
432
362
|
logger.debug("Worker message received", { type: message.type });
|
|
433
|
-
const pending = this.#pendingWorkerInit.get(worker);
|
|
434
|
-
if (message.type === "worker-ready" && pending?.workerReady) {
|
|
435
|
-
pending.workerReady();
|
|
436
|
-
} else if (message.type === "initialized" && pending?.initialized) {
|
|
437
|
-
pending.initialized();
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
363
|
switch (message.type) {
|
|
364
|
+
case "ready": {
|
|
365
|
+
const pending = this.#pendingWorkerReady.get(worker);
|
|
366
|
+
if (pending) {
|
|
367
|
+
pending.resolve();
|
|
368
|
+
}
|
|
369
|
+
logger.debug("ServiceWorker ready");
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
441
372
|
case "response":
|
|
442
373
|
this.#handleResponse(message);
|
|
443
374
|
break;
|
|
444
375
|
case "error":
|
|
445
376
|
this.#handleError(message);
|
|
446
377
|
break;
|
|
447
|
-
case "ready":
|
|
448
|
-
case "worker-ready":
|
|
449
|
-
this.#handleReady(message);
|
|
450
|
-
break;
|
|
451
|
-
case "initialized":
|
|
452
|
-
break;
|
|
453
378
|
default:
|
|
454
379
|
if (message.type?.startsWith("cache:")) {
|
|
455
380
|
logger.debug("Cache message received", { type: message.type });
|
|
@@ -497,23 +422,38 @@ var ServiceWorkerPool = class {
|
|
|
497
422
|
pending.reject(new Error(message.error));
|
|
498
423
|
this.#pendingRequests.delete(message.requestID);
|
|
499
424
|
}
|
|
500
|
-
} else {
|
|
501
|
-
logger.error("Worker error: {error}", { error: message.error });
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
#handleReady(message) {
|
|
505
|
-
if (message.type === "ready") {
|
|
506
|
-
logger.info("ServiceWorker ready", { entrypoint: message.entrypoint });
|
|
507
|
-
} else if (message.type === "worker-ready") {
|
|
508
|
-
logger.info("Worker initialized", {});
|
|
509
425
|
}
|
|
510
426
|
}
|
|
511
427
|
/**
|
|
512
428
|
* Handle HTTP request using round-robin worker selection
|
|
513
429
|
*/
|
|
514
430
|
async handleRequest(request) {
|
|
431
|
+
if (this.#workers.length === 0) {
|
|
432
|
+
logger.debug("No workers available, waiting for worker to be ready");
|
|
433
|
+
await new Promise((resolve, reject) => {
|
|
434
|
+
const waiter = { resolve, reject };
|
|
435
|
+
this.#workerAvailableWaiters.push(waiter);
|
|
436
|
+
const timeoutId = setTimeout(() => {
|
|
437
|
+
const index = this.#workerAvailableWaiters.indexOf(waiter);
|
|
438
|
+
if (index !== -1) {
|
|
439
|
+
this.#workerAvailableWaiters.splice(index, 1);
|
|
440
|
+
reject(new Error("Timeout waiting for worker to become available"));
|
|
441
|
+
}
|
|
442
|
+
}, this.#options.requestTimeout);
|
|
443
|
+
const originalResolve = waiter.resolve;
|
|
444
|
+
const originalReject = waiter.reject;
|
|
445
|
+
waiter.resolve = () => {
|
|
446
|
+
clearTimeout(timeoutId);
|
|
447
|
+
originalResolve();
|
|
448
|
+
};
|
|
449
|
+
waiter.reject = (error) => {
|
|
450
|
+
clearTimeout(timeoutId);
|
|
451
|
+
originalReject(error);
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
}
|
|
515
455
|
const worker = this.#workers[this.#currentWorker];
|
|
516
|
-
logger.
|
|
456
|
+
logger.debug("Dispatching to worker", {
|
|
517
457
|
workerIndex: this.#currentWorker + 1,
|
|
518
458
|
totalWorkers: this.#workers.length
|
|
519
459
|
});
|
|
@@ -530,9 +470,6 @@ var ServiceWorkerPool = class {
|
|
|
530
470
|
this.#sendRequest(worker, request, requestID).catch(reject);
|
|
531
471
|
});
|
|
532
472
|
}
|
|
533
|
-
/**
|
|
534
|
-
* Send request to worker (async helper to avoid async promise executor)
|
|
535
|
-
*/
|
|
536
473
|
async #sendRequest(worker, request, requestID) {
|
|
537
474
|
let body = null;
|
|
538
475
|
if (request.body) {
|
|
@@ -555,75 +492,89 @@ var ServiceWorkerPool = class {
|
|
|
555
492
|
}
|
|
556
493
|
}
|
|
557
494
|
/**
|
|
558
|
-
*
|
|
559
|
-
* The entrypoint path contains a content hash for cache busting
|
|
495
|
+
* Gracefully shutdown a worker by closing all resources first
|
|
560
496
|
*/
|
|
561
|
-
async
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (timeoutId) {
|
|
571
|
-
clearTimeout(timeoutId);
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
const handleReady = (event) => {
|
|
575
|
-
const message = event.data || event;
|
|
576
|
-
if (message.type === "ready" && message.entrypoint === entrypoint) {
|
|
577
|
-
cleanup();
|
|
497
|
+
async #gracefulShutdown(worker, timeout = 5e3) {
|
|
498
|
+
return new Promise((resolve) => {
|
|
499
|
+
let resolved = false;
|
|
500
|
+
const onMessage = (event) => {
|
|
501
|
+
const message = event.data || event;
|
|
502
|
+
if (message?.type === "shutdown-complete") {
|
|
503
|
+
if (!resolved) {
|
|
504
|
+
resolved = true;
|
|
505
|
+
worker.removeEventListener("message", onMessage);
|
|
578
506
|
resolve();
|
|
579
|
-
} else if (message.type === "error") {
|
|
580
|
-
cleanup();
|
|
581
|
-
reject(
|
|
582
|
-
new Error(
|
|
583
|
-
`Worker failed to load ServiceWorker: ${message.error}`
|
|
584
|
-
)
|
|
585
|
-
);
|
|
586
507
|
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
);
|
|
600
|
-
}, 3e4);
|
|
601
|
-
logger.info("Sending load message", {
|
|
602
|
-
entrypoint
|
|
603
|
-
});
|
|
604
|
-
worker.addEventListener("message", handleReady);
|
|
605
|
-
worker.addEventListener("error", handleError);
|
|
606
|
-
const loadMessage = {
|
|
607
|
-
type: "load",
|
|
608
|
-
entrypoint
|
|
609
|
-
};
|
|
610
|
-
logger.debug("[WorkerPool] Sending load message", {
|
|
611
|
-
entrypoint
|
|
612
|
-
});
|
|
613
|
-
worker.postMessage(loadMessage);
|
|
614
|
-
});
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
worker.addEventListener("message", onMessage);
|
|
511
|
+
worker.postMessage({ type: "shutdown" });
|
|
512
|
+
setTimeout(() => {
|
|
513
|
+
if (!resolved) {
|
|
514
|
+
resolved = true;
|
|
515
|
+
worker.removeEventListener("message", onMessage);
|
|
516
|
+
logger.warn("Worker shutdown timed out, forcing termination");
|
|
517
|
+
resolve();
|
|
518
|
+
}
|
|
519
|
+
}, timeout);
|
|
615
520
|
});
|
|
616
|
-
|
|
617
|
-
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Reload workers with new entrypoint (hot reload)
|
|
524
|
+
*
|
|
525
|
+
* With unified builds, hot reload means:
|
|
526
|
+
* 1. Gracefully shutdown existing workers (close databases, etc.)
|
|
527
|
+
* 2. Terminate workers after resources are closed
|
|
528
|
+
* 3. Create new workers with the new bundle
|
|
529
|
+
*/
|
|
530
|
+
async reloadWorkers(entrypoint) {
|
|
531
|
+
logger.debug("Reloading workers", { entrypoint });
|
|
532
|
+
this.#appEntrypoint = entrypoint;
|
|
533
|
+
const shutdownPromises = this.#workers.map(
|
|
534
|
+
(worker) => this.#gracefulShutdown(worker)
|
|
535
|
+
);
|
|
536
|
+
await Promise.allSettled(shutdownPromises);
|
|
537
|
+
const terminatePromises = this.#workers.map((worker) => worker.terminate());
|
|
538
|
+
await Promise.allSettled(terminatePromises);
|
|
539
|
+
this.#workers = [];
|
|
540
|
+
this.#currentWorker = 0;
|
|
541
|
+
try {
|
|
542
|
+
const createPromises = [];
|
|
543
|
+
for (let i = 0; i < this.#options.workerCount; i++) {
|
|
544
|
+
createPromises.push(this.#createWorker(entrypoint));
|
|
545
|
+
}
|
|
546
|
+
await Promise.all(createPromises);
|
|
547
|
+
logger.debug("All workers reloaded", { entrypoint });
|
|
548
|
+
} catch (error) {
|
|
549
|
+
const waiters = this.#workerAvailableWaiters;
|
|
550
|
+
this.#workerAvailableWaiters = [];
|
|
551
|
+
const reloadError = error instanceof Error ? error : new Error("Worker creation failed during reload");
|
|
552
|
+
for (const waiter of waiters) {
|
|
553
|
+
waiter.reject(reloadError);
|
|
554
|
+
}
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
618
557
|
}
|
|
619
558
|
/**
|
|
620
559
|
* Graceful shutdown of all workers
|
|
621
560
|
*/
|
|
622
561
|
async terminate() {
|
|
562
|
+
const shutdownPromises = this.#workers.map(
|
|
563
|
+
(worker) => this.#gracefulShutdown(worker)
|
|
564
|
+
);
|
|
565
|
+
await Promise.allSettled(shutdownPromises);
|
|
623
566
|
const terminatePromises = this.#workers.map((worker) => worker.terminate());
|
|
624
567
|
await Promise.allSettled(terminatePromises);
|
|
625
568
|
this.#workers = [];
|
|
569
|
+
this.#currentWorker = 0;
|
|
626
570
|
this.#pendingRequests.clear();
|
|
571
|
+
this.#pendingWorkerReady.clear();
|
|
572
|
+
const waiters = this.#workerAvailableWaiters;
|
|
573
|
+
this.#workerAvailableWaiters = [];
|
|
574
|
+
const terminateError = new Error("Worker pool terminated");
|
|
575
|
+
for (const waiter of waiters) {
|
|
576
|
+
waiter.reject(terminateError);
|
|
577
|
+
}
|
|
627
578
|
}
|
|
628
579
|
/**
|
|
629
580
|
* Get the number of active workers
|
|
@@ -640,9 +591,12 @@ var ServiceWorkerPool = class {
|
|
|
640
591
|
};
|
|
641
592
|
export {
|
|
642
593
|
BasePlatform,
|
|
594
|
+
ConfigValidationError,
|
|
595
|
+
CustomDatabaseStorage,
|
|
643
596
|
CustomLoggerStorage,
|
|
644
597
|
ServiceWorkerPool,
|
|
645
598
|
SingleThreadedRuntime,
|
|
599
|
+
createDatabaseFactory,
|
|
646
600
|
createPlatform,
|
|
647
601
|
detectDeploymentPlatform,
|
|
648
602
|
detectDevelopmentPlatform,
|
|
@@ -650,5 +604,6 @@ export {
|
|
|
650
604
|
getPlatform,
|
|
651
605
|
getPlatformAsync,
|
|
652
606
|
platformRegistry,
|
|
653
|
-
resolvePlatform
|
|
607
|
+
resolvePlatform,
|
|
608
|
+
validateConfig
|
|
654
609
|
};
|