@b9g/platform-cloudflare 0.1.5 → 0.1.6

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.5",
3
+ "version": "0.1.6",
4
4
  "description": "Cloudflare Workers platform adapter for Shovel - already ServiceWorker-based!",
5
5
  "keywords": [
6
6
  "shovel",
@@ -11,9 +11,9 @@
11
11
  "serviceworker"
12
12
  ],
13
13
  "dependencies": {
14
- "@b9g/assets": "^0.1.5",
14
+ "@b9g/assets": "^0.1.12",
15
15
  "@b9g/cache": "^0.1.4",
16
- "@b9g/platform": "^0.1.5",
16
+ "@b9g/platform": "^0.1.9",
17
17
  "@cloudflare/workers-types": "^4.20241218.0",
18
18
  "miniflare": "^4.20251118.1"
19
19
  },
@@ -1,11 +1,12 @@
1
1
  /// <reference types="./filesystem-assets.d.ts" />
2
2
  // src/filesystem-assets.ts
3
3
  var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
4
- kind = "directory";
4
+ kind;
5
5
  name;
6
6
  #assets;
7
7
  #basePath;
8
8
  constructor(assets, basePath = "/") {
9
+ this.kind = "directory";
9
10
  this.#assets = assets;
10
11
  this.#basePath = basePath.endsWith("/") ? basePath : basePath + "/";
11
12
  this.name = basePath.split("/").filter(Boolean).pop() || "assets";
@@ -32,18 +33,21 @@ var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
32
33
  async resolve(_possibleDescendant) {
33
34
  return null;
34
35
  }
36
+ // eslint-disable-next-line require-yield
35
37
  async *entries() {
36
38
  throw new DOMException(
37
39
  "Directory listing not supported for ASSETS binding. Use an asset manifest for enumeration.",
38
40
  "NotSupportedError"
39
41
  );
40
42
  }
43
+ // eslint-disable-next-line require-yield
41
44
  async *keys() {
42
45
  throw new DOMException(
43
46
  "Directory listing not supported for ASSETS binding",
44
47
  "NotSupportedError"
45
48
  );
46
49
  }
50
+ // eslint-disable-next-line require-yield
47
51
  async *values() {
48
52
  throw new DOMException(
49
53
  "Directory listing not supported for ASSETS binding",
@@ -60,11 +64,12 @@ var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
60
64
  }
61
65
  };
62
66
  var CFAssetsFileHandle = class _CFAssetsFileHandle {
63
- kind = "file";
67
+ kind;
64
68
  name;
65
69
  #assets;
66
70
  #path;
67
71
  constructor(assets, path, name) {
72
+ this.kind = "file";
68
73
  this.#assets = assets;
69
74
  this.#path = path;
70
75
  this.name = name;
package/src/index.d.ts CHANGED
@@ -65,7 +65,7 @@ export declare const cloudflareWorkerBanner = "// Cloudflare Worker ES Module wr
65
65
  /**
66
66
  * Generate footer code for ServiceWorker → ES Module conversion
67
67
  */
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// 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\t// Return detailed error in response body for debugging\n\t\t\t\t\treturn new Response(JSON.stringify({\n\t\t\t\t\t\terror: error.message,\n\t\t\t\t\t\tstack: error.stack,\n\t\t\t\t\t\tname: error.name,\n\t\t\t\t\t\turl: request.url\n\t\t\t\t\t}, null, 2), { \n\t\t\t\t\t\tstatus: 500,\n\t\t\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\t\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\treturn new Response(JSON.stringify({\n\t\t\t\terror: 'Top-level wrapper error: ' + topLevelError.message,\n\t\t\t\tstack: topLevelError.stack,\n\t\t\t\tname: topLevelError.name,\n\t\t\t\turl: request?.url || 'unknown'\n\t\t\t}, null, 2), { \n\t\t\t\tstatus: 500,\n\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t});\n\t\t}\n\t}\n};";
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};";
69
69
  /**
70
70
  * Default export for easy importing
71
71
  */
package/src/index.js CHANGED
@@ -8,12 +8,15 @@ var logger = getLogger(["platform-cloudflare"]);
8
8
  var CloudflarePlatform = class extends BasePlatform {
9
9
  name;
10
10
  #options;
11
- #miniflare = null;
12
- #assetsMiniflare = null;
11
+ #miniflare;
12
+ #assetsMiniflare;
13
13
  // Separate instance for ASSETS binding
14
- #assetsBinding = null;
14
+ #assetsBinding;
15
15
  constructor(options = {}) {
16
16
  super(options);
17
+ this.#miniflare = null;
18
+ this.#assetsMiniflare = null;
19
+ this.#assetsBinding = null;
17
20
  this.name = "cloudflare";
18
21
  this.#options = {
19
22
  environment: "production",
@@ -115,7 +118,17 @@ var CloudflarePlatform = class extends BasePlatform {
115
118
  const instance = {
116
119
  runtime: mf,
117
120
  handleRequest: async (request) => {
118
- return mf.dispatchFetch(request);
121
+ const cfResponse = await mf.dispatchFetch(request.url, {
122
+ method: request.method,
123
+ headers: request.headers,
124
+ body: request.body,
125
+ duplex: request.body ? "half" : void 0
126
+ });
127
+ return new Response(cfResponse.body, {
128
+ status: cfResponse.status,
129
+ statusText: cfResponse.statusText,
130
+ headers: cfResponse.headers
131
+ });
119
132
  },
120
133
  install: () => Promise.resolve(),
121
134
  activate: () => Promise.resolve(),
@@ -315,6 +328,33 @@ export default {
315
328
  respondWith: (response) => { responseReceived = response; }
316
329
  };
317
330
 
331
+ // Helper for error responses
332
+ const createErrorResponse = (err) => {
333
+ const isDev = typeof import.meta !== "undefined" && import.meta.env?.MODE !== "production";
334
+ if (isDev) {
335
+ const escapeHtml = (str) => str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
336
+ return new Response(\`<!DOCTYPE html>
337
+ <html>
338
+ <head>
339
+ <title>500 Internal Server Error</title>
340
+ <style>
341
+ body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
342
+ h1 { color: #c00; }
343
+ .message { font-size: 1.2em; color: #333; }
344
+ pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }
345
+ </style>
346
+ </head>
347
+ <body>
348
+ <h1>500 Internal Server Error</h1>
349
+ <p class="message">\${escapeHtml(err.message)}</p>
350
+ <pre>\${escapeHtml(err.stack || "No stack trace available")}</pre>
351
+ </body>
352
+ </html>\`, { status: 500, headers: { "Content-Type": "text/html; charset=utf-8" } });
353
+ } else {
354
+ return new Response("Internal Server Error", { status: 500, headers: { "Content-Type": "text/plain" } });
355
+ }
356
+ };
357
+
318
358
  // Dispatch to ServiceWorker fetch handlers
319
359
  for (const handler of fetchHandlers) {
320
360
  try {
@@ -327,31 +367,36 @@ export default {
327
367
  } catch (error) {
328
368
  logger.error("Handler error", {error});
329
369
  logger.error("Error stack", {stack: error.stack});
330
- // Return detailed error in response body for debugging
331
- return new Response(JSON.stringify({
332
- error: error.message,
333
- stack: error.stack,
334
- name: error.name,
335
- url: request.url
336
- }, null, 2), {
337
- status: 500,
338
- headers: { 'Content-Type': 'application/json' }
339
- });
370
+ return createErrorResponse(error);
340
371
  }
341
372
  }
342
-
373
+
343
374
  return new Response('No ServiceWorker handler', { status: 404 });
344
375
  } catch (topLevelError) {
345
376
  logger.error("Top-level error", {error: topLevelError});
346
- return new Response(JSON.stringify({
347
- error: 'Top-level wrapper error: ' + topLevelError.message,
348
- stack: topLevelError.stack,
349
- name: topLevelError.name,
350
- url: request?.url || 'unknown'
351
- }, null, 2), {
352
- status: 500,
353
- headers: { 'Content-Type': 'application/json' }
354
- });
377
+ const isDev = typeof import.meta !== "undefined" && import.meta.env?.MODE !== "production";
378
+ if (isDev) {
379
+ const escapeHtml = (str) => String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
380
+ return new Response(\`<!DOCTYPE html>
381
+ <html>
382
+ <head>
383
+ <title>500 Internal Server Error</title>
384
+ <style>
385
+ body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
386
+ h1 { color: #c00; }
387
+ .message { font-size: 1.2em; color: #333; }
388
+ pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }
389
+ </style>
390
+ </head>
391
+ <body>
392
+ <h1>500 Internal Server Error</h1>
393
+ <p class="message">\${escapeHtml(topLevelError.message)}</p>
394
+ <pre>\${escapeHtml(topLevelError.stack || "No stack trace available")}</pre>
395
+ </body>
396
+ </html>\`, { status: 500, headers: { "Content-Type": "text/html; charset=utf-8" } });
397
+ } else {
398
+ return new Response("Internal Server Error", { status: 500, headers: { "Content-Type": "text/plain" } });
399
+ }
355
400
  }
356
401
  }
357
402
  };`;