@b9g/platform 0.1.9 → 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.9",
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
@@ -56,17 +56,48 @@ export interface BucketConfig {
56
56
  region?: string | number;
57
57
  endpoint?: string | number;
58
58
  }
59
+ /** Log level for filtering */
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
+ }
72
+ export interface LoggingConfig {
73
+ /** Default log level. Defaults to "info" */
74
+ level?: 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>;
79
+ }
59
80
  export interface ShovelConfig {
81
+ platform?: string;
60
82
  port?: number | string;
61
83
  host?: string;
62
84
  workers?: number | string;
85
+ logging?: LoggingConfig;
63
86
  caches?: Record<string, CacheConfig>;
64
87
  buckets?: Record<string, BucketConfig>;
65
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
+ }
66
95
  export interface ProcessedShovelConfig {
96
+ platform?: string;
67
97
  port: number;
68
98
  host: string;
69
99
  workers: number;
100
+ logging: ProcessedLoggingConfig;
70
101
  caches: Record<string, CacheConfig>;
71
102
  buckets: Record<string, BucketConfig>;
72
103
  }
@@ -76,6 +107,19 @@ export interface ProcessedShovelConfig {
76
107
  * @param cwd - Current working directory (must be provided by runtime adapter)
77
108
  */
78
109
  export declare function loadConfig(cwd: string): ProcessedShovelConfig;
110
+ /**
111
+ * Configure LogTape logging based on Shovel config.
112
+ * Call this in both main thread and workers.
113
+ *
114
+ * @param loggingConfig - The logging configuration from ProcessedShovelConfig.logging
115
+ * @param options - Additional options
116
+ * @param options.reset - Whether to reset existing LogTape config (default: true)
117
+ * @param options.cwd - Working directory for resolving relative paths
118
+ */
119
+ export declare function configureLogging(loggingConfig: ProcessedLoggingConfig, options?: {
120
+ reset?: boolean;
121
+ cwd?: string;
122
+ }): Promise<void>;
79
123
  /**
80
124
  * Get cache config for a specific cache name (with pattern matching)
81
125
  */
@@ -108,6 +152,8 @@ export declare function createBucketFactory(options: BucketFactoryOptions): (nam
108
152
  export interface CacheFactoryOptions {
109
153
  /** Shovel configuration for cache settings */
110
154
  config?: ProcessedShovelConfig;
155
+ /** Default provider when not specified in config. Defaults to "memory". */
156
+ defaultProvider?: string;
111
157
  }
112
158
  /**
113
159
  * Creates a cache factory function for CustomCacheStorage.
@@ -116,7 +162,8 @@ export interface CacheFactoryOptions {
116
162
  * Provider resolution:
117
163
  * 1. "memory" -> built-in MemoryCache
118
164
  * 2. "redis" -> @b9g/cache-redis (blessed module)
119
- * 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")
120
167
  *
121
168
  * Custom cache modules must export a class that:
122
169
  * - Extends Cache (from @b9g/cache)
package/src/config.js CHANGED
@@ -1,8 +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";
8
+ import { configure } from "@logtape/logtape";
6
9
  function getEnv() {
7
10
  if (typeof import.meta !== "undefined" && import.meta.env) {
8
11
  return import.meta.env;
@@ -391,15 +394,131 @@ function loadConfig(cwd) {
391
394
  const processed = processConfigValue(rawConfig, env, {
392
395
  strict: true
393
396
  });
397
+ const defaultSinks = [{ provider: "console" }];
394
398
  const config = {
399
+ platform: processed.platform,
395
400
  port: typeof processed.port === "number" ? processed.port : 3e3,
396
401
  host: processed.host || "localhost",
397
402
  workers: typeof processed.workers === "number" ? processed.workers : 1,
403
+ logging: {
404
+ level: processed.logging?.level || "info",
405
+ sinks: processed.logging?.sinks || defaultSinks,
406
+ categories: processed.logging?.categories || {}
407
+ },
398
408
  caches: processed.caches || {},
399
409
  buckets: processed.buckets || {}
400
410
  };
401
411
  return config;
402
412
  }
413
+ var SHOVEL_CATEGORIES = [
414
+ "cli",
415
+ "watcher",
416
+ "worker",
417
+ "single-threaded",
418
+ "assets",
419
+ "platform-node",
420
+ "platform-bun",
421
+ "platform-cloudflare",
422
+ "cache",
423
+ "cache-redis",
424
+ "router"
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
+ }
456
+ async function configureLogging(loggingConfig, options = {}) {
457
+ const { level, sinks: defaultSinkConfigs, categories } = loggingConfig;
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);
501
+ const loggers = SHOVEL_CATEGORIES.map((category) => {
502
+ const categoryConfig = categories[category];
503
+ const categoryLevel = categoryConfig?.level || level;
504
+ const categorySinks = categoryConfig?.sinks ? getSinkNames(categoryConfig.sinks) : defaultSinkNames;
505
+ return {
506
+ category: [category],
507
+ level: categoryLevel,
508
+ sinks: categorySinks
509
+ };
510
+ });
511
+ loggers.push({
512
+ category: ["logtape", "meta"],
513
+ level: "warning",
514
+ sinks: []
515
+ });
516
+ await configure({
517
+ reset,
518
+ sinks,
519
+ loggers
520
+ });
521
+ }
403
522
  function getCacheConfig(config, name) {
404
523
  return matchPattern(name, config.caches) || {};
405
524
  }
@@ -468,21 +587,26 @@ var BUILTIN_CACHE_PROVIDERS = {
468
587
  redis: "@b9g/cache-redis"
469
588
  };
470
589
  function createCacheFactory(options = {}) {
471
- const { config } = options;
590
+ const { config, defaultProvider = "memory" } = options;
472
591
  return async (name) => {
473
592
  const cacheConfig = config ? getCacheConfig(config, name) : {};
474
- const provider = String(cacheConfig.provider || "memory");
475
- const modulePath = BUILTIN_CACHE_PROVIDERS[provider] || provider;
476
- if (modulePath === "@b9g/cache/memory.js") {
477
- const { MemoryCache } = await import("@b9g/cache/memory.js");
478
- return new MemoryCache(name, {
479
- maxEntries: typeof cacheConfig.maxEntries === "number" ? cacheConfig.maxEntries : 1e3
480
- });
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);
481
602
  }
603
+ const { provider: _, ...cacheOptions } = cacheConfig;
604
+ const modulePath = BUILTIN_CACHE_PROVIDERS[provider] || provider;
482
605
  try {
483
606
  const module = await import(modulePath);
484
607
  const CacheClass = module.default || // Default export
485
608
  module.RedisCache || // Named export for redis
609
+ module.MemoryCache || // Named export for memory
486
610
  module.Cache || // Generic Cache export
487
611
  Object.values(module).find(
488
612
  (v) => typeof v === "function" && v.prototype instanceof Cache
@@ -492,7 +616,6 @@ function createCacheFactory(options = {}) {
492
616
  `Cache module "${modulePath}" does not export a valid cache class. Expected a default export or named export (RedisCache, Cache) that extends Cache.`
493
617
  );
494
618
  }
495
- const { provider: _, ...cacheOptions } = cacheConfig;
496
619
  return new CacheClass(name, cacheOptions);
497
620
  } catch (error) {
498
621
  if (error.code === "ERR_MODULE_NOT_FOUND" || error.code === "MODULE_NOT_FOUND") {
@@ -506,6 +629,7 @@ For redis: npm install @b9g/cache-redis`
506
629
  };
507
630
  }
508
631
  export {
632
+ configureLogging,
509
633
  createBucketFactory,
510
634
  createCacheFactory,
511
635
  getBucketConfig,
@@ -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
- export { loadConfig, getCacheConfig, getBucketConfig, parseConfigExpr, processConfigValue, matchPattern, createBucketFactory, createCacheFactory, type ShovelConfig, type CacheConfig, type BucketConfig, type BucketFactoryOptions, type CacheFactoryOptions, type ProcessedShovelConfig, } from "./config.js";
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,
@@ -27,6 +29,7 @@ import {
27
29
  import { CustomBucketStorage } from "@b9g/filesystem";
28
30
  import {
29
31
  loadConfig,
32
+ configureLogging,
30
33
  getCacheConfig,
31
34
  getBucketConfig,
32
35
  parseConfigExpr,
@@ -54,7 +57,10 @@ function detectPlatformFromPackageJSON(cwd) {
54
57
  const pkg = JSON.parse(pkgContent);
55
58
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
56
59
  return selectPlatformFromDeps(deps);
57
- } catch {
60
+ } catch (err) {
61
+ if (err.code !== "ENOENT") {
62
+ throw err;
63
+ }
58
64
  return null;
59
65
  }
60
66
  }
@@ -121,6 +127,9 @@ function resolvePlatform(options) {
121
127
  if (options.target) {
122
128
  return options.target;
123
129
  }
130
+ if (options.config?.platform) {
131
+ return options.config.platform;
132
+ }
124
133
  const deploymentPlatform = detectDeploymentPlatform();
125
134
  if (deploymentPlatform) {
126
135
  return deploymentPlatform;
@@ -233,10 +242,11 @@ export {
233
242
  FetchEvent,
234
243
  InstallEvent,
235
244
  RequestCookieStore,
245
+ ServiceWorkerGlobals,
236
246
  ServiceWorkerPool,
237
- ShovelGlobalScope,
238
247
  ShovelServiceWorkerRegistration,
239
248
  SingleThreadedRuntime,
249
+ configureLogging,
240
250
  createBucketFactory,
241
251
  createCacheFactory,
242
252
  createPlatform,
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";
@@ -6,10 +8,28 @@ import { CustomBucketStorage } from "@b9g/filesystem";
6
8
  import { CustomCacheStorage } from "@b9g/cache";
7
9
  import { createBucketFactory, createCacheFactory } from "./config.js";
8
10
  import { getLogger } from "@logtape/logtape";
11
+ import { configureLogging } from "./config.js";
9
12
  if (import.meta.env && !import.meta.env.MODE && import.meta.env.NODE_ENV) {
10
13
  import.meta.env.MODE = import.meta.env.NODE_ENV;
11
14
  }
12
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
+ ];
13
33
  function promiseWithTimeout(promise, timeoutMs, errorMessage) {
14
34
  return Promise.race([
15
35
  promise,
@@ -608,7 +628,7 @@ var WorkerGlobalScope = class {
608
628
  };
609
629
  var DedicatedWorkerGlobalScope = class extends WorkerGlobalScope {
610
630
  };
611
- var ShovelGlobalScope = class {
631
+ var ServiceWorkerGlobals = class {
612
632
  // Self-reference (standard in ServiceWorkerGlobalScope)
613
633
  // Type assertion: we provide a compatible subset of WorkerGlobalScope
614
634
  self;
@@ -623,6 +643,8 @@ var ShovelGlobalScope = class {
623
643
  clients;
624
644
  // Shovel-specific development features
625
645
  #isDevelopment;
646
+ // Snapshot of original globals before patching (for restore())
647
+ #originals;
626
648
  // Web API required properties
627
649
  // Note: Using RequestCookieStore but typing as any for flexibility with global CookieStore type
628
650
  // cookieStore is retrieved from AsyncContext for per-request isolation
@@ -664,7 +686,22 @@ var ShovelGlobalScope = class {
664
686
  );
665
687
  }
666
688
  fetch(input, init) {
667
- 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
+ });
668
705
  }
669
706
  queueMicrotask(callback) {
670
707
  globalThis.queueMicrotask(callback);
@@ -702,7 +739,12 @@ var ShovelGlobalScope = class {
702
739
  onrejectionhandled;
703
740
  onunhandledrejection;
704
741
  constructor(options) {
705
- 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;
706
748
  this.registration = options.registration;
707
749
  this.caches = options.caches;
708
750
  this.buckets = options.buckets;
@@ -773,28 +815,43 @@ var ShovelGlobalScope = class {
773
815
  }
774
816
  /**
775
817
  * Install this scope as the global scope
776
- * Sets up globalThis with all ServiceWorker globals
818
+ * Patches globalThis with ServiceWorker globals while maintaining self === globalThis
777
819
  */
778
820
  install() {
779
- globalThis.WorkerGlobalScope = WorkerGlobalScope;
780
- globalThis.DedicatedWorkerGlobalScope = DedicatedWorkerGlobalScope;
781
- globalThis.self = this;
782
- const isWorker = "onmessage" in globalThis;
783
- if (isWorker && typeof postMessage === "function") {
784
- this.postMessage = postMessage.bind(globalThis);
785
- }
786
- globalThis.addEventListener = this.addEventListener.bind(this);
787
- globalThis.removeEventListener = this.removeEventListener.bind(this);
788
- globalThis.dispatchEvent = this.dispatchEvent.bind(this);
789
- if (this.caches) {
790
- 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;
791
826
  }
792
- if (this.buckets) {
793
- 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
+ }
794
854
  }
795
- globalThis.registration = this.registration;
796
- globalThis.skipWaiting = this.skipWaiting.bind(this);
797
- globalThis.clients = this.clients;
798
855
  }
799
856
  };
