@b9g/platform-cloudflare 0.1.7 → 0.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/platform-cloudflare",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Cloudflare Workers platform adapter for Shovel - already ServiceWorker-based!",
5
5
  "keywords": [
6
6
  "shovel",
@@ -11,14 +11,15 @@
11
11
  "serviceworker"
12
12
  ],
13
13
  "dependencies": {
14
- "@b9g/assets": "^0.1.13",
15
- "@b9g/cache": "^0.1.4",
16
- "@b9g/platform": "^0.1.10",
14
+ "@b9g/assets": "^0.1.15",
15
+ "@b9g/cache": "^0.1.5",
16
+ "@b9g/platform": "^0.1.11",
17
17
  "@cloudflare/workers-types": "^4.20241218.0",
18
+ "@logtape/logtape": "^1.2.0",
18
19
  "miniflare": "^4.20251118.1"
19
20
  },
20
21
  "devDependencies": {
21
- "@b9g/libuild": "^0.1.11",
22
+ "@b9g/libuild": "^0.1.18",
22
23
  "bun-types": "latest"
23
24
  },
24
25
  "type": "module",
package/src/index.d.ts CHANGED
@@ -4,20 +4,15 @@
4
4
  * Provides ServiceWorker-native deployment for Cloudflare Workers with KV/R2/D1 integration.
5
5
  */
6
6
  import { BasePlatform, PlatformConfig, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance } from "@b9g/platform";
7
+ import { CustomCacheStorage } from "@b9g/cache";
7
8
  export type { Platform, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance, } from "@b9g/platform";
8
9
  export interface CloudflarePlatformOptions extends PlatformConfig {
9
10
  /** Cloudflare Workers environment (production, preview, dev) */
10
11
  environment?: "production" | "preview" | "dev";
11
12
  /** Static assets directory for ASSETS binding (dev mode) */
12
13
  assetsDirectory?: string;
13
- /** KV namespace bindings */
14
- kvNamespaces?: Record<string, any>;
15
- /** R2 bucket bindings */
16
- r2Buckets?: Record<string, any>;
17
- /** D1 database bindings */
18
- d1Databases?: Record<string, any>;
19
- /** Durable Object bindings */
20
- durableObjects?: Record<string, any>;
14
+ /** Working directory for config file resolution */
15
+ cwd?: string;
21
16
  }
22
17
  /**
23
18
  * Cloudflare Workers platform implementation
@@ -26,15 +21,25 @@ export declare class CloudflarePlatform extends BasePlatform {
26
21
  #private;
27
22
  readonly name: string;
28
23
  constructor(options?: CloudflarePlatformOptions);
24
+ /**
25
+ * Create cache storage
26
+ * Uses config from shovel.json with memory cache default.
27
+ *
28
+ * Note: This is for the platform/test runner context. Inside actual
29
+ * Cloudflare Workers, native caches are available via globalThis.caches
30
+ * (captured by the banner as globalThis.__cloudflareCaches).
31
+ */
32
+ createCaches(): Promise<CustomCacheStorage>;
29
33
  /**
30
34
  * Create "server" for Cloudflare Workers (which is really just the handler)
31
35
  */
32
36
  createServer(handler: Handler, _options?: ServerOptions): Server;
33
37
  /**
34
- * Load ServiceWorker-style entrypoint in Cloudflare Workers
38
+ * Load ServiceWorker-style entrypoint using miniflare (workerd)
35
39
  *
36
- * In production: Uses the native CF Worker environment
37
- * In dev mode: Uses miniflare (workerd) for true dev/prod parity
40
+ * Note: In production Cloudflare Workers, the banner/footer wrapper code
41
+ * handles request dispatch directly - loadServiceWorker is only used for
42
+ * local development with miniflare.
38
43
  */
