@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/src/runtime.js CHANGED
@@ -1,15 +1,17 @@
1
1
  /// <reference types="./runtime.d.ts" />
2
- import "../chunk-P57PW2II.js";
3
-
4
2
  // src/runtime.ts
5
3
  import { AsyncContext } from "@b9g/async-context";
4
+ import { getLogger, getConsoleSink } from "@logtape/logtape";
5
+ import { CustomDirectoryStorage } from "@b9g/filesystem";
6
+ import { CustomCacheStorage } from "@b9g/cache";
7
+ import { handleCacheResponse, PostMessageCache } from "@b9g/cache/postmessage";
6
8
  import {
7
9
  configure
8
10
  } from "@logtape/logtape";
11
+ import { Database } from "@b9g/zen";
9
12
  function parseCookieHeader(cookieHeader) {
10
13
  const cookies = /* @__PURE__ */ new Map();
11
- if (!cookieHeader)
12
- return cookies;
14
+ if (!cookieHeader) return cookies;
13
15
  const pairs = cookieHeader.split(/;\s*/);
14
16
  for (const pair of pairs) {
15
17
  const [name, ...valueParts] = pair.split("=");
@@ -114,8 +116,7 @@ var RequestCookieStore = class extends EventTarget {
114
116
  }
115
117
  if (this.#changes.has(name)) {
116
118
  const change = this.#changes.get(name);
117
- if (change === null || change === void 0)
118
- return null;
119
+ if (change === null || change === void 0) return null;
119
120
  return {
120
121
  name: change.name,
121
122
  value: change.value,
@@ -136,8 +137,7 @@ var RequestCookieStore = class extends EventTarget {
136
137
  ...this.#changes.keys()
137
138
  ]);
138
139
  for (const cookieName of allNames) {
139
- if (name && cookieName !== name)
140
- continue;
140
+ if (name && cookieName !== name) continue;
141
141
  if (this.#changes.has(cookieName) && this.#changes.get(cookieName) === null) {
142
142
  continue;
143
143
  }
@@ -216,10 +216,122 @@ var CustomLoggerStorage = class {
216
216
  constructor(factory) {
217
217
  this.#factory = factory;
218
218
  }
219
- open(...categories) {
220
- return this.#factory(...categories);
219
+ get(categories) {
220
+ return this.#factory(categories);
221
+ }
222
+ };
223
+ var CustomDatabaseStorage = class {
224
+ #factory;
225
+ #databases;
226
+ #closers;
227
+ #pending;
228
+ constructor(factory) {
229
+ this.#factory = factory;
230
+ this.#databases = /* @__PURE__ */ new Map();
231
+ this.#closers = /* @__PURE__ */ new Map();
232
+ this.#pending = /* @__PURE__ */ new Map();
233
+ }
234
+ async open(name, version, onUpgrade) {
235
+ const existing = this.#databases.get(name);
236
+ if (existing) {
237
+ return existing;
238
+ }
239
+ const pending = this.#pending.get(name);
240
+ if (pending) {
241
+ return pending;
242
+ }
243
+ const promise = (async () => {
244
+ const { db, close } = await this.#factory(name);
245
+ if (onUpgrade) {
246
+ db.addEventListener("upgradeneeded", (e) => {
247
+ const event = e;
248
+ onUpgrade({
249
+ db,
250
+ oldVersion: event.oldVersion,
251
+ newVersion: event.newVersion,
252
+ waitUntil: (p) => event.waitUntil(p)
253
+ });
254
+ });
255
+ }
256
+ try {
257
+ await db.open(version);
258
+ } catch (err) {
259
+ await close();
260
+ throw err;
261
+ }
262
+ this.#databases.set(name, db);
263
+ this.#closers.set(name, close);
264
+ return db;
265
+ })().finally(() => {
266
+ this.#pending.delete(name);
267
+ });
268
+ this.#pending.set(name, promise);
269
+ return promise;
270
+ }
271
+ get(name) {
272
+ const db = this.#databases.get(name);
273
+ if (!db) {
274
+ throw new Error(
275
+ `Database "${name}" has not been opened. Call self.databases.open("${name}", version) in your activate handler first.`
276
+ );
277
+ }
278
+ return db;
279
+ }
280
+ async close(name) {
281
+ const pending = this.#pending.get(name);
282
+ if (pending) {
283
+ try {
284
+ await pending;
285
+ } catch (_err) {
286
+ return;
287
+ }
288
+ }
289
+ const closer = this.#closers.get(name);
290
+ if (closer) {
291
+ await closer();
292
+ this.#databases.delete(name);
293
+ this.#closers.delete(name);
294
+ }
295
+ }
296
+ async closeAll() {
297
+ if (this.#pending.size > 0) {
298
+ await Promise.allSettled(this.#pending.values());
299
+ }
300
+ const promises = Array.from(this.#databases.keys()).map(
301
+ (name) => this.close(name)
302
+ );
303
+ await Promise.allSettled(promises);
221
304
  }
222
305
  };
306
+ function createDatabaseFactory(configs) {
307
+ return async (name) => {
308
+ const config = configs[name];
309
+ if (!config) {
310
+ throw new Error(
311
+ `Database "${name}" is not configured. Available databases: ${Object.keys(configs).join(", ") || "(none)"}`
312
+ );
313
+ }
314
+ const { impl, url, ...driverOptions } = config;
315
+ if (!impl) {
316
+ throw new Error(
317
+ `Database "${name}" has no impl. Ensure the database module is configured in shovel.json.`
318
+ );
319
+ }
320
+ if (!url) {
321
+ throw new Error(
322
+ `Database "${name}" has no url. Ensure the database URL is configured.`
323
+ );
324
+ }
325
+ const driver = new impl(url, driverOptions);
326
+ const db = new Database(driver);
327
+ return {
328
+ db,
329
+ close: async () => {
330
+ await driver.close();
331
+ }
332
+ };
333
+ };
334
+ }
223
335
  var SERVICE_WORKER_EVENTS = ["fetch", "install", "activate"];
224
336
  function isServiceWorkerEvent(type) {
225
337
  return SERVICE_WORKER_EVENTS.includes(type);
@@ -235,6 +347,7 @@ var PATCHED_KEYS = [
235
347
  "fetch",
236
348
  "caches",
237
349
  "directories",
350
+ "databases",
238
351
  "loggers",
239
352
  "registration",
240
353
  "clients",
@@ -254,9 +367,9 @@ function promiseWithTimeout(promise, timeoutMs, errorMessage) {
254
367
  )
255
368
  ]);
256
369
  }
257
- var kEndDispatchPhase = Symbol.for("shovel.endDispatchPhase");
258
- var kCanExtend = Symbol.for("shovel.canExtend");
259
- var ExtendableEvent = class extends Event {
370
+ var kEndDispatchPhase = /* @__PURE__ */ Symbol.for("shovel.endDispatchPhase");
371
+ var kCanExtend = /* @__PURE__ */ Symbol.for("shovel.canExtend");
372
+ var ShovelExtendableEvent = class extends Event {
260
373
  #promises;
261
374
  #dispatchPhase;
262
375
  #pendingCount;
@@ -293,17 +406,33 @@ var ExtendableEvent = class extends Event {
293
406
  return this.#dispatchPhase || this.#pendingCount > 0;
294
407
  }
295
408
  };
296
- var FetchEvent = class extends ExtendableEvent {
409
+ var ShovelFetchEvent = class extends ShovelExtendableEvent {
297
410
  request;
298
411
  cookieStore;
412
+ clientId;
413
+ handled;
414
+ preloadResponse;
415
+ resultingClientId;
299
416
  #responsePromise;
300
417
  #responded;
301
- constructor(request, eventInitDict) {
302
- super("fetch", eventInitDict);
418
+ #platformWaitUntil;
419
+ constructor(request, options) {
420
+ super("fetch", options);
303
421
  this.request = request;
304
422
  this.cookieStore = new RequestCookieStore(request);
423
+ this.clientId = "";
424
+ this.handled = Promise.resolve(void 0);
425
+ this.preloadResponse = Promise.resolve(void 0);
426
+ this.resultingClientId = "";
305
427
  this.#responsePromise = null;
306
428
  this.#responded = false;
429
+ this.#platformWaitUntil = options?.platformWaitUntil;
430
+ }
431
+ waitUntil(promise) {
432
+ if (this.#platformWaitUntil) {
433
+ this.#platformWaitUntil(promise);
434
+ }
435
+ super.waitUntil(promise);
307
436
  }
308
437
  respondWith(response) {
309
438
  if (this.#responded) {
@@ -325,13 +454,17 @@ var FetchEvent = class extends ExtendableEvent {
325
454
  hasResponded() {
326
455
  return this.#responded;
327
456
  }
457
+ /** The URL of the request (convenience property) */
458
+ get url() {
459
+ return this.request.url;
460
+ }
328
461
  };
329
- var InstallEvent = class extends ExtendableEvent {
462
+ var ShovelInstallEvent = class extends ShovelExtendableEvent {
330
463
  constructor(eventInitDict) {
331
464
  super("install", eventInitDict);
332
465
  }
333
466
  };
334
- var ActivateEvent = class extends ExtendableEvent {
467
+ var ShovelActivateEvent = class extends ShovelExtendableEvent {
335
468
  constructor(eventInitDict) {
336
469
  super("activate", eventInitDict);
337
470
  }
@@ -383,7 +516,7 @@ var ShovelClients = class {
383
516
  return null;
384
517
  }
385
518
  };
386
- var ExtendableMessageEvent = class extends ExtendableEvent {
519
+ var ExtendableMessageEvent = class extends ShovelExtendableEvent {
387
520
  data;
388
521
  origin;
389
522
  lastEventId;
@@ -485,11 +618,10 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
485
618
  * Install the ServiceWorker (Shovel extension)
486
619
  */
487
620
  async install() {
488
- if (this._serviceWorker.state !== "parsed")
489
- return;
621
+ if (this._serviceWorker.state !== "parsed") return;
490
622
  this._serviceWorker._setState("installing");
491
623
  return new Promise((resolve, reject) => {
492
- const event = new InstallEvent();
624
+ const event = new ShovelInstallEvent();
493
625
  process.nextTick(() => {
494
626
  try {
495
627
  this.dispatchEvent(event);
@@ -524,7 +656,7 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
524
656
  }
525
657
  this._serviceWorker._setState("activating");
526
658
  return new Promise((resolve, reject) => {
527
- const event = new ActivateEvent();
659
+ const event = new ShovelActivateEvent();
528
660
  process.nextTick(() => {
529
661
  try {
530
662
  this.dispatchEvent(event);
@@ -552,12 +684,16 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
552
684
  }
553
685
  /**
554
686
  * Handle a fetch request (Shovel extension)
687
+ *
688
+ * Platforms create a ShovelFetchEvent (or subclass) with platform-specific
689
+ * properties and hooks, then pass it to this method for dispatching.
690
+ *
691
+ * @param event - The fetch event to handle (created by platform adapter)
555
692
  */
556
- async handleRequest(request) {
693
+ async handleRequest(event) {
557
694
  if (this._serviceWorker.state !== "activated") {
558
695
  throw new Error("ServiceWorker not activated");
559
696
  }
560
- const event = new FetchEvent(request);
561
697
  return cookieStoreStorage.run(event.cookieStore, async () => {
562
698
  this.dispatchEvent(event);
563
699
  event[kEndDispatchPhase]();
@@ -567,12 +703,6 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
567
703
  );
568
704
  }
569
705
  const response = await event.getResponse();
570
- const promises = event.getPromises();
571
- if (promises.length > 0) {
572
- Promise.allSettled(promises).catch(
573
- (err) => self.loggers.open("platform").error`waitUntil error: ${err}`
574
- );
575
- }
576
706
  if (event.cookieStore.hasChanges()) {
577
707
  const setCookieHeaders = event.cookieStore.getSetCookieHeaders();
578
708
  const headers = new Headers(response.headers);
@@ -666,7 +796,8 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
666
796
  if (matchingScope) {
667
797
  const registration = this.#registrations.get(matchingScope);
668
798
  if (registration && registration.ready) {
669
- return await registration.handleRequest(request);
799
+ const event = new ShovelFetchEvent(request);
800
+ return await registration.handleRequest(event);
670
801
  }
671
802
  }
672
803
  return null;
@@ -775,7 +906,7 @@ var Notification = class extends EventTarget {
775
906
  // Events: click, close, error, show
776
907
  };
777
908
  Notification.permission = "denied";
778
- var NotificationEvent = class extends ExtendableEvent {
909
+ var NotificationEvent = class extends ShovelExtendableEvent {
779
910
  action;
780
911
  notification;
781
912
  reply;
@@ -786,7 +917,7 @@ var NotificationEvent = class extends ExtendableEvent {
786
917
  this.reply = eventInitDict.reply ?? null;
787
918
  }
788
919
  };
789
- var PushEvent = class extends ExtendableEvent {
920
+ var PushEvent = class extends ShovelExtendableEvent {
790
921
  data;
791
922
  constructor(type, eventInitDict) {
792
923
  super(type, eventInitDict);
@@ -819,7 +950,7 @@ var ShovelPushMessageData = class {
819
950
  return new TextDecoder().decode(this._data);
820
951
  }
821
952
  };
822
- var SyncEvent = class extends ExtendableEvent {
953
+ var SyncEvent = class extends ShovelExtendableEvent {
823
954
  tag;
824
955
  lastChance;
825
956
  constructor(type, eventInitDict) {
@@ -842,6 +973,7 @@ var ServiceWorkerGlobals = class {
842
973
  // Storage APIs
843
974
  caches;
844
975
  directories;
976
+ databases;
845
977
  loggers;
846
978
  // Clients API
847
979
  // Our custom Clients implementation provides core functionality compatible with the Web API
@@ -903,14 +1035,15 @@ var ServiceWorkerGlobals = class {
903
1035
  }
904
1036
  const request = new Request(new URL(urlString, "http://localhost"), init);
905
1037
  return fetchDepthStorage.run(currentDepth + 1, () => {
906
- return this.registration.handleRequest(request);
1038
+ const event = new ShovelFetchEvent(request);
1039
+ return this.registration.handleRequest(event);
907
1040
  });
908
1041
  }
909
1042
  queueMicrotask(callback) {
910
1043
  globalThis.queueMicrotask(callback);
911
1044
  }
912
1045
  reportError(e) {
913
- self.loggers.open("platform").error`reportError: ${e}`;
1046
+ getLogger(["shovel", "platform"]).error`reportError: ${e}`;
914
1047
  }
915
1048
  setInterval(handler, timeout, ...args) {
916
1049
  return globalThis.setInterval(handler, timeout, ...args);
@@ -951,6 +1084,7 @@ var ServiceWorkerGlobals = class {
951
1084
  this.registration = options.registration;
952
1085
  this.caches = options.caches;
953
1086
  this.directories = options.directories;
1087
+ this.databases = options.databases;
954
1088
  this.loggers = options.loggers;
955
1089
  this.#isDevelopment = options.isDevelopment ?? false;
956
1090
  this.clients = this.#createClientsAPI();
@@ -987,9 +1121,11 @@ var ServiceWorkerGlobals = class {
987
1121
  * Allows the ServiceWorker to activate immediately
988
1122
  */
989
1123
  async skipWaiting() {
990
- self.loggers.open("platform").info("skipWaiting() called");
1124
+ getLogger(["shovel", "platform"]).info("skipWaiting() called");
991
1125
  if (!this.#isDevelopment) {
992
- self.loggers.open("platform").info("skipWaiting() - production graceful restart not implemented");
1126
+ getLogger(["shovel", "platform"]).info(
1127
+ "skipWaiting() - production graceful restart not implemented"
1128
+ );
993
1129
  }
994
1130
  }
995
1131
  /**
@@ -997,8 +1133,7 @@ var ServiceWorkerGlobals = class {
997
1133
  * other events (like "message" for worker threads) go to native handler
998
1134
  */
999
1135
  addEventListener(type, listener, options) {
1000
- if (!listener)
1001
- return;
1136
+ if (!listener) return;
1002
1137
  if (isServiceWorkerEvent(type)) {
1003
1138
  this.registration.addEventListener(type, listener, options);
1004
1139
  } else {
@@ -1009,8 +1144,7 @@ var ServiceWorkerGlobals = class {
1009
1144
  }
1010
1145
  }
1011
1146
  removeEventListener(type, listener, options) {
1012
- if (!listener)
1013
- return;
1147
+ if (!listener) return;
1014
1148
  if (isServiceWorkerEvent(type)) {
1015
1149
  this.registration.removeEventListener(type, listener, options);
1016
1150
  } else {
@@ -1053,6 +1187,9 @@ var ServiceWorkerGlobals = class {
1053
1187
  g.dispatchEvent = this.dispatchEvent.bind(this);
1054
1188
  g.caches = this.caches;
1055
1189
  g.directories = this.directories;
1190
+ if (this.databases) {
1191
+ g.databases = this.databases;
1192
+ }
1056
1193
  g.loggers = this.loggers;
1057
1194
  g.registration = this.registration;
1058
1195
  g.skipWaiting = this.skipWaiting.bind(this);
@@ -1079,117 +1216,270 @@ var ServiceWorkerGlobals = class {
1079
1216
  }
1080
1217
  }
1081
1218
  };
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}"`
1123
- );
1124
- }
1125
- return factory(sinkOptions);
1219
+ function isClass(fn) {
1220
+ return typeof fn === "function" && fn.prototype !== void 0;
1221
+ }
1222
+ function createDirectoryFactory(configs) {
1223
+ return async (name) => {
1224
+ const config = configs[name];
1225
+ if (!config) {
1226
+ throw new Error(
1227
+ `Directory "${name}" is not configured. Available directories: ${Object.keys(configs).join(", ") || "(none)"}`
1228
+ );
1229
+ }
1230
+ const { impl, ...dirOptions } = config;
1231
+ if (!impl) {
1232
+ throw new Error(
1233
+ `Directory "${name}" has no impl. Ensure the directory module is configured.`
1234
+ );
1235
+ }
1236
+ if (isClass(impl)) {
1237
+ return new impl(name, dirOptions);
1238
+ } else {
1239
+ return impl(name, dirOptions);
1240
+ }
1241
+ };
1242
+ }
1243
+ function createCacheFactory(options) {
1244
+ const { configs, usePostMessage = false } = options;
1245
+ return async (name) => {
1246
+ if (usePostMessage) {
1247
+ return new PostMessageCache(name);
1248
+ }
1249
+ const config = configs[name];
1250
+ if (!config) {
1251
+ throw new Error(
1252
+ `Cache "${name}" is not configured. Available caches: ${Object.keys(configs).join(", ") || "(none)"}`
1253
+ );
1254
+ }
1255
+ const { impl, ...cacheOptions } = config;
1256
+ if (!impl) {
1257
+ throw new Error(
1258
+ `Cache "${name}" has no impl. Ensure the cache module is configured.`
1259
+ );
1260
+ }
1261
+ if (isClass(impl)) {
1262
+ return new impl(name, cacheOptions);
1263
+ } else {
1264
+ return impl(name, cacheOptions);
1265
+ }
1266
+ };
1267
+ }
1268
+ async function initWorkerRuntime(options) {
1269
+ const { config } = options;
1270
+ const runtimeLogger = getLogger(["shovel", "platform"]);
1271
+ if (config?.logging) {
1272
+ await configureLogging(config.logging);
1273
+ }
1274
+ runtimeLogger.debug("Initializing worker runtime");
1275
+ const caches = new CustomCacheStorage(
1276
+ createCacheFactory({
1277
+ configs: config?.caches ?? {},
1278
+ usePostMessage: true
1279
+ })
1280
+ );
1281
+ const directories = new CustomDirectoryStorage(
1282
+ createDirectoryFactory(config?.directories ?? {})
1283
+ );
1284
+ let databases;
1285
+ if (config?.databases && Object.keys(config.databases).length > 0) {
1286
+ const factory = createDatabaseFactory(config.databases);
1287
+ databases = new CustomDatabaseStorage(factory);
1288
+ }
1289
+ const loggers = new CustomLoggerStorage(
1290
+ (categories) => getLogger(categories)
1291
+ );
1292
+ const registration = new ShovelServiceWorkerRegistration();
1293
+ const scope = new ServiceWorkerGlobals({
1294
+ registration,
1295
+ caches,
1296
+ directories,
1297
+ databases,
1298
+ loggers
1299
+ });
1300
+ scope.install();
1301
+ runtimeLogger.debug("Worker runtime initialized");
1302
+ return { registration, scope, caches, directories, databases, loggers };
1126
1303
  }
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}` });
1304
+ function startWorkerMessageLoop(options) {
1305
+ const registration = options instanceof ShovelServiceWorkerRegistration ? options : options.registration;
1306
+ const databases = options instanceof ShovelServiceWorkerRegistration ? void 0 : options.databases;
1307
+ const messageLogger = getLogger(["shovel", "platform"]);
1308
+ const workerId = Math.random().toString(36).substring(2, 8);
1309
+ function sendMessage(message, transfer) {
1310
+ if (transfer && transfer.length > 0) {
1311
+ postMessage(message, transfer);
1312
+ } else {
1313
+ postMessage(message);
1137
1314
  }
1138
1315
  }
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
- }
1316
+ async function handleFetchRequest(message) {
1317
+ try {
1318
+ const request = new Request(message.request.url, {
1319
+ method: message.request.method,
1320
+ headers: message.request.headers,
1321
+ body: message.request.body
1322
+ });
1323
+ const event = new ShovelFetchEvent(request);
1324
+ const response = await registration.handleRequest(event);
1325
+ const body = await response.arrayBuffer();
1326
+ const headers = Object.fromEntries(response.headers.entries());
1327
+ if (!headers["Content-Type"] && !headers["content-type"]) {
1328
+ headers["Content-Type"] = "text/plain; charset=utf-8";
1146
1329
  }
1330
+ const responseMsg = {
1331
+ type: "response",
1332
+ response: {
1333
+ status: response.status,
1334
+ statusText: response.statusText,
1335
+ headers,
1336
+ body
1337
+ },
1338
+ requestID: message.requestID
1339
+ };
1340
+ sendMessage(responseMsg, [body]);
1341
+ } catch (error) {
1342
+ messageLogger.error(`[Worker-${workerId}] Request failed: {error}`, {
1343
+ error
1344
+ });
1345
+ const errorMsg = {
1346
+ type: "error",
1347
+ error: error instanceof Error ? error.message : String(error),
1348
+ stack: error instanceof Error ? error.stack : void 0,
1349
+ requestID: message.requestID
1350
+ };
1351
+ sendMessage(errorMsg);
1147
1352
  }
1148
1353
  }
1149
- const sinks = {};
1150
- for (const { config, name } of sinkByKey.values()) {
1151
- sinks[name] = await createSink(config);
1354
+ function handleMessage(event) {
1355
+ const message = event.data;
1356
+ if (message?.type === "cache:response" || message?.type === "cache:error") {
1357
+ messageLogger.debug(`[Worker-${workerId}] Forwarding cache message`, {
1358
+ type: message.type,
1359
+ requestID: message.requestID
1360
+ });
1361
+ handleCacheResponse(message);
1362
+ return;
1363
+ }
1364
+ if (message?.type === "request") {
1365
+ handleFetchRequest(message).catch((error) => {
1366
+ messageLogger.error(`[Worker-${workerId}] Unhandled error: {error}`, {
1367
+ error
1368
+ });
1369
+ });
1370
+ return;
1371
+ }
1372
+ if (message?.type === "shutdown") {
1373
+ messageLogger.debug(`[Worker-${workerId}] Received shutdown signal`);
1374
+ (async () => {
1375
+ try {
1376
+ if (databases) {
1377
+ await databases.closeAll();
1378
+ messageLogger.debug(`[Worker-${workerId}] Databases closed`);
1379
+ }
1380
+ sendMessage({ type: "shutdown-complete" });
1381
+ messageLogger.debug(`[Worker-${workerId}] Shutdown complete`);
1382
+ } catch (error) {
1383
+ messageLogger.error(`[Worker-${workerId}] Shutdown error: {error}`, {
1384
+ error
1385
+ });
1386
+ sendMessage({ type: "shutdown-complete" });
1387
+ }
1388
+ })();
1389
+ return;
1390
+ }
1391
+ if (message?.type) {
1392
+ messageLogger.debug(`[Worker-${workerId}] Unknown message type`, {
1393
+ type: message.type
1394
+ });
1395
+ }
1396
+ }
1397
+ self.addEventListener("message", handleMessage);
1398
+ sendMessage({ type: "ready" });
1399
+ messageLogger.debug(`[Worker-${workerId}] Message loop started`);
1400
+ }
1401
+ var SHOVEL_DEFAULT_LOGGERS = [
1402
+ { category: ["shovel"], level: "info", sinks: ["console"] },
1403
+ { category: ["logtape", "meta"], level: "warning", sinks: ["console"] }
1404
+ ];
1405
+ async function createSink(config) {
1406
+ const {
1407
+ impl,
1408
+ path,
1409
+ // Extract path for file-based sinks
1410
+ ...sinkOptions
1411
+ } = config;
1412
+ if (!impl) {
1413
+ throw new Error(
1414
+ `Sink has no impl. Ensure the sink module is configured in shovel.json.`
1415
+ );
1152
1416
  }
1153
- const getSinkNames = (configs) => {
1154
- return configs.map((config) => sinkByKey.get(JSON.stringify(config))?.name ?? "").filter(Boolean);
1417
+ if (path !== void 0) {
1418
+ return impl(path, sinkOptions);
1419
+ } else if (Object.keys(sinkOptions).length > 0) {
1420
+ return impl(sinkOptions);
1421
+ } else {
1422
+ return impl();
1423
+ }
1424
+ }
1425
+ function normalizeCategory(category) {
1426
+ return typeof category === "string" ? [category] : category;
1427
+ }
1428
+ async function configureLogging(loggingConfig) {
1429
+ const userSinks = loggingConfig.sinks || {};
1430
+ const userLoggers = loggingConfig.loggers || [];
1431
+ const sinks = {
1432
+ console: getConsoleSink()
1155
1433
  };
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
1434
+ for (const [name, config] of Object.entries(userSinks)) {
1435
+ sinks[name] = await createSink(config);
1436
+ }
1437
+ const userCategoryKeys = new Set(
1438
+ userLoggers.map((l) => JSON.stringify(normalizeCategory(l.category)))
1439
+ );
1440
+ const mergedLoggers = [
1441
+ // Shovel defaults (unless overridden by user)
1442
+ ...SHOVEL_DEFAULT_LOGGERS.filter(
1443
+ (l) => !userCategoryKeys.has(JSON.stringify(normalizeCategory(l.category)))
1444
+ ),
1445
+ // User loggers
1446
+ ...userLoggers
1447
+ ];
1448
+ const loggers = mergedLoggers.map((loggerConfig) => {
1449
+ const result = {
1450
+ category: normalizeCategory(loggerConfig.category)
1165
1451
  };
1166
- });
1167
- loggers.push({
1168
- category: ["logtape", "meta"],
1169
- level: "warning",
1170
- sinks: []
1452
+ if (loggerConfig.level) {
1453
+ result.lowestLevel = loggerConfig.level;
1454
+ }
1455
+ result.sinks = loggerConfig.sinks ?? ["console"];
1456
+ if (loggerConfig.parentSinks) {
1457
+ result.parentSinks = loggerConfig.parentSinks;
1458
+ }
1459
+ return result;
1171
1460
  });
1172
1461
  await configure({
1173
- reset,
1462
+ reset: true,
1174
1463
  sinks,
1175
1464
  loggers
1176
1465
  });
1177
1466
  }
1178
1467
  export {
1179
- ActivateEvent,
1468
+ CustomDatabaseStorage,
1180
1469
  CustomLoggerStorage,
1181
1470
  DedicatedWorkerGlobalScope,
1182
- ExtendableEvent,
1183
1471
  ExtendableMessageEvent,
1184
- FetchEvent,
1185
- InstallEvent,
1186
1472
  Notification,
1187
1473
  NotificationEvent,
1188
1474
  PushEvent,
1189
1475
  RequestCookieStore,
1190
1476
  ServiceWorkerGlobals,
1477
+ ShovelActivateEvent,
1191
1478
  ShovelClient,
1192
1479
  ShovelClients,
1480
+ ShovelExtendableEvent,
1481
+ ShovelFetchEvent,
1482
+ ShovelInstallEvent,
1193
1483
  ShovelNavigationPreloadManager,
1194
1484
  ShovelPushMessageData,
1195
1485
  ShovelServiceWorker,
@@ -1199,7 +1489,12 @@ export {
1199
1489
  SyncEvent,
1200
1490
  WorkerGlobalScope,
1201
1491
  configureLogging,
1492
+ createCacheFactory,
1493
+ createDatabaseFactory,
1494
+ createDirectoryFactory,
1495
+ initWorkerRuntime,
1202
1496
  parseCookieHeader,
1203
1497
  parseSetCookieHeader,
1204
- serializeCookie
1498
+ serializeCookie,
1499
+ startWorkerMessageLoop
1205
1500
  };