@b9g/platform-cloudflare 0.1.10 → 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/src/runtime.js ADDED
@@ -0,0 +1,65 @@
1
+ /// <reference types="./runtime.d.ts" />
2
+ // src/runtime.ts
3
+ import {
4
+ ServiceWorkerGlobals,
5
+ ShovelServiceWorkerRegistration,
6
+ ShovelFetchEvent,
7
+ CustomLoggerStorage,
8
+ configureLogging,
9
+ createCacheFactory,
10
+ createDirectoryFactory
11
+ } from "@b9g/platform/runtime";
12
+ import { CustomCacheStorage } from "@b9g/cache";
13
+ import { CustomDirectoryStorage } from "@b9g/filesystem";
14
+ import { getLogger } from "@logtape/logtape";
15
+ import { envStorage } from "./variables.js";
16
+ var CloudflareFetchEvent = class extends ShovelFetchEvent {
17
+ /** Cloudflare environment bindings (KV, R2, D1, Durable Objects, etc.) */
18
+ env;
19
+ constructor(request, options) {
20
+ super(request, options);
21
+ this.env = options.env;
22
+ }
23
+ };
24
+ var _registration = null;
25
+ var _globals = null;
26
+ async function initializeRuntime(config) {
27
+ if (_registration) {
28
+ return _registration;
29
+ }
30
+ if (config.logging) {
31
+ await configureLogging(config.logging);
32
+ }
33
+ _registration = new ShovelServiceWorkerRegistration();
34
+ const caches = new CustomCacheStorage(
35
+ createCacheFactory({ configs: config.caches ?? {} })
36
+ );
37
+ const directories = new CustomDirectoryStorage(
38
+ createDirectoryFactory(config.directories ?? {})
39
+ );
40
+ _globals = new ServiceWorkerGlobals({
41
+ registration: _registration,
42
+ caches,
43
+ directories,
44
+ loggers: new CustomLoggerStorage((cats) => getLogger(cats))
45
+ });
46
+ _globals.install();
47
+ return _registration;
48
+ }
49
+ function createFetchHandler(registration) {
50
+ return async (request, env, ctx) => {
51
+ const event = new CloudflareFetchEvent(request, {
52
+ env,
53
+ platformWaitUntil: (promise) => ctx.waitUntil(promise)
54
+ });
55
+ return envStorage.run(
56
+ env,
57
+ () => registration.handleRequest(event)
58
+ );
59
+ };
60
+ }
61
+ export {
62
+ CloudflareFetchEvent,
63
+ createFetchHandler,
64
+ initializeRuntime
65
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cloudflare Environment Storage
3
+ *
4
+ * Provides per-request access to Cloudflare's env object (KV, R2, D1 bindings, etc.)
5
+ * via AsyncContext. Used by directory implementations to resolve bindings at runtime.
6
+ */
7
+ /**
8
+ * Per-request storage for Cloudflare's env object.
9
+ * Set by createFetchHandler() via envStorage.run().
10
+ */
11
+ export declare const envStorage: any;
12
+ /**
13
+ * Get the current Cloudflare env or throw if not in request context.
14
+ */
15
+ export declare function getEnv(): Record<string, unknown>;
@@ -0,0 +1,17 @@
1
+ /// <reference types="./variables.d.ts" />
2
+ // src/variables.ts
3
+ import { AsyncContext } from "@b9g/async-context";
4
+ var envStorage = new AsyncContext.Variable();
5
+ function getEnv() {
6
+ const env = envStorage.get();
7
+ if (!env) {
8
+ throw new Error(
9
+ "Cloudflare env not available. Are you accessing bindings outside of a request context?"
10
+ );
11
+ }
12
+ return env;
13
+ }
14
+ export {
15
+ envStorage,
16
+ getEnv
17
+ };
@@ -1,39 +0,0 @@
1
- /**
2
- * Cloudflare Worker Runtime - Browser-safe ServiceWorkerGlobals setup
3
- *
4
- * This module is BROWSER-SAFE and can be bundled into Cloudflare Workers.
5
- * It only imports from browser-compatible modules:
6
- * - @b9g/platform/runtime (no fs/path)
7
- * - @b9g/filesystem (no fs/path in the index)
8
- * - @b9g/async-context (browser-safe)
9
- *
10
- * DO NOT import from @b9g/platform (the index) - it pulls in Node-only code.
11
- */
12
- import { ShovelServiceWorkerRegistration } from "@b9g/platform/runtime";
13
- /**
14
- * Cloudflare's ExecutionContext - passed to each request handler
15
- * Used for ctx.waitUntil() to extend request lifetime
16
- */
17
- export interface ExecutionContext {
18
- waitUntil(promise: Promise<unknown>): void;
19
- passThroughOnException(): void;
20
- }
21
- /**
22
- * Get the current request's Cloudflare env object
23
- * Contains all bindings: KV namespaces, R2 buckets, D1 databases, etc.
24
- */
25
- export declare function getEnv<T = Record<string, unknown>>(): T | undefined;
26
- /**
27
- * Get the current request's Cloudflare ExecutionContext
28
- * Used for ctx.waitUntil() and other lifecycle methods
29
- */
30
- export declare function getCtx(): ExecutionContext | undefined;
31
- /**
32
- * Initialize the Cloudflare runtime with ServiceWorkerGlobals
33
- * Called once when the worker module loads (before user code runs)
34
- */
35
- export declare function initializeRuntime(): ShovelServiceWorkerRegistration;
36
- /**
37
- * Create the ES module fetch handler for Cloudflare Workers
38
- */
39
- export declare function createFetchHandler(registration: ShovelServiceWorkerRegistration): (request: Request, env: unknown, ctx: ExecutionContext) => Promise<Response>;
@@ -1,102 +0,0 @@
1
- /// <reference types="./cloudflare-runtime.d.ts" />
2
- // src/cloudflare-runtime.ts
3
- import {
4
- ServiceWorkerGlobals,
5
- ShovelServiceWorkerRegistration,
6
- CustomLoggerStorage
7
- } from "@b9g/platform/runtime";
8
- import { CustomDirectoryStorage } from "@b9g/filesystem";
9
- import { AsyncContext } from "@b9g/async-context";
10
- import { getLogger } from "@logtape/logtape";
11
- import { R2FileSystemDirectoryHandle } from "./filesystem-r2.js";
12
- var envStorage = new AsyncContext.Variable();
13
- var ctxStorage = new AsyncContext.Variable();
14
- function getEnv() {
15
- return envStorage.get();
16
- }
17
- function getCtx() {
18
- return ctxStorage.get();
19
- }
20
- var _registration = null;
21
- var _globals = null;
22
- function initializeRuntime() {
23
- if (_registration) {
24
- return _registration;
25
- }
26
- _registration = new ShovelServiceWorkerRegistration();
27
- const directories = new CustomDirectoryStorage(
28
- createCloudflareR2DirectoryFactory()
29
- );
30
- _globals = new ServiceWorkerGlobals({
31
- registration: _registration,
32
- caches: globalThis.caches,
33
- // Cloudflare's native Cache API
34
- directories,
35
- loggers: new CustomLoggerStorage((...cats) => getLogger(cats))
36
- });
37
- _globals.install();
38
- return _registration;
39
- }
40
- function createFetchHandler(registration) {
41
- return async (request, env, ctx) => {
42
- return envStorage.run(
43
- env,
44
- () => ctxStorage.run(ctx, async () => {
45
- try {
46
- return await registration.handleRequest(request);
47
- } catch (error) {
48
- console.error("ServiceWorker error:", error);
49
- const err = error instanceof Error ? error : new Error(String(error));
50
- const isDev = typeof import.meta !== "undefined" && import.meta.env?.MODE !== "production";
51
- if (isDev) {
52
- return new Response(
53
- `<!DOCTYPE html>
54
- <html>
55
- <head><title>500 Internal Server Error</title>
56
- <style>body{font-family:system-ui;padding:2rem;max-width:800px;margin:0 auto}h1{color:#c00}pre{background:#f5f5f5;padding:1rem;overflow-x:auto}</style>
57
- </head>
58
- <body>
59
- <h1>500 Internal Server Error</h1>
60
- <p>${escapeHtml(err.message)}</p>
61
- <pre>${escapeHtml(err.stack || "No stack trace")}</pre>
62
- </body></html>`,
63
- { status: 500, headers: { "Content-Type": "text/html" } }
64
- );
65
- }
66
- return new Response("Internal Server Error", { status: 500 });
67
- }
68
- })
69
- );
70
- };
71
- }
72
- function escapeHtml(str) {
73
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
74
- }
75
- function createCloudflareR2DirectoryFactory() {
76
- return async (name) => {
77
- const env = getEnv();
78
- if (!env) {
79
- throw new Error(
80
- `Cannot access directory "${name}": Cloudflare env not available. Are you accessing directories outside of a request context?`
81
- );
82
- }
83
- const bindingName = `${name.toUpperCase()}_R2`;
84
- const r2Bucket = env[bindingName];
85
- if (!r2Bucket) {
86
- throw new Error(
87
- `R2 bucket binding "${bindingName}" not found. Configure in wrangler.toml:
88
-
89
- [[r2_buckets]]
90
- binding = "${bindingName}"
91
- bucket_name = "your-bucket-name"`
92
- );
93
- }
94
- return new R2FileSystemDirectoryHandle(r2Bucket, "");
95
- };
96
- }
97
- export {
98
- createFetchHandler,
99
- getCtx,
100
- getEnv,
101
- initializeRuntime
102
- };
package/src/env.d.ts DELETED
@@ -1,9 +0,0 @@
1
- // Vite-style import.meta.env declaration
2
- interface ImportMetaEnv {
3
- MODE?: string;
4
- [key: string]: string | undefined;
5
- }
6
-
7
- interface ImportMeta {
8
- readonly env?: ImportMetaEnv;
9
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * CFAssetsDirectoryHandle - FileSystemDirectoryHandle over CF ASSETS binding
3
- *
4
- * Wraps Cloudflare's Workers Static Assets binding to provide the standard
5
- * File System Access API interface, enabling shovel's `self.dirs.open("dist")`
6
- * to work seamlessly with bundled static assets.
7
- *
8
- * @example
9
- * ```ts
10
- * // In production CF Worker
11
- * const dist = new CFAssetsDirectoryHandle(env.ASSETS, "/assets");
12
- * const file = await dist.getFileHandle("style.abc123.css");
13
- * const content = await (await file.getFile()).text();
14
- * ```
15
- */
16
- /**
17
- * Cloudflare ASSETS binding interface
18
- */
19
- export interface CFAssetsBinding {
20
- fetch(request: Request | string): Promise<Response>;
21
- }
22
- /**
23
- * FileSystemDirectoryHandle implementation over Cloudflare ASSETS binding.
24
- *
25
- * Provides read-only access to static assets deployed with a CF Worker.
26
- * Directory listing is not supported (ASSETS binding limitation).
27
- */
28
- export declare class CFAssetsDirectoryHandle implements FileSystemDirectoryHandle {
29
- #private;
30
- readonly kind: "directory";
31
- readonly name: string;
32
- constructor(assets: CFAssetsBinding, basePath?: string);
33
- getFileHandle(name: string, _options?: FileSystemGetFileOptions): Promise<FileSystemFileHandle>;
34
- getDirectoryHandle(name: string, _options?: FileSystemGetDirectoryOptions): Promise<FileSystemDirectoryHandle>;
35
- removeEntry(_name: string, _options?: FileSystemRemoveOptions): Promise<void>;
36
- resolve(_possibleDescendant: FileSystemHandle): Promise<string[] | null>;
37
- entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
38
- keys(): AsyncIterableIterator<string>;
39
- values(): AsyncIterableIterator<FileSystemHandle>;
40
- [Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;
41
- isSameEntry(other: FileSystemHandle): Promise<boolean>;
42
- }
43
- /**
44
- * FileSystemFileHandle implementation for CF ASSETS binding files.
45
- */
46
- export declare class CFAssetsFileHandle implements FileSystemFileHandle {
47
- #private;
48
- readonly kind: "file";
49
- readonly name: string;
50
- constructor(assets: CFAssetsBinding, path: string, name: string);
51
- getFile(): Promise<File>;
52
- createWritable(_options?: FileSystemCreateWritableOptions): Promise<FileSystemWritableFileStream>;
53
- createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
54
- isSameEntry(other: FileSystemHandle): Promise<boolean>;
55
- }
@@ -1,106 +0,0 @@
1
- /// <reference types="./filesystem-assets.d.ts" />
2
- // src/filesystem-assets.ts
3
- var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
4
- kind;
5
- name;
6
- #assets;
7
- #basePath;
8
- constructor(assets, basePath = "/") {
9
- this.kind = "directory";
10
- this.#assets = assets;
11
- this.#basePath = basePath.endsWith("/") ? basePath : basePath + "/";
12
- this.name = basePath.split("/").filter(Boolean).pop() || "assets";
13
- }
14
- async getFileHandle(name, _options) {
15
- const path = this.#basePath + name;
16
- const response = await this.#assets.fetch(
17
- new Request("https://assets" + path)
18
- );
19
- if (!response.ok) {
20
- throw new DOMException(
21
- `A requested file or directory could not be found: ${name}`,
22
- "NotFoundError"
23
- );
24
- }
25
- return new CFAssetsFileHandle(this.#assets, path, name);
26
- }
27
- async getDirectoryHandle(name, _options) {
28
- return new _CFAssetsDirectoryHandle(this.#assets, this.#basePath + name);
29
- }
30
- async removeEntry(_name, _options) {
31
- throw new DOMException("Assets directory is read-only", "NotAllowedError");
32
- }
33
- async resolve(_possibleDescendant) {
34
- return null;
35
- }
36
- // eslint-disable-next-line require-yield
37
- async *entries() {
38
- throw new DOMException(
39
- "Directory listing not supported for ASSETS binding. Use an asset manifest for enumeration.",
40
- "NotSupportedError"
41
- );
42
- }
43
- // eslint-disable-next-line require-yield
44
- async *keys() {
45
- throw new DOMException(
46
- "Directory listing not supported for ASSETS binding",
47
- "NotSupportedError"
48
- );
49
- }
50
- // eslint-disable-next-line require-yield
51
- async *values() {
52
- throw new DOMException(
53
- "Directory listing not supported for ASSETS binding",
54
- "NotSupportedError"
55
- );
56
- }
57
- [Symbol.asyncIterator]() {
58
- return this.entries();
59
- }
60
- isSameEntry(other) {
61
- return Promise.resolve(
62
- other instanceof _CFAssetsDirectoryHandle && other.#basePath === this.#basePath
63
- );
64
- }
65
- };
66
- var CFAssetsFileHandle = class _CFAssetsFileHandle {
67
- kind;
68
- name;
69
- #assets;
70
- #path;
71
- constructor(assets, path, name) {
72
- this.kind = "file";
73
- this.#assets = assets;
74
- this.#path = path;
75
- this.name = name;
76
- }
77
- async getFile() {
78
- const response = await this.#assets.fetch(
79
- new Request("https://assets" + this.#path)
80
- );
81
- if (!response.ok) {
82
- throw new DOMException(
83
- `A requested file or directory could not be found: ${this.name}`,
84
- "NotFoundError"
85
- );
86
- }
87
- const blob = await response.blob();
88
- const contentType = response.headers.get("content-type") || "application/octet-stream";
89
- return new File([blob], this.name, { type: contentType });
90
- }
91
- async createWritable(_options) {
92
- throw new DOMException("Assets are read-only", "NotAllowedError");
93
- }
94
- async createSyncAccessHandle() {
95
- throw new DOMException("Sync access not supported", "NotSupportedError");
96
- }
97
- isSameEntry(other) {
98
- return Promise.resolve(
99
- other instanceof _CFAssetsFileHandle && other.#path === this.#path
100
- );
101
- }
102
- };
103
- export {
104
- CFAssetsDirectoryHandle,
105
- CFAssetsFileHandle
106
- };
@@ -1,91 +0,0 @@
1
- /**
2
- * Cloudflare R2 implementation of File System Access API
3
- *
4
- * Implements FileSystemDirectoryHandle and FileSystemFileHandle using Cloudflare R2 bindings
5
- * to provide R2 cloud storage with File System Access API compatibility.
6
- */
7
- import type { FileSystemConfig } from "@b9g/filesystem";
8
- /** R2 object metadata */
9
- export interface R2Object {
10
- key: string;
11
- uploaded: Date;
12
- httpMetadata?: {
13
- contentType?: string;
14
- };
15
- arrayBuffer(): Promise<ArrayBuffer>;
16
- }
17
- /** R2 list result */
18
- export interface R2Objects {
19
- objects: Array<{
20
- key: string;
21
- }>;
22
- delimitedPrefixes: string[];
23
- }
24
- /** R2 bucket interface */
25
- export interface R2Bucket {
26
- get(key: string): Promise<R2Object | null>;
27
- head(key: string): Promise<R2Object | null>;
28
- put(key: string, value: ArrayBuffer | Uint8Array): Promise<R2Object>;
29
- delete(key: string): Promise<void>;
30
- list(options?: {
31
- prefix?: string;
32
- delimiter?: string;
33
- }): Promise<R2Objects>;
34
- }
35
- /**
36
- * Cloudflare R2 implementation of FileSystemWritableFileStream
37
- */
38
- export declare class R2FileSystemWritableFileStream extends WritableStream<Uint8Array> {
39
- #private;
40
- constructor(r2Bucket: R2Bucket, key: string);
41
- }
42
- /**
43
- * Cloudflare R2 implementation of FileSystemFileHandle
44
- */
45
- export declare class R2FileSystemFileHandle implements FileSystemFileHandle {
46
- #private;
47
- readonly kind: "file";
48
- readonly name: string;
49
- constructor(r2Bucket: R2Bucket, key: string);
50
- getFile(): Promise<File>;
51
- createWritable(): Promise<FileSystemWritableFileStream>;
52
- createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
53
- isSameEntry(other: FileSystemHandle): Promise<boolean>;
54
- queryPermission(): Promise<PermissionState>;
55
- requestPermission(): Promise<PermissionState>;
56
- }
57
- /**
58
- * Cloudflare R2 implementation of FileSystemDirectoryHandle
59
- */
60
- export declare class R2FileSystemDirectoryHandle implements FileSystemDirectoryHandle {
61
- #private;
62
- readonly kind: "directory";
63
- readonly name: string;
64
- constructor(r2Bucket: R2Bucket, prefix: string);
65
- getFileHandle(name: string, options?: {
66
- create?: boolean;
67
- }): Promise<FileSystemFileHandle>;
68
- getDirectoryHandle(name: string, options?: {
69
- create?: boolean;
70
- }): Promise<FileSystemDirectoryHandle>;
71
- removeEntry(name: string, options?: {
72
- recursive?: boolean;
73
- }): Promise<void>;
74
- resolve(_possibleDescendant: FileSystemHandle): Promise<string[] | null>;
75
- entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
76
- keys(): AsyncIterableIterator<string>;
77
- values(): AsyncIterableIterator<FileSystemHandle>;
78
- isSameEntry(other: FileSystemHandle): Promise<boolean>;
79
- queryPermission(): Promise<PermissionState>;
80
- requestPermission(): Promise<PermissionState>;
81
- }
82
- /**
83
- * R2 filesystem adapter
84
- */
85
- export declare class R2FileSystemAdapter {
86
- #private;
87
- constructor(r2Bucket: R2Bucket, config?: FileSystemConfig);
88
- getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
89
- getConfig(): FileSystemConfig;
90
- dispose(): Promise<void>;
91
- }