@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 CHANGED
@@ -1,14 +1,6 @@
1
1
  # @b9g/platform-cloudflare
2
2
 
3
- Cloudflare Workers platform adapter for Shovel. Runs ServiceWorker applications on Cloudflare's edge network with KV storage and Durable Objects support.
4
-
5
- ## Features
6
-
7
- - Cloudflare Workers integration
8
- - KV storage for caching
9
- - R2 bucket support for assets
10
- - Durable Objects for stateful apps
11
- - Standards-compliant ServiceWorker API
3
+ Cloudflare Workers platform adapter for Shovel. Runs ServiceWorker applications on Cloudflare's edge network with R2 storage and static assets support.
12
4
 
13
5
  ## Installation
14
6
 
@@ -16,95 +8,106 @@ Cloudflare Workers platform adapter for Shovel. Runs ServiceWorker applications
16
8
  npm install @b9g/platform-cloudflare
17
9
  ```
18
10
 
19
- ## Usage
11
+ ## Module Structure
20
12
 
21
- ```javascript
22
- import CloudflarePlatform from '@b9g/platform-cloudflare';
23
-
24
- const platform = new CloudflarePlatform({
25
- cache: { type: 'kv', binding: 'CACHE_KV' },
26
- filesystem: { type: 'r2', binding: 'ASSETS_R2' }
27
- });
13
+ ```
14
+ @b9g/platform-cloudflare
15
+ ├── /caches # CloudflareNativeCache (Cloudflare Cache API wrapper)
16
+ ├── /directories # CloudflareR2Directory, CloudflareAssetsDirectory
17
+ ├── /variables # envStorage (per-request Cloudflare env access)
18
+ └── /runtime # Worker bootstrap (initializeRuntime, createFetchHandler)
19
+ ```
28
20
 
