@b9g/platform 0.1.11 → 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
@@ -2,13 +2,228 @@
2
2
  import "../chunk-P57PW2II.js";
3
3
 
4
4
  // src/runtime.ts
5
- import { RequestCookieStore } from "./cookie-store.js";
6
5
  import { AsyncContext } from "@b9g/async-context";
7
- import { CustomBucketStorage } from "@b9g/filesystem";
8
- import { CustomCacheStorage } from "@b9g/cache";
9
- import { createBucketFactory, createCacheFactory } from "./config.js";
10
- import { getLogger } from "@logtape/logtape";
11
- 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
+ }
12
227
  if (import.meta.env && !import.meta.env.MODE && import.meta.env.NODE_ENV) {
13
228
  import.meta.env.MODE = import.meta.env.NODE_ENV;
14
229
  }
@@ -19,7 +234,8 @@ var PATCHED_KEYS = [
19
234
  "self",
20
235
  "fetch",
21
236
  "caches",
22
- "buckets",
237
+ "directories",
238
+ "loggers",
23
239
  "registration",
24
240
  "clients",
25
241
  "skipWaiting",
@@ -133,9 +349,7 @@ var ShovelClient = class {
133
349
  }
134
350
  // Implementation
135
351
  postMessage(_message, _transferOrOptions) {
136
- logger.warn(
137
- "[ServiceWorker] Client.postMessage() not supported in server context"
138
- );
352
+ self.loggers.open("platform").warn("Client.postMessage() not supported in server context");
139
353
  }
140
354
  };
141
355
  var ShovelWindowClient = class extends ShovelClient {
@@ -147,15 +361,11 @@ var ShovelWindowClient = class extends ShovelClient {
147
361
  this.visibilityState = options.visibilityState || "hidden";
148
362
  }
149
363
  async focus() {
150
- logger.warn(
151
- "[ServiceWorker] WindowClient.focus() not supported in server context"
152
- );
364
+ self.loggers.open("platform").warn("WindowClient.focus() not supported in server context");
153
365
  return this;
154
366
  }
155
367
  async navigate(_url) {
156
- logger.warn(
157
- "[ServiceWorker] WindowClient.navigate() not supported in server context"
158
- );
368
+ self.loggers.open("platform").warn("WindowClient.navigate() not supported in server context");
159
369
  return null;
160
370
  }
161
371
  };
@@ -169,9 +379,7 @@ var ShovelClients = class {
169
379
  return [];
170
380
  }
171
381
  async openWindow(_url) {
172
- logger.warn(
173
- "[ServiceWorker] Clients.openWindow() not supported in server context"
174
- );
382
+ self.loggers.open("platform").warn("Clients.openWindow() not supported in server context");
175
383
  return null;
176
384
  }
177
385
  };
@@ -205,9 +413,7 @@ var ShovelServiceWorker = class extends EventTarget {
205
413
  }
206
414
  // Implementation
207
415
  postMessage(_message, _transferOrOptions) {
208
- logger.warn(
209
- "[ServiceWorker] ServiceWorker.postMessage() not implemented in server context"
210
- );
416
+ self.loggers.open("platform").warn("ServiceWorker.postMessage() not implemented in server context");
211
417
  }
212
418
  // Internal method to update state and dispatch statechange event