39
44
  loadServiceWorker(entrypoint: string, _options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
40
45
  /**
@@ -61,11 +66,11 @@ export declare function generateWranglerConfig(options: {
61
66
  /**
62
67
  * Generate banner code for ServiceWorker → ES Module conversion
63
68
  */
64
- export declare const cloudflareWorkerBanner = "// Cloudflare Worker ES Module wrapper\nlet serviceWorkerGlobals = null;\n\n// Set up ServiceWorker environment\nif (typeof globalThis.self === 'undefined') {\n\tglobalThis.self = globalThis;\n}\n\n// Capture fetch event handlers\nconst fetchHandlers = [];\nconst originalAddEventListener = globalThis.addEventListener;\nglobalThis.addEventListener = function(type, handler, options) {\n\tif (type === 'fetch') {\n\t\tfetchHandlers.push(handler);\n\t} else {\n\t\toriginalAddEventListener?.call(this, type, handler, options);\n\t}\n};\n\n// Create a promise-based FetchEvent that can be awaited\nclass FetchEvent {\n\tconstructor(type, init) {\n\t\tthis.type = type;\n\t\tthis.request = init.request;\n\t\tthis._response = null;\n\t\tthis._responsePromise = new Promise((resolve) => {\n\t\t\tthis._resolveResponse = resolve;\n\t\t});\n\t}\n\t\n\trespondWith(response) {\n\t\tthis._response = response;\n\t\tthis._resolveResponse(response);\n\t}\n\t\n\tasync waitUntil(promise) {\n\t\tawait promise;\n\t}\n}";
69
+ export declare const cloudflareWorkerBanner = "// Cloudflare Worker ES Module wrapper\nlet serviceWorkerGlobals = null;\n\n// Capture native Cloudflare caches before any framework code runs\nconst nativeCaches = globalThis.caches;\n\n// Set up ServiceWorker environment\nif (typeof globalThis.self === 'undefined') {\n\tglobalThis.self = globalThis;\n}\n\n// Store native caches for access via globalThis.__cloudflareCaches\nglobalThis.__cloudflareCaches = nativeCaches;\n\n// Capture fetch event handlers\nconst fetchHandlers = [];\nconst originalAddEventListener = globalThis.addEventListener;\nglobalThis.addEventListener = function(type, handler, options) {\n\tif (type === 'fetch') {\n\t\tfetchHandlers.push(handler);\n\t} else {\n\t\toriginalAddEventListener?.call(this, type, handler, options);\n\t}\n};\n\n// Create a promise-based FetchEvent that can be awaited\nclass FetchEvent {\n\tconstructor(type, init) {\n\t\tthis.type = type;\n\t\tthis.request = init.request;\n\t\tthis._response = null;\n\t\tthis._responsePromise = new Promise((resolve) => {\n\t\t\tthis._resolveResponse = resolve;\n\t\t});\n\t}\n\t\n\trespondWith(response) {\n\t\tthis._response = response;\n\t\tthis._resolveResponse(response);\n\t}\n\t\n\tasync waitUntil(promise) {\n\t\tawait promise;\n\t}\n}";
65
70
  /**
66
71
  * Generate footer code for ServiceWorker → ES Module conversion
67
72
  */
68
- export declare const cloudflareWorkerFooter = "\n// Export ES Module for Cloudflare Workers\nexport default {\n\tasync fetch(request, env, ctx) {\n\t\ttry {\n\t\t\t// Set up ServiceWorker-like dirs API for bundled deployment\n\t\t\tif (!globalThis.self.dirs) {\n\t\t\t\t// For bundled deployment, assets are served via static middleware\n\t\t\t\t// not through the dirs API\n\t\t\t\tglobalThis.self.dirs = {\n\t\t\t\t\tasync open(directoryName) {\n\t\t\t\t\t\tif (directoryName === 'assets') {\n\t\t\t\t\t\t\t// Return a minimal interface that indicates no files available\n\t\t\t\t\t\t\t// The assets middleware will fall back to dev mode behavior\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tasync getFileHandle(fileName) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`NotFoundError: ${fileName} not found in bundled assets`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow new Error(`Directory ${directoryName} not available in bundled deployment`);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t\t\n\t\t\t// Set up caches API\n\t\t\tif (!globalThis.self.caches) {\n\t\t\t\tglobalThis.self.caches = globalThis.caches;\n\t\t\t}\n\t\t\t\n\t\t\t// Ensure request.url is a string\n\t\t\tif (typeof request.url !== 'string') {\n\t\t\t\treturn new Response('Invalid request URL: ' + typeof request.url, { status: 500 });\n\t\t\t}\n\t\t\t\n\t\t\t// Create proper FetchEvent-like object\n\t\t\tlet responseReceived = null;\n\t\t\tconst event = { \n\t\t\t\trequest, \n\t\t\t\trespondWith: (response) => { responseReceived = response; }\n\t\t\t};\n\t\t\t\n\t\t\t// Helper for error responses\n\t\t\tconst createErrorResponse = (err) => {\n\t\t\t\tconst isDev = typeof import.meta !== \"undefined\" && import.meta.env?.MODE !== \"production\";\n\t\t\t\tif (isDev) {\n\t\t\t\t\tconst escapeHtml = (str) => str.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\n\t\t\t\t\treturn new Response(`<!DOCTYPE html>\n<html>\n<head>\n <title>500 Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }\n h1 { color: #c00; }\n .message { font-size: 1.2em; color: #333; }\n pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }\n </style>\n</head>\n<body>\n <h1>500 Internal Server Error</h1>\n <p class=\"message\">${escapeHtml(err.message)}</p>\n <pre>${escapeHtml(err.stack || \"No stack trace available\")}</pre>\n</body>\n</html>`, { status: 500, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n\t\t\t\t} else {\n\t\t\t\t\treturn new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain\" } });\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Dispatch to ServiceWorker fetch handlers\n\t\t\tfor (const handler of fetchHandlers) {\n\t\t\t\ttry {\n\t\t\t\t\tlogger.debug(\"Calling handler\", {url: request.url});\n\t\t\t\t\tawait handler(event);\n\t\t\t\t\tlogger.debug(\"Handler completed\", {hasResponse: !!responseReceived});\n\t\t\t\t\tif (responseReceived) {\n\t\t\t\t\t\treturn responseReceived;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(\"Handler error\", {error});\n\t\t\t\t\tlogger.error(\"Error stack\", {stack: error.stack});\n\t\t\t\t\treturn createErrorResponse(error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn new Response('No ServiceWorker handler', { status: 404 });\n\t\t} catch (topLevelError) {\n\t\t\tlogger.error(\"Top-level error\", {error: topLevelError});\n\t\t\tconst isDev = typeof import.meta !== \"undefined\" && import.meta.env?.MODE !== \"production\";\n\t\t\tif (isDev) {\n\t\t\t\tconst escapeHtml = (str) => String(str).replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\n\t\t\t\treturn new Response(`<!DOCTYPE html>\n<html>\n<head>\n <title>500 Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }\n h1 { color: #c00; }\n .message { font-size: 1.2em; color: #333; }\n pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }\n </style>\n</head>\n<body>\n <h1>500 Internal Server Error</h1>\n <p class=\"message\">${escapeHtml(topLevelError.message)}</p>\n <pre>${escapeHtml(topLevelError.stack || \"No stack trace available\")}</pre>\n</body>\n</html>`, { status: 500, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n\t\t\t} else {\n\t\t\t\treturn new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain\" } });\n\t\t\t}\n\t\t}\n\t}\n};";
73
+ export declare const cloudflareWorkerFooter = "\n// Export ES Module for Cloudflare Workers\nexport default {\n\tasync fetch(request, env, ctx) {\n\t\ttry {\n\t\t\t// Set up ServiceWorker-like dirs API for bundled deployment\n\t\t\tif (!globalThis.self.dirs) {\n\t\t\t\t// For bundled deployment, assets are served via static middleware\n\t\t\t\t// not through the dirs API\n\t\t\t\tglobalThis.self.dirs = {\n\t\t\t\t\tasync open(directoryName) {\n\t\t\t\t\t\tif (directoryName === 'assets') {\n\t\t\t\t\t\t\t// Return a minimal interface that indicates no files available\n\t\t\t\t\t\t\t// The assets middleware will fall back to dev mode behavior\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tasync getFileHandle(fileName) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`NotFoundError: ${fileName} not found in bundled assets`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow new Error(`Directory ${directoryName} not available in bundled deployment`);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t\t\n\t\t\t// Set up caches API\n\t\t\tif (!globalThis.self.caches) {\n\t\t\t\tglobalThis.self.caches = globalThis.caches;\n\t\t\t}\n\t\t\t\n\t\t\t// Ensure request.url is a string\n\t\t\tif (typeof request.url !== 'string') {\n\t\t\t\treturn new Response('Invalid request URL: ' + typeof request.url, { status: 500 });\n\t\t\t}\n\t\t\t\n\t\t\t// Create proper FetchEvent-like object\n\t\t\tlet responseReceived = null;\n\t\t\tconst event = { \n\t\t\t\trequest, \n\t\t\t\trespondWith: (response) => { responseReceived = response; }\n\t\t\t};\n\t\t\t\n\t\t\t// Helper for error responses\n\t\t\tconst createErrorResponse = (err) => {\n\t\t\t\tconst isDev = typeof import.meta !== \"undefined\" && import.meta.env?.MODE !== \"production\";\n\t\t\t\tif (isDev) {\n\t\t\t\t\tconst escapeHtml = (str) => str.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\n\t\t\t\t\treturn new Response(`<!DOCTYPE html>\n<html>\n<head>\n <title>500 Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }\n h1 { color: #c00; }\n .message { font-size: 1.2em; color: #333; }\n pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }\n </style>\n</head>\n<body>\n <h1>500 Internal Server Error</h1>\n <p class=\"message\">${escapeHtml(err.message)}</p>\n <pre>${escapeHtml(err.stack || \"No stack trace available\")}</pre>\n</body>\n</html>`, { status: 500, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n\t\t\t\t} else {\n\t\t\t\t\treturn new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain\" } });\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Dispatch to ServiceWorker fetch handlers\n\t\t\tfor (const handler of fetchHandlers) {\n\t\t\t\ttry {\n\t\t\t\t\tawait handler(event);\n\t\t\t\t\tif (responseReceived) {\n\t\t\t\t\t\treturn responseReceived;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Handler error:\", error);\n\t\t\t\t\treturn createErrorResponse(error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn new Response('No ServiceWorker handler', { status: 404 });\n\t\t} catch (topLevelError) {\n\t\t\tconsole.error(\"Top-level error:\", topLevelError);\n\t\t\tconst isDev = typeof import.meta !== \"undefined\" && import.meta.env?.MODE !== \"production\";\n\t\t\tif (isDev) {\n\t\t\t\tconst escapeHtml = (str) => String(str).replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\n\t\t\t\treturn new Response(`<!DOCTYPE html>\n<html>\n<head>\n <title>500 Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }\n h1 { color: #c00; }\n .message { font-size: 1.2em; color: #333; }\n pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }\n </style>\n</head>\n<body>\n <h1>500 Internal Server Error</h1>\n <p class=\"message\">${escapeHtml(topLevelError.message)}</p>\n <pre>${escapeHtml(topLevelError.stack || \"No stack trace available\")}</pre>\n</body>\n</html>`, { status: 500, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n\t\t\t} else {\n\t\t\t\treturn new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain\" } });\n\t\t\t}\n\t\t}\n\t}\n};";
69
74
  /**
70
75
  * Default export for easy importing
71
76
  */
package/src/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  /// <reference types="./index.d.ts" />
2
2
  // src/index.ts
3
3
  import {
4
- BasePlatform
4
+ BasePlatform,
5
+ loadConfig,
6
+ createCacheFactory
5
7
  } from "@b9g/platform";
8
+ import { CustomCacheStorage } from "@b9g/cache";
6
9
  import { getLogger } from "@logtape/logtape";
7
10
  var logger = getLogger(["platform-cloudflare"]);
8
11
  var CloudflarePlatform = class extends BasePlatform {
@@ -11,23 +14,33 @@ var CloudflarePlatform = class extends BasePlatform {
11
14
  #miniflare;
12
15
  #assetsMiniflare;
13
16
  // Separate instance for ASSETS binding
14
- #assetsBinding;
17
+ #config;
15
18
  constructor(options = {}) {
16
19
  super(options);
17
20
  this.#miniflare = null;
18
21
  this.#assetsMiniflare = null;
19
- this.#assetsBinding = null;
20
22
  this.name = "cloudflare";
23
+ const cwd = options.cwd ?? ".";
24
+ this.#config = loadConfig(cwd);
21
25
  this.#options = {
22
- environment: "production",
23
- assetsDirectory: void 0,
24
- kvNamespaces: {},
25
- r2Buckets: {},
26
- d1Databases: {},
27
- durableObjects: {},
28
- ...options
26
+ environment: options.environment ?? "production",
27
+ assetsDirectory: options.assetsDirectory,
28
+ cwd
29
29
  };
30
30
  }
31
+ /**
32
+ * Create cache storage
33
+ * Uses config from shovel.json with memory cache default.
34
+ *
35
+ * Note: This is for the platform/test runner context. Inside actual
36
+ * Cloudflare Workers, native caches are available via globalThis.caches
37
+ * (captured by the banner as globalThis.__cloudflareCaches).
38
+ */
39
+ async createCaches() {
40
+ return new CustomCacheStorage(
41
+ createCacheFactory({ config: this.#config, defaultProvider: "cloudflare" })
42
+ );
43
+ }
31
44
  /**
32
45
  * Create "server" for Cloudflare Workers (which is really just the handler)
33
46
  */
@@ -49,35 +62,14 @@ var CloudflarePlatform = class extends BasePlatform {
49
62
  };
50
63
  }
51
64
  /**
52
- * Load ServiceWorker-style entrypoint in Cloudflare Workers
65
+ * Load ServiceWorker-style entrypoint using miniflare (workerd)
53
66
  *
54
- * In production: Uses the native CF Worker environment
55
- * In dev mode: Uses miniflare (workerd) for true dev/prod parity
67
+ * Note: In production Cloudflare Workers, the banner/footer wrapper code
68
+ * handles request dispatch directly - loadServiceWorker is only used for
69
+ * local development with miniflare.
56
70
  */
57
71
  async loadServiceWorker(entrypoint, _options = {}) {
58
- const isCloudflareWorker = typeof globalThis.addEventListener === "function" && typeof globalThis.caches !== "undefined" && typeof globalThis.FetchEvent !== "undefined";
59
- if (isCloudflareWorker) {
60
- logger.info("Running in native ServiceWorker environment", {});
61
- const instance = {
62
- runtime: globalThis,
63
- handleRequest: async (request) => {
64
- const event = new FetchEvent("fetch", { request });
65
- globalThis.dispatchEvent(event);
66
- return new Response("Worker handler", { status: 200 });
67
- },
68
- install: () => Promise.resolve(),
69
- activate: () => Promise.resolve(),
70
- get ready() {
71
- return true;
72
- },
73
- dispose: async () => {
74
- }
75
- };
76
- await import(entrypoint);
77
- return instance;
78
- } else {
79
- return this.#loadServiceWorkerWithMiniflare(entrypoint);
80
- }
72
+ return this.#loadServiceWorkerWithMiniflare(entrypoint);
81
73
  }
82
74
  /**
83
75
  * Load ServiceWorker using miniflare (workerd) for dev mode
@@ -108,11 +100,8 @@ var CloudflarePlatform = class extends BasePlatform {
108
100
  },
109
101
  compatibilityDate: "2024-09-23"
110
102
  });
111
- const assetsEnv = await this.#assetsMiniflare.getBindings();
112
- if (assetsEnv.ASSETS) {
113
- this.#assetsBinding = assetsEnv.ASSETS;
114
- logger.info("ASSETS binding available", {});
115
- }
103
+ await this.#assetsMiniflare.ready;
104
+ logger.info("ASSETS binding available", {});
116
105
  }
117
106
  const mf = this.#miniflare;
118
107
  const instance = {
@@ -154,54 +143,13 @@ var CloudflarePlatform = class extends BasePlatform {
154
143
  await this.#assetsMiniflare.dispose();
155
144
  this.#assetsMiniflare = null;
156
145
  }
157
- this.#assetsBinding = null;
158
146
  }
159
147
  };
160
148
  function createOptionsFromEnv(env) {
161
149
  return {
162
- environment: env.ENVIRONMENT || "production",
163
- kvNamespaces: extractKVNamespaces(env),
164
- r2Buckets: extractR2Buckets(env),
165
- d1Databases: extractD1Databases(env),
166
- durableObjects: extractDurableObjects(env)
150
+ environment: env.ENVIRONMENT || "production"
167
151
  };
168
152
  }
169
- function extractKVNamespaces(env) {
170
- const kvNamespaces = {};
171
- for (const [key, value] of Object.entries(env)) {
172
- if (key.endsWith("_KV") || key.includes("KV")) {
173
- kvNamespaces[key] = value;
174
- }
175
- }
176
- return kvNamespaces;
177
- }
178
- function extractR2Buckets(env) {
179
- const r2Buckets = {};
180
- for (const [key, value] of Object.entries(env)) {
181
- if (key.endsWith("_R2") || key.includes("R2")) {
182
- r2Buckets[key] = value;
183
- }
184
- }
185
- return r2Buckets;
186
- }
187
- function extractD1Databases(env) {
188
- const d1Databases = {};
189
- for (const [key, value] of Object.entries(env)) {
190
- if (key.endsWith("_D1") || key.includes("D1") || key.endsWith("_DB")) {
191
- d1Databases[key] = value;
192
- }
193
- }
194
- return d1Databases;
195
- }
196
- function extractDurableObjects(env) {
197
- const durableObjects = {};
198
- for (const [key, value] of Object.entries(env)) {
199
- if (key.endsWith("_DO") || key.includes("DURABLE")) {
200
- durableObjects[key] = value;
201
- }
202
- }
203
- return durableObjects;
204
- }
205
153
  function generateWranglerConfig(options) {
206
154
  const {
207
155
  name,
@@ -250,11 +198,17 @@ database_id = "your-database-id"`
250
198
  var cloudflareWorkerBanner = `// Cloudflare Worker ES Module wrapper
251
199
  let serviceWorkerGlobals = null;
252
200
 
201
+ // Capture native Cloudflare caches before any framework code runs
202
+ const nativeCaches = globalThis.caches;
203
+
253
204
  // Set up ServiceWorker environment
254
205
  if (typeof globalThis.self === 'undefined') {
255
206
  globalThis.self = globalThis;
256
207
  }
257
208
 
209
+ // Store native caches for access via globalThis.__cloudflareCaches
210
+ globalThis.__cloudflareCaches = nativeCaches;
211
+
258
212
  // Capture fetch event handlers
259
213
  const fetchHandlers = [];
260
214
  const originalAddEventListener = globalThis.addEventListener;
@@ -358,22 +312,19 @@ export default {
358
312
  // Dispatch to ServiceWorker fetch handlers
359
313
  for (const handler of fetchHandlers) {
360
314
  try {
361
- logger.debug("Calling handler", {url: request.url});
362
315
  await handler(event);
363
- logger.debug("Handler completed", {hasResponse: !!responseReceived});
364
316
  if (responseReceived) {
365
317
  return responseReceived;
366
318
  }
367
319
  } catch (error) {
368
- logger.error("Handler error", {error});
369
- logger.error("Error stack", {stack: error.stack});
320
+ console.error("Handler error:", error);
370
321
  return createErrorResponse(error);
371
322
  }
372
323
  }
373
324
 
374
325
  return new Response('No ServiceWorker handler', { status: 404 });
375
326
  } catch (topLevelError) {
376
- logger.error("Top-level error", {error: topLevelError});
327
+ console.error("Top-level error:", topLevelError);
377
328
  const isDev = typeof import.meta !== "undefined" && import.meta.env?.MODE !== "production";
378
329
  if (isDev) {
379
330
  const escapeHtml = (str) => String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");