@b9g/platform 0.1.10 → 0.1.12

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/src/runtime.js CHANGED
@@ -1,16 +1,251 @@
1
1
  /// <reference types="./runtime.d.ts" />
2
+ import "../chunk-P57PW2II.js";
3
+
2
4
  // src/runtime.ts
3
- import { RequestCookieStore } from "./cookie-store.js";
4
5
  import { AsyncContext } from "@b9g/async-context";
5
- import { CustomBucketStorage } from "@b9g/filesystem";
6
- import { CustomCacheStorage } from "@b9g/cache";
7
- import { createBucketFactory, createCacheFactory } from "./config.js";
8
- import { getLogger } from "@logtape/logtape";
9
- import { configureLogging } from "./config.js";
6
+ import {
7
+ configure
8
+ } from "@logtape/logtape";
9
+ function parseCookieHeader(cookieHeader) {
10
+ const cookies = /* @__PURE__ */ new Map();
11
+ if (!cookieHeader)
12
+ return cookies;
13
+ const pairs = cookieHeader.split(/;\s*/);
14
+ for (const pair of pairs) {
15
+ const [name, ...valueParts] = pair.split("=");
16
+ if (name) {
17
+ const value = valueParts.join("=");
18
+ cookies.set(name.trim(), decodeURIComponent(value || ""));
19
+ }
20
+ }
21
+ return cookies;
22
+ }
23
+ function serializeCookie(cookie) {
24
+ let header = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
25
+ if (cookie.expires !== void 0 && cookie.expires !== null) {
26
+ const date = new Date(cookie.expires);
27
+ header += `; Expires=${date.toUTCString()}`;
28
+ }
29
+ if (cookie.domain) {
30
+ header += `; Domain=${cookie.domain}`;
31
+ }
32
+ if (cookie.path) {
33
+ header += `; Path=${cookie.path}`;
34
+ } else {
35
+ header += `; Path=/`;
36
+ }
37
+ if (cookie.sameSite) {
38
+ header += `; SameSite=${cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1)}`;
39
+ } else {
40
+ header += `; SameSite=Strict`;
41
+ }
42
+ if (cookie.partitioned) {
43
+ header += `; Partitioned`;
44
+ }
45
+ header += `; Secure`;
46
+ return header;
47
+ }
48
+ function parseSetCookieHeader(setCookieHeader) {
49
+ const parts = setCookieHeader.split(/;\s*/);
50
+ const [nameValue, ...attributes] = parts;
51
+ const [name, ...valueParts] = nameValue.split("=");
52
+ const value = valueParts.join("=");
53
+ const cookie = {
54
+ name: decodeURIComponent(name.trim()),
55
+ value: decodeURIComponent(value || "")
56
+ };
57
+ for (const attr of attributes) {
58
+ const [key, ...attrValueParts] = attr.split("=");
59
+ const attrKey = key.trim().toLowerCase();
60
+ const attrValue = attrValueParts.join("=").trim();
61
+ switch (attrKey) {
62
+ case "expires":
63
+ cookie.expires = new Date(attrValue).getTime();
64
+ break;
65
+ case "max-age":
66
+ cookie.expires = Date.now() + parseInt(attrValue, 10) * 1e3;
67
+ break;
68
+ case "domain":
69
+ cookie.domain = attrValue;
70
+ break;
71
+ case "path":
72
+ cookie.path = attrValue;
73
+ break;
74
+ case "secure":
75
+ cookie.secure = true;
76
+ break;
77
+ case "samesite":
78
+ cookie.sameSite = attrValue.toLowerCase();
79
+ break;
80
+ case "partitioned":
81
+ cookie.partitioned = true;
82
+ break;
83
+ }
84
+ }
85
+ return cookie;
86
+ }
87
+ var RequestCookieStore = class extends EventTarget {
88
+ #cookies;
89
+ #changes;
90
+ // null = deleted
91
+ #request;
92
+ // Event handler for cookie changes (spec compliance)
93
+ // eslint-disable-next-line no-restricted-syntax
94
+ onchange = null;
95
+ constructor(request) {
96
+ super();
97
+ this.#cookies = /* @__PURE__ */ new Map();
98
+ this.#changes = /* @__PURE__ */ new Map();
99
+ this.#request = request || null;
100
+ if (request) {
101
+ const cookieHeader = request.headers.get("cookie");
102
+ if (cookieHeader) {
103
+ const parsed = parseCookieHeader(cookieHeader);
104
+ for (const [name, value] of parsed) {
105
+ this.#cookies.set(name, { name, value });
106
+ }
107
+ }
108
+ }
109
+ }
110
+ async get(nameOrOptions) {
111
+ const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
112
+ if (!name) {
113
+ throw new TypeError("Cookie name is required");
114
+ }
115
+ if (this.#changes.has(name)) {
116
+ const change = this.#changes.get(name);
117
+ if (change === null || change === void 0)
118
+ return null;
119
+ return {
120
+ name: change.name,
121
+ value: change.value,
122
+ domain: change.domain ?? void 0,
123
+ path: change.path,
124
+ expires: change.expires ?? void 0,
125
+ sameSite: change.sameSite,
126
+ partitioned: change.partitioned
127
+ };
128
+ }
129
+ return this.#cookies.get(name) || null;
130
+ }
131
+ async getAll(nameOrOptions) {
132
+ const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name;
133
+ const result = [];
134
+ const allNames = /* @__PURE__ */ new Set([
135
+ ...this.#cookies.keys(),
136
+ ...this.#changes.keys()
137
+ ]);
138
+ for (const cookieName of allNames) {
139
+ if (name && cookieName !== name)
140
+ continue;
141
+ if (this.#changes.has(cookieName) && this.#changes.get(cookieName) === null) {
142
+ continue;
143
+ }
144
+ const cookie = await this.get(cookieName);
145
+ if (cookie) {
146
+ result.push(cookie);
147
+ }
148
+ }
149
+ return result;
150
+ }
151
+ async set(nameOrOptions, value) {
152
+ let cookie;
153
+ if (typeof nameOrOptions === "string") {
154
+ if (value === void 0) {
155
+ throw new TypeError("Cookie value is required");
156
+ }
157
+ cookie = {
158
+ name: nameOrOptions,
159
+ value,
160
+ path: "/",
161
+ sameSite: "strict"
162
+ };
163
+ } else {
164
+ cookie = {
165
+ path: "/",
166
+ sameSite: "strict",
167
+ ...nameOrOptions
168
+ };
169
+ }
170
+ const size = cookie.name.length + cookie.value.length;
171
+ if (size > 4096) {
172
+ throw new TypeError(
173
+ `Cookie name+value too large: ${size} bytes (max 4096)`
174
+ );
175
+ }
176
+ this.#changes.set(cookie.name, cookie);
177
+ }
178
+ async delete(nameOrOptions) {
179
+ const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
180
+ if (!name) {
181
+ throw new TypeError("Cookie name is required");
182
+ }
183
+ this.#changes.set(name, null);
184
+ }
185
+ /**
186
+ * Get Set-Cookie headers for all changes
187
+ * This should be called when constructing the Response
188
+ */
189
+ getSetCookieHeaders() {
190
+ const headers = [];
191
+ for (const [name, change] of this.#changes) {
192
+ if (change === null) {
193
+ headers.push(
194
+ serializeCookie({
195
+ name,
196
+ value: "",
197
+ expires: 0,
198
+ path: "/"
199
+ })
200
+ );
201
+ } else {
202
+ headers.push(serializeCookie(change));
203
+ }
204
+ }
205
+ return headers;
206
+ }
207
+ hasChanges() {
208
+ return this.#changes.size > 0;
209
+ }
210
+ clearChanges() {
211
+ this.#changes.clear();
212
+ }
213
+ };
214
+ var CustomLoggerStorage = class {
215
+ #factory;
216
+ constructor(factory) {
217
+ this.#factory = factory;
218
+ }
219
+ open(...categories) {
220
+ return this.#factory(...categories);
221
+ }
222
+ };
223
+ var SERVICE_WORKER_EVENTS = ["fetch", "install", "activate"];
224
+ function isServiceWorkerEvent(type) {
225
+ return SERVICE_WORKER_EVENTS.includes(type);
226
+ }
10
227
  if (import.meta.env && !import.meta.env.MODE && import.meta.env.NODE_ENV) {
11
228
  import.meta.env.MODE = import.meta.env.NODE_ENV;
12
229
  }
