@b9g/platform-cloudflare 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.
- package/README.md +67 -64
- package/package.json +38 -13
- package/src/caches.d.ts +24 -0
- package/src/caches.js +52 -0
- package/src/directories.d.ts +160 -0
- package/src/directories.js +344 -0
- package/src/index.d.ts +49 -42
- package/src/index.js +121 -244
- package/src/runtime.d.ts +47 -0
- package/src/runtime.js +65 -0
- package/src/variables.d.ts +15 -0
- package/src/variables.js +17 -0
- package/src/filesystem-assets.d.ts +0 -55
- package/src/filesystem-assets.js +0 -106
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/// <reference types="./directories.d.ts" />
|
|
2
|
+
// src/directories.ts
|
|
3
|
+
import mime from "mime";
|
|
4
|
+
import { getEnv } from "./variables.js";
|
|
5
|
+
var R2FileSystemWritableFileStream = class extends WritableStream {
|
|
6
|
+
constructor(r2Bucket, key) {
|
|
7
|
+
const chunks = [];
|
|
8
|
+
super({
|
|
9
|
+
write: (chunk) => {
|
|
10
|
+
chunks.push(chunk);
|
|
11
|
+
return Promise.resolve();
|
|
12
|
+
},
|
|
13
|
+
close: async () => {
|
|
14
|
+
const totalLength = chunks.reduce(
|
|
15
|
+
(sum, chunk) => sum + chunk.length,
|
|
16
|
+
0
|
|
17
|
+
);
|
|
18
|
+
const buffer = new Uint8Array(totalLength);
|
|
19
|
+
let offset = 0;
|
|
20
|
+
for (const chunk of chunks) {
|
|
21
|
+
buffer.set(chunk, offset);
|
|
22
|
+
offset += chunk.length;
|
|
23
|
+
}
|
|
24
|
+
await r2Bucket.put(key, buffer);
|
|
25
|
+
},
|
|
26
|
+
abort: async () => {
|
|
27
|
+
chunks.length = 0;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var R2FileSystemFileHandle = class _R2FileSystemFileHandle {
|
|
33
|
+
kind;
|
|
34
|
+
name;
|
|
35
|
+
#r2Bucket;
|
|
36
|
+
#key;
|
|
37
|
+
constructor(r2Bucket, key) {
|
|
38
|
+
this.kind = "file";
|
|
39
|
+
this.#r2Bucket = r2Bucket;
|
|
40
|
+
this.#key = key;
|
|
41
|
+
this.name = key.split("/").pop() || key;
|
|
42
|
+
}
|
|
43
|
+
async getFile() {
|
|
44
|
+
const r2Object = await this.#r2Bucket.get(this.#key);
|
|
45
|
+
if (!r2Object) {
|
|
46
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
47
|
+
}
|
|
48
|
+
const arrayBuffer = await r2Object.arrayBuffer();
|
|
49
|
+
return new File([arrayBuffer], this.name, {
|
|
50
|
+
lastModified: r2Object.uploaded.getTime(),
|
|
51
|
+
type: r2Object.httpMetadata?.contentType || this.#getMimeType(this.#key)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async createWritable() {
|
|
55
|
+
return new R2FileSystemWritableFileStream(
|
|
56
|
+
this.#r2Bucket,
|
|
57
|
+
this.#key
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
async createSyncAccessHandle() {
|
|
61
|
+
throw new DOMException(
|
|
62
|
+
"Synchronous access handles are not supported for R2 storage",
|
|
63
|
+
"InvalidStateError"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
async isSameEntry(other) {
|
|
67
|
+
if (other.kind !== "file") return false;
|
|
68
|
+
if (!(other instanceof _R2FileSystemFileHandle)) return false;
|
|
69
|
+
return this.#key === other.#key;
|
|
70
|
+
}
|
|
71
|
+
async queryPermission() {
|
|
72
|
+
return "granted";
|
|
73
|
+
}
|
|
74
|
+
async requestPermission() {
|
|
75
|
+
return "granted";
|
|
76
|
+
}
|
|
77
|
+
#getMimeType(key) {
|
|
78
|
+
return mime.getType(key) || "application/octet-stream";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var R2FileSystemDirectoryHandle = class _R2FileSystemDirectoryHandle {
|
|
82
|
+
kind;
|
|
83
|
+
name;
|
|
84
|
+
#r2Bucket;
|
|
85
|
+
#prefix;
|
|
86
|
+
constructor(r2Bucket, prefix) {
|
|
87
|
+
this.kind = "directory";
|
|
88
|
+
this.#r2Bucket = r2Bucket;
|
|
89
|
+
this.#prefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
90
|
+
this.name = this.#prefix.split("/").pop() || "root";
|
|
91
|
+
}
|
|
92
|
+
async getFileHandle(name, options) {
|
|
93
|
+
const key = this.#prefix ? `${this.#prefix}/${name}` : name;
|
|
94
|
+
const exists = await this.#r2Bucket.head(key);
|
|
95
|
+
if (!exists && options?.create) {
|
|
96
|
+
await this.#r2Bucket.put(key, new Uint8Array(0));
|
|
97
|
+
} else if (!exists) {
|
|
98
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
99
|
+
}
|
|
100
|
+
return new R2FileSystemFileHandle(this.#r2Bucket, key);
|
|
101
|
+
}
|
|
102
|
+
async getDirectoryHandle(name, options) {
|
|
103
|
+
const newPrefix = this.#prefix ? `${this.#prefix}/${name}` : name;
|
|
104
|
+
if (options?.create) {
|
|
105
|
+
const markerKey = `${newPrefix}/.shovel_directory_marker`;
|
|
106
|
+
const exists = await this.#r2Bucket.head(markerKey);
|
|
107
|
+
if (!exists) {
|
|
108
|
+
await this.#r2Bucket.put(markerKey, new Uint8Array(0));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return new _R2FileSystemDirectoryHandle(this.#r2Bucket, newPrefix);
|
|
112
|
+
}
|
|
113
|
+
async removeEntry(name, options) {
|
|
114
|
+
const key = this.#prefix ? `${this.#prefix}/${name}` : name;
|
|
115
|
+
const fileExists = await this.#r2Bucket.head(key);
|
|
116
|
+
if (fileExists) {
|
|
117
|
+
await this.#r2Bucket.delete(key);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (options?.recursive) {
|
|
121
|
+
const dirPrefix = `${key}/`;
|
|
122
|
+
const listed = await this.#r2Bucket.list({ prefix: dirPrefix });
|
|
123
|
+
const deletePromises = listed.objects.map(
|
|
124
|
+
(object) => this.#r2Bucket.delete(object.key)
|
|
125
|
+
);
|
|
126
|
+
await Promise.all(deletePromises);
|
|
127
|
+
const markerKey = `${key}/.shovel_directory_marker`;
|
|
128
|
+
const markerExists = await this.#r2Bucket.head(markerKey);
|
|
129
|
+
if (markerExists) {
|
|
130
|
+
await this.#r2Bucket.delete(markerKey);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
throw new DOMException(
|
|
134
|
+
"Directory is not empty",
|
|
135
|
+
"InvalidModificationError"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async resolve(_possibleDescendant) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
async *entries() {
|
|
143
|
+
const listPrefix = this.#prefix ? `${this.#prefix}/` : "";
|
|
144
|
+
try {
|
|
145
|
+
const result = await this.#r2Bucket.list({
|
|
146
|
+
prefix: listPrefix,
|
|
147
|
+
delimiter: "/"
|
|
148
|
+
});
|
|
149
|
+
for (const object of result.objects) {
|
|
150
|
+
if (object.key !== listPrefix) {
|
|
151
|
+
const name = object.key.substring(listPrefix.length);
|
|
152
|
+
if (!name.includes("/") && !name.endsWith(".shovel_directory_marker")) {
|
|
153
|
+
yield [
|
|
154
|
+
name,
|
|
155
|
+
new R2FileSystemFileHandle(this.#r2Bucket, object.key)
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const prefix of result.delimitedPrefixes) {
|
|
161
|
+
const name = prefix.substring(listPrefix.length).replace(/\/$/, "");
|
|
162
|
+
if (name) {
|
|
163
|
+
yield [
|
|
164
|
+
name,
|
|
165
|
+
new _R2FileSystemDirectoryHandle(
|
|
166
|
+
this.#r2Bucket,
|
|
167
|
+
prefix.replace(/\/$/, "")
|
|
168
|
+
)
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
throw new DOMException("Directory not found", "NotFoundError");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async *keys() {
|
|
177
|
+
for await (const [name] of this.entries()) {
|
|
178
|
+
yield name;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async *values() {
|
|
182
|
+
for await (const [, handle] of this.entries()) {
|
|
183
|
+
yield handle;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async isSameEntry(other) {
|
|
187
|
+
if (other.kind !== "directory") return false;
|
|
188
|
+
if (!(other instanceof _R2FileSystemDirectoryHandle)) return false;
|
|
189
|
+
return this.#prefix === other.#prefix;
|
|
190
|
+
}
|
|
191
|
+
async queryPermission() {
|
|
192
|
+
return "granted";
|
|
193
|
+
}
|
|
194
|
+
async requestPermission() {
|
|
195
|
+
return "granted";
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var CFAssetsFileHandle = class _CFAssetsFileHandle {
|
|
199
|
+
kind;
|
|
200
|
+
name;
|
|
201
|
+
#assets;
|
|
202
|
+
#path;
|
|
203
|
+
constructor(assets, path, name) {
|
|
204
|
+
this.kind = "file";
|
|
205
|
+
this.#assets = assets;
|
|
206
|
+
this.#path = path;
|
|
207
|
+
this.name = name;
|
|
208
|
+
}
|
|
209
|
+
async getFile() {
|
|
210
|
+
const response = await this.#assets.fetch(
|
|
211
|
+
new Request("https://assets" + this.#path)
|
|
212
|
+
);
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
throw new DOMException(
|
|
215
|
+
`A requested file or directory could not be found: ${this.name}`,
|
|
216
|
+
"NotFoundError"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const blob = await response.blob();
|
|
220
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
221
|
+
return new File([blob], this.name, { type: contentType });
|
|
222
|
+
}
|
|
223
|
+
async createWritable(_options) {
|
|
224
|
+
throw new DOMException("Assets are read-only", "NotAllowedError");
|
|
225
|
+
}
|
|
226
|
+
async createSyncAccessHandle() {
|
|
227
|
+
throw new DOMException("Sync access not supported", "NotSupportedError");
|
|
228
|
+
}
|
|
229
|
+
isSameEntry(other) {
|
|
230
|
+
return Promise.resolve(
|
|
231
|
+
other instanceof _CFAssetsFileHandle && other.#path === this.#path
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
|
|
236
|
+
kind;
|
|
237
|
+
name;
|
|
238
|
+
#assets;
|
|
239
|
+
#basePath;
|
|
240
|
+
constructor(assets, basePath = "/") {
|
|
241
|
+
this.kind = "directory";
|
|
242
|
+
this.#assets = assets;
|
|
243
|
+
this.#basePath = basePath.endsWith("/") ? basePath : basePath + "/";
|
|
244
|
+
this.name = basePath.split("/").filter(Boolean).pop() || "assets";
|
|
245
|
+
}
|
|
246
|
+
async getFileHandle(name, _options) {
|
|
247
|
+
const path = this.#basePath + name;
|
|
248
|
+
const response = await this.#assets.fetch(
|
|
249
|
+
new Request("https://assets" + path)
|
|
250
|
+
);
|
|
251
|
+
if (!response.ok) {
|
|
252
|
+
throw new DOMException(
|
|
253
|
+
`A requested file or directory could not be found: ${name}`,
|
|
254
|
+
"NotFoundError"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return new CFAssetsFileHandle(this.#assets, path, name);
|
|
258
|
+
}
|
|
259
|
+
async getDirectoryHandle(name, _options) {
|
|
260
|
+
return new _CFAssetsDirectoryHandle(this.#assets, this.#basePath + name);
|
|
261
|
+
}
|
|
262
|
+
async removeEntry(_name, _options) {
|
|
263
|
+
throw new DOMException("Assets directory is read-only", "NotAllowedError");
|
|
264
|
+
}
|
|
265
|
+
async resolve(_possibleDescendant) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
// eslint-disable-next-line require-yield
|
|
269
|
+
async *entries() {
|
|
270
|
+
throw new DOMException(
|
|
271
|
+
"Directory listing not supported for ASSETS binding. Use an asset manifest for enumeration.",
|
|
272
|
+
"NotSupportedError"
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
// eslint-disable-next-line require-yield
|
|
276
|
+
async *keys() {
|
|
277
|
+
throw new DOMException(
|
|
278
|
+
"Directory listing not supported for ASSETS binding",
|
|
279
|
+
"NotSupportedError"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
// eslint-disable-next-line require-yield
|
|
283
|
+
async *values() {
|
|
284
|
+
throw new DOMException(
|
|
285
|
+
"Directory listing not supported for ASSETS binding",
|
|
286
|
+
"NotSupportedError"
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
[Symbol.asyncIterator]() {
|
|
290
|
+
return this.entries();
|
|
291
|
+
}
|
|
292
|
+
isSameEntry(other) {
|
|
293
|
+
return Promise.resolve(
|
|
294
|
+
other instanceof _CFAssetsDirectoryHandle && other.#basePath === this.#basePath
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var CloudflareR2Directory = class extends R2FileSystemDirectoryHandle {
|
|
299
|
+
constructor(name, options = {}) {
|
|
300
|
+
const env = getEnv();
|
|
301
|
+
const bindingName = options.binding || `${name.toUpperCase()}_R2`;
|
|
302
|
+
const r2Bucket = env[bindingName];
|
|
303
|
+
if (!r2Bucket) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`R2 bucket binding "${bindingName}" not found. Configure in wrangler.toml:
|
|
306
|
+
|
|
307
|
+
[[r2_buckets]]
|
|
308
|
+
binding = "${bindingName}"
|
|
309
|
+
bucket_name = "your-bucket-name"`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const prefix = options.path ?? "";
|
|
313
|
+
const normalizedPrefix = prefix.startsWith("/") ? prefix.slice(1) : prefix;
|
|
314
|
+
super(r2Bucket, normalizedPrefix);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var CloudflareAssetsDirectory = class extends CFAssetsDirectoryHandle {
|
|
318
|
+
constructor(_name, options = {}) {
|
|
319
|
+
const env = getEnv();
|
|
320
|
+
const assets = env.ASSETS;
|
|
321
|
+
if (!assets) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`ASSETS binding not found. Configure in wrangler.toml:
|
|
324
|
+
|
|
325
|
+
[assets]
|
|
326
|
+
directory = "./public"`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const basePath = options.path ?? "/";
|
|
330
|
+
const normalizedBase = basePath === "/" ? "/" : basePath.startsWith("/") ? basePath : `/${basePath}`;
|
|
331
|
+
super(assets, normalizedBase);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var directories_default = CloudflareR2Directory;
|
|
335
|
+
export {
|
|
336
|
+
CFAssetsDirectoryHandle,
|
|
337
|
+
CFAssetsFileHandle,
|
|
338
|
+
CloudflareAssetsDirectory,
|
|
339
|
+
CloudflareR2Directory,
|
|
340
|
+
R2FileSystemDirectoryHandle,
|
|
341
|
+
R2FileSystemFileHandle,
|
|
342
|
+
R2FileSystemWritableFileStream,
|
|
343
|
+
directories_default as default
|
|
344
|
+
};
|
package/src/index.d.ts
CHANGED
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
* @b9g/platform-cloudflare - Cloudflare Workers platform adapter for Shovel
|
|
3
3
|
*
|
|
4
4
|
* Provides ServiceWorker-native deployment for Cloudflare Workers with KV/R2/D1 integration.
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* - Uses ServiceWorkerGlobals from @b9g/platform for full feature parity with Node/Bun
|
|
8
|
+
* - AsyncContext provides per-request access to Cloudflare's env/ctx
|
|
9
|
+
* - Directories use R2 via lazy factory (accessed when directories.open() is called)
|
|
10
|
+
* - Caches use Cloudflare's native Cache API
|
|
5
11
|
*/
|
|
6
|
-
import { BasePlatform, PlatformConfig, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance } from "@b9g/platform";
|
|
12
|
+
import { BasePlatform, PlatformConfig, type PlatformDefaults, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance, EntryWrapperOptions, PlatformESBuildConfig, type LoggerStorage } from "@b9g/platform";
|
|
13
|
+
import { type ShovelConfig } from "@b9g/platform/runtime";
|
|
7
14
|
import { CustomCacheStorage } from "@b9g/cache";
|
|
15
|
+
import type { DirectoryStorage } from "@b9g/filesystem";
|
|
8
16
|
export type { Platform, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance, } from "@b9g/platform";
|
|
9
17
|
export interface CloudflarePlatformOptions extends PlatformConfig {
|
|
10
18
|
/** Cloudflare Workers environment (production, preview, dev) */
|
|
@@ -13,6 +21,8 @@ export interface CloudflarePlatformOptions extends PlatformConfig {
|
|
|
13
21
|
assetsDirectory?: string;
|
|
14
22
|
/** Working directory for config file resolution */
|
|
15
23
|
cwd?: string;
|
|
24
|
+
/** Shovel configuration (caches, directories, etc.) */
|
|
25
|
+
config?: ShovelConfig;
|
|
16
26
|
}
|
|
17
27
|
/**
|
|
18
28
|
* Cloudflare Workers platform implementation
|
|
@@ -22,56 +32,53 @@ export declare class CloudflarePlatform extends BasePlatform {
|
|
|
22
32
|
readonly name: string;
|
|
23
33
|
constructor(options?: CloudflarePlatformOptions);
|
|
24
34
|
/**
|
|
25
|
-
* Create cache storage
|
|
26
|
-
*
|
|
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).
|
|
35
|
+
* Create cache storage using config from shovel.json
|
|
36
|
+
* Default: Cloudflare's native Cache API
|
|
37
|
+
* Merges with runtime defaults (actual class references) for fallback behavior
|
|
31
38
|
*/
|
|
32
39
|
createCaches(): Promise<CustomCacheStorage>;
|
|
33
40
|
/**
|
|
34
|
-
* Create
|
|
41
|
+
* Create directory storage for Cloudflare Workers
|
|
42
|
+
* Directories must be configured via shovel.json (no platform defaults)
|
|
43
|
+
*/
|
|
44
|
+
createDirectories(): Promise<DirectoryStorage>;
|
|
45
|
+
/**
|
|
46
|
+
* Create logger storage for Cloudflare Workers
|
|
47
|
+
*/
|
|
48
|
+
createLoggers(): Promise<LoggerStorage>;
|
|
49
|
+
/**
|
|
50
|
+
* Create "server" for Cloudflare Workers (stub for Platform interface)
|
|
35
51
|
*/
|
|
36
52
|
createServer(handler: Handler, _options?: ServerOptions): Server;
|
|
37
53
|
/**
|
|
38
|
-
* Load ServiceWorker
|
|
39
|
-
*
|
|
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.
|
|
54
|
+
* Load ServiceWorker using miniflare (workerd) for dev mode
|
|
43
55
|
*/
|
|
44
56
|
loadServiceWorker(entrypoint: string, _options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
|
|
57
|
+
dispose(): Promise<void>;
|
|
45
58
|
/**
|
|
46
|
-
*
|
|
59
|
+
* Get virtual entry wrapper for Cloudflare Workers
|
|
60
|
+
*
|
|
61
|
+
* Wraps user code with:
|
|
62
|
+
* 1. Config import (shovel:config virtual module)
|
|
63
|
+
* 2. Runtime initialization (ServiceWorkerGlobals)
|
|
64
|
+
* 3. User code import (registers fetch handlers)
|
|
65
|
+
* 4. ES module export for Cloudflare Workers format
|
|
66
|
+
*
|
|
67
|
+
* Note: Unlike Node/Bun, Cloudflare bundles user code inline, so the
|
|
68
|
+
* entryPath is embedded directly in the wrapper.
|
|
47
69
|
*/
|
|
48
|
-
|
|
70
|
+
getEntryWrapper(entryPath: string, _options?: EntryWrapperOptions): string;
|
|
71
|
+
/**
|
|
72
|
+
* Get Cloudflare-specific esbuild configuration
|
|
73
|
+
*
|
|
74
|
+
* Note: Cloudflare Workers natively support import.meta.env, so no define alias
|
|
75
|
+
* is needed. The nodejs_compat flag enables node:* built-in modules at runtime,
|
|
76
|
+
* so we externalize them during bundling.
|
|
77
|
+
*/
|
|
78
|
+
getESBuildConfig(): PlatformESBuildConfig;
|
|
79
|
+
/**
|
|
80
|
+
* Get Cloudflare-specific defaults for config generation
|
|
81
|
+
*/
|
|
82
|
+
getDefaults(): PlatformDefaults;
|
|
49
83
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Create platform options from Wrangler environment
|
|
52
|
-
*/
|
|
53
|
-
export declare function createOptionsFromEnv(env: any): CloudflarePlatformOptions;
|
|
54
|
-
/**
|
|
55
|
-
* Generate wrangler.toml configuration for a Shovel app from CLI flags
|
|
56
|
-
*/
|
|
57
|
-
export declare function generateWranglerConfig(options: {
|
|
58
|
-
name: string;
|
|
59
|
-
entrypoint: string;
|
|
60
|
-
cacheAdapter?: string;
|
|
61
|
-
filesystemAdapter?: string;
|
|
62
|
-
kvNamespaces?: string[];
|
|
63
|
-
r2Buckets?: string[];
|
|
64
|
-
d1Databases?: string[];
|
|
65
|
-
}): string;
|
|
66
|
-
/**
|
|
67
|
-
* Generate banner code for ServiceWorker → ES Module conversion
|
|
68
|
-
*/
|
|
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}";
|
|
70
|
-
/**
|
|
71
|
-
* Generate footer code for ServiceWorker → ES Module conversion
|
|
72
|
-
*/
|
|
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};";
|
|
74
|
-
/**
|
|
75
|
-
* Default export for easy importing
|
|
76
|
-
*/
|
|
77
84
|
export default CloudflarePlatform;
|