800
857
  var logger = getLogger(["worker"]);
@@ -831,7 +888,7 @@ async function handleFetchEvent(request) {
831
888
  const response = await registration.handleRequest(request);
832
889
  return response;
833
890
  } catch (error) {
834
- logger.error("[Worker] ServiceWorker request failed", { error });
891
+ logger.error("[Worker] ServiceWorker request failed: {error}", { error });
835
892
  console.error("[Worker] ServiceWorker request failed:", error);
836
893
  const response = new Response("ServiceWorker request failed", {
837
894
  status: 500
@@ -855,7 +912,7 @@ async function loadServiceWorker(entrypoint) {
855
912
  if (!caches || !buckets) {
856
913
  throw new Error("Runtime not initialized - missing caches or buckets");
857
914
  }
858
- scope = new ShovelGlobalScope({
915
+ scope = new ServiceWorkerGlobals({
859
916
  registration,
860
917
  caches,
861
918
  buckets
@@ -899,6 +956,9 @@ var workerId = Math.random().toString(36).substring(2, 8);
899
956
  var sendMessage;
900
957
  async function initializeRuntime(config, baseDir) {
901
958
  try {
959
+ if (config?.logging) {
960
+ await configureLogging(config.logging);
961
+ }
902
962
  logger.info(`[Worker-${workerId}] Initializing runtime with config`, {
903
963
  config,
904
964
  baseDir
@@ -909,7 +969,7 @@ async function initializeRuntime(config, baseDir) {
909
969
  buckets = new CustomBucketStorage(createBucketFactory({ baseDir, config }));
910
970
  logger.info(`[Worker-${workerId}] Creating and installing scope`);
911
971
  registration = new ShovelServiceWorkerRegistration();
912
- scope = new ShovelGlobalScope({ registration, caches, buckets });
972
+ scope = new ServiceWorkerGlobals({ registration, caches, buckets });
913
973
  scope.install();
914
974
  _workerSelf = scope;
915
975
  logger.info(`[Worker-${workerId}] Runtime initialized successfully`);
@@ -983,9 +1043,9 @@ export {
983
1043
  Notification,
984
1044
  NotificationEvent,
985
1045
  PushEvent,
1046
+ ServiceWorkerGlobals,
986
1047
  ShovelClient,
987
1048
  ShovelClients,
988
- ShovelGlobalScope,
989
1049
  ShovelNavigationPreloadManager,
990
1050
  ShovelPushMessageData,
991
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,10 +1,16 @@
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 {
13
+ configureLogging,
8
14
  createBucketFactory,
9
15
  createCacheFactory
10
16
  } from "./config.js";
@@ -23,7 +29,7 @@ var SingleThreadedRuntime = class {
23
29
  createBucketFactory({ baseDir: options.baseDir, config: options.config })
24
30
  );
25
31
  this.#registration = new ShovelServiceWorkerRegistration();
26
- this.#scope = new ShovelGlobalScope({
32
+ this.#scope = new ServiceWorkerGlobals({
27
33
  registration: this.#registration,
28
34
  caches: cacheStorage,
29
35
  buckets: bucketStorage
@@ -31,11 +37,14 @@ var SingleThreadedRuntime = class {
31
37
  logger.info("SingleThreadedRuntime created", { baseDir: options.baseDir });
32
38
  }
33
39
  /**
34
- * Initialize the runtime (install scope as globalThis.self)
40
+ * Initialize the runtime (install ServiceWorker globals)
35
41
  */
36
42
  async init() {
43
+ if (this.#config?.logging) {
44
+ await configureLogging(this.#config.logging);
45
+ }
37
46
  this.#scope.install();
38
- logger.info("SingleThreadedRuntime initialized - scope installed");
47
+ logger.info("SingleThreadedRuntime initialized - globals installed");
39
48
  }
40
49
  /**
41
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) {