13
230
  var cookieStoreStorage = new AsyncContext.Variable();
231
+ var fetchDepthStorage = new AsyncContext.Variable();
232
+ var MAX_FETCH_DEPTH = 10;
233
+ var PATCHED_KEYS = [
234
+ "self",
235
+ "fetch",
236
+ "caches",
237
+ "directories",
238
+ "loggers",
239
+ "registration",
240
+ "clients",
241
+ "skipWaiting",
242
+ "addEventListener",
243
+ "removeEventListener",
244
+ "dispatchEvent",
245
+ "WorkerGlobalScope",
246
+ "DedicatedWorkerGlobalScope",
247
+ "cookieStore"
248
+ ];
14
249
  function promiseWithTimeout(promise, timeoutMs, errorMessage) {
15
250
  return Promise.race([
16
251
  promise,
@@ -114,9 +349,7 @@ var ShovelClient = class {
114
349
  }
115
350
  // Implementation
116
351
  postMessage(_message, _transferOrOptions) {
117
- logger.warn(
118
- "[ServiceWorker] Client.postMessage() not supported in server context"
119
- );
352
+ self.loggers.open("platform").warn("Client.postMessage() not supported in server context");
120
353
  }
121
354
  };
122
355
  var ShovelWindowClient = class extends ShovelClient {
@@ -128,15 +361,11 @@ var ShovelWindowClient = class extends ShovelClient {
128
361
  this.visibilityState = options.visibilityState || "hidden";
129
362
  }
130
363
  async focus() {
131
- logger.warn(
132
- "[ServiceWorker] WindowClient.focus() not supported in server context"
133
- );
364
+ self.loggers.open("platform").warn("WindowClient.focus() not supported in server context");
134
365
  return this;
135
366
  }
136
367
  async navigate(_url) {
137
- logger.warn(
138
- "[ServiceWorker] WindowClient.navigate() not supported in server context"
139
- );
368
+ self.loggers.open("platform").warn("WindowClient.navigate() not supported in server context");
140
369
  return null;
141
370
  }
142
371
  };
@@ -150,9 +379,7 @@ var ShovelClients = class {
150
379
  return [];
151
380
  }
152
381
  async openWindow(_url) {
153
- logger.warn(
154
- "[ServiceWorker] Clients.openWindow() not supported in server context"
155
- );
382
+ self.loggers.open("platform").warn("Clients.openWindow() not supported in server context");
156
383
  return null;
157
384
  }
158
385
  };
@@ -186,9 +413,7 @@ var ShovelServiceWorker = class extends EventTarget {
186
413
  }
187
414
  // Implementation
188
415
  postMessage(_message, _transferOrOptions) {
189
- logger.warn(
190
- "[ServiceWorker] ServiceWorker.postMessage() not implemented in server context"
191
- );
416
+ self.loggers.open("platform").warn("ServiceWorker.postMessage() not implemented in server context");
192
417
  }
193
418
  // Internal method to update state and dispatch statechange event
194
419
  _setState(newState) {
@@ -220,9 +445,9 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
220
445
  cookies;
221
446
  pushManager;
222
447
  onupdatefound;
223
- constructor(scope2 = "/", scriptURL = "/") {
448
+ constructor(scope = "/", scriptURL = "/") {
224
449
  super();
225
- this.scope = scope2;
450
+ this.scope = scope;
226
451
  this.updateViaCache = "imports";
227
452
  this.navigationPreload = new ShovelNavigationPreloadManager();
228
453
  this._serviceWorker = new ShovelServiceWorker(scriptURL, "parsed");
@@ -245,9 +470,7 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
245
470
  return [];
246
471
  }
247
472
  async showNotification(_title, _options) {
248
- logger.warn(
249
- "[ServiceWorker] Notifications not supported in server context"
250
- );
473
+ self.loggers.open("platform").warn("Notifications not supported in server context");
251
474
  }
252
475
  async sync() {
253
476
  }
@@ -346,7 +569,9 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
346
569
  const response = await event.getResponse();
347
570
  const promises = event.getPromises();
348
571
  if (promises.length > 0) {
349
- Promise.allSettled(promises).catch(logger.error);
572
+ Promise.allSettled(promises).catch(
573
+ (err) => self.loggers.open("platform").error`waitUntil error: ${err}`
574
+ );
350
575
  }
351
576
  if (event.cookieStore.hasChanges()) {
352
577
  const setCookieHeaders = event.cookieStore.getSetCookieHeaders();
@@ -393,8 +618,8 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
393
618
  /**
394
619
  * Get registration for a specific scope
395
620
  */
396
- async getRegistration(scope2 = "/") {
397
- return this.#registrations.get(scope2);
621
+ async getRegistration(scope = "/") {
622
+ return this.#registrations.get(scope);
398
623
  }
399
624
  /**
400
625
  * Get all registrations
@@ -407,26 +632,26 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
407
632
  */
408
633
  async register(scriptURL, options) {
409
634
  const url = typeof scriptURL === "string" ? scriptURL : scriptURL.toString();
410
- const scope2 = this.#normalizeScope(options?.scope || "/");
411
- let registration2 = this.#registrations.get(scope2);
412
- if (registration2) {
413
- registration2._serviceWorker.scriptURL = url;
414
- registration2._serviceWorker._setState("parsed");
635
+ const scope = this.#normalizeScope(options?.scope || "/");
636
+ let registration = this.#registrations.get(scope);
637
+ if (registration) {
638
+ registration._serviceWorker.scriptURL = url;
639
+ registration._serviceWorker._setState("parsed");
415
640
  } else {
416
- registration2 = new ShovelServiceWorkerRegistration(scope2, url);
417
- this.#registrations.set(scope2, registration2);
641
+ registration = new ShovelServiceWorkerRegistration(scope, url);
642
+ this.#registrations.set(scope, registration);
418
643
  this.dispatchEvent(new Event("updatefound"));
419
644
  }
420
- return registration2;
645
+ return registration;
421
646
  }
422
647
  /**
423
648
  * Unregister a ServiceWorker registration
424
649
  */
425
- async unregister(scope2) {
426
- const registration2 = this.#registrations.get(scope2);
427
- if (registration2) {
428
- await registration2.unregister();
429
- this.#registrations.delete(scope2);
650
+ async unregister(scope) {
651
+ const registration = this.#registrations.get(scope);
652
+ if (registration) {
653
+ await registration.unregister();
654
+ this.#registrations.delete(scope);
430
655
  return true;
431
656
  }
432
657
  return false;
@@ -439,9 +664,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
439
664
  const pathname = url.pathname;
440
665
  const matchingScope = this.#findMatchingScope(pathname);
441
666
  if (matchingScope) {
442
- const registration2 = this.#registrations.get(matchingScope);
443
- if (registration2 && registration2.ready) {
444
- return await registration2.handleRequest(request);
667
+ const registration = this.#registrations.get(matchingScope);
668
+ if (registration && registration.ready) {
669
+ return await registration.handleRequest(request);
445
670
  }
446
671
  }
447
672
  return null;
@@ -451,9 +676,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
451
676
  */
452
677
  async installAll() {
453
678
  const installations = Array.from(this.#registrations.values()).map(
454
- async (registration2) => {
455
- await registration2.install();
456
- await registration2.activate();
679
+ async (registration) => {
680
+ await registration.install();
681
+ await registration.activate();
457
682
  }
458
683
  );
459
684
  await promiseWithTimeout(
@@ -473,14 +698,14 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
473
698
  /**
474
699
  * Normalize scope to ensure it starts and ends correctly
475
700
  */
476
- #normalizeScope(scope2) {
477
- if (!scope2.startsWith("/")) {
478
- scope2 = "/" + scope2;
701
+ #normalizeScope(scope) {
702
+ if (!scope.startsWith("/")) {
703
+ scope = "/" + scope;
479
704
  }
480
- if (scope2 !== "/" && !scope2.endsWith("/")) {
481
- scope2 = scope2 + "/";
705
+ if (scope !== "/" && !scope.endsWith("/")) {
706
+ scope = scope + "/";
482
707
  }
483
- return scope2;
708
+ return scope;
484
709
  }
485
710
  /**
486
711
  * Find the most specific scope that matches a pathname
@@ -488,9 +713,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
488
713
  #findMatchingScope(pathname) {
489
714
  const scopes = Array.from(this.#registrations.keys());
490
715
  scopes.sort((a, b) => b.length - a.length);
491
- for (const scope2 of scopes) {
492
- if (pathname.startsWith(scope2 === "/" ? "/" : scope2)) {
493
- return scope2;
716
+ for (const scope of scopes) {
717
+ if (pathname.startsWith(scope === "/" ? "/" : scope)) {
718
+ return scope;
494
719
  }
495
720
  }
496
721
  return null;
@@ -542,9 +767,7 @@ var Notification = class extends EventTarget {
542
767
  this.onshow = null;
543
768
  }
544
769
  close() {
545
- logger.warn(
546
- "[ServiceWorker] Notification.close() not supported in server context"
547
- );
770
+ self.loggers.open("platform").warn("Notification.close() not supported in server context");
548
771
  }
549
772
  static async requestPermission() {
550
773
  return "denied";
@@ -609,7 +832,7 @@ var WorkerGlobalScope = class {
609
832
  };
610
833
  var DedicatedWorkerGlobalScope = class extends WorkerGlobalScope {
611
834
  };
612
- var ShovelGlobalScope = class {
835
+ var ServiceWorkerGlobals = class {
613
836
  // Self-reference (standard in ServiceWorkerGlobalScope)
614
837
  // Type assertion: we provide a compatible subset of WorkerGlobalScope
615
838
  self;
@@ -618,12 +841,15 @@ var ShovelGlobalScope = class {
618
841
  registration;
619
842
  // Storage APIs
620
843
  caches;
621
- buckets;
844
+ directories;
845
+ loggers;
622
846
  // Clients API
623
847
  // Our custom Clients implementation provides core functionality compatible with the Web API
624
848
  clients;
625
849
  // Shovel-specific development features
626
850
  #isDevelopment;
851
+ // Snapshot of original globals before patching (for restore())
852
+ #originals;
627
853
  // Web API required properties
628
854
  // Note: Using RequestCookieStore but typing as any for flexibility with global CookieStore type
629
855
  // cookieStore is retrieved from AsyncContext for per-request isolation
@@ -643,9 +869,7 @@ var ShovelGlobalScope = class {
643
869
  crypto;
644
870
  // WorkerGlobalScope methods (stubs for server context)
645
871
  importScripts(..._urls) {
646
- logger.warn(
647
- "[ServiceWorker] importScripts() not supported in server context"
648
- );
872
+ self.loggers.open("platform").warn("importScripts() not supported in server context");
649
873
  }
650
874
  atob(data) {
651
875
  return globalThis.atob(data);
@@ -665,13 +889,28 @@ var ShovelGlobalScope = class {
665
889
  );
666
890
  }
667
891
  fetch(input, init) {
668
- return globalThis.fetch(input, init);
892
+ const urlString = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
893
+ const isRelative = urlString.startsWith("/") || urlString.startsWith("./");
894
+ if (!isRelative) {
895
+ const originalFetch = this.#originals.fetch;
896
+ return originalFetch(input, init);
897
+ }
898
+ const currentDepth = fetchDepthStorage.get() ?? 0;
899
+ if (currentDepth >= MAX_FETCH_DEPTH) {
900
+ return Promise.reject(
901
+ new Error(`Maximum self-fetch depth (${MAX_FETCH_DEPTH}) exceeded`)
902
+ );
903
+ }
904
+ const request = new Request(new URL(urlString, "http://localhost"), init);
905
+ return fetchDepthStorage.run(currentDepth + 1, () => {
906
+ return this.registration.handleRequest(request);
907
+ });
669
908
  }
670
909
  queueMicrotask(callback) {
671
910
  globalThis.queueMicrotask(callback);
672
911
  }
673
912
  reportError(e) {
674
- logger.error("[ServiceWorker] reportError:", e);
913
+ self.loggers.open("platform").error`reportError: ${e}`;
675
914
  }
676
915
  setInterval(handler, timeout, ...args) {
677
916
  return globalThis.setInterval(handler, timeout, ...args);
@@ -703,10 +942,16 @@ var ShovelGlobalScope = class {
703
942
  onrejectionhandled;
704
943
  onunhandledrejection;
705
944
  constructor(options) {
706
- this.self = this;
945
+ const g = globalThis;
946
+ this.#originals = {};
947
+ for (const key of PATCHED_KEYS) {
948
+ this.#originals[key] = g[key];
949
+ }
950
+ this.self = globalThis;
707
951
  this.registration = options.registration;
708
952
  this.caches = options.caches;
709
- this.buckets = options.buckets;
953
+ this.directories = options.directories;
954
+ this.loggers = options.loggers;
710
955
  this.#isDevelopment = options.isDevelopment ?? false;
711
956
  this.clients = this.#createClientsAPI();
712
957
  this.serviceWorker = null;
@@ -742,28 +987,48 @@ var ShovelGlobalScope = class {
742
987
  * Allows the ServiceWorker to activate immediately
743
988
  */
744
989
  async skipWaiting() {
745
- logger.info("[ServiceWorker] skipWaiting() called");
990
+ self.loggers.open("platform").info("skipWaiting() called");
746
991
  if (!this.#isDevelopment) {
747
- logger.info(
748
- "[ServiceWorker] skipWaiting() - production graceful restart not implemented"
749
- );
992
+ self.loggers.open("platform").info("skipWaiting() - production graceful restart not implemented");
750
993
  }
751
994
  }
752
995
  /**
753
- * Event target delegation to registration
996
+ * Event target delegation - ServiceWorker events go to registration,
997
+ * other events (like "message" for worker threads) go to native handler
754
998
  */
755
999
  addEventListener(type, listener, options) {
756
- if (listener) {
1000
+ if (!listener)
1001
+ return;
1002
+ if (isServiceWorkerEvent(type)) {
757
1003
  this.registration.addEventListener(type, listener, options);
1004
+ } else {
1005
+ const original = this.#originals.addEventListener;
1006
+ if (original) {
1007
+ original.call(globalThis, type, listener, options);
1008
+ }
758
1009
  }
759
1010
  }
760
1011
  removeEventListener(type, listener, options) {
761
- if (listener) {
1012
+ if (!listener)
1013
+ return;
1014
+ if (isServiceWorkerEvent(type)) {
762
1015
  this.registration.removeEventListener(type, listener, options);
1016
+ } else {
1017
+ const original = this.#originals.removeEventListener;
1018
+ if (original) {
1019
+ original.call(globalThis, type, listener, options);
1020
+ }
763
1021
  }
764
1022
  }
765
1023
  dispatchEvent(event) {
766
- return this.registration.dispatchEvent(event);
1024
+ if (isServiceWorkerEvent(event.type)) {
1025
+ return this.registration.dispatchEvent(event);
1026
+ }
1027
+ const original = this.#originals.dispatchEvent;
1028
+ if (original) {
1029
+ return original.call(globalThis, event);
1030
+ }
1031
+ return false;
767
1032
  }
768
1033
  /**
769
1034
  * Create Clients API implementation
@@ -774,211 +1039,145 @@ var ShovelGlobalScope = class {
774
1039
  }
775
1040
  /**
776
1041
  * Install this scope as the global scope
777
- * Sets up globalThis with all ServiceWorker globals
1042
+ * Patches globalThis with ServiceWorker globals while maintaining self === globalThis
778
1043
  */
779
1044
  install() {
780
- globalThis.WorkerGlobalScope = WorkerGlobalScope;
781
- globalThis.DedicatedWorkerGlobalScope = DedicatedWorkerGlobalScope;
782
- globalThis.self = this;
783
- const isWorker = "onmessage" in globalThis;
784
- if (isWorker && typeof postMessage === "function") {
785
- this.postMessage = postMessage.bind(globalThis);
786
- }
787
- globalThis.addEventListener = this.addEventListener.bind(this);
788
- globalThis.removeEventListener = this.removeEventListener.bind(this);
789
- globalThis.dispatchEvent = this.dispatchEvent.bind(this);
790
- if (this.caches) {
791
- globalThis.caches = this.caches;
792
- }
793
- if (this.buckets) {
794
- globalThis.buckets = this.buckets;
795
- }
796
- globalThis.registration = this.registration;
797
- globalThis.skipWaiting = this.skipWaiting.bind(this);
798
- globalThis.clients = this.clients;
799
- }
800
- };
801
- var logger = getLogger(["worker"]);
802
- async function initializeWorker() {
803
- const messagePort = globalThis;
804
- const sendMessage2 = (message, transfer) => {
805
- if (transfer && transfer.length > 0) {
806
- postMessage(message, transfer);
807
- } else {
808
- postMessage(message);
1045
+ const g = globalThis;
1046
+ g.WorkerGlobalScope = WorkerGlobalScope;
1047
+ g.DedicatedWorkerGlobalScope = DedicatedWorkerGlobalScope;
1048
+ if (typeof g.self === "undefined") {
1049
+ g.self = globalThis;
809
1050
  }
810
- };
811
- onmessage = function(event) {
812
- void handleMessage(event.data);
813
- };
814
- return { messagePort, sendMessage: sendMessage2 };
815
- }
816
- var registration = null;
817
- var scope = null;
818
- var _workerSelf = null;
819
- var currentApp = null;
820
- var serviceWorkerReady = false;
821
- var loadedEntrypoint = null;
822
- var caches;
823
- var buckets;
824
- async function handleFetchEvent(request) {
825
- if (!currentApp || !serviceWorkerReady) {
826
- throw new Error("ServiceWorker not ready");
827
- }
828
- if (!registration) {
829
- throw new Error("ServiceWorker runtime not initialized");
830
- }
831
- try {
832
- const response = await registration.handleRequest(request);
833
- return response;
834
- } catch (error) {
835
- logger.error("[Worker] ServiceWorker request failed", { error });
836
- console.error("[Worker] ServiceWorker request failed:", error);
837
- const response = new Response("ServiceWorker request failed", {
838
- status: 500
1051
+ g.addEventListener = this.addEventListener.bind(this);
1052
+ g.removeEventListener = this.removeEventListener.bind(this);
1053
+ g.dispatchEvent = this.dispatchEvent.bind(this);
1054
+ g.caches = this.caches;
1055
+ g.directories = this.directories;
1056
+ g.loggers = this.loggers;
1057
+ g.registration = this.registration;
1058
+ g.skipWaiting = this.skipWaiting.bind(this);
1059
+ g.clients = this.clients;
1060
+ g.fetch = this.fetch.bind(this);
1061
+ Object.defineProperty(g, "cookieStore", {
1062
+ get: () => cookieStoreStorage.get(),
1063
+ configurable: true
839
1064
  });
840
- return response;
841
1065
  }
842
- }
843
- async function loadServiceWorker(entrypoint) {
844
- try {
845
- logger.debug("loadServiceWorker called", {
846
- entrypoint,
847
- loadedEntrypoint
848
- });
849
- logger.info("[Worker] Loading from", { entrypoint });
850
- if (loadedEntrypoint !== null && loadedEntrypoint !== entrypoint) {
851
- logger.info(
852
- `[Worker] Hot reload detected: ${loadedEntrypoint} -> ${entrypoint}`
853
- );
854
- logger.info("[Worker] Creating completely fresh ServiceWorker context");
855
- registration = new ShovelServiceWorkerRegistration();
856
- if (!caches || !buckets) {
857
- throw new Error("Runtime not initialized - missing caches or buckets");
1066
+ /**
1067
+ * Restore original globals (for testing)
1068
+ * Reverts all patched globals to their original values
1069
+ */
1070
+ restore() {
1071
+ const g = globalThis;
1072
+ for (const key of PATCHED_KEYS) {
1073
+ const original = this.#originals[key];
1074
+ if (original === void 0) {
1075
+ delete g[key];
1076
+ } else {
1077
+ g[key] = original;
858
1078
  }
859
- scope = new ShovelGlobalScope({
860
- registration,
861
- caches,
862
- buckets
863
- });
864
- scope.install();
865
- _workerSelf = scope;
866
- currentApp = null;
867
- serviceWorkerReady = false;
868
- }
869
- if (loadedEntrypoint === entrypoint) {
870
- logger.info("[Worker] ServiceWorker already loaded for entrypoint", {
871
- entrypoint
872
- });
873
- return;
874
1079
  }
875
- const appModule = await import(entrypoint);
876
- loadedEntrypoint = entrypoint;
877
- currentApp = appModule;
878
- if (!registration) {
879
- throw new Error("ServiceWorker runtime not initialized");
880
- }
881
- await registration.install();
882
- await registration.activate();
883
- serviceWorkerReady = true;
884
- logger.info(
885
- `[Worker] ServiceWorker loaded and activated from ${entrypoint}`
1080
+ }
1081
+ };
1082
+ var SHOVEL_CATEGORIES = [
1083
+ "cli",
1084
+ "build",
1085
+ "platform",
1086
+ "watcher",
1087
+ "worker",
1088
+ "single-threaded",
1089
+ "assets",
1090
+ "platform-node",
1091
+ "platform-bun",
1092
+ "platform-cloudflare",
1093
+ "cache",
1094
+ "cache-redis",
1095
+ "router"
1096
+ ];
1097
+ var BUILTIN_SINK_PROVIDERS = {
1098
+ console: { module: "@logtape/logtape", factory: "getConsoleSink" },
1099
+ file: { module: "@logtape/file", factory: "getFileSink" },
1100
+ rotating: { module: "@logtape/file", factory: "getRotatingFileSink" },
1101
+ "stream-file": { module: "@logtape/file", factory: "getStreamFileSink" },
1102
+ otel: { module: "@logtape/otel", factory: "getOpenTelemetrySink" },
1103
+ sentry: { module: "@logtape/sentry", factory: "getSentrySink" },
1104
+ syslog: { module: "@logtape/syslog", factory: "getSyslogSink" },
1105
+ cloudwatch: {
1106
+ module: "@logtape/cloudwatch-logs",
1107
+ factory: "getCloudWatchLogsSink"
1108
+ }
1109
+ };
1110
+ async function createSink(config) {
1111
+ const { provider, factory: preImportedFactory, ...sinkOptions } = config;
1112
+ if (preImportedFactory) {
1113
+ return preImportedFactory(sinkOptions);
1114
+ }
1115
+ const builtin = BUILTIN_SINK_PROVIDERS[provider];
1116
+ const modulePath = builtin?.module || provider;
1117
+ const factoryName = builtin?.factory || "default";
1118
+ const module = await import(modulePath);
1119
+ const factory = module[factoryName] || module.default;
1120
+ if (!factory) {
1121
+ throw new Error(
1122
+ `Sink module "${modulePath}" has no export "${factoryName}"`
886
1123
  );
887
- } catch (error) {
888
- const errorMessage = error instanceof Error ? error.message : String(error);
889
- const errorStack = error instanceof Error ? error.stack : void 0;
890
- logger.error("[Worker] Failed to load ServiceWorker", {
891
- error: errorMessage,
892
- stack: errorStack,
893
- entrypoint
894
- });
895
- serviceWorkerReady = false;
896
- throw error;
897
1124
  }
1125
+ return factory(sinkOptions);
898
1126
  }
899
- var workerId = Math.random().toString(36).substring(2, 8);
900
- var sendMessage;
901
- async function initializeRuntime(config, baseDir) {
902
- try {
903
- if (config?.logging) {
904
- await configureLogging(config.logging);
905
- }
906
- logger.info(`[Worker-${workerId}] Initializing runtime with config`, {
907
- config,
908
- baseDir
909
- });
910
- logger.info(`[Worker-${workerId}] Creating cache storage`);
911
- caches = new CustomCacheStorage(createCacheFactory({ config }));
912
- logger.info(`[Worker-${workerId}] Creating bucket storage`);
913
- buckets = new CustomBucketStorage(createBucketFactory({ baseDir, config }));
914
- logger.info(`[Worker-${workerId}] Creating and installing scope`);
915
- registration = new ShovelServiceWorkerRegistration();
916
- scope = new ShovelGlobalScope({ registration, caches, buckets });
917
- scope.install();
918
- _workerSelf = scope;
919
- logger.info(`[Worker-${workerId}] Runtime initialized successfully`);
920
- } catch (error) {
921
- logger.error(`[Worker-${workerId}] Failed to initialize runtime`, { error });
922
- throw error;
1127
+ async function configureLogging(loggingConfig, options = {}) {
1128
+ const level = loggingConfig.level || "info";
1129
+ const defaultSinkConfigs = loggingConfig.sinks || [{ provider: "console" }];
1130
+ const categories = loggingConfig.categories || {};
1131
+ const reset = options.reset !== false;
1132
+ const sinkByKey = /* @__PURE__ */ new Map();
1133
+ for (const config of defaultSinkConfigs) {
1134
+ const key = JSON.stringify(config);
1135
+ if (!sinkByKey.has(key)) {
1136
+ sinkByKey.set(key, { config, name: `sink_${sinkByKey.size}` });
1137
+ }
923
1138
  }
924
- }
925
- async function handleMessage(message) {
926
- try {
927
- logger.info(`[Worker-${workerId}] Received message`, { type: message.type });
928
- if (message.type === "init") {
929
- const initMsg = message;
930
- await initializeRuntime(initMsg.config, initMsg.baseDir);
931
- logger.info(`[Worker-${workerId}] Sending initialized message`);
932
- sendMessage({ type: "initialized" });
933
- } else if (message.type === "load") {
934
- const loadMsg = message;
935
- await loadServiceWorker(loadMsg.entrypoint);
936
- sendMessage({ type: "ready", entrypoint: loadMsg.entrypoint });
937
- } else if (message.type === "request") {
938
- const reqMsg = message;
939
- const request = new Request(reqMsg.request.url, {
940
- method: reqMsg.request.method,
941
- headers: reqMsg.request.headers,
942
- body: reqMsg.request.body
943
- });
944
- const response = await handleFetchEvent(request);
945
- const body = await response.arrayBuffer();
946
- const headers = Object.fromEntries(response.headers.entries());
947
- if (!headers["Content-Type"] && !headers["content-type"]) {
948
- headers["Content-Type"] = "text/plain; charset=utf-8";
1139
+ for (const [_, categoryConfig] of Object.entries(categories)) {
1140
+ if (categoryConfig.sinks) {
1141
+ for (const config of categoryConfig.sinks) {
1142
+ const key = JSON.stringify(config);
1143
+ if (!sinkByKey.has(key)) {
1144
+ sinkByKey.set(key, { config, name: `sink_${sinkByKey.size}` });
1145
+ }
949
1146
  }
950
- const responseMsg = {
951
- type: "response",
952
- response: {
953
- status: response.status,
954
- statusText: response.statusText,
955
- headers,
956
- body
957
- },
958
- requestID: reqMsg.requestID
959
- };
960
- sendMessage(responseMsg, [body]);
961
- }
962
- } catch (error) {
963
- const errorMsg = {
964
- type: "error",
965
- error: error instanceof Error ? error.message : String(error),
966
- stack: error instanceof Error ? error.stack : void 0,
967
- requestID: message.requestID
968
- };
969
- sendMessage(errorMsg);
1147
+ }
970
1148
  }
971
- }
972
- if (typeof onmessage !== "undefined") {
973
- initializeWorker().then(({ messagePort: _messagePort, sendMessage: send }) => {
974
- sendMessage = send;
975
- sendMessage({ type: "worker-ready" });
976
- }).catch((error) => {
977
- logger.error("[Worker] Failed to initialize:", error);
1149
+ const sinks = {};
1150
+ for (const { config, name } of sinkByKey.values()) {
1151
+ sinks[name] = await createSink(config);
1152
+ }
1153
+ const getSinkNames = (configs) => {
1154
+ return configs.map((config) => sinkByKey.get(JSON.stringify(config))?.name ?? "").filter(Boolean);
1155
+ };
1156
+ const defaultSinkNames = getSinkNames(defaultSinkConfigs);
1157
+ const loggers = SHOVEL_CATEGORIES.map((category) => {
1158
+ const categoryConfig = categories[category];
1159
+ const categoryLevel = categoryConfig?.level || level;
1160
+ const categorySinks = categoryConfig?.sinks ? getSinkNames(categoryConfig.sinks) : defaultSinkNames;
1161
+ return {
1162
+ category: [category],
1163
+ level: categoryLevel,
1164
+ sinks: categorySinks
1165
+ };
1166
+ });
1167
+ loggers.push({
1168
+ category: ["logtape", "meta"],
1169
+ level: "warning",
1170
+ sinks: []
1171
+ });
1172
+ await configure({
1173
+ reset,
1174
+ sinks,
1175
+ loggers
978
1176
  });
979
1177
  }
980
1178
  export {
981
1179
  ActivateEvent,
1180
+ CustomLoggerStorage,
982
1181
  DedicatedWorkerGlobalScope,
983
1182
  ExtendableEvent,
984
1183
  ExtendableMessageEvent,
@@ -987,9 +1186,10 @@ export {
987
1186
  Notification,
988
1187
  NotificationEvent,
989
1188
  PushEvent,
1189
+ RequestCookieStore,
1190
+ ServiceWorkerGlobals,
990
1191
  ShovelClient,
991
1192
  ShovelClients,
992
- ShovelGlobalScope,
993
1193
  ShovelNavigationPreloadManager,
994
1194
  ShovelPushMessageData,
995
1195
  ShovelServiceWorker,
@@ -997,5 +1197,9 @@ export {
997
1197
  ShovelServiceWorkerRegistration,
998
1198
  ShovelWindowClient,
999
1199
  SyncEvent,
1000
- WorkerGlobalScope
1200
+ WorkerGlobalScope,
1201
+ configureLogging,
1202
+ parseCookieHeader,
1203
+ parseSetCookieHeader,
1204
+ serializeCookie
1001
1205
  };