213
419
  _setState(newState) {
@@ -239,9 +445,9 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
239
445
  cookies;
240
446
  pushManager;
241
447
  onupdatefound;
242
- constructor(scope2 = "/", scriptURL = "/") {
448
+ constructor(scope = "/", scriptURL = "/") {
243
449
  super();
244
- this.scope = scope2;
450
+ this.scope = scope;
245
451
  this.updateViaCache = "imports";
246
452
  this.navigationPreload = new ShovelNavigationPreloadManager();
247
453
  this._serviceWorker = new ShovelServiceWorker(scriptURL, "parsed");
@@ -264,9 +470,7 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
264
470
  return [];
265
471
  }
266
472
  async showNotification(_title, _options) {
267
- logger.warn(
268
- "[ServiceWorker] Notifications not supported in server context"
269
- );
473
+ self.loggers.open("platform").warn("Notifications not supported in server context");
270
474
  }
271
475
  async sync() {
272
476
  }
@@ -365,7 +569,9 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
365
569
  const response = await event.getResponse();
366
570
  const promises = event.getPromises();
367
571
  if (promises.length > 0) {
368
- Promise.allSettled(promises).catch(logger.error);
572
+ Promise.allSettled(promises).catch(
573
+ (err) => self.loggers.open("platform").error`waitUntil error: ${err}`
574
+ );
369
575
  }
370
576
  if (event.cookieStore.hasChanges()) {
371
577
  const setCookieHeaders = event.cookieStore.getSetCookieHeaders();
@@ -412,8 +618,8 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
412
618
  /**
413
619
  * Get registration for a specific scope
414
620
  */
415
- async getRegistration(scope2 = "/") {
416
- return this.#registrations.get(scope2);
621
+ async getRegistration(scope = "/") {
622
+ return this.#registrations.get(scope);
417
623
  }
418
624
  /**
419
625
  * Get all registrations
@@ -426,26 +632,26 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
426
632
  */
427
633
  async register(scriptURL, options) {
428
634
  const url = typeof scriptURL === "string" ? scriptURL : scriptURL.toString();
429
- const scope2 = this.#normalizeScope(options?.scope || "/");
430
- let registration2 = this.#registrations.get(scope2);
431
- if (registration2) {
432
- registration2._serviceWorker.scriptURL = url;
433
- 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");
434
640
  } else {
435
- registration2 = new ShovelServiceWorkerRegistration(scope2, url);
436
- this.#registrations.set(scope2, registration2);
641
+ registration = new ShovelServiceWorkerRegistration(scope, url);
642
+ this.#registrations.set(scope, registration);
437
643
  this.dispatchEvent(new Event("updatefound"));
438
644
  }
439
- return registration2;
645
+ return registration;
440
646
  }
441
647
  /**
442
648
  * Unregister a ServiceWorker registration
443
649
  */
444
- async unregister(scope2) {
445
- const registration2 = this.#registrations.get(scope2);
446
- if (registration2) {
447
- await registration2.unregister();
448
- 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);
449
655
  return true;
450
656
  }
451
657
  return false;
@@ -458,9 +664,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
458
664
  const pathname = url.pathname;
459
665
  const matchingScope = this.#findMatchingScope(pathname);
460
666
  if (matchingScope) {
461
- const registration2 = this.#registrations.get(matchingScope);
462
- if (registration2 && registration2.ready) {
463
- return await registration2.handleRequest(request);
667
+ const registration = this.#registrations.get(matchingScope);
668
+ if (registration && registration.ready) {
669
+ return await registration.handleRequest(request);
464
670
  }
465
671
  }
466
672
  return null;
@@ -470,9 +676,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
470
676
  */
471
677
  async installAll() {
472
678
  const installations = Array.from(this.#registrations.values()).map(
473
- async (registration2) => {
474
- await registration2.install();
475
- await registration2.activate();
679
+ async (registration) => {
680
+ await registration.install();
681
+ await registration.activate();
476
682
  }
477
683
  );
478
684
  await promiseWithTimeout(
@@ -492,14 +698,14 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
492
698
  /**
493
699
  * Normalize scope to ensure it starts and ends correctly
494
700
  */
495
- #normalizeScope(scope2) {
496
- if (!scope2.startsWith("/")) {
497
- scope2 = "/" + scope2;
701
+ #normalizeScope(scope) {
702
+ if (!scope.startsWith("/")) {
703
+ scope = "/" + scope;
498
704
  }
499
- if (scope2 !== "/" && !scope2.endsWith("/")) {
500
- scope2 = scope2 + "/";
705
+ if (scope !== "/" && !scope.endsWith("/")) {
706
+ scope = scope + "/";
501
707
  }
502
- return scope2;
708
+ return scope;
503
709
  }
504
710
  /**
505
711
  * Find the most specific scope that matches a pathname
@@ -507,9 +713,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
507
713
  #findMatchingScope(pathname) {
508
714
  const scopes = Array.from(this.#registrations.keys());
509
715
  scopes.sort((a, b) => b.length - a.length);
510
- for (const scope2 of scopes) {
511
- if (pathname.startsWith(scope2 === "/" ? "/" : scope2)) {
512
- return scope2;
716
+ for (const scope of scopes) {
717
+ if (pathname.startsWith(scope === "/" ? "/" : scope)) {
718
+ return scope;
513
719
  }
514
720
  }
515
721
  return null;
@@ -561,9 +767,7 @@ var Notification = class extends EventTarget {
561
767
  this.onshow = null;
562
768
  }
563
769
  close() {
564
- logger.warn(
565
- "[ServiceWorker] Notification.close() not supported in server context"
566
- );
770
+ self.loggers.open("platform").warn("Notification.close() not supported in server context");
567
771
  }
568
772
  static async requestPermission() {
569
773
  return "denied";
@@ -637,7 +841,8 @@ var ServiceWorkerGlobals = class {
637
841
  registration;
638
842
  // Storage APIs
639
843
  caches;
640
- buckets;
844
+ directories;
845
+ loggers;
641
846
  // Clients API
642
847
  // Our custom Clients implementation provides core functionality compatible with the Web API
643
848
  clients;
@@ -664,9 +869,7 @@ var ServiceWorkerGlobals = class {
664
869
  crypto;
665
870
  // WorkerGlobalScope methods (stubs for server context)
666
871
  importScripts(..._urls) {
667
- logger.warn(
668
- "[ServiceWorker] importScripts() not supported in server context"
669
- );
872
+ self.loggers.open("platform").warn("importScripts() not supported in server context");
670
873
  }
671
874
  atob(data) {
672
875
  return globalThis.atob(data);
@@ -707,7 +910,7 @@ var ServiceWorkerGlobals = class {
707
910
  globalThis.queueMicrotask(callback);
708
911
  }
709
912
  reportError(e) {
710
- logger.error("[ServiceWorker] reportError:", e);
913
+ self.loggers.open("platform").error`reportError: ${e}`;
711
914
  }
712
915
  setInterval(handler, timeout, ...args) {
713
916
  return globalThis.setInterval(handler, timeout, ...args);
@@ -747,7 +950,8 @@ var ServiceWorkerGlobals = class {
747
950
  this.self = globalThis;
748
951
  this.registration = options.registration;
749
952
  this.caches = options.caches;
750
- this.buckets = options.buckets;
953
+ this.directories = options.directories;
954
+ this.loggers = options.loggers;
751
955
  this.#isDevelopment = options.isDevelopment ?? false;
752
956
  this.clients = this.#createClientsAPI();
753
957
  this.serviceWorker = null;
@@ -783,28 +987,48 @@ var ServiceWorkerGlobals = class {
783
987
  * Allows the ServiceWorker to activate immediately
784
988
  */
785
989
  async skipWaiting() {
786
- logger.info("[ServiceWorker] skipWaiting() called");
990
+ self.loggers.open("platform").info("skipWaiting() called");
787
991
  if (!this.#isDevelopment) {
788
- logger.info(
789
- "[ServiceWorker] skipWaiting() - production graceful restart not implemented"
790
- );
992
+ self.loggers.open("platform").info("skipWaiting() - production graceful restart not implemented");
791
993
  }
792
994
  }
793
995
  /**
794
- * 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
795
998
  */
796
999
  addEventListener(type, listener, options) {
797
- if (listener) {
1000
+ if (!listener)
1001
+ return;
1002
+ if (isServiceWorkerEvent(type)) {
798
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
+ }
799
1009
  }
800
1010
  }
801
1011
  removeEventListener(type, listener, options) {
802
- if (listener) {
1012
+ if (!listener)
1013
+ return;
1014
+ if (isServiceWorkerEvent(type)) {
803
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
+ }
804
1021
  }
805
1022
  }
806
1023
  dispatchEvent(event) {
807
- 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;
808
1032
  }
809
1033
  /**
810
1034
  * Create Clients API implementation
@@ -828,7 +1052,8 @@ var ServiceWorkerGlobals = class {
828
1052
  g.removeEventListener = this.removeEventListener.bind(this);
829
1053
  g.dispatchEvent = this.dispatchEvent.bind(this);
830
1054
  g.caches = this.caches;
831
- g.buckets = this.buckets;
1055
+ g.directories = this.directories;
1056
+ g.loggers = this.loggers;
832
1057
  g.registration = this.registration;
833
1058
  g.skipWaiting = this.skipWaiting.bind(this);
834
1059
  g.clients = this.clients;
@@ -854,187 +1079,105 @@ var ServiceWorkerGlobals = class {
854
1079
  }
855
1080
  }
856
1081
  };
857
- var logger = getLogger(["worker"]);
858
- async function initializeWorker() {
859
- const messagePort = globalThis;
860
- const sendMessage2 = (message, transfer) => {
861
- if (transfer && transfer.length > 0) {
862
- postMessage(message, transfer);
863
- } else {
864
- postMessage(message);
865
- }
866
- };
867
- onmessage = function(event) {
868
- void handleMessage(event.data);
869
- };
870
- return { messagePort, sendMessage: sendMessage2 };
871
- }
872
- var registration = null;
873
- var scope = null;
874
- var _workerSelf = null;
875
- var currentApp = null;
876
- var serviceWorkerReady = false;
877
- var loadedEntrypoint = null;
878
- var caches;
879
- var buckets;
880
- async function handleFetchEvent(request) {
881
- if (!currentApp || !serviceWorkerReady) {
882
- throw new Error("ServiceWorker not ready");
883
- }
884
- if (!registration) {
885
- throw new Error("ServiceWorker runtime not initialized");
886
- }
887
- try {
888
- const response = await registration.handleRequest(request);
889
- return response;
890
- } catch (error) {
891
- logger.error("[Worker] ServiceWorker request failed: {error}", { error });
892
- console.error("[Worker] ServiceWorker request failed:", error);
893
- const response = new Response("ServiceWorker request failed", {
894
- status: 500
895
- });
896
- return response;
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"
897
1108
  }
898
- }
899
- async function loadServiceWorker(entrypoint) {
900
- try {
901
- logger.debug("loadServiceWorker called", {
902
- entrypoint,
903
- loadedEntrypoint
904
- });
905
- logger.info("[Worker] Loading from", { entrypoint });
906
- if (loadedEntrypoint !== null && loadedEntrypoint !== entrypoint) {
907
- logger.info(
908
- `[Worker] Hot reload detected: ${loadedEntrypoint} -> ${entrypoint}`
909
- );
910
- logger.info("[Worker] Creating completely fresh ServiceWorker context");
911
- registration = new ShovelServiceWorkerRegistration();
912
- if (!caches || !buckets) {
913
- throw new Error("Runtime not initialized - missing caches or buckets");
914
- }
915
- scope = new ServiceWorkerGlobals({
916
- registration,
917
- caches,
918
- buckets
919
- });
920
- scope.install();
921
- _workerSelf = scope;
922
- currentApp = null;
923
- serviceWorkerReady = false;
924
- }
925
- if (loadedEntrypoint === entrypoint) {
926
- logger.info("[Worker] ServiceWorker already loaded for entrypoint", {
927
- entrypoint
928
- });
929
- return;
930
- }
931
- const appModule = await import(entrypoint);
932
- loadedEntrypoint = entrypoint;
933
- currentApp = appModule;
934
- if (!registration) {
935
- throw new Error("ServiceWorker runtime not initialized");
936
- }
937
- await registration.install();
938
- await registration.activate();
939
- serviceWorkerReady = true;
940
- logger.info(
941
- `[Worker] ServiceWorker loaded and activated from ${entrypoint}`
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}"`
942
1123
  );
943
- } catch (error) {
944
- const errorMessage = error instanceof Error ? error.message : String(error);
945
- const errorStack = error instanceof Error ? error.stack : void 0;
946
- logger.error("[Worker] Failed to load ServiceWorker", {
947
- error: errorMessage,
948
- stack: errorStack,
949
- entrypoint
950
- });
951
- serviceWorkerReady = false;
952
- throw error;
953
1124
  }
1125
+ return factory(sinkOptions);
954
1126
  }
955
- var workerId = Math.random().toString(36).substring(2, 8);
956
- var sendMessage;
957
- async function initializeRuntime(config, baseDir) {
958
- try {
959
- if (config?.logging) {
960
- await configureLogging(config.logging);
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}` });
961
1137
  }
962
- logger.info(`[Worker-${workerId}] Initializing runtime with config`, {
963
- config,
964
- baseDir
965
- });
966
- logger.info(`[Worker-${workerId}] Creating cache storage`);
967
- caches = new CustomCacheStorage(createCacheFactory({ config }));
968
- logger.info(`[Worker-${workerId}] Creating bucket storage`);
969
- buckets = new CustomBucketStorage(createBucketFactory({ baseDir, config }));
970
- logger.info(`[Worker-${workerId}] Creating and installing scope`);
971
- registration = new ShovelServiceWorkerRegistration();
972
- scope = new ServiceWorkerGlobals({ registration, caches, buckets });
973
- scope.install();
974
- _workerSelf = scope;
975
- logger.info(`[Worker-${workerId}] Runtime initialized successfully`);
976
- } catch (error) {
977
- logger.error(`[Worker-${workerId}] Failed to initialize runtime`, { error });
978
- throw error;
979
1138
  }
980
- }
981
- async function handleMessage(message) {
982
- try {
983
- logger.info(`[Worker-${workerId}] Received message`, { type: message.type });
984
- if (message.type === "init") {
985
- const initMsg = message;
986
- await initializeRuntime(initMsg.config, initMsg.baseDir);
987
- logger.info(`[Worker-${workerId}] Sending initialized message`);
988
- sendMessage({ type: "initialized" });
989
- } else if (message.type === "load") {
990
- const loadMsg = message;
991
- await loadServiceWorker(loadMsg.entrypoint);
992
- sendMessage({ type: "ready", entrypoint: loadMsg.entrypoint });
993
- } else if (message.type === "request") {
994
- const reqMsg = message;
995
- const request = new Request(reqMsg.request.url, {
996
- method: reqMsg.request.method,
997
- headers: reqMsg.request.headers,
998
- body: reqMsg.request.body
999
- });
1000
- const response = await handleFetchEvent(request);
1001
- const body = await response.arrayBuffer();
1002
- const headers = Object.fromEntries(response.headers.entries());
1003
- if (!headers["Content-Type"] && !headers["content-type"]) {
1004
- 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
+ }
1005
1146
  }
1006
- const responseMsg = {
1007
- type: "response",
1008
- response: {
1009
- status: response.status,
1010
- statusText: response.statusText,
1011
- headers,
1012
- body
1013
- },
1014
- requestID: reqMsg.requestID
1015
- };
1016
- sendMessage(responseMsg, [body]);
1017
1147
  }
1018
- } catch (error) {
1019
- const errorMsg = {
1020
- type: "error",
1021
- error: error instanceof Error ? error.message : String(error),
1022
- stack: error instanceof Error ? error.stack : void 0,
1023
- requestID: message.requestID
1024
- };
1025
- sendMessage(errorMsg);
1026
1148
  }
1027
- }
1028
- if (typeof onmessage !== "undefined") {
1029
- initializeWorker().then(({ messagePort: _messagePort, sendMessage: send }) => {
1030
- sendMessage = send;
1031
- sendMessage({ type: "worker-ready" });
1032
- }).catch((error) => {
1033
- 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
1034
1176
  });
1035
1177
  }
1036
1178
  export {
1037
1179
  ActivateEvent,
1180
+ CustomLoggerStorage,
1038
1181
  DedicatedWorkerGlobalScope,
1039
1182
  ExtendableEvent,
1040
1183
  ExtendableMessageEvent,
@@ -1043,6 +1186,7 @@ export {
1043
1186
  Notification,
1044
1187
  NotificationEvent,
1045
1188
  PushEvent,
1189
+ RequestCookieStore,
1046
1190
  ServiceWorkerGlobals,
1047
1191
  ShovelClient,
1048
1192
  ShovelClients,
@@ -1053,5 +1197,9 @@ export {
1053
1197
  ShovelServiceWorkerRegistration,
1054
1198
  ShovelWindowClient,
1055
1199
  SyncEvent,
1056
- WorkerGlobalScope
1200
+ WorkerGlobalScope,
1201
+ configureLogging,
1202
+ parseCookieHeader,
1203
+ parseSetCookieHeader,
1204
+ serializeCookie
1057
1205
  };