@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/platform",
3
- "version": "0.1.7",
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.5",
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 loadedVersion = null;
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(version, entrypoint) {
842
+ async function loadServiceWorker(entrypoint) {
842
843
  try {
843
844
  logger.debug("loadServiceWorker called", {
844
- version,
845
- loadedVersion
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 (loadedVersion !== null && loadedVersion !== version) {
849
+ if (loadedEntrypoint !== null && loadedEntrypoint !== entrypoint) {
854
850
  logger.info(
855
- `[Worker] Hot reload detected: ${loadedVersion} -> ${version}`
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 (loadedVersion === version) {
873
- logger.info("[Worker] ServiceWorker already loaded for version", {
874
- version
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(`${entrypoint}?v=${version}`);
879
- loadedVersion = version;
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 (v${version}) from ${entrypoint}`
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.version, loadMsg.entrypoint);
936
- sendMessage({ type: "ready", version: loadMsg.version });
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: Object.fromEntries(response.headers.entries()),
951
+ headers,
952
952
  body
953
953
  },
954
954
  requestID: reqMsg.requestID
@@ -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(version?: number | string): Promise<void>;
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, version?: number | string): Promise<void>;
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!
@@ -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(version) {
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
- version,
49
- entrypoint: this.#entrypoint
46
+ oldEntrypoint: this.#entrypoint,
47
+ newEntrypoint: entrypoint
50
48
  });
51
- const importPath = version ? `${this.#entrypoint}?v=${version}` : this.#entrypoint;
49
+ this.#entrypoint = entrypoint;
52
50
  this.#registration._serviceWorker._setState("parsed");
53
51
  this.#ready = false;
54
- await import(importPath);
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", { version });
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, version) {
62
+ async loadEntrypoint(entrypoint) {
64
63
  this.#entrypoint = entrypoint;
65
- logger.info("Loading ServiceWorker entrypoint", { entrypoint, version });
66
- const importPath = version ? `${entrypoint}?v=${version}` : entrypoint;
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;
@@ -38,12 +38,11 @@ export interface WorkerResponse extends WorkerMessage {
38
38
  }
39
39
  export interface WorkerLoadMessage extends WorkerMessage {
40
40
  type: "load";
41
- version: number | string;
42
- entrypoint?: string;
41
+ entrypoint: string;
43
42
  }
44
43
  export interface WorkerReadyMessage extends WorkerMessage {
45
44
  type: "ready" | "worker-ready";
46
- version?: number | string;
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 version (hot reload simulation)
77
+ * Reload ServiceWorker with new entrypoint (hot reload)
78
+ * The entrypoint path contains a content hash for cache busting
79
79
  */
80
- reloadWorkers(version?: number | string): Promise<void>;
80
+ reloadWorkers(entrypoint: string): Promise<void>;
81
81
  /**
82
82
  * Graceful shutdown of all workers
83
83
  */
@@ -251,7 +251,7 @@ var ServiceWorkerPool = class {
251
251
  }
252
252
  #handleReady(message) {
253
253
  if (message.type === "ready") {
254
- logger.info("ServiceWorker ready", { version: message.version });
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 version (hot reload simulation)
306
+ * Reload ServiceWorker with new entrypoint (hot reload)
307
+ * The entrypoint path contains a content hash for cache busting
307
308
  */
308
- async reloadWorkers(version = Date.now()) {
309
- logger.info("Reloading ServiceWorker", { version });
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.version === version) {
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 (version ${version})`
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
- version,
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
- version,
356
- entrypoint: this.#appEntrypoint
356
+ entrypoint
357
357
  };
358
358
  logger.debug("[WorkerPool] Sending load message", {
359
- entrypoint: this.#appEntrypoint,
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", { version });
365
+ logger.info("All workers reloaded", { entrypoint });
367
366
  }
368
367
  /**
369
368
  * Graceful shutdown of all workers