@b9g/platform 0.1.10 → 0.1.11

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.
@@ -0,0 +1,11 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ export {
10
+ __require
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/platform",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "ServiceWorker-first universal deployment platform. Write ServiceWorker apps once, deploy anywhere (Node/Bun/Cloudflare). Registry-based multi-app orchestration.",
5
5
  "keywords": [
6
6
  "serviceworker",
@@ -15,15 +15,39 @@
15
15
  "shovel"
16
16
  ],
17
17
  "dependencies": {
18
- "@b9g/async-context": "^0.1.1",
19
- "@b9g/cache": "^0.1.4",
20
- "@b9g/filesystem": "^0.1.6",
18
+ "@b9g/async-context": "^0.1.4",
19
+ "@b9g/cache": "^0.1.5",
20
+ "@b9g/filesystem": "^0.1.7",
21
21
  "@logtape/logtape": "^1.2.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@b9g/libuild": "^0.1.11",
24
+ "@b9g/libuild": "^0.1.18",
25
25
  "bun-types": "latest"
26
26
  },
27
+ "peerDependencies": {
28
+ "@logtape/file": "^1.0.0",
29
+ "@logtape/otel": "^1.0.0",
30
+ "@logtape/sentry": "^1.0.0",
31
+ "@logtape/syslog": "^1.0.0",
32
+ "@logtape/cloudwatch-logs": "^1.0.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "@logtape/file": {
36
+ "optional": true
37
+ },
38
+ "@logtape/otel": {
39
+ "optional": true
40
+ },
41
+ "@logtape/sentry": {
42
+ "optional": true
43
+ },
44
+ "@logtape/syslog": {
45
+ "optional": true
46
+ },
47
+ "@logtape/cloudwatch-logs": {
48
+ "optional": true
49
+ }
50
+ },
27
51
  "type": "module",
28
52
  "types": "src/index.d.ts",
29
53
  "module": "src/index.js",
package/src/config.d.ts CHANGED
@@ -58,13 +58,27 @@ export interface BucketConfig {
58
58
  }
59
59
  /** Log level for filtering */
60
60
  export type LogLevel = "debug" | "info" | "warning" | "error";
61
+ /** Sink configuration */
62
+ export interface SinkConfig {
63
+ provider: string;
64
+ /** Provider-specific options (path, maxSize, etc.) */
65
+ [key: string]: any;
66
+ }
67
+ /** Per-category logging configuration */
68
+ export interface CategoryLoggingConfig {
69
+ level?: LogLevel;
70
+ sinks?: SinkConfig[];
71
+ }
61
72
  export interface LoggingConfig {
62
- /** Default log level. Defaults to "error" */
73
+ /** Default log level. Defaults to "info" */
63
74
  level?: LogLevel;
64
- /** Per-category log levels (overrides default) */
65
- categories?: Record<string, LogLevel>;
75
+ /** Default sinks. Defaults to console */
76
+ sinks?: SinkConfig[];
77
+ /** Per-category config (inherits from top-level, can override level and/or sinks) */
78
+ categories?: Record<string, CategoryLoggingConfig>;
66
79
  }
