@b9g/platform 0.1.7 → 0.1.9
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 +2 -2
- package/src/runtime.js +20 -20
- package/src/single-threaded.d.ts +4 -2
- package/src/single-threaded.js +11 -13
- package/src/worker-pool.d.ts +5 -5
- package/src/worker-pool.js +12 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "ServiceWorker-first universal deployment platform. Write ServiceWorker apps once, deploy anywhere (Node/Bun/Cloudflare). Registry-based multi-app orchestration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"serviceworker",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@b9g/async-context": "^0.1.1",
|
|
19
19
|
"@b9g/cache": "^0.1.4",
|
|
20
|
-
"@b9g/filesystem": "^0.1.
|
|
20
|
+
"@b9g/filesystem": "^0.1.6",
|
|
21
21
|
"@logtape/logtape": "^1.2.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
package/src/runtime.js
CHANGED
|
@@ -817,7 +817,7 @@ var scope = null;
|
|
|
817
817
|
var _workerSelf = null;
|
|
818
818
|
var currentApp = null;
|
|
819
819
|
var serviceWorkerReady = false;
|
|
820
|
-
var
|
|
820
|
+
var loadedEntrypoint = null;
|
|
821
821
|
var caches;
|
|
822
822
|
var buckets;
|
|
823
823
|
async function handleFetchEvent(request) {
|
|
@@ -832,27 +832,23 @@ async function handleFetchEvent(request) {
|
|
|
832
832
|
return response;
|
|
833
833
|
} catch (error) {
|
|
834
834
|
logger.error("[Worker] ServiceWorker request failed", { error });
|
|
835
|
+
console.error("[Worker] ServiceWorker request failed:", error);
|
|
835
836
|
const response = new Response("ServiceWorker request failed", {
|
|
836
837
|
status: 500
|
|
837
838
|
});
|
|
838
839
|
return response;
|
|
839
840
|
}
|
|
840
841
|
}
|
|
841
|
-
async function loadServiceWorker(
|
|
842
|
+
async function loadServiceWorker(entrypoint) {
|
|
842
843
|
try {
|
|
843
844
|
logger.debug("loadServiceWorker called", {
|
|
844
|
-
|
|
845
|
-
|
|
845
|
+
entrypoint,
|
|
846
|
+
loadedEntrypoint
|
|
846
847
|
});
|
|
847
|
-
if (!entrypoint) {
|
|
848
|
-
throw new Error(
|
|
849
|
-
"ServiceWorker entrypoint must be provided via loadServiceWorker() call"
|
|
850
|
-
);
|
|
851
|
-
}
|
|
852
848
|
logger.info("[Worker] Loading from", { entrypoint });
|
|
853
|
-
if (
|
|
849
|
+
if (loadedEntrypoint !== null && loadedEntrypoint !== entrypoint) {
|
|
854
850
|
logger.info(
|
|
855
|
-
`[Worker] Hot reload detected: ${
|
|
851
|
+
`[Worker] Hot reload detected: ${loadedEntrypoint} -> ${entrypoint}`
|
|
856
852
|
);
|
|
857
853
|
logger.info("[Worker] Creating completely fresh ServiceWorker context");
|
|
858
854
|
registration = new ShovelServiceWorkerRegistration();
|
|
@@ -869,14 +865,14 @@ async function loadServiceWorker(version, entrypoint) {
|
|
|
869
865
|
currentApp = null;
|
|
870
866
|
serviceWorkerReady = false;
|
|
871
867
|
}
|
|
872
|
-
if (
|
|
873
|
-
logger.info("[Worker] ServiceWorker already loaded for
|
|
874
|
-
|
|
868
|
+
if (loadedEntrypoint === entrypoint) {
|
|
869
|
+
logger.info("[Worker] ServiceWorker already loaded for entrypoint", {
|
|
870
|
+
entrypoint
|
|
875
871
|
});
|
|
876
872
|
return;
|
|
877
873
|
}
|
|
878
|
-
const appModule = await import(
|
|
879
|
-
|
|
874
|
+
const appModule = await import(entrypoint);
|
|
875
|
+
loadedEntrypoint = entrypoint;
|
|
880
876
|
currentApp = appModule;
|
|
881
877
|
if (!registration) {
|
|
882
878
|
throw new Error("ServiceWorker runtime not initialized");
|
|
@@ -885,7 +881,7 @@ async function loadServiceWorker(version, entrypoint) {
|
|
|
885
881
|
await registration.activate();
|
|
886
882
|
serviceWorkerReady = true;
|
|
887
883
|
logger.info(
|
|
888
|
-
`[Worker] ServiceWorker loaded and activated
|
|
884
|
+
`[Worker] ServiceWorker loaded and activated from ${entrypoint}`
|
|
889
885
|
);
|
|
890
886
|
} catch (error) {
|
|
891
887
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -932,8 +928,8 @@ async function handleMessage(message) {
|
|
|
932
928
|
sendMessage({ type: "initialized" });
|
|
933
929
|
} else if (message.type === "load") {
|
|
934
930
|
const loadMsg = message;
|
|
935
|
-
await loadServiceWorker(loadMsg.
|
|
936
|
-
sendMessage({ type: "ready",
|
|
931
|
+
await loadServiceWorker(loadMsg.entrypoint);
|
|
932
|
+
sendMessage({ type: "ready", entrypoint: loadMsg.entrypoint });
|
|
937
933
|
} else if (message.type === "request") {
|
|
938
934
|
const reqMsg = message;
|
|
939
935
|
const request = new Request(reqMsg.request.url, {
|
|
@@ -943,12 +939,16 @@ async function handleMessage(message) {
|
|
|
943
939
|
});
|
|
944
940
|
const response = await handleFetchEvent(request);
|
|
945
941
|
const body = await response.arrayBuffer();
|
|
942
|
+
const headers = Object.fromEntries(response.headers.entries());
|
|
943
|
+
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
944
|
+
headers["Content-Type"] = "text/plain; charset=utf-8";
|
|
945
|
+
}
|
|
946
946
|
const responseMsg = {
|
|
947
947
|
type: "response",
|
|
948
948
|
response: {
|
|
949
949
|
status: response.status,
|
|
950
950
|
statusText: response.statusText,
|
|
951
|
-
headers
|
|
951
|
+
headers,
|
|
952
952
|
body
|
|
953
953
|
},
|
|
954
954
|
requestID: reqMsg.requestID
|
package/src/single-threaded.d.ts
CHANGED
|
@@ -31,12 +31,14 @@ export declare class SingleThreadedRuntime {
|
|
|
31
31
|
init(): Promise<void>;
|
|
32
32
|
/**
|
|
33
33
|
* Load and run a ServiceWorker entrypoint
|
|
34
|
+
* @param entrypoint - Path to the new entrypoint (hashed filename for cache busting)
|
|
34
35
|
*/
|
|
35
|
-
reloadWorkers(
|
|
36
|
+
reloadWorkers(entrypoint: string): Promise<void>;
|
|
36
37
|
/**
|
|
37
38
|
* Load a ServiceWorker entrypoint for the first time
|
|
39
|
+
* @param entrypoint - Path to the entrypoint file (content-hashed filename)
|
|
38
40
|
*/
|
|
39
|
-
loadEntrypoint(entrypoint: string
|
|
41
|
+
loadEntrypoint(entrypoint: string): Promise<void>;
|
|
40
42
|
/**
|
|
41
43
|
* Handle an HTTP request
|
|
42
44
|
* This is the key method - direct call, no postMessage!
|
package/src/single-threaded.js
CHANGED
|
@@ -39,32 +39,30 @@ var SingleThreadedRuntime = class {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Load and run a ServiceWorker entrypoint
|
|
42
|
+
* @param entrypoint - Path to the new entrypoint (hashed filename for cache busting)
|
|
42
43
|
*/
|
|
43
|
-
async reloadWorkers(
|
|
44
|
-
if (!this.#entrypoint) {
|
|
45
|
-
throw new Error("No entrypoint set - call loadEntrypoint first");
|
|
46
|
-
}
|
|
44
|
+
async reloadWorkers(entrypoint) {
|
|
47
45
|
logger.info("Reloading ServiceWorker", {
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
oldEntrypoint: this.#entrypoint,
|
|
47
|
+
newEntrypoint: entrypoint
|
|
50
48
|
});
|
|
51
|
-
|
|
49
|
+
this.#entrypoint = entrypoint;
|
|
52
50
|
this.#registration._serviceWorker._setState("parsed");
|
|
53
51
|
this.#ready = false;
|
|
54
|
-
await import(
|
|
52
|
+
await import(entrypoint);
|
|
55
53
|
await this.#registration.install();
|
|
56
54
|
await this.#registration.activate();
|
|
57
55
|
this.#ready = true;
|
|
58
|
-
logger.info("ServiceWorker loaded and activated", {
|
|
56
|
+
logger.info("ServiceWorker loaded and activated", { entrypoint });
|
|
59
57
|
}
|
|
60
58
|
/**
|
|
61
59
|
* Load a ServiceWorker entrypoint for the first time
|
|
60
|
+
* @param entrypoint - Path to the entrypoint file (content-hashed filename)
|
|
62
61
|
*/
|
|
63
|
-
async loadEntrypoint(entrypoint
|
|
62
|
+
async loadEntrypoint(entrypoint) {
|
|
64
63
|
this.#entrypoint = entrypoint;
|
|
65
|
-
logger.info("Loading ServiceWorker entrypoint", { entrypoint
|
|
66
|
-
|
|
67
|
-
await import(importPath);
|
|
64
|
+
logger.info("Loading ServiceWorker entrypoint", { entrypoint });
|
|
65
|
+
await import(entrypoint);
|
|
68
66
|
await this.#registration.install();
|
|
69
67
|
await this.#registration.activate();
|
|
70
68
|
this.#ready = true;
|
package/src/worker-pool.d.ts
CHANGED
|
@@ -38,12 +38,11 @@ export interface WorkerResponse extends WorkerMessage {
|
|
|
38
38
|
}
|
|
39
39
|
export interface WorkerLoadMessage extends WorkerMessage {
|
|
40
40
|
type: "load";
|
|
41
|
-
|
|
42
|
-
entrypoint?: string;
|
|
41
|
+
entrypoint: string;
|
|
43
42
|
}
|
|
44
43
|
export interface WorkerReadyMessage extends WorkerMessage {
|
|
45
44
|
type: "ready" | "worker-ready";
|
|
46
|
-
|
|
45
|
+
entrypoint?: string;
|
|
47
46
|
}
|
|
48
47
|
export interface WorkerErrorMessage extends WorkerMessage {
|
|
49
48
|
type: "error";
|
|
@@ -75,9 +74,10 @@ export declare class ServiceWorkerPool {
|
|
|
75
74
|
*/
|
|
76
75
|
handleRequest(request: Request): Promise<Response>;
|
|
77
76
|
/**
|
|
78
|
-
* Reload ServiceWorker with new
|
|
77
|
+
* Reload ServiceWorker with new entrypoint (hot reload)
|
|
78
|
+
* The entrypoint path contains a content hash for cache busting
|
|
79
79
|
*/
|
|
80
|
-
reloadWorkers(
|
|
80
|
+
reloadWorkers(entrypoint: string): Promise<void>;
|
|
81
81
|
/**
|
|
82
82
|
* Graceful shutdown of all workers
|
|
83
83
|
*/
|
package/src/worker-pool.js
CHANGED
|
@@ -251,7 +251,7 @@ var ServiceWorkerPool = class {
|
|
|
251
251
|
}
|
|
252
252
|
#handleReady(message) {
|
|
253
253
|
if (message.type === "ready") {
|
|
254
|
-
logger.info("ServiceWorker ready", {
|
|
254
|
+
logger.info("ServiceWorker ready", { entrypoint: message.entrypoint });
|
|
255
255
|
} else if (message.type === "worker-ready") {
|
|
256
256
|
logger.info("Worker initialized", {});
|
|
257
257
|
}
|
|
@@ -303,10 +303,12 @@ var ServiceWorkerPool = class {
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
/**
|
|
306
|
-
* Reload ServiceWorker with new
|
|
306
|
+
* Reload ServiceWorker with new entrypoint (hot reload)
|
|
307
|
+
* The entrypoint path contains a content hash for cache busting
|
|
307
308
|
*/
|
|
308
|
-
async reloadWorkers(
|
|
309
|
-
logger.info("Reloading ServiceWorker", {
|
|
309
|
+
async reloadWorkers(entrypoint) {
|
|
310
|
+
logger.info("Reloading ServiceWorker", { entrypoint });
|
|
311
|
+
this.#appEntrypoint = entrypoint;
|
|
310
312
|
const loadPromises = this.#workers.map((worker) => {
|
|
311
313
|
return new Promise((resolve, reject) => {
|
|
312
314
|
let timeoutId;
|
|
@@ -319,7 +321,7 @@ var ServiceWorkerPool = class {
|
|
|
319
321
|
};
|
|
320
322
|
const handleReady = (event) => {
|
|
321
323
|
const message = event.data || event;
|
|
322
|
-
if (message.type === "ready" && message.
|
|
324
|
+
if (message.type === "ready" && message.entrypoint === entrypoint) {
|
|
323
325
|
cleanup();
|
|
324
326
|
resolve();
|
|
325
327
|
} else if (message.type === "error") {
|
|
@@ -340,30 +342,27 @@ var ServiceWorkerPool = class {
|
|
|
340
342
|
cleanup();
|
|
341
343
|
reject(
|
|
342
344
|
new Error(
|
|
343
|
-
`Worker failed to load ServiceWorker within 30000ms (
|
|
345
|
+
`Worker failed to load ServiceWorker within 30000ms (entrypoint ${entrypoint})`
|
|
344
346
|
)
|
|
345
347
|
);
|
|
346
348
|
}, 3e4);
|
|
347
349
|
logger.info("Sending load message", {
|
|
348
|
-
|
|
349
|
-
entrypoint: this.#appEntrypoint
|
|
350
|
+
entrypoint
|
|
350
351
|
});
|
|
351
352
|
worker.addEventListener("message", handleReady);
|
|
352
353
|
worker.addEventListener("error", handleError);
|
|
353
354
|
const loadMessage = {
|
|
354
355
|
type: "load",
|
|
355
|
-
|
|
356
|
-
entrypoint: this.#appEntrypoint
|
|
356
|
+
entrypoint
|
|
357
357
|
};
|
|
358
358
|
logger.debug("[WorkerPool] Sending load message", {
|
|
359
|
-
entrypoint
|
|
360
|
-
version
|
|
359
|
+
entrypoint
|
|
361
360
|
});
|
|
362
361
|
worker.postMessage(loadMessage);
|
|
363
362
|
});
|
|
364
363
|
});
|
|
365
364
|
await Promise.all(loadPromises);
|
|
366
|
-
logger.info("All workers reloaded", {
|
|
365
|
+
logger.info("All workers reloaded", { entrypoint });
|
|
367
366
|
}
|
|
368
367
|
/**
|
|
369
368
|
* Graceful shutdown of all workers
|