29
- export default {
30
- async fetch(request, env, ctx) {
31
- return await platform.handleRequest(request);
21
+ ## Configuration
22
+
23
+ Configure in `shovel.json`:
24
+
25
+ ```json
26
+ {
27
+ "platform": "cloudflare",
28
+ "caches": {
29
+ "default": {
30
+ "module": "@b9g/platform-cloudflare/caches"
31
+ }
32
+ },
33
+ "directories": {
34
+ "public": {
35
+ "module": "@b9g/platform-cloudflare/directories",
36
+ "export": "CloudflareAssetsDirectory"
37
+ },
38
+ "uploads": {
39
+ "module": "@b9g/platform-cloudflare/directories",
40
+ "binding": "uploads_r2"
41
+ }
32
42
  }
33
- };
43
+ }
34
44
  ```
35
45
 
36
46
  ## Requirements
37
47
 
38
- Shovel requires Node.js compatibility for AsyncLocalStorage (used by AsyncContext polyfill for `self.cookieStore`). Add to your `wrangler.toml`:
48
+ Shovel requires Node.js compatibility for AsyncLocalStorage and process.env support. Add to your `wrangler.toml`:
39
49
 
40
50
  ```toml
41
- compatibility_date = "2024-09-23"
51
+ compatibility_date = "2025-04-01"
42
52
  compatibility_flags = ["nodejs_compat"]
43
53
  ```
44
54
 
45
- ## Exports
46
-
47
- ### Classes
55
+ With `compatibility_date` of 2025-04-01 or later, `process.env` is automatically populated with your environment variables and secrets at module load time. For earlier dates, add `nodejs_compat_populate_process_env` to compatibility_flags.
48
56
 
49
- - `CloudflarePlatform` - Cloudflare Workers platform implementation (extends BasePlatform)
50
- - `CFAssetsDirectoryHandle` - FileSystemDirectoryHandle for Cloudflare Workers Static Assets
51
- - `CFAssetsFileHandle` - FileSystemFileHandle for Cloudflare Workers Static Assets
52
-
53
- ### Functions
54
-
55
- - `createOptionsFromEnv(env)` - Create platform options from Cloudflare env bindings
56
- - `generateWranglerConfig(options)` - Generate wrangler.toml configuration
57
+ ## Exports
57
58
 
58
- ### Types
59
+ ### Main Package (`@b9g/platform-cloudflare`)
59
60
 
60
- - `CloudflarePlatformOptions` - Configuration options for CloudflarePlatform
61
- - `CFAssetsBinding` - Type for Cloudflare Workers Static Assets binding
61
+ - `CloudflarePlatform` - Platform adapter (default export)
62
62
 
63
- ### Constants
63
+ ### Caches (`@b9g/platform-cloudflare/caches`)
64
64
 
65
- - `cloudflareWorkerBanner` - ES Module wrapper banner for Cloudflare Workers
66
- - `cloudflareWorkerFooter` - ES Module wrapper footer
65
+ - `CloudflareNativeCache` - Wrapper around Cloudflare's Cache API (default export)
67
66
 
68
- ### Default Export
67
+ ### Directories (`@b9g/platform-cloudflare/directories`)
69
68
 
70
- - `CloudflarePlatform` - The platform class
69
+ - `CloudflareR2Directory` - FileSystemDirectoryHandle for R2 buckets (default export)
70
+ - `CloudflareAssetsDirectory` - FileSystemDirectoryHandle for static assets
71
+ - `R2FileSystemDirectoryHandle` - Base R2 directory implementation
72
+ - `R2FileSystemFileHandle` - Base R2 file implementation
73
+ - `CFAssetsDirectoryHandle` - Base assets directory implementation
74
+ - `CFAssetsFileHandle` - Base assets file implementation
71
75
 
72
- ## API
76
+ ### Runtime (`@b9g/platform-cloudflare/runtime`)
73
77
 
74
- ### `new CloudflarePlatform(options?)`
78
+ - `initializeRuntime(config)` - Initialize worker runtime
79
+ - `createFetchHandler(registration)` - Create ES module fetch handler
80
+ - `CloudflareFetchEvent` - Extended FetchEvent with env bindings
75
81
 
76
- Creates a new Cloudflare platform instance.
82
+ ### Variables (`@b9g/platform-cloudflare/variables`)
77
83
 
78
- **Options:**
79
- - `cache`: Cache configuration (KV binding)
80
- - `filesystem`: Filesystem configuration (R2 binding)
81
- - `env`: Cloudflare environment bindings
84
+ - `envStorage` - AsyncContext for per-request Cloudflare env access
85
+ - `getEnv()` - Get current request's env bindings
82
86
 
83
- ### Bindings
87
+ ## Bindings
84
88
 
85
- Configure bindings in `wrangler.toml`:
89
+ Configure bindings in `wrangler.toml`. Use lowercase binding names to avoid env expression parsing issues:
86
90
 
87
91
  ```toml
88
92
  compatibility_date = "2024-09-23"
89
93
  compatibility_flags = ["nodejs_compat"]
90
94
 
91
- [[kv_namespaces]]
92
- binding = "CACHE_KV"
93
- id = "your-kv-namespace-id"
94
-
95
+ # R2 bucket for uploads directory
95
96
  [[r2_buckets]]
96
- binding = "ASSETS_R2"
97
- bucket_name = "your-bucket-name"
98
- ```
99
-
100
- ## Cache Backends
97
+ binding = "uploads_r2"
98
+ bucket_name = "my-uploads-bucket"
101
99
 
102
- - `kv`: Cloudflare KV storage
103
- - `cache-api`: Cloudflare Cache API (default)
100
+ # Static assets (always uses ASSETS binding)
101
+ [assets]
102
+ directory = "./public"
103
+ ```
104
104
 
105
- ## Filesystem Backends
105
+ ## Directory Types
106
106
 
107
- - `r2`: Cloudflare R2 bucket storage
107
+ | Type | Use Case | Read/Write | Binding |
108
+ |------|----------|------------|---------|
109
+ | R2 | User uploads, dynamic storage | Both | User-defined |
110
+ | Assets | Static files deployed with worker | Read-only | Always `ASSETS` |
108
111
 
109
112
  ## License
110
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/platform-cloudflare",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Cloudflare Workers platform adapter for Shovel - already ServiceWorker-based!",
5
5
  "keywords": [
6
6
  "shovel",
@@ -11,16 +11,17 @@
11
11
  "serviceworker"
12
12
  ],
13
13
  "dependencies": {
14
- "@b9g/assets": "^0.1.15",
15
- "@b9g/cache": "^0.1.5",
16
- "@b9g/platform": "^0.1.11",
17
- "@cloudflare/workers-types": "^4.20241218.0",
14
+ "@b9g/assets": "^0.2.0-beta.0",
15
+ "@b9g/async-context": "^0.2.0-beta.0",
16
+ "@b9g/cache": "^0.2.0-beta.0",
17
+ "@b9g/filesystem": "^0.1.8",
18
+ "@b9g/platform": "^0.1.13",
18
19
  "@logtape/logtape": "^1.2.0",
20
+ "mime": "^4.0.4",
19
21
  "miniflare": "^4.20251118.1"
20
22
  },
21
23
  "devDependencies": {
22
- "@b9g/libuild": "^0.1.18",
23
- "bun-types": "latest"
24
+ "@b9g/libuild": "^0.1.18"
24
25
  },
25
26
  "type": "module",
26
27
  "types": "src/index.d.ts",
@@ -39,13 +40,37 @@
39
40
  "types": "./src/index.d.ts",
40
41
  "import": "./src/index.js"
41
42
  },
42
- "./filesystem-assets": {
43
- "types": "./src/filesystem-assets.d.ts",
44
- "import": "./src/filesystem-assets.js"
43
+ "./runtime": {
44
+ "types": "./src/runtime.d.ts",
45
+ "import": "./src/runtime.js"
45
46
  },
46
- "./filesystem-assets.js": {
47
- "types": "./src/filesystem-assets.d.ts",
48
- "import": "./src/filesystem-assets.js"
47
+ "./runtime.js": {
48
+ "types": "./src/runtime.d.ts",
49
+ "import": "./src/runtime.js"
50
+ },
51
+ "./variables": {
52
+ "types": "./src/variables.d.ts",
53
+ "import": "./src/variables.js"
54
+ },
55
+ "./variables.js": {
56
+ "types": "./src/variables.d.ts",
57
+ "import": "./src/variables.js"
58
+ },
59
+ "./caches": {
60
+ "types": "./src/caches.d.ts",
61
+ "import": "./src/caches.js"
62
+ },
63
+ "./caches.js": {
64
+ "types": "./src/caches.d.ts",
65
+ "import": "./src/caches.js"
66
+ },
67
+ "./directories": {
68
+ "types": "./src/directories.d.ts",
69
+ "import": "./src/directories.js"
70
+ },
71
+ "./directories.js": {
72
+ "types": "./src/directories.d.ts",
73
+ "import": "./src/directories.js"
49
74
  }
50
75
  }
51
76
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Cloudflare Native Cache
3
+ *
4
+ * Wrapper around Cloudflare's native Cache API for use with the factory pattern.
5
+ */
6
+ /**
7
+ * CloudflareNativeCache - Wrapper around Cloudflare's native Cache API.
8
+ * This allows the native cache to be used with the factory pattern.
9
+ *
10
+ * Note: This must only be used in a Cloudflare Worker context where
11
+ * globalThis.caches is available.
12
+ */
13
+ export declare class CloudflareNativeCache implements Cache {
14
+ #private;
15
+ constructor(name: string, _options?: Record<string, unknown>);
16
+ add(request: RequestInfo | URL): Promise<void>;
17
+ addAll(requests: RequestInfo[]): Promise<void>;
18
+ delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<boolean>;
19
+ keys(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<readonly Request[]>;
20
+ match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>;
21
+ matchAll(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<readonly Response[]>;
22
+ put(request: RequestInfo | URL, response: Response): Promise<void>;
23
+ }
24
+ export default CloudflareNativeCache;
package/src/caches.js ADDED
@@ -0,0 +1,52 @@
1
+ /// <reference types="./caches.d.ts" />
2
+ // src/caches.ts
3
+ var CloudflareNativeCache = class {
4
+ #name;
5
+ #cachePromise;
6
+ constructor(name, _options) {
7
+ this.#name = name;
8
+ this.#cachePromise = null;
9
+ }
10
+ #getCache() {
11
+ if (!this.#cachePromise) {
12
+ if (!globalThis.caches) {
13
+ throw new Error("Cloudflare caches not available in this context");
14
+ }
15
+ this.#cachePromise = globalThis.caches.open(this.#name);
16
+ }
17
+ return this.#cachePromise;
18
+ }
19
+ async add(request) {
20
+ const cache = await this.#getCache();
21
+ return cache.add(request);
22
+ }
23
+ async addAll(requests) {
24
+ const cache = await this.#getCache();
25
+ return cache.addAll(requests);
26
+ }
27
+ async delete(request, options) {
28
+ const cache = await this.#getCache();
29
+ return cache.delete(request, options);
30
+ }
31
+ async keys(request, options) {
32
+ const cache = await this.#getCache();
33
+ return cache.keys(request, options);
34
+ }
35
+ async match(request, options) {
36
+ const cache = await this.#getCache();
37
+ return cache.match(request, options);
38
+ }
39
+ async matchAll(request, options) {
40
+ const cache = await this.#getCache();
41
+ return cache.matchAll(request, options);
42
+ }
43
+ async put(request, response) {
44
+ const cache = await this.#getCache();
45
+ return cache.put(request, response);
46
+ }
47
+ };
48
+ var caches_default = CloudflareNativeCache;
49
+ export {
50
+ CloudflareNativeCache,
51
+ caches_default as default
52
+ };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Cloudflare Directory Implementations
3
+ *
4
+ * Provides FileSystemDirectoryHandle/FileSystemFileHandle implementations
5
+ * for Cloudflare Workers:
6
+ *
7
+ * - R2: Read-write storage backed by Cloudflare R2 buckets
8
+ * - Assets: Read-only access to static assets deployed with the Worker
9
+ *
10
+ * Default export: CloudflareR2Directory (general purpose, user-configurable)
11
+ * Named export: CloudflareAssetsDirectory (singleton for public assets)
12
+ */
13
+ /** R2 object metadata */
14
+ export interface R2Object {
15
+ key: string;
16
+ uploaded: Date;
17
+ httpMetadata?: {
18
+ contentType?: string;
19
+ };
20
+ arrayBuffer(): Promise<ArrayBuffer>;
21
+ }
22
+ /** R2 list result */
23
+ export interface R2Objects {
24
+ objects: Array<{
25
+ key: string;
26
+ }>;
27
+ delimitedPrefixes: string[];
28
+ }
29
+ /** R2 bucket interface */
30
+ export interface R2Bucket {
31
+ get(key: string): Promise<R2Object | null>;
32
+ head(key: string): Promise<R2Object | null>;
33
+ put(key: string, value: ArrayBuffer | Uint8Array): Promise<R2Object>;
34
+ delete(key: string): Promise<void>;
35
+ list(options?: {
36
+ prefix?: string;
37
+ delimiter?: string;
38
+ }): Promise<R2Objects>;
39
+ }
40
+ /**
41
+ * Cloudflare ASSETS binding interface
42
+ */
43
+ export interface CFAssetsBinding {
44
+ fetch(request: Request | string): Promise<Response>;
45
+ }
46
+ /**
47
+ * Cloudflare R2 implementation of FileSystemWritableFileStream
48
+ */
49
+ export declare class R2FileSystemWritableFileStream extends WritableStream<Uint8Array> {
50
+ constructor(r2Bucket: R2Bucket, key: string);
51
+ }
52
+ /**
53
+ * Cloudflare R2 implementation of FileSystemFileHandle
54
+ */
55
+ export declare class R2FileSystemFileHandle implements FileSystemFileHandle {
56
+ #private;
57
+ readonly kind: "file";
58
+ readonly name: string;
59
+ constructor(r2Bucket: R2Bucket, key: string);
60
+ getFile(): Promise<File>;
61
+ createWritable(): Promise<FileSystemWritableFileStream>;
62
+ createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
63
+ isSameEntry(other: FileSystemHandle): Promise<boolean>;
64
+ queryPermission(): Promise<PermissionState>;
65
+ requestPermission(): Promise<PermissionState>;
66
+ }
67
+ /**
68
+ * Cloudflare R2 implementation of FileSystemDirectoryHandle
69
+ */
70
+ export declare class R2FileSystemDirectoryHandle implements FileSystemDirectoryHandle {
71
+ #private;
72
+ readonly kind: "directory";
73
+ readonly name: string;
74
+ constructor(r2Bucket: R2Bucket, prefix: string);
75
+ getFileHandle(name: string, options?: {
76
+ create?: boolean;
77
+ }): Promise<FileSystemFileHandle>;
78
+ getDirectoryHandle(name: string, options?: {
79
+ create?: boolean;
80
+ }): Promise<FileSystemDirectoryHandle>;
81
+ removeEntry(name: string, options?: {
82
+ recursive?: boolean;
83
+ }): Promise<void>;
84
+ resolve(_possibleDescendant: FileSystemHandle): Promise<string[] | null>;
85
+ entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
86
+ keys(): AsyncIterableIterator<string>;
87
+ values(): AsyncIterableIterator<FileSystemHandle>;
88
+ isSameEntry(other: FileSystemHandle): Promise<boolean>;
89
+ queryPermission(): Promise<PermissionState>;
90
+ requestPermission(): Promise<PermissionState>;
91
+ }
92
+ /**
93
+ * FileSystemFileHandle implementation for CF ASSETS binding files.
94
+ */
95
+ export declare class CFAssetsFileHandle implements FileSystemFileHandle {
96
+ #private;
97
+ readonly kind: "file";
98
+ readonly name: string;
99
+ constructor(assets: CFAssetsBinding, path: string, name: string);
100
+ getFile(): Promise<File>;
101
+ createWritable(_options?: FileSystemCreateWritableOptions): Promise<FileSystemWritableFileStream>;
102
+ createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
103
+ isSameEntry(other: FileSystemHandle): Promise<boolean>;
104
+ }
105
+ /**
106
+ * FileSystemDirectoryHandle implementation over Cloudflare ASSETS binding.
107
+ *
108
+ * Provides read-only access to static assets deployed with a CF Worker.
109
+ * Directory listing is not supported (ASSETS binding limitation).
110
+ */
111
+ export declare class CFAssetsDirectoryHandle implements FileSystemDirectoryHandle {
112
+ #private;
113
+ readonly kind: "directory";
114
+ readonly name: string;
115
+ constructor(assets: CFAssetsBinding, basePath?: string);
116
+ getFileHandle(name: string, _options?: FileSystemGetFileOptions): Promise<FileSystemFileHandle>;
117
+ getDirectoryHandle(name: string, _options?: FileSystemGetDirectoryOptions): Promise<FileSystemDirectoryHandle>;
118
+ removeEntry(_name: string, _options?: FileSystemRemoveOptions): Promise<void>;
119
+ resolve(_possibleDescendant: FileSystemHandle): Promise<string[] | null>;
120
+ entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
121
+ keys(): AsyncIterableIterator<string>;
122
+ values(): AsyncIterableIterator<FileSystemHandle>;
123
+ [Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;
124
+ isSameEntry(other: FileSystemHandle): Promise<boolean>;
125
+ }
126
+ export interface CloudflareR2DirectoryOptions {
127
+ /** R2 binding name (must match wrangler.toml binding). Defaults to "${NAME}_R2" */
128
+ binding?: string;
129
+ /** Optional prefix/path within the bucket */
130
+ path?: string;
131
+ }
132
+ /**
133
+ * DirectoryClass for Cloudflare R2 buckets.
134
+ * Uses env bindings to resolve the bucket at runtime.
135
+ *
136
+ * Config example:
137
+ * ```json
138
+ * { "module": "@b9g/platform-cloudflare/directories", "binding": "uploads_r2" }
139
+ * ```
140
+ */
141
+ export declare class CloudflareR2Directory extends R2FileSystemDirectoryHandle {
142
+ constructor(name: string, options?: CloudflareR2DirectoryOptions);
143
+ }
144
+ export interface CloudflareAssetsDirectoryOptions {
145
+ /** Base path within assets (defaults to "/") */
146
+ path?: string;
147
+ }
148
+ /**
149
+ * DirectoryClass for Cloudflare ASSETS binding (static assets).
150
+ * Always uses the "ASSETS" binding (Cloudflare convention).
151
+ *
152
+ * Config example:
153
+ * ```json
154
+ * { "module": "@b9g/platform-cloudflare/directories", "export": "CloudflareAssetsDirectory" }
155
+ * ```
156
+ */
157
+ export declare class CloudflareAssetsDirectory extends CFAssetsDirectoryHandle {
158
+ constructor(_name: string, options?: CloudflareAssetsDirectoryOptions);
159
+ }
160
+ export default CloudflareR2Directory;