67
80
  export interface ShovelConfig {
81
+ platform?: string;
68
82
  port?: number | string;
69
83
  host?: string;
70
84
  workers?: number | string;
@@ -72,11 +86,18 @@ export interface ShovelConfig {
72
86
  caches?: Record<string, CacheConfig>;
73
87
  buckets?: Record<string, BucketConfig>;
74
88
  }
89
+ /** Processed logging config with all defaults applied */
90
+ export interface ProcessedLoggingConfig {
91
+ level: LogLevel;
92
+ sinks: SinkConfig[];
93
+ categories: Record<string, CategoryLoggingConfig>;
94
+ }
75
95
  export interface ProcessedShovelConfig {
96
+ platform?: string;
76
97
  port: number;
77
98
  host: string;
78
99
  workers: number;
79
- logging: Required<LoggingConfig>;
100
+ logging: ProcessedLoggingConfig;
80
101
  caches: Record<string, CacheConfig>;
81
102
  buckets: Record<string, BucketConfig>;
82
103
  }
@@ -93,9 +114,11 @@ export declare function loadConfig(cwd: string): ProcessedShovelConfig;
93
114
  * @param loggingConfig - The logging configuration from ProcessedShovelConfig.logging
94
115
  * @param options - Additional options
95
116
  * @param options.reset - Whether to reset existing LogTape config (default: true)
117
+ * @param options.cwd - Working directory for resolving relative paths
96
118
  */
97
- export declare function configureLogging(loggingConfig: Required<LoggingConfig>, options?: {
119
+ export declare function configureLogging(loggingConfig: ProcessedLoggingConfig, options?: {
98
120
  reset?: boolean;
121
+ cwd?: string;
99
122
  }): Promise<void>;
100
123
  /**
101
124
  * Get cache config for a specific cache name (with pattern matching)
@@ -129,6 +152,8 @@ export declare function createBucketFactory(options: BucketFactoryOptions): (nam
129
152
  export interface CacheFactoryOptions {
130
153
  /** Shovel configuration for cache settings */
131
154
  config?: ProcessedShovelConfig;
155
+ /** Default provider when not specified in config. Defaults to "memory". */
156
+ defaultProvider?: string;
132
157
  }
133
158
  /**
134
159
  * Creates a cache factory function for CustomCacheStorage.
@@ -137,7 +162,8 @@ export interface CacheFactoryOptions {
137
162
  * Provider resolution:
138
163
  * 1. "memory" -> built-in MemoryCache
139
164
  * 2. "redis" -> @b9g/cache-redis (blessed module)
140
- * 3. Any other string -> treated as a module name (e.g., "my-custom-cache")
165
+ * 3. "cloudflare" -> native Cloudflare caches.open(name)
166
+ * 4. Any other string -> treated as a module name (e.g., "my-custom-cache")
141
167
  *
142
168
  * Custom cache modules must export a class that:
143
169
  * - Extends Cache (from @b9g/cache)
package/src/config.js CHANGED
@@ -1,9 +1,11 @@
1
1
  /// <reference types="./config.d.ts" />
2
+ import "../chunk-P57PW2II.js";
3
+
2
4
  // src/config.ts
3
5
  import { readFileSync } from "fs";
4
6
  import { resolve } from "path";
5
7
  import { Cache } from "@b9g/cache";
6
- import { configure, getConsoleSink } from "@logtape/logtape";
8
+ import { configure } from "@logtape/logtape";
7
9
  function getEnv() {
8
10
  if (typeof import.meta !== "undefined" && import.meta.env) {
9
11
  return import.meta.env;
@@ -392,12 +394,15 @@ function loadConfig(cwd) {
392
394
  const processed = processConfigValue(rawConfig, env, {
393
395
  strict: true
394
396
  });
397
+ const defaultSinks = [{ provider: "console" }];
395
398
  const config = {
399
+ platform: processed.platform,
396
400
  port: typeof processed.port === "number" ? processed.port : 3e3,
397
401
  host: processed.host || "localhost",
398
402
  workers: typeof processed.workers === "number" ? processed.workers : 1,
399
403
  logging: {
400
- level: processed.logging?.level || "error",
404
+ level: processed.logging?.level || "info",
405
+ sinks: processed.logging?.sinks || defaultSinks,
401
406
  categories: processed.logging?.categories || {}
402
407
  },
403
408
  caches: processed.caches || {},
@@ -418,15 +423,89 @@ var SHOVEL_CATEGORIES = [
418
423
  "cache-redis",
419
424
  "router"
420
425
  ];
426
+ var BUILTIN_SINK_PROVIDERS = {
427
+ console: { module: "@logtape/logtape", factory: "getConsoleSink" },
428
+ file: { module: "@logtape/file", factory: "getFileSink" },
429
+ rotating: { module: "@logtape/file", factory: "getRotatingFileSink" },
430
+ "stream-file": { module: "@logtape/file", factory: "getStreamFileSink" },
431
+ otel: { module: "@logtape/otel", factory: "getOpenTelemetrySink" },
432
+ sentry: { module: "@logtape/sentry", factory: "getSentrySink" },
433
+ syslog: { module: "@logtape/syslog", factory: "getSyslogSink" },
434
+ cloudwatch: {
435
+ module: "@logtape/cloudwatch-logs",
436
+ factory: "getCloudWatchLogsSink"
437
+ }
438
+ };
439
+ async function createSink(config, options = {}) {
440
+ const { provider, ...sinkOptions } = config;
441
+ if (sinkOptions.path && options.cwd) {
442
+ sinkOptions.path = resolve(options.cwd, sinkOptions.path);
443
+ }
444
+ const builtin = BUILTIN_SINK_PROVIDERS[provider];
445
+ const modulePath = builtin?.module || provider;
446
+ const factoryName = builtin?.factory || "default";
447
+ const module = await import(modulePath);
448
+ const factory = module[factoryName] || module.default;
449
+ if (!factory) {
450
+ throw new Error(
451
+ `Sink module "${modulePath}" has no export "${factoryName}"`
452
+ );
453
+ }
454
+ return factory(sinkOptions);
455
+ }
421
456
  async function configureLogging(loggingConfig, options = {}) {
422
- const { level, categories } = loggingConfig;
457
+ const { level, sinks: defaultSinkConfigs, categories } = loggingConfig;
423
458
  const reset = options.reset !== false;
459
+ const allSinkConfigs = /* @__PURE__ */ new Map();
460
+ const sinkNameMap = /* @__PURE__ */ new Map();
461
+ for (let i = 0; i < defaultSinkConfigs.length; i++) {
462
+ const config = defaultSinkConfigs[i];
463
+ const name = `sink_${i}`;
464
+ allSinkConfigs.set(name, config);
465
+ sinkNameMap.set(config, name);
466
+ }
467
+ let sinkIndex = defaultSinkConfigs.length;
468
+ for (const [_, categoryConfig] of Object.entries(categories)) {
469
+ if (categoryConfig.sinks) {
470
+ for (const config of categoryConfig.sinks) {
471
+ let found = false;
472
+ for (const [existingConfig, _name] of sinkNameMap) {
473
+ if (JSON.stringify(existingConfig) === JSON.stringify(config)) {
474
+ found = true;
475
+ break;
476
+ }
477
+ }
478
+ if (!found) {
479
+ const name = `sink_${sinkIndex++}`;
480
+ allSinkConfigs.set(name, config);
481
+ sinkNameMap.set(config, name);
482
+ }
483
+ }
484
+ }
485
+ }
486
+ const sinks = {};
487
+ for (const [name, config] of allSinkConfigs) {
488
+ sinks[name] = await createSink(config, { cwd: options.cwd });
489
+ }
490
+ const getSinkNames = (configs) => {
491
+ return configs.map((config) => {
492
+ for (const [existingConfig, name] of sinkNameMap) {
493
+ if (JSON.stringify(existingConfig) === JSON.stringify(config)) {
494
+ return name;
495
+ }
496
+ }
497
+ return "";
498
+ }).filter(Boolean);
499
+ };
500
+ const defaultSinkNames = getSinkNames(defaultSinkConfigs);
424
501
  const loggers = SHOVEL_CATEGORIES.map((category) => {
425
- const categoryLevel = categories[category] || level;
502
+ const categoryConfig = categories[category];
503
+ const categoryLevel = categoryConfig?.level || level;
504
+ const categorySinks = categoryConfig?.sinks ? getSinkNames(categoryConfig.sinks) : defaultSinkNames;
426
505
  return {
427
506
  category: [category],
428
507
  level: categoryLevel,
429
- sinks: ["console"]
508
+ sinks: categorySinks
430
509
  };
431
510
  });
432
511
  loggers.push({
@@ -436,9 +515,7 @@ async function configureLogging(loggingConfig, options = {}) {
436
515
  });
437
516
  await configure({
438
517
  reset,
439
- sinks: {
440
- console: getConsoleSink()
441
- },
518
+ sinks,
442
519
  loggers
443
520
  });
444
521
  }
@@ -510,21 +587,26 @@ var BUILTIN_CACHE_PROVIDERS = {
510
587
  redis: "@b9g/cache-redis"
511
588
  };
512
589
  function createCacheFactory(options = {}) {
513
- const { config } = options;
590
+ const { config, defaultProvider = "memory" } = options;
514
591
  return async (name) => {
515
592
  const cacheConfig = config ? getCacheConfig(config, name) : {};
516
- const provider = String(cacheConfig.provider || "memory");
517
- const modulePath = BUILTIN_CACHE_PROVIDERS[provider] || provider;
518
- if (modulePath === "@b9g/cache/memory.js") {
519
- const { MemoryCache } = await import("@b9g/cache/memory.js");
520
- return new MemoryCache(name, {
521
- maxEntries: typeof cacheConfig.maxEntries === "number" ? cacheConfig.maxEntries : 1e3
522
- });
593
+ const provider = String(cacheConfig.provider || defaultProvider);
594
+ if (provider === "cloudflare") {
595
+ const nativeCaches = globalThis.__cloudflareCaches ?? globalThis.caches;
596
+ if (!nativeCaches) {
597
+ throw new Error(
598
+ "Cloudflare cache provider requires native caches API. This provider only works in Cloudflare Workers environment."
599
+ );
600
+ }
601
+ return nativeCaches.open(name);
523
602
  }
603
+ const { provider: _, ...cacheOptions } = cacheConfig;
604
+ const modulePath = BUILTIN_CACHE_PROVIDERS[provider] || provider;
524
605
  try {
525
606
  const module = await import(modulePath);
526
607
  const CacheClass = module.default || // Default export
527
608
  module.RedisCache || // Named export for redis
609
+ module.MemoryCache || // Named export for memory
528
610
  module.Cache || // Generic Cache export
529
611
  Object.values(module).find(
530
612
  (v) => typeof v === "function" && v.prototype instanceof Cache
@@ -534,7 +616,6 @@ function createCacheFactory(options = {}) {
534
616
  `Cache module "${modulePath}" does not export a valid cache class. Expected a default export or named export (RedisCache, Cache) that extends Cache.`
535
617
  );
536
618
  }
537
- const { provider: _, ...cacheOptions } = cacheConfig;
538
619
  return new CacheClass(name, cacheOptions);
539
620
  } catch (error) {
540
621
  if (error.code === "ERR_MODULE_NOT_FOUND" || error.code === "MODULE_NOT_FOUND") {
@@ -1,4 +1,6 @@
1
1
  /// <reference types="./cookie-store.d.ts" />
2
+ import "../chunk-P57PW2II.js";
3
+
2
4
  // src/cookie-store.ts
3
5
  function parseCookieHeader(cookieHeader) {
4
6
  const cookies = /* @__PURE__ */ new Map();
package/src/index.d.ts CHANGED
@@ -134,16 +134,20 @@ export declare function detectDeploymentPlatform(): string | null;
134
134
  */
135
135
  export declare function detectDevelopmentPlatform(): string;
136
136
  /**
137
- * Resolve platform name from options or auto-detect
137
+ * Resolve platform name from options, config, or auto-detect
138
138
  *
139
139
  * Priority:
140
- * 1. Explicit --platform or --target flag
141
- * 2. Deployment platform detection (production environments)
142
- * 3. Development platform detection (local runtime)
140
+ * 1. Explicit --platform or --target CLI flag
141
+ * 2. shovel.json or package.json "shovel.platform" field
142
+ * 3. Deployment platform detection (production environments)
143
+ * 4. Development platform detection (local runtime)
143
144
  */
144
145
  export declare function resolvePlatform(options: {
145
146
  platform?: string;
146
147
  target?: string;
148
+ config?: {
149
+ platform?: string;
150
+ };
147
151
  }): string;
148
152
  /**
149
153
  * Create platform instance based on name
@@ -189,7 +193,7 @@ export declare function getPlatform(name?: string): Platform;
189
193
  export declare function getPlatformAsync(name?: string): Promise<Platform>;
190
194
  export { ServiceWorkerPool, type WorkerPoolOptions, type WorkerMessage, type WorkerRequest, type WorkerResponse, type WorkerLoadMessage, type WorkerReadyMessage, type WorkerErrorMessage, type WorkerInitMessage, type WorkerInitializedMessage, } from "./worker-pool.js";
191
195
  export { SingleThreadedRuntime, type SingleThreadedRuntimeOptions, } from "./single-threaded.js";
192
- export { ShovelServiceWorkerRegistration, ShovelGlobalScope, FetchEvent, InstallEvent, ActivateEvent, ExtendableEvent, } from "./runtime.js";
196
+ export { ShovelServiceWorkerRegistration, ServiceWorkerGlobals, FetchEvent, InstallEvent, ActivateEvent, ExtendableEvent, } from "./runtime.js";
193
197
  export { RequestCookieStore, type CookieListItem, type CookieInit, type CookieStoreGetOptions, type CookieStoreDeleteOptions, type CookieSameSite, type CookieList, parseCookieHeader, serializeCookie, parseSetCookieHeader, } from "./cookie-store.js";
194
198
  export { CustomBucketStorage } from "@b9g/filesystem";
195
199
  export { loadConfig, configureLogging, getCacheConfig, getBucketConfig, parseConfigExpr, processConfigValue, matchPattern, createBucketFactory, createCacheFactory, type ShovelConfig, type CacheConfig, type BucketConfig, type LoggingConfig, type LogLevel, type BucketFactoryOptions, type CacheFactoryOptions, type ProcessedShovelConfig, } from "./config.js";
package/src/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  /// <reference types="./index.d.ts" />
2
+ import "../chunk-P57PW2II.js";
3
+
2
4
  // src/index.ts
3
5
  import * as Path from "path";
4
6
  import { readFileSync } from "fs";
@@ -12,7 +14,7 @@ import {
12
14
  } from "./single-threaded.js";
13
15
  import {
14
16
  ShovelServiceWorkerRegistration,
15
- ShovelGlobalScope,
17
+ ServiceWorkerGlobals,
16
18
  FetchEvent,
17
19
  InstallEvent,
18
20
  ActivateEvent,
@@ -55,7 +57,10 @@ function detectPlatformFromPackageJSON(cwd) {
55
57
  const pkg = JSON.parse(pkgContent);
56
58
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
57
59
  return selectPlatformFromDeps(deps);
58
- } catch {
60
+ } catch (err) {
61
+ if (err.code !== "ENOENT") {
62
+ throw err;
63
+ }
59
64
  return null;
60
65
  }
61
66
  }
@@ -122,6 +127,9 @@ function resolvePlatform(options) {
122
127
  if (options.target) {
123
128
  return options.target;
124
129
  }
130
+ if (options.config?.platform) {
131
+ return options.config.platform;
132
+ }
125
133
  const deploymentPlatform = detectDeploymentPlatform();
126
134
  if (deploymentPlatform) {
127
135
  return deploymentPlatform;
@@ -234,8 +242,8 @@ export {
234
242
  FetchEvent,
235
243
  InstallEvent,
236
244
  RequestCookieStore,
245
+ ServiceWorkerGlobals,
237
246
  ServiceWorkerPool,
238
- ShovelGlobalScope,
239
247
  ShovelServiceWorkerRegistration,
240
248
  SingleThreadedRuntime,
241
249
  configureLogging,
package/src/runtime.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * This module provides the complete ServiceWorker runtime environment for Shovel:
5
5
  * - Base Event Classes (ExtendableEvent, FetchEvent, InstallEvent, ActivateEvent)
6
6
  * - ServiceWorker API Type Shims (Client, Clients, ServiceWorkerRegistration, etc.)
7
- * - ShovelGlobalScope (implements ServiceWorkerGlobalScope for any JavaScript runtime)
7
+ * - ServiceWorkerGlobals (installs ServiceWorker globals onto any JavaScript runtime)
8
8
  *
9
9
  * Based on: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API#interfaces
10
10
  */
@@ -330,7 +330,7 @@ interface NotificationOptions {
330
330
  timestamp?: number;
331
331
  vibrate?: number[];
332
332
  }
333
- export interface ShovelGlobalScopeOptions {
333
+ export interface ServiceWorkerGlobalsOptions {
334
334
  /** ServiceWorker registration instance */
335
335
  registration: ServiceWorkerRegistration;
336
336
  /** Bucket storage (file system access) - REQUIRED */
@@ -353,12 +353,15 @@ export declare class WorkerGlobalScope {
353
353
  export declare class DedicatedWorkerGlobalScope extends WorkerGlobalScope {
354
354
  }
355
355
  /**
356
- * ShovelGlobalScope implements ServiceWorkerGlobalScope
356
+ * ServiceWorkerGlobals - Installs ServiceWorker globals onto globalThis
357
357
  *
358
- * This is the `self` object in Shovel ServiceWorker applications.
359
- * It provides all standard ServiceWorker APIs plus Shovel-specific extensions.
358
+ * This class holds ServiceWorker API implementations (caches, buckets, clients, etc.)
359
+ * and patches them onto globalThis via install(). It maintains the browser invariant
360
+ * that self === globalThis while providing ServiceWorker APIs.
361
+ *
362
+ * Use restore() to revert all patches (useful for testing).
360
363
  */
361
- export declare class ShovelGlobalScope implements ServiceWorkerGlobalScope {
364
+ export declare class ServiceWorkerGlobals implements ServiceWorkerGlobalScope {
362
365
  #private;
363
366
  readonly self: any;
364
367
  readonly registration: ServiceWorkerRegistration;
@@ -405,7 +408,7 @@ export declare class ShovelGlobalScope implements ServiceWorkerGlobalScope {
405
408
  ononline: ((ev: Event) => any) | null;
406
409
  onrejectionhandled: ((ev: PromiseRejectionEvent) => any) | null;
407
410
  onunhandledrejection: ((ev: PromiseRejectionEvent) => any) | null;
408
- constructor(options: ShovelGlobalScopeOptions);
411
+ constructor(options: ServiceWorkerGlobalsOptions);
409
412
  /**
410
413
  * Standard ServiceWorker skipWaiting() implementation
411
414
  * Allows the ServiceWorker to activate immediately
@@ -419,8 +422,13 @@ export declare class ShovelGlobalScope implements ServiceWorkerGlobalScope {
419
422
  dispatchEvent(event: Event): boolean;
420
423
  /**
421
424
  * Install this scope as the global scope
422
- * Sets up globalThis with all ServiceWorker globals
425
+ * Patches globalThis with ServiceWorker globals while maintaining self === globalThis
423
426
  */
424
427
  install(): void;
428
+ /**
429
+ * Restore original globals (for testing)
430
+ * Reverts all patched globals to their original values
431
+ */
432
+ restore(): void;
425
433
  }
426
434
  export {};
package/src/runtime.js CHANGED
@@ -1,4 +1,6 @@
1
1
  /// <reference types="./runtime.d.ts" />
2
+ import "../chunk-P57PW2II.js";
3
+
2
4
  // src/runtime.ts
3
5
  import { RequestCookieStore } from "./cookie-store.js";
4
6
  import { AsyncContext } from "@b9g/async-context";
@@ -11,6 +13,23 @@ if (import.meta.env && !import.meta.env.MODE && import.meta.env.NODE_ENV) {
11
13
  import.meta.env.MODE = import.meta.env.NODE_ENV;
12
14
  }
13
15
  var cookieStoreStorage = new AsyncContext.Variable();
16
+ var fetchDepthStorage = new AsyncContext.Variable();
17
+ var MAX_FETCH_DEPTH = 10;
18
+ var PATCHED_KEYS = [
19
+ "self",
20
+ "fetch",
21
+ "caches",
22
+ "buckets",
23
+ "registration",
24
+ "clients",
25
+ "skipWaiting",
26
+ "addEventListener",
27
+ "removeEventListener",
28
+ "dispatchEvent",
29
+ "WorkerGlobalScope",
30
+ "DedicatedWorkerGlobalScope",
31
+ "cookieStore"
32
+ ];
14
33
  function promiseWithTimeout(promise, timeoutMs, errorMessage) {
15
34
  return Promise.race([
16
35
  promise,
@@ -609,7 +628,7 @@ var WorkerGlobalScope = class {
609
628
  };
610
629
  var DedicatedWorkerGlobalScope = class extends WorkerGlobalScope {
611
630
  };
612
- var ShovelGlobalScope = class {
631
+ var ServiceWorkerGlobals = class {
613
632
  // Self-reference (standard in ServiceWorkerGlobalScope)
614
633
  // Type assertion: we provide a compatible subset of WorkerGlobalScope
615
634
  self;
@@ -624,6 +643,8 @@ var ShovelGlobalScope = class {
624
643
  clients;
625
644
  // Shovel-specific development features
626
645
  #isDevelopment;
646
+ // Snapshot of original globals before patching (for restore())
647
+ #originals;
627
648
  // Web API required properties
628
649
  // Note: Using RequestCookieStore but typing as any for flexibility with global CookieStore type
629
650
  // cookieStore is retrieved from AsyncContext for per-request isolation
@@ -665,7 +686,22 @@ var ShovelGlobalScope = class {
665
686
  );
666
687
  }
667
688
  fetch(input, init) {
668
- return globalThis.fetch(input, init);
689
+ const urlString = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
690
+ const isRelative = urlString.startsWith("/") || urlString.startsWith("./");
691
+ if (!isRelative) {
692
+ const originalFetch = this.#originals.fetch;
693
+ return originalFetch(input, init);
694
+ }
695
+ const currentDepth = fetchDepthStorage.get() ?? 0;
696
+ if (currentDepth >= MAX_FETCH_DEPTH) {
697
+ return Promise.reject(
698
+ new Error(`Maximum self-fetch depth (${MAX_FETCH_DEPTH}) exceeded`)
699
+ );
700
+ }
701
+ const request = new Request(new URL(urlString, "http://localhost"), init);
702
+ return fetchDepthStorage.run(currentDepth + 1, () => {
703
+ return this.registration.handleRequest(request);
704
+ });
669
705
  }
670
706
  queueMicrotask(callback) {
671
707
  globalThis.queueMicrotask(callback);
@@ -703,7 +739,12 @@ var ShovelGlobalScope = class {
703
739
  onrejectionhandled;
704
740
  onunhandledrejection;
705
741
  constructor(options) {
706
- this.self = this;
742
+ const g = globalThis;
743
+ this.#originals = {};
744
+ for (const key of PATCHED_KEYS) {
745
+ this.#originals[key] = g[key];
746
+ }
747
+ this.self = globalThis;
707
748
  this.registration = options.registration;
708
749
  this.caches = options.caches;
709
750
  this.buckets = options.buckets;
@@ -774,28 +815,43 @@ var ShovelGlobalScope = class {
774
815
  }
775
816
  /**
776
817
  * Install this scope as the global scope
777
- * Sets up globalThis with all ServiceWorker globals
818
+ * Patches globalThis with ServiceWorker globals while maintaining self === globalThis
778
819
  */
779
820
  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;
821
+ const g = globalThis;
822
+ g.WorkerGlobalScope = WorkerGlobalScope;
823
+ g.DedicatedWorkerGlobalScope = DedicatedWorkerGlobalScope;
824
+ if (typeof g.self === "undefined") {
825
+ g.self = globalThis;
792
826
  }
793
- if (this.buckets) {
794
- globalThis.buckets = this.buckets;
827
+ g.addEventListener = this.addEventListener.bind(this);
828
+ g.removeEventListener = this.removeEventListener.bind(this);
829
+ g.dispatchEvent = this.dispatchEvent.bind(this);
830
+ g.caches = this.caches;
831
+ g.buckets = this.buckets;
832
+ g.registration = this.registration;
833
+ g.skipWaiting = this.skipWaiting.bind(this);
834
+ g.clients = this.clients;
835
+ g.fetch = this.fetch.bind(this);
836
+ Object.defineProperty(g, "cookieStore", {
837
+ get: () => cookieStoreStorage.get(),
838
+ configurable: true
839
+ });
840
+ }
841
+ /**
842
+ * Restore original globals (for testing)
843
+ * Reverts all patched globals to their original values
844
+ */
845
+ restore() {
846
+ const g = globalThis;
847
+ for (const key of PATCHED_KEYS) {
848
+ const original = this.#originals[key];
849
+ if (original === void 0) {
850
+ delete g[key];
851
+ } else {
852
+ g[key] = original;
853
+ }
795
854
  }
796
- globalThis.registration = this.registration;
797
- globalThis.skipWaiting = this.skipWaiting.bind(this);
798
- globalThis.clients = this.clients;
799
855
  }
800
856
  };
801
857
  var logger = getLogger(["worker"]);
@@ -832,7 +888,7 @@ async function handleFetchEvent(request) {
832
888
  const response = await registration.handleRequest(request);
833
889
  return response;
834
890
  } catch (error) {
835
- logger.error("[Worker] ServiceWorker request failed", { error });
891
+ logger.error("[Worker] ServiceWorker request failed: {error}", { error });
836
892
  console.error("[Worker] ServiceWorker request failed:", error);
837
893
  const response = new Response("ServiceWorker request failed", {
838
894
  status: 500
@@ -856,7 +912,7 @@ async function loadServiceWorker(entrypoint) {
856
912
  if (!caches || !buckets) {
857
913
  throw new Error("Runtime not initialized - missing caches or buckets");
858
914
  }
859
- scope = new ShovelGlobalScope({
915
+ scope = new ServiceWorkerGlobals({
860
916
  registration,
861
917
  caches,
862
918
  buckets
@@ -913,7 +969,7 @@ async function initializeRuntime(config, baseDir) {
913
969
  buckets = new CustomBucketStorage(createBucketFactory({ baseDir, config }));
914
970
  logger.info(`[Worker-${workerId}] Creating and installing scope`);
915
971
  registration = new ShovelServiceWorkerRegistration();
916
- scope = new ShovelGlobalScope({ registration, caches, buckets });
972
+ scope = new ServiceWorkerGlobals({ registration, caches, buckets });
917
973
  scope.install();
918
974
  _workerSelf = scope;
919
975
  logger.info(`[Worker-${workerId}] Runtime initialized successfully`);
@@ -987,9 +1043,9 @@ export {
987
1043
  Notification,
988
1044
  NotificationEvent,
989
1045
  PushEvent,
1046
+ ServiceWorkerGlobals,
990
1047
  ShovelClient,
991
1048
  ShovelClients,
992
- ShovelGlobalScope,
993
1049
  ShovelNavigationPreloadManager,
994
1050
  ShovelPushMessageData,
995
1051
  ShovelServiceWorker,
@@ -26,7 +26,7 @@ export declare class SingleThreadedRuntime {
26
26
  #private;
27
27
  constructor(options: SingleThreadedRuntimeOptions);
28
28
  /**
29
- * Initialize the runtime (install scope as globalThis.self)
29
+ * Initialize the runtime (install ServiceWorker globals)
30
30
  */
31
31
  init(): Promise<void>;
32
32
  /**
@@ -1,7 +1,12 @@
1
1
  /// <reference types="./single-threaded.d.ts" />
2
+ import "../chunk-P57PW2II.js";
3
+
2
4
  // src/single-threaded.ts
3
5
  import { getLogger } from "@logtape/logtape";
4
- import { ShovelGlobalScope, ShovelServiceWorkerRegistration } from "./runtime.js";
6
+ import {
7
+ ServiceWorkerGlobals,
8
+ ShovelServiceWorkerRegistration
9
+ } from "./runtime.js";
5
10
  import { CustomBucketStorage } from "@b9g/filesystem";
6
11
  import { CustomCacheStorage } from "@b9g/cache";
7
12
  import {
@@ -24,7 +29,7 @@ var SingleThreadedRuntime = class {
24
29
  createBucketFactory({ baseDir: options.baseDir, config: options.config })
25
30
  );
26
31
  this.#registration = new ShovelServiceWorkerRegistration();
27
- this.#scope = new ShovelGlobalScope({
32
+ this.#scope = new ServiceWorkerGlobals({
28
33
  registration: this.#registration,
29
34
  caches: cacheStorage,
30
35
  buckets: bucketStorage
@@ -32,14 +37,14 @@ var SingleThreadedRuntime = class {
32
37
  logger.info("SingleThreadedRuntime created", { baseDir: options.baseDir });
33
38
  }
34
39
  /**
35
- * Initialize the runtime (install scope as globalThis.self)
40
+ * Initialize the runtime (install ServiceWorker globals)
36
41
  */
37
42
  async init() {
38
43
  if (this.#config?.logging) {
39
44
  await configureLogging(this.#config.logging);
40
45
  }
41
46
  this.#scope.install();
42
- logger.info("SingleThreadedRuntime initialized - scope installed");
47
+ logger.info("SingleThreadedRuntime initialized - globals installed");
43
48
  }
44
49
  /**
45
50
  * Load and run a ServiceWorker entrypoint
@@ -1,11 +1,7 @@
1
1
  /// <reference types="./worker-pool.d.ts" />
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined")
6
- return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
2
+ import {
3
+ __require
4
+ } from "../chunk-P57PW2II.js";
9
5
 
10
6
  // src/worker-pool.ts
11
7
  import * as Path from "path";
@@ -29,7 +25,10 @@ function resolveWorkerScript(entrypoint) {
29
25
  return bundledWorker;
30
26
  }
31
27
  }
32
- } catch {
28
+ } catch (err) {
29
+ if (err.code !== "ENOENT") {
30
+ throw err;
31
+ }
33
32
  }
34
33
  }
35
34
  try {
@@ -246,7 +245,7 @@ var ServiceWorkerPool = class {
246
245
  this.#pendingRequests.delete(message.requestID);
247
246
  }
248
247
  } else {
249
- logger.error("Worker error", { error: message.error });
248
+ logger.error("Worker error: {error}", { error: message.error });
250
249
  }
251
250
  }
252
251
  #handleReady(message) {