@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 +6 -5
- package/src/index.d.ts +18 -13
- package/src/index.js +39 -88
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform-cloudflare",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
15
|
-
"@b9g/cache": "^0.1.
|
|
16
|
-
"@b9g/platform": "^0.1.
|
|
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.
|
|
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
|
-
/**
|
|
14
|
-
|
|
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
|
|
38
|
+
* Load ServiceWorker-style entrypoint using miniflare (workerd)
|
|
35
39
|
*
|
|
36
|
-
* In production
|
|
37
|
-
*
|
|
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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\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\
|
|
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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\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
|
-
#
|
|
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:
|
|
24
|
-
|
|
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
|
|
65
|
+
* Load ServiceWorker-style entrypoint using miniflare (workerd)
|
|
53
66
|
*
|
|
54
|
-
* In production
|
|
55
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|