From ffdecc3e31b3601d8a087e4c8102dc5e29e3137e Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Tue, 3 Jun 2025 06:40:36 -0400 Subject: [PATCH 01/11] Add SvelteKit resource and cloudflare-sveltekit example with KV/R2 bindings demo --- alchemy/src/cloudflare/index.ts | 2 + alchemy/src/cloudflare/sveltekit.ts | 68 ++++++++ bun.lock | 51 +++++- examples/cloudflare-sveltekit/.gitignore | 23 +++ examples/cloudflare-sveltekit/.npmrc | 1 + examples/cloudflare-sveltekit/README.md | 154 ++++++++++++++++++ examples/cloudflare-sveltekit/alchemy.run.ts | 46 ++++++ examples/cloudflare-sveltekit/package.json | 28 ++++ examples/cloudflare-sveltekit/src/app.d.ts | 21 +++ examples/cloudflare-sveltekit/src/app.html | 12 ++ .../src/routes/+page.server.ts | 58 +++++++ .../src/routes/+page.svelte | 153 +++++++++++++++++ .../cloudflare-sveltekit/static/favicon.png | Bin 0 -> 1571 bytes .../cloudflare-sveltekit/svelte.config.js | 29 ++++ examples/cloudflare-sveltekit/tsconfig.json | 22 +++ examples/cloudflare-sveltekit/vite.config.ts | 6 + 16 files changed, 673 insertions(+), 1 deletion(-) create mode 100644 alchemy/src/cloudflare/sveltekit.ts create mode 100644 examples/cloudflare-sveltekit/.gitignore create mode 100644 examples/cloudflare-sveltekit/.npmrc create mode 100644 examples/cloudflare-sveltekit/README.md create mode 100644 examples/cloudflare-sveltekit/alchemy.run.ts create mode 100644 examples/cloudflare-sveltekit/package.json create mode 100644 examples/cloudflare-sveltekit/src/app.d.ts create mode 100644 examples/cloudflare-sveltekit/src/app.html create mode 100644 examples/cloudflare-sveltekit/src/routes/+page.server.ts create mode 100644 examples/cloudflare-sveltekit/src/routes/+page.svelte create mode 100644 examples/cloudflare-sveltekit/static/favicon.png create mode 100644 examples/cloudflare-sveltekit/svelte.config.js create mode 100644 examples/cloudflare-sveltekit/tsconfig.json create mode 100644 examples/cloudflare-sveltekit/vite.config.ts diff --git a/alchemy/src/cloudflare/index.ts b/alchemy/src/cloudflare/index.ts index cb625d898..6c4f5a196 100644 --- a/alchemy/src/cloudflare/index.ts +++ b/alchemy/src/cloudflare/index.ts @@ -30,6 +30,7 @@ export * from "./r2-rest-state-store.ts"; export * from "./react-router.ts"; export * from "./redwood.ts"; export * from "./route.ts"; +export * from "./sveltekit.ts"; export * from "./tanstack-start.ts"; export * from "./vectorize-index.ts"; export * from "./vectorize-metadata-index.ts"; @@ -40,3 +41,4 @@ export * from "./worker.ts"; export { Workflow } from "./workflow.ts"; export * from "./wrangler.json.ts"; export * from "./zone.ts"; + diff --git a/alchemy/src/cloudflare/sveltekit.ts b/alchemy/src/cloudflare/sveltekit.ts new file mode 100644 index 000000000..bb749a626 --- /dev/null +++ b/alchemy/src/cloudflare/sveltekit.ts @@ -0,0 +1,68 @@ +import type { Assets } from "./assets.ts"; +import type { Bindings } from "./bindings.ts"; +import { Website, type WebsiteProps } from "./website.ts"; +import type { Worker } from "./worker.ts"; + +export interface SvelteKitProps extends WebsiteProps {} + +// don't allow the ASSETS to be overriden +export type SvelteKit = B extends { ASSETS: any } + ? never + : Worker; + +/** + * Deploy a SvelteKit application to Cloudflare Pages with automatically configured defaults. + * + * This resource handles the deployment of SvelteKit applications with optimized settings for + * Cloudflare Workers, including proper build commands and compatibility flags. It expects + * the SvelteKit app to be configured with the @sveltejs/adapter-cloudflare adapter. + * + * @example + * // Deploy a basic SvelteKit application with default settings + * const svelteApp = await SvelteKit("my-svelte-app"); + * + * @example + * // Deploy with a database binding and KV storage + * import { D1Database, KVNamespace } from "alchemy/cloudflare"; + * + * const database = await D1Database("svelte-db"); + * const sessions = await KVNamespace("sessions"); + * + * const svelteApp = await SvelteKit("svelte-with-bindings", { + * bindings: { + * DB: database, + * SESSIONS: sessions + * } + * }); + * + * @example + * // Deploy with custom build command and assets directory + * const customSvelteApp = await SvelteKit("custom-svelte", { + * command: "npm run build:cloudflare", + * assets: "./static" + * }); + * + * @param id - Unique identifier for the SvelteKit application + * @param props - Configuration properties for the SvelteKit deployment + * @returns A Cloudflare Worker resource representing the deployed SvelteKit application + */ +export async function SvelteKit( + id: string, + props?: Partial>, +): Promise> { + return Website(id, { + ...props, + // Default build command for SvelteKit + command: props?.command ?? "bun run build", + // Default entry point for @sveltejs/adapter-cloudflare + main: props?.main ?? "./.svelte-kit/output/server/index.js", + // Default static assets directory for SvelteKit + assets: props?.assets ?? "./.svelte-kit/output/client", + // SvelteKit with Cloudflare adapter needs nodejs_compat + compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])], + // Enable wrangler by default for SvelteKit deployments + wrangler: props?.wrangler ?? true, + // Set compatibility date for modern features + compatibilityDate: props?.compatibilityDate ?? "2024-04-01", + }); +} \ No newline at end of file diff --git a/bun.lock b/bun.lock index 933aa43c6..a9ae4719c 100644 --- a/bun.lock +++ b/bun.lock @@ -174,6 +174,21 @@ "wrangler": "^4.12.1", }, }, + "examples/cloudflare-sveltekit": { + "name": "cloudflare-sveltekit", + "version": "0.0.1", + "devDependencies": { + "@cloudflare/workers-types": "^4.20250603.0", + "@sveltejs/adapter-auto": "^6.0.0", + "@sveltejs/adapter-cloudflare": "^7.0.3", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.2.6", + }, + }, "examples/cloudflare-tanstack-start": { "name": "tanstack-start-example-basic", "dependencies": { @@ -523,7 +538,7 @@ "@cloudflare/workers-shared": ["@cloudflare/workers-shared@0.17.5", "", { "dependencies": { "mime": "^3.0.0", "zod": "^3.22.3" } }, "sha512-e2tjozEy3/8JnPcddYFuMjW9As+aX0i7egciPE8b+mufS33QCtdFEzZKCK8utFzby0tx9TkxGFLJ+cmSrJ+tLw=="], - "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250601.0", "", {}, "sha512-foAgsuo+u+swy5I+xzPwo4MquPhLZW0fuLLsl4uZlZv2k10WziSvZ4wTIkK/AADFtCVRjLNduTT8E/b7DDoInA=="], + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250603.0", "", {}, "sha512-62g5wVdXiRRu9sfC9fmwgZfE9W0rWy2txlgRKn1Xx1NWHshSdh4RWMX6gO4EBKPqgwlHKaMuSPZ1qM0hHsq7bA=="], "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], @@ -1085,6 +1100,16 @@ "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], + "@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@6.0.1", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ=="], + + "@sveltejs/adapter-cloudflare": ["@sveltejs/adapter-cloudflare@7.0.3", "", { "dependencies": { "@cloudflare/workers-types": "^4.20250507.0", "worktop": "0.8.0-next.18" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0", "wrangler": "^4.0.0" } }, "sha512-hEbaEukbHuTUtq4a+CPb57YnyQdx+iWVuLZAmNzaJwGSsAgMafY+GY4UblsIACjSYpX5tZuhjBck+X5IoWLIWw=="], + + "@sveltejs/kit": ["@sveltejs/kit@2.21.1", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-vLbtVwtDcK8LhJKnFkFYwM0uCdFmzioQnif0bjEYH1I24Arz22JPr/hLUiXGVYAwhu8INKx5qrdvr4tHgPwX6w=="], + + "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.0", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw=="], + + "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="], + "@swc/core": ["@swc/core@1.11.29", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.21" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.11.29", "@swc/core-darwin-x64": "1.11.29", "@swc/core-linux-arm-gnueabihf": "1.11.29", "@swc/core-linux-arm64-gnu": "1.11.29", "@swc/core-linux-arm64-musl": "1.11.29", "@swc/core-linux-x64-gnu": "1.11.29", "@swc/core-linux-x64-musl": "1.11.29", "@swc/core-win32-arm64-msvc": "1.11.29", "@swc/core-win32-ia32-msvc": "1.11.29", "@swc/core-win32-x64-msvc": "1.11.29" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA=="], "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.11.29", "", { "os": "darwin", "cpu": "arm64" }, "sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ=="], @@ -1217,6 +1242,8 @@ "@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="], + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], @@ -1665,6 +1692,8 @@ "cloudflare-react-router": ["cloudflare-react-router@workspace:examples/cloudflare-react-router"], + "cloudflare-sveltekit": ["cloudflare-sveltekit@workspace:examples/cloudflare-sveltekit"], + "cloudflare-worker": ["cloudflare-worker@workspace:examples/cloudflare-worker"], "cloudflare-worker-bootstrap": ["cloudflare-worker-bootstrap@workspace:examples/cloudflare-worker-bootstrap"], @@ -2953,6 +2982,8 @@ "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + "regexparam": ["regexparam@3.0.0", "", {}, "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q=="], + "remove-trailing-separator": ["remove-trailing-separator@1.1.0", "", {}, "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw=="], "repeat-element": ["repeat-element@1.1.4", "", {}, "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ=="], @@ -3001,6 +3032,8 @@ "rwsdk": ["rwsdk@0.0.83", "", { "dependencies": { "@cloudflare/vite-plugin": "^1.1.0", "@cloudflare/workers-types": "^4.20250407.0", "@puppeteer/browsers": "^2.8.0", "@types/fs-extra": "^11.0.4", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@types/react-is": "^19.0.0", "@vitejs/plugin-react": "^4.3.4", "debug": "^4.4.0", "enhanced-resolve": "^5.18.1", "es-module-lexer": "^1.5.4", "eventsource-parser": "^3.0.0", "execa": "^9.5.2", "fs-extra": "^11.3.0", "glob": "^11.0.1", "ignore": "^7.0.4", "jsonc-parser": "^3.3.1", "lodash": "^4.17.21", "magic-string": "^0.30.17", "miniflare": "^4.20250405.0", "picocolors": "^1.1.1", "puppeteer-core": "^22.8.1", "react": "19.2.0-canary-39cad7af-20250411", "react-dom": "19.2.0-canary-39cad7af-20250411", "react-is": "^19.0.0", "react-server-dom-webpack": "19.2.0-canary-39cad7af-20250411", "rsc-html-stream": "^0.0.6", "tmp-promise": "^3.0.3", "ts-morph": "^25.0.1", "unique-names-generator": "^4.7.1", "vibe-rules": "^0.2.31", "vite-tsconfig-paths": "^5.1.4", "wrangler": "^4.14.1" }, "peerDependencies": { "vite": "^6.2.6" }, "bin": { "rw-scripts": "bin/rw-scripts.mjs" } }, "sha512-Uwe8zou31gRKMvRnQ/Qt2VD+RhG4WS6eP8dBzFCw+d/6c87JhW7tx0jsk6WTN5hII7qytvKRR4cgAvHVHlwmKg=="], + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-regex": ["safe-regex@1.1.0", "", { "dependencies": { "ret": "~0.1.10" } }, "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg=="], @@ -3179,6 +3212,8 @@ "svelte": ["svelte@5.33.14", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-kRlbhIlMTijbFmVDQFDeKXPLlX1/ovXwV0I162wRqQhRcygaqDIcu1d/Ese3H2uI+yt3uT8E7ndgDthQv5v5BA=="], + "svelte-check": ["svelte-check@4.2.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e49SU1RStvQhoipkQ/aonDhHnG3qxHSBtNfBRb9pxVXoa+N7qybAo32KgA9wEb2PCYFNaDg7bZCdhLD1vHpdYA=="], + "svgo": ["svgo@3.3.2", "", { "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0" }, "bin": "./bin/svgo" }, "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw=="], "swr": ["swr@2.3.3", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A=="], @@ -3409,6 +3444,8 @@ "vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="], + "vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="], + "vitepress": ["vitepress@1.6.3", "", { "dependencies": { "@docsearch/css": "3.8.2", "@docsearch/js": "3.8.2", "@iconify-json/simple-icons": "^1.2.21", "@shikijs/core": "^2.1.0", "@shikijs/transformers": "^2.1.0", "@shikijs/types": "^2.1.0", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^5.2.1", "@vue/devtools-api": "^7.7.0", "@vue/shared": "^3.5.13", "@vueuse/core": "^12.4.0", "@vueuse/integrations": "^12.4.0", "focus-trap": "^7.6.4", "mark.js": "8.11.1", "minisearch": "^7.1.1", "shiki": "^2.1.0", "vite": "^5.4.14", "vue": "^3.5.13" }, "peerDependencies": { "markdown-it-mathjax3": "^4", "postcss": "^8" }, "optionalPeers": ["markdown-it-mathjax3", "postcss"], "bin": { "vitepress": "bin/vitepress.js" } }, "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw=="], "vitepress-plugin-group-icons": ["vitepress-plugin-group-icons@1.5.5", "", { "dependencies": { "@iconify-json/logos": "^1.2.4", "@iconify-json/vscode-icons": "^1.2.18", "@iconify/utils": "^2.3.0" }, "peerDependencies": { "markdown-it": ">=14", "vite": ">=3" } }, "sha512-eVnBL3lVOYxByQg5xo44QZtGPv41JyxWI7YxuwrGcNUU+W8MMIjb9XlivBXb3W8CosFllJlLGiqNCBTnFZHFTA=="], @@ -3455,6 +3492,8 @@ "workerd": ["workerd@1.20250525.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250525.0", "@cloudflare/workerd-darwin-arm64": "1.20250525.0", "@cloudflare/workerd-linux-64": "1.20250525.0", "@cloudflare/workerd-linux-arm64": "1.20250525.0", "@cloudflare/workerd-windows-64": "1.20250525.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-SXJgLREy/Aqw2J71Oah0Pbu+SShbqbTExjVQyRBTM1r7MG7fS5NUlknhnt6sikjA/t4cO09Bi8OJqHdTkrcnYQ=="], + "worktop": ["worktop@0.8.0-next.18", "", { "dependencies": { "mrmime": "^2.0.0", "regexparam": "^3.0.0" } }, "sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw=="], + "wrangler": ["wrangler@4.18.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250525.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250525.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250525.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-/ng0KI9io97SNsBU1rheADBLLTE5Djybgsi4gXuvH1RBKJGpyj1xWvZ2fuWu8vAonit3EiZkwtERTm6kESHP3A=="], "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], @@ -3767,6 +3806,8 @@ "@shikijs/vitepress-twoslash/shiki": ["shiki@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/engine-javascript": "3.4.2", "@shikijs/engine-oniguruma": "3.4.2", "@shikijs/langs": "3.4.2", "@shikijs/themes": "3.4.2", "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ=="], + "@sveltejs/kit/cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], @@ -3881,6 +3922,8 @@ "cloudflare/@types/node": ["@types/node@18.19.110", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q=="], + "cloudflare-react-router/@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250601.0", "", {}, "sha512-foAgsuo+u+swy5I+xzPwo4MquPhLZW0fuLLsl4uZlZv2k10WziSvZ4wTIkK/AADFtCVRjLNduTT8E/b7DDoInA=="], + "cloudflare-react-router/@types/node": ["@types/node@20.17.57", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ=="], "cloudflare-worker/braintrust": ["braintrust@0.0.201", "", { "dependencies": { "@ai-sdk/provider": "^1.0.1", "@braintrust/core": "0.0.86", "@next/env": "^14.2.3", "@vercel/functions": "^1.0.2", "ai": "^3.2.16", "argparse": "^2.0.1", "chalk": "^4.1.2", "cli-progress": "^3.12.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "esbuild": "^0.25.3", "eventsource-parser": "^1.1.2", "express": "^4.21.2", "graceful-fs": "^4.2.11", "http-errors": "^2.0.0", "minimatch": "^9.0.3", "mustache": "^4.2.0", "pluralize": "^8.0.0", "simple-git": "^3.21.0", "slugify": "^1.6.6", "source-map": "^0.7.4", "uuid": "^9.0.1", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.5" }, "bin": { "braintrust": "dist/cli.js" } }, "sha512-qH4esyOskiQ25OzbmlnPMVKEImDuFANEuYknNEsgS2f3M7t5SfbZxdelbYWcFPbUwQO3TR3XktszWcc3+oAZKg=="], @@ -4069,6 +4112,8 @@ "rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], + "rwsdk/@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250601.0", "", {}, "sha512-foAgsuo+u+swy5I+xzPwo4MquPhLZW0fuLLsl4uZlZv2k10WziSvZ4wTIkK/AADFtCVRjLNduTT8E/b7DDoInA=="], + "rwsdk/eventsource-parser": ["eventsource-parser@3.0.2", "", {}, "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA=="], "rwsdk/glob": ["glob@11.0.2", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^4.0.1", "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ=="], @@ -4113,6 +4158,8 @@ "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "svelte-check/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "svgo/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "tanstack-start-example-basic/tailwindcss": ["tailwindcss@3.4.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og=="], @@ -4703,6 +4750,8 @@ "static-extend/define-property/is-descriptor": ["is-descriptor@0.1.7", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg=="], + "svelte-check/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "tanstack-start-example-basic/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tanstack-start-example-basic/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], diff --git a/examples/cloudflare-sveltekit/.gitignore b/examples/cloudflare-sveltekit/.gitignore new file mode 100644 index 000000000..3b462cb0c --- /dev/null +++ b/examples/cloudflare-sveltekit/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/cloudflare-sveltekit/.npmrc b/examples/cloudflare-sveltekit/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/examples/cloudflare-sveltekit/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/cloudflare-sveltekit/README.md b/examples/cloudflare-sveltekit/README.md new file mode 100644 index 000000000..55eab8a69 --- /dev/null +++ b/examples/cloudflare-sveltekit/README.md @@ -0,0 +1,154 @@ +# Cloudflare SvelteKit Example + +This example demonstrates how to deploy a SvelteKit application to Cloudflare Workers using Alchemy's infrastructure-as-code approach. + +## Features + +- 🚀 SvelteKit app deployed to Cloudflare Workers +- 📦 KV Namespace for key-value storage +- 🗄️ R2 Bucket for object storage +- 🔧 Alchemy-managed infrastructure +- 💻 TypeScript support with Cloudflare Workers types + +## Prerequisites + +1. [Bun](https://bun.sh/) installed +2. Cloudflare account with API token +3. Environment variables configured (see below) + +## Environment Setup + +Create a `.env` file in the project root (`../../.env` relative to this directory) with: + +```bash +# Cloudflare API credentials +CLOUDFLARE_API_TOKEN=your_cloudflare_api_token +CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id + +# Optional: Alchemy configuration +ALCHEMY_PASSWORD=your_encryption_password +BRANCH_PREFIX=your_branch_prefix +USER=your_username +``` + +## Getting Started + +1. **Install dependencies:** + ```bash + bun install + ``` + +2. **Run the development server:** + ```bash + bun run dev + ``` + +3. **Deploy to Cloudflare:** + ```bash + bun run deploy + ``` + +4. **Destroy resources:** + ```bash + bun run destroy + ``` + +## Project Structure + +``` +├── src/ +│ ├── routes/ +│ │ ├── +page.svelte # Main demo page +│ │ └── +page.server.ts # Server-side logic with Cloudflare bindings +│ ├── app.d.ts # Type definitions for Cloudflare Platform +│ └── app.html # HTML template +├── alchemy.run.ts # Alchemy infrastructure definition +├── svelte.config.js # SvelteKit config with Cloudflare adapter +└── package.json +``` + +## Infrastructure Resources + +The Alchemy configuration creates: + +- **KV Namespace**: `cloudflare-sveltekit-auth-store{BRANCH_PREFIX}` +- **R2 Bucket**: `cloudflare-sveltekit-storage{BRANCH_PREFIX}` +- **Cloudflare Worker**: Hosts the SvelteKit application + +## How It Works + +1. **SvelteKit Configuration**: Uses `@sveltejs/adapter-cloudflare` to build for Cloudflare Workers +2. **Alchemy Infrastructure**: Defines KV and R2 resources in `alchemy.run.ts` +3. **Bindings**: Resources are automatically bound to the worker environment +4. **Server-side Logic**: `+page.server.ts` demonstrates using the Cloudflare bindings +5. **Type Safety**: Full TypeScript support with Cloudflare Workers types + +## Development vs Production + +- **Development**: Run `bun run dev` for local development with Vite +- **Production**: Deploy with `bun run deploy` to create real Cloudflare resources + +## Customization + +### Adding More Resources + +Edit `alchemy.run.ts` to add additional Cloudflare resources: + +```typescript +// Add a D1 Database +const database = await D1Database("my-database", { + name: `my-app-db${BRANCH_PREFIX}` +}); + +// Add to bindings +export const website = await SvelteKit(`cloudflare-sveltekit-website${BRANCH_PREFIX}`, { + bindings: { + STORAGE: storage, + AUTH_STORE: authStore, + DATABASE: database, // Add the new resource + }, +}); +``` + +### Updating Types + +Update `src/app.d.ts` to include new bindings: + +```typescript +interface Platform { + env: { + STORAGE: R2Bucket; + AUTH_STORE: KVNamespace; + DATABASE: D1Database; // Add new binding type + }; + context: ExecutionContext; + caches: CacheStorage & { default: Cache }; +} +``` + +## Learn More + +- [SvelteKit Documentation](https://svelte.dev/docs/kit) +- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/) +- [Alchemy Documentation](https://alchemy.run) +- [@sveltejs/adapter-cloudflare](https://www.npmjs.com/package/@sveltejs/adapter-cloudflare) + +## Troubleshooting + +### Build Issues + +If you encounter build issues, try: +1. `bun install` to ensure all dependencies are installed +2. `bun run check` to verify TypeScript types +3. Check that the Cloudflare adapter is properly configured + +### Deployment Issues + +If deployment fails: +1. Verify your Cloudflare API token has the necessary permissions +2. Check that your account ID is correct +3. Ensure you're not hitting Cloudflare's free tier limits + +### Type Errors + +Some TypeScript errors are expected during development until SvelteKit generates the types. Run `svelte-kit sync` to generate missing types. diff --git a/examples/cloudflare-sveltekit/alchemy.run.ts b/examples/cloudflare-sveltekit/alchemy.run.ts new file mode 100644 index 000000000..1ffe31a66 --- /dev/null +++ b/examples/cloudflare-sveltekit/alchemy.run.ts @@ -0,0 +1,46 @@ +/// + +import alchemy from "alchemy"; +import { + KVNamespace, + R2Bucket, + R2RestStateStore, + SvelteKit, +} from "alchemy/cloudflare"; + +const BRANCH_PREFIX = process.env.BRANCH_PREFIX ?? ""; +const app = await alchemy("cloudflare-sveltekit", { + stage: process.env.USER ?? "dev", + phase: process.argv.includes("--destroy") ? "destroy" : "up", + quiet: !process.argv.includes("--verbose"), + password: process.env.ALCHEMY_PASSWORD, + stateStore: + process.env.ALCHEMY_STATE_STORE === "cloudflare" + ? (scope) => new R2RestStateStore(scope) + : undefined, +}); + +export const [authStore, storage] = await Promise.all([ + KVNamespace("AUTH_STORE", { + title: `cloudflare-sveltekit-auth-store${BRANCH_PREFIX}`, + adopt: true, + }), + R2Bucket(`cloudflare-sveltekit-storage${BRANCH_PREFIX}`, { + allowPublicAccess: false, + // so that CI is idempotent + adopt: true, + }), +]); + +export const website = await SvelteKit(`cloudflare-sveltekit-website${BRANCH_PREFIX}`, { + bindings: { + STORAGE: storage, + AUTH_STORE: authStore, + }, +}); + +console.log({ + url: website.url, +}); + +await app.finalize(); \ No newline at end of file diff --git a/examples/cloudflare-sveltekit/package.json b/examples/cloudflare-sveltekit/package.json new file mode 100644 index 000000000..3da7903b9 --- /dev/null +++ b/examples/cloudflare-sveltekit/package.json @@ -0,0 +1,28 @@ +{ + "name": "cloudflare-sveltekit", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "deploy": "bun run --env-file ../../.env ./alchemy.run.ts", + "destroy": "bun run --env-file ../../.env ./alchemy.run.ts --destroy" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250603.0", + "@sveltejs/adapter-auto": "^6.0.0", + "@sveltejs/adapter-cloudflare": "^7.0.3", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "alchemy": "workspace:*", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.2.6" + } +} diff --git a/examples/cloudflare-sveltekit/src/app.d.ts b/examples/cloudflare-sveltekit/src/app.d.ts new file mode 100644 index 000000000..030fc1040 --- /dev/null +++ b/examples/cloudflare-sveltekit/src/app.d.ts @@ -0,0 +1,21 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + interface Platform { + env: { + STORAGE: R2Bucket; + AUTH_STORE: KVNamespace; + }; + context: ExecutionContext; + caches: CacheStorage & { default: Cache }; + } + } +} + +export { }; + diff --git a/examples/cloudflare-sveltekit/src/app.html b/examples/cloudflare-sveltekit/src/app.html new file mode 100644 index 000000000..77a5ff52c --- /dev/null +++ b/examples/cloudflare-sveltekit/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/cloudflare-sveltekit/src/routes/+page.server.ts b/examples/cloudflare-sveltekit/src/routes/+page.server.ts new file mode 100644 index 000000000..6a04affa4 --- /dev/null +++ b/examples/cloudflare-sveltekit/src/routes/+page.server.ts @@ -0,0 +1,58 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ platform }) => { + // Demonstrate KV Store usage + const kv = platform?.env.AUTH_STORE; + let kvData = null; + + if (kv) { + try { + // Try to get a value from KV + kvData = await kv.get('demo-key'); + + // If no value exists, set one + if (!kvData) { + await kv.put('demo-key', JSON.stringify({ + message: 'Hello from Cloudflare KV!', + timestamp: new Date().toISOString() + })); + kvData = await kv.get('demo-key'); + } + } catch (error) { + console.error('KV error:', error); + } + } + + // Demonstrate R2 Storage info + const r2 = platform?.env.STORAGE; + let r2Info = null; + + if (r2) { + try { + // List objects in the bucket (up to 1000) + const objects = await r2.list({ limit: 10 }); + r2Info = { + bucketName: 'Storage bucket connected!', + objectCount: objects.objects?.length || 0, + objects: objects.objects?.map(obj => ({ + key: obj.key, + size: obj.size, + modified: obj.uploaded + })) || [] + }; + } catch (error) { + console.error('R2 error:', error); + r2Info = { error: 'Failed to access R2 bucket' }; + } + } + + return { + kv: kvData ? JSON.parse(kvData) : null, + r2: r2Info, + platform: { + hasKV: !!kv, + hasR2: !!r2, + hasContext: !!platform?.context + } + }; +}; \ No newline at end of file diff --git a/examples/cloudflare-sveltekit/src/routes/+page.svelte b/examples/cloudflare-sveltekit/src/routes/+page.svelte new file mode 100644 index 000000000..b1ccd96f0 --- /dev/null +++ b/examples/cloudflare-sveltekit/src/routes/+page.svelte @@ -0,0 +1,153 @@ + + +
+

🚀 SvelteKit + Cloudflare + Alchemy

+

+ This demo shows SvelteKit running on Cloudflare Workers with Alchemy-managed + resources. +

+ +
+

🔧 Platform Status

+
    +
  • + KV Namespace: {data.platform.hasKV + ? "✅ Connected" + : "❌ Not available"} +
  • +
  • + R2 Bucket: {data.platform.hasR2 ? "✅ Connected" : "❌ Not available"} +
  • +
  • + Execution Context: {data.platform.hasContext + ? "✅ Available" + : "❌ Not available"} +
  • +
+
+ + {#if data.kv} +
+

📦 KV Store Demo

+
+
{JSON.stringify(data.kv, null, 2)}
+
+
+ {/if} + + {#if data.r2} +
+

🗄️ R2 Storage Demo

+ {#if data.r2.error} +

Error: {data.r2.error}

+ {:else} +

Bucket Status: {data.r2.bucketName}

+

Objects in bucket: {data.r2.objectCount}

+ + {#if data.r2.objects && data.r2.objects.length > 0} +

Recent Objects:

+
+ {#each data.r2.objects as obj} +
+ {obj.key} - {obj.size} bytes + (modified: {new Date(obj.modified).toLocaleString()}) +
+ {/each} +
+ {:else} +

No objects in bucket yet

+ {/if} + {/if} +
+ {/if} + +
+

📚 Documentation

+

+ Visit svelte.dev/docs/kit to read + the SvelteKit documentation +

+

+ Visit alchemy.run to learn more about Alchemy +

+
+
+ + diff --git a/examples/cloudflare-sveltekit/static/favicon.png b/examples/cloudflare-sveltekit/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH'] + }, + platformProxy: { + configPath: 'wrangler.toml', + environment: undefined, + experimentalJsonConfig: false, + persist: false + } + }) + } +}; + +export default config; diff --git a/examples/cloudflare-sveltekit/tsconfig.json b/examples/cloudflare-sveltekit/tsconfig.json new file mode 100644 index 000000000..8e7286b86 --- /dev/null +++ b/examples/cloudflare-sveltekit/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler", + "types": [ + "@cloudflare/workers-types" + ] + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/examples/cloudflare-sveltekit/vite.config.ts b/examples/cloudflare-sveltekit/vite.config.ts new file mode 100644 index 000000000..bbf8c7da4 --- /dev/null +++ b/examples/cloudflare-sveltekit/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); From 1bbc076006a8de1e839c8e8cc5965ce566959cd8 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Tue, 3 Jun 2025 07:32:04 -0400 Subject: [PATCH 02/11] feat(examples): add cloudflare-sveltekit example - Add SvelteKit resource with Cloudflare adapter integration - Include KV and R2 bindings example - Complete demo with styling and resource access - Follow standard Alchemy example patterns --- alchemy/src/cloudflare/sveltekit.ts | 16 +- bun.lock | 15 +- examples/cloudflare-sveltekit/README.md | 173 ++++++------------ examples/cloudflare-sveltekit/alchemy.run.ts | 27 +-- examples/cloudflare-sveltekit/package.json | 14 +- .../cloudflare-sveltekit/svelte.config.js | 14 +- 6 files changed, 85 insertions(+), 174 deletions(-) diff --git a/alchemy/src/cloudflare/sveltekit.ts b/alchemy/src/cloudflare/sveltekit.ts index bb749a626..724c15a14 100644 --- a/alchemy/src/cloudflare/sveltekit.ts +++ b/alchemy/src/cloudflare/sveltekit.ts @@ -11,7 +11,7 @@ export type SvelteKit = B extends { ASSETS: any } : Worker; /** - * Deploy a SvelteKit application to Cloudflare Pages with automatically configured defaults. + * Deploy a SvelteKit application to Cloudflare Workers with automatically configured defaults. * * This resource handles the deployment of SvelteKit applications with optimized settings for * Cloudflare Workers, including proper build commands and compatibility flags. It expects @@ -54,15 +54,13 @@ export async function SvelteKit( ...props, // Default build command for SvelteKit command: props?.command ?? "bun run build", - // Default entry point for @sveltejs/adapter-cloudflare - main: props?.main ?? "./.svelte-kit/output/server/index.js", - // Default static assets directory for SvelteKit - assets: props?.assets ?? "./.svelte-kit/output/client", + // FIXED: Use the correct entry point that SvelteKit adapter generates + main: props?.main ?? "./.svelte-kit/cloudflare/_worker.js", + // FIXED: Use the cloudflare directory which contains all static assets + assets: props?.assets ?? "./.svelte-kit/cloudflare", // SvelteKit with Cloudflare adapter needs nodejs_compat compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])], - // Enable wrangler by default for SvelteKit deployments - wrangler: props?.wrangler ?? true, - // Set compatibility date for modern features - compatibilityDate: props?.compatibilityDate ?? "2024-04-01", + // Set compatibility date for nodejs_compat (must be >= 2024-09-23) + compatibilityDate: props?.compatibilityDate ?? "2024-09-23", }); } \ No newline at end of file diff --git a/bun.lock b/bun.lock index a9ae4719c..bd2a8902a 100644 --- a/bun.lock +++ b/bun.lock @@ -179,14 +179,15 @@ "version": "0.0.1", "devDependencies": { "@cloudflare/workers-types": "^4.20250603.0", - "@sveltejs/adapter-auto": "^6.0.0", + "@sveltejs/adapter-auto": "^6.0.1", "@sveltejs/adapter-cloudflare": "^7.0.3", - "@sveltejs/kit": "^2.16.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", - "typescript": "^5.0.0", - "vite": "^6.2.6", + "@sveltejs/kit": "^2.21.1", + "@sveltejs/vite-plugin-svelte": "^5.1.0", + "alchemy": "workspace:*", + "svelte": "^5.33.14", + "svelte-check": "^4.2.1", + "typescript": "^5.8.3", + "vite": "^6.3.5", }, }, "examples/cloudflare-tanstack-start": { diff --git a/examples/cloudflare-sveltekit/README.md b/examples/cloudflare-sveltekit/README.md index 55eab8a69..d17eaf7e9 100644 --- a/examples/cloudflare-sveltekit/README.md +++ b/examples/cloudflare-sveltekit/README.md @@ -1,154 +1,85 @@ # Cloudflare SvelteKit Example -This example demonstrates how to deploy a SvelteKit application to Cloudflare Workers using Alchemy's infrastructure-as-code approach. +This example demonstrates deploying a SvelteKit application to Cloudflare Workers using Alchemy. -## Features +It includes: -- 🚀 SvelteKit app deployed to Cloudflare Workers -- 📦 KV Namespace for key-value storage -- 🗄️ R2 Bucket for object storage -- 🔧 Alchemy-managed infrastructure -- 💻 TypeScript support with Cloudflare Workers types +- A SvelteKit frontend with server-side rendering +- KV Namespace for key-value storage (`AUTH_STORE`) +- R2 Bucket for object storage (`STORAGE`) +- Cloudflare Workers with Static Assets deployment +- Configuration using `alchemy.run.ts` -## Prerequisites +## Setup -1. [Bun](https://bun.sh/) installed -2. Cloudflare account with API token -3. Environment variables configured (see below) - -## Environment Setup - -Create a `.env` file in the project root (`../../.env` relative to this directory) with: - -```bash -# Cloudflare API credentials -CLOUDFLARE_API_TOKEN=your_cloudflare_api_token -CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id - -# Optional: Alchemy configuration -ALCHEMY_PASSWORD=your_encryption_password -BRANCH_PREFIX=your_branch_prefix -USER=your_username -``` - -## Getting Started - -1. **Install dependencies:** +1. **Install Dependencies:** Navigate to the root of the Alchemy repository and run: ```bash bun install ``` + This will install dependencies for the core library and all examples, including this one. -2. **Run the development server:** +2. **Cloudflare Credentials:** Ensure you have your Cloudflare API token and Account ID configured as environment variables: ```bash - bun run dev + export CLOUDFLARE_API_TOKEN="your_api_token" + export CLOUDFLARE_ACCOUNT_ID="your_account_id" ``` + You can also place these in a `.env` file in the repository root. + +## Running the Deployment -3. **Deploy to Cloudflare:** +1. **Navigate to Example Directory:** ```bash - bun run deploy + cd examples/cloudflare-sveltekit ``` -4. **Destroy resources:** +2. **Run Alchemy Deployment:** ```bash - bun run destroy + bun run deploy ``` -## Project Structure - -``` -├── src/ -│ ├── routes/ -│ │ ├── +page.svelte # Main demo page -│ │ └── +page.server.ts # Server-side logic with Cloudflare bindings -│ ├── app.d.ts # Type definitions for Cloudflare Platform -│ └── app.html # HTML template -├── alchemy.run.ts # Alchemy infrastructure definition -├── svelte.config.js # SvelteKit config with Cloudflare adapter -└── package.json -``` - -## Infrastructure Resources - -The Alchemy configuration creates: - -- **KV Namespace**: `cloudflare-sveltekit-auth-store{BRANCH_PREFIX}` -- **R2 Bucket**: `cloudflare-sveltekit-storage{BRANCH_PREFIX}` -- **Cloudflare Worker**: Hosts the SvelteKit application +This command will: -## How It Works +- Execute the `bun run build` script to build the SvelteKit application +- Provision the KV Namespace (`cloudflare-sveltekit-auth-store`) +- Provision the R2 Bucket (`cloudflare-sveltekit-storage`) +- Upload the static assets from `./.svelte-kit/cloudflare` to Cloudflare Workers Assets +- Deploy the Cloudflare Worker using the entrypoint `./.svelte-kit/cloudflare/_worker.js` +- Bind the KV and R2 resources to the worker environment +- Output the URL of the deployed worker -1. **SvelteKit Configuration**: Uses `@sveltejs/adapter-cloudflare` to build for Cloudflare Workers -2. **Alchemy Infrastructure**: Defines KV and R2 resources in `alchemy.run.ts` -3. **Bindings**: Resources are automatically bound to the worker environment -4. **Server-side Logic**: `+page.server.ts` demonstrates using the Cloudflare bindings -5. **Type Safety**: Full TypeScript support with Cloudflare Workers types +## Development -## Development vs Production +To run the SvelteKit development server locally: -- **Development**: Run `bun run dev` for local development with Vite -- **Production**: Deploy with `bun run deploy` to create real Cloudflare resources +1. Navigate to the example directory: + ```bash + cd examples/cloudflare-sveltekit + ``` +2. Run the development server: + ```bash + bun run dev + ``` -## Customization +Note: The Cloudflare bindings will not function correctly in the local development environment as they rely on Cloudflare environment bindings injected by Alchemy during deployment. -### Adding More Resources +## Accessing Cloudflare Resources -Edit `alchemy.run.ts` to add additional Cloudflare resources: +In your SvelteKit application, access the Cloudflare resources via `platform.env`: ```typescript -// Add a D1 Database -const database = await D1Database("my-database", { - name: `my-app-db${BRANCH_PREFIX}` -}); - -// Add to bindings -export const website = await SvelteKit(`cloudflare-sveltekit-website${BRANCH_PREFIX}`, { - bindings: { - STORAGE: storage, - AUTH_STORE: authStore, - DATABASE: database, // Add the new resource - }, -}); -``` - -### Updating Types - -Update `src/app.d.ts` to include new bindings: - -```typescript -interface Platform { - env: { - STORAGE: R2Bucket; - AUTH_STORE: KVNamespace; - DATABASE: D1Database; // Add new binding type - }; - context: ExecutionContext; - caches: CacheStorage & { default: Cache }; +// In a +page.server.ts file +export async function load({ platform }) { + const kvData = await platform?.env?.AUTH_STORE?.get('some-key'); + const r2Object = await platform?.env?.STORAGE?.get('some-file'); + return { kvData }; } ``` -## Learn More - -- [SvelteKit Documentation](https://svelte.dev/docs/kit) -- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/) -- [Alchemy Documentation](https://alchemy.run) -- [@sveltejs/adapter-cloudflare](https://www.npmjs.com/package/@sveltejs/adapter-cloudflare) - -## Troubleshooting - -### Build Issues +## Cleanup -If you encounter build issues, try: -1. `bun install` to ensure all dependencies are installed -2. `bun run check` to verify TypeScript types -3. Check that the Cloudflare adapter is properly configured +To destroy the Cloudflare resources created by this example, run: -### Deployment Issues - -If deployment fails: -1. Verify your Cloudflare API token has the necessary permissions -2. Check that your account ID is correct -3. Ensure you're not hitting Cloudflare's free tier limits - -### Type Errors - -Some TypeScript errors are expected during development until SvelteKit generates the types. Run `svelte-kit sync` to generate missing types. +```bash +cd examples/cloudflare-sveltekit +bun run destroy +``` \ No newline at end of file diff --git a/examples/cloudflare-sveltekit/alchemy.run.ts b/examples/cloudflare-sveltekit/alchemy.run.ts index 1ffe31a66..e454fe10c 100644 --- a/examples/cloudflare-sveltekit/alchemy.run.ts +++ b/examples/cloudflare-sveltekit/alchemy.run.ts @@ -20,27 +20,20 @@ const app = await alchemy("cloudflare-sveltekit", { : undefined, }); -export const [authStore, storage] = await Promise.all([ - KVNamespace("AUTH_STORE", { - title: `cloudflare-sveltekit-auth-store${BRANCH_PREFIX}`, - adopt: true, - }), - R2Bucket(`cloudflare-sveltekit-storage${BRANCH_PREFIX}`, { - allowPublicAccess: false, - // so that CI is idempotent - adopt: true, - }), -]); - export const website = await SvelteKit(`cloudflare-sveltekit-website${BRANCH_PREFIX}`, { bindings: { - STORAGE: storage, - AUTH_STORE: authStore, + AUTH_STORE: await KVNamespace("AUTH_STORE", { + title: `cloudflare-sveltekit-auth-store${BRANCH_PREFIX}`, + adopt: true, + }), + STORAGE: await R2Bucket(`cloudflare-sveltekit-storage${BRANCH_PREFIX}`, { + allowPublicAccess: false, + adopt: true, + }), }, + url: true, }); -console.log({ - url: website.url, -}); +console.log(website.url); await app.finalize(); \ No newline at end of file diff --git a/examples/cloudflare-sveltekit/package.json b/examples/cloudflare-sveltekit/package.json index 3da7903b9..5c6bf735e 100644 --- a/examples/cloudflare-sveltekit/package.json +++ b/examples/cloudflare-sveltekit/package.json @@ -15,14 +15,14 @@ }, "devDependencies": { "@cloudflare/workers-types": "^4.20250603.0", - "@sveltejs/adapter-auto": "^6.0.0", + "@sveltejs/adapter-auto": "^6.0.1", "@sveltejs/adapter-cloudflare": "^7.0.3", - "@sveltejs/kit": "^2.16.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/kit": "^2.21.1", + "@sveltejs/vite-plugin-svelte": "^5.1.0", "alchemy": "workspace:*", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", - "typescript": "^5.0.0", - "vite": "^6.2.6" + "svelte": "^5.33.14", + "svelte-check": "^4.2.1", + "typescript": "^5.8.3", + "vite": "^6.3.5" } } diff --git a/examples/cloudflare-sveltekit/svelte.config.js b/examples/cloudflare-sveltekit/svelte.config.js index 20be17b8e..7e3502f20 100644 --- a/examples/cloudflare-sveltekit/svelte.config.js +++ b/examples/cloudflare-sveltekit/svelte.config.js @@ -10,19 +10,7 @@ const config = { kit: { // Using Cloudflare adapter for deployment to Cloudflare Workers // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter({ - // See below for an explanation of these options - routes: { - include: ['/*'], - exclude: [''] - }, - platformProxy: { - configPath: 'wrangler.toml', - environment: undefined, - experimentalJsonConfig: false, - persist: false - } - }) + adapter: adapter() } }; From fed0678c0ce0660229adde844d99518e810a6b55 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Tue, 3 Jun 2025 09:44:25 -0400 Subject: [PATCH 03/11] docs: update README with additional Cloudflare examples --- README.md | 7 +++++++ examples/cloudflare-sveltekit/alchemy.run.ts | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81165e4db..02832d193 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,14 @@ await app.finalize(); # Examples +- CloudFlare Worker with Queue, R2 Bucket, Durable Objects, Workflows and RPC: [examples/cloudflare-worker/](./examples/cloudflare-worker/alchemy.run.ts) +- CloudFlare Worker Bootstrap with Queue and R2 End-to-End Testing: [examples/cloudflare-worker-bootstrap/](./examples/cloudflare-worker-bootstrap/index.ts) - CloudFlare ViteJS Website + API Backend with Durable Objects: [examples/cloudflare-vite/](./examples/cloudflare-vite/alchemy.run.ts) +- CloudFlare TanStack Start Application Deployment: [examples/cloudflare-tanstack-start/](./examples/cloudflare-tanstack-start/alchemy.run.ts) +- CloudFlare RedwoodJS Application with D1 Database: [examples/cloudflare-redwood/](./examples/cloudflare-redwood/alchemy.run.ts) +- CloudFlare React Router Application Deployment: [examples/cloudflare-react-router/](./examples/cloudflare-react-router/alchemy.run.ts) +- CloudFlare Nuxt 3 Application with Pipeline and R2 Bucket: [examples/cloudflare-nuxt-pipeline/](./examples/cloudflare-nuxt-pipeline/alchemy.run.ts) +- CloudFlare SvelteKit Application with KV and R2 Storage: [examples/cloudflare-sveltekit/](./examples/cloudflare-sveltekit/alchemy.run.ts) - Deploy an AWS Lambda Function with a DynamoDB Table and IAM Role: [examples/aws-app/](./examples/aws-app/alchemy.run.ts) # Getting Started diff --git a/examples/cloudflare-sveltekit/alchemy.run.ts b/examples/cloudflare-sveltekit/alchemy.run.ts index e454fe10c..a626feaef 100644 --- a/examples/cloudflare-sveltekit/alchemy.run.ts +++ b/examples/cloudflare-sveltekit/alchemy.run.ts @@ -1,5 +1,3 @@ -/// - import alchemy from "alchemy"; import { KVNamespace, From a40bebb82fd03d87429f8407ac387d3f9e53eb50 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Wed, 4 Jun 2025 05:36:39 -0400 Subject: [PATCH 04/11] fix(cloudflare): update SvelteKit entry point and asset paths - Import path module for constructing paths - Correct the main entry point to use the SvelteKit adapter's generated path - Clarify comments regarding asset directory and compatibility flags --- alchemy/src/cloudflare/sveltekit.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/alchemy/src/cloudflare/sveltekit.ts b/alchemy/src/cloudflare/sveltekit.ts index 724c15a14..4d1724f59 100644 --- a/alchemy/src/cloudflare/sveltekit.ts +++ b/alchemy/src/cloudflare/sveltekit.ts @@ -1,3 +1,4 @@ +import path from "node:path"; import type { Assets } from "./assets.ts"; import type { Bindings } from "./bindings.ts"; import { Website, type WebsiteProps } from "./website.ts"; @@ -54,13 +55,11 @@ export async function SvelteKit( ...props, // Default build command for SvelteKit command: props?.command ?? "bun run build", - // FIXED: Use the correct entry point that SvelteKit adapter generates - main: props?.main ?? "./.svelte-kit/cloudflare/_worker.js", - // FIXED: Use the cloudflare directory which contains all static assets + // Use the correct entry point that SvelteKit adapter generates + main: path.join(".svelte-kit/cloudflare/_worker.js"), + // The cloudflare directory which contains all static assets assets: props?.assets ?? "./.svelte-kit/cloudflare", // SvelteKit with Cloudflare adapter needs nodejs_compat compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])], - // Set compatibility date for nodejs_compat (must be >= 2024-09-23) - compatibilityDate: props?.compatibilityDate ?? "2024-09-23", }); } \ No newline at end of file From 2cbd2917b7fdc0c19e5f8747c047c6ab0788b368 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Wed, 4 Jun 2025 07:33:33 -0400 Subject: [PATCH 05/11] fix(cloudflare): enhance SvelteKit configuration and compatibility checks --- alchemy/src/cloudflare/sveltekit.ts | 44 +++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/alchemy/src/cloudflare/sveltekit.ts b/alchemy/src/cloudflare/sveltekit.ts index 4d1724f59..6fdf898f6 100644 --- a/alchemy/src/cloudflare/sveltekit.ts +++ b/alchemy/src/cloudflare/sveltekit.ts @@ -1,4 +1,4 @@ -import path from "node:path"; +import { join } from "node:path"; import type { Assets } from "./assets.ts"; import type { Bindings } from "./bindings.ts"; import { Website, type WebsiteProps } from "./website.ts"; @@ -18,31 +18,31 @@ export type SvelteKit = B extends { ASSETS: any } * Cloudflare Workers, including proper build commands and compatibility flags. It expects * the SvelteKit app to be configured with the @sveltejs/adapter-cloudflare adapter. * + * For local development, SvelteKit provides excellent built-in dev server support with + * emulated platform.env bindings for Cloudflare-specific APIs. + * + * @see https://svelte.dev/docs/kit/adapter-cloudflare - Official SvelteKit Cloudflare adapter docs + * * @example - * // Deploy a basic SvelteKit application with default settings + * // Deploy a basic SvelteKit application * const svelteApp = await SvelteKit("my-svelte-app"); * * @example - * // Deploy with a database binding and KV storage - * import { D1Database, KVNamespace } from "alchemy/cloudflare"; + * // Deploy with Cloudflare bindings + * import { D1Database, KVNamespace, R2Bucket } from "alchemy/cloudflare"; * * const database = await D1Database("svelte-db"); * const sessions = await KVNamespace("sessions"); + * const storage = await R2Bucket("app-storage"); * * const svelteApp = await SvelteKit("svelte-with-bindings", { * bindings: { * DB: database, - * SESSIONS: sessions + * AUTH_STORE: sessions, + * STORAGE: storage * } * }); * - * @example - * // Deploy with custom build command and assets directory - * const customSvelteApp = await SvelteKit("custom-svelte", { - * command: "npm run build:cloudflare", - * assets: "./static" - * }); - * * @param id - Unique identifier for the SvelteKit application * @param props - Configuration properties for the SvelteKit deployment * @returns A Cloudflare Worker resource representing the deployed SvelteKit application @@ -51,15 +51,23 @@ export async function SvelteKit( id: string, props?: Partial>, ): Promise> { + + if (props?.compatibilityDate) { + const providedDate = new Date(props.compatibilityDate); + const minDate = new Date("2024-09-23"); + if (providedDate < minDate) { + throw new Error( + `SvelteKit compatibility date must be >= 2024-09-23 for nodejs_compat support, got ${props.compatibilityDate}` + ); + } + } + return Website(id, { ...props, - // Default build command for SvelteKit command: props?.command ?? "bun run build", - // Use the correct entry point that SvelteKit adapter generates - main: path.join(".svelte-kit/cloudflare/_worker.js"), - // The cloudflare directory which contains all static assets - assets: props?.assets ?? "./.svelte-kit/cloudflare", - // SvelteKit with Cloudflare adapter needs nodejs_compat + main: props?.main ?? join(".svelte-kit", "cloudflare", "_worker.js"), + assets: props?.assets ?? join(".svelte-kit", "cloudflare"), compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])], + compatibilityDate: props?.compatibilityDate, }); } \ No newline at end of file From 280eb0f844cf4fd57a8f650a5c87da0ed7d6f608 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Wed, 4 Jun 2025 07:34:59 -0400 Subject: [PATCH 06/11] refactor(cloudflare): - Demonstrate API routes - Update Svelte component to reflect updated data structure and improve user experience --- examples/cloudflare-sveltekit/src/app.css | 219 +++++++++++ .../src/routes/+page.server.ts | 94 ++--- .../src/routes/+page.svelte | 355 +++++++++++------- .../src/routes/api/+server.ts | 86 +++++ .../src/routes/api/r2/[file]/+server.ts | 42 +++ .../static/alchemist.webp | Bin 0 -> 5304 bytes .../static/cloudflare-icon.svg | 1 + .../static/svelte-logo.svg | 1 + 8 files changed, 613 insertions(+), 185 deletions(-) create mode 100644 examples/cloudflare-sveltekit/src/app.css create mode 100644 examples/cloudflare-sveltekit/src/routes/api/+server.ts create mode 100644 examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts create mode 100644 examples/cloudflare-sveltekit/static/alchemist.webp create mode 100644 examples/cloudflare-sveltekit/static/cloudflare-icon.svg create mode 100644 examples/cloudflare-sveltekit/static/svelte-logo.svg diff --git a/examples/cloudflare-sveltekit/src/app.css b/examples/cloudflare-sveltekit/src/app.css new file mode 100644 index 000000000..34bda4395 --- /dev/null +++ b/examples/cloudflare-sveltekit/src/app.css @@ -0,0 +1,219 @@ +/* Custom Dark Theme CSS */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #0f172a; + color: #ffffff; + line-height: 1.6; + min-height: 100vh; +} + +.container { + max-width: 50rem; + margin: 0 auto; + padding: 1.5rem; +} + +/* Typography */ +h1 { + font-size: 1.875rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: #ffffff; +} + +h3 { + font-size: 1.25rem; + font-weight: 600; + color: #ffffff; + margin-bottom: 1rem; +} + +p { + color: #94a3b8; + margin-bottom: 1rem; +} + +/* Cards */ +article { + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.5rem; + padding: 1.5rem; + margin-bottom: 2rem; +} + +/* Status badges */ +.badge { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 600; + background: #14532d; + color: #86efac; + border: 1px solid #166534; +} + +/* Status list */ +.status-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.status-item { + display: flex; + justify-content: space-between; + align-items: center; +} + +.status-label { + color: #94a3b8; +} + +/* Buttons */ +button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem 1rem; + border-radius: 0.375rem; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: none; + background: #ffffff; + color: #000000; +} + +button:hover { + background: #e2e8f0; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +button.secondary { + background: transparent; + color: #94a3b8; + border: 1px solid #334155; +} + +button.secondary:hover { + background: #334155; + color: #ffffff; +} + +/* Form elements */ +label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: #94a3b8; + margin-bottom: 0.5rem; +} + +input, textarea { + width: 100%; + padding: 0.625rem; + border-radius: 0.375rem; + border: 1px solid #334155; + background: #1e293b; + color: #ffffff; + font-size: 0.875rem; + margin-bottom: 1rem; +} + +input:focus, textarea:focus { + outline: none; + border-color: #475569; +} + +input::placeholder, textarea::placeholder { + color: #64748b; +} + +textarea { + min-height: 6rem; + resize: vertical; +} + +/* Form grid */ +.form-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-bottom: 1rem; +} + +/* Code blocks */ +pre { + background: #0f172a; + border: 1px solid #334155; + border-radius: 0.375rem; + padding: 1rem; + color: #94a3b8; + font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace; + font-size: 0.875rem; + overflow-x: auto; + margin: 1rem 0; +} + +code { + overflow-x: auto; +} + +/* Content grid */ +.content-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; + margin-bottom: 2rem; +} + +/* Object list */ +.object-list { + max-height: 12rem; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.object-item { + font-size: 0.875rem; + color: #64748b; + padding: 0.25rem 0; +} + +/* Links */ +a { + color: #ffffff; + text-decoration: underline; +} + +a:hover { + text-decoration: none; +} + +/* Responsive */ +@media (max-width: 768px) { + .form-grid, + .content-grid { + grid-template-columns: 1fr; + } + + .container { + padding: 1rem; + } +} \ No newline at end of file diff --git a/examples/cloudflare-sveltekit/src/routes/+page.server.ts b/examples/cloudflare-sveltekit/src/routes/+page.server.ts index 6a04affa4..50543aee0 100644 --- a/examples/cloudflare-sveltekit/src/routes/+page.server.ts +++ b/examples/cloudflare-sveltekit/src/routes/+page.server.ts @@ -1,57 +1,67 @@ import type { PageServerLoad } from './$types'; -export const load: PageServerLoad = async ({ platform }) => { - // Demonstrate KV Store usage - const kv = platform?.env.AUTH_STORE; - let kvData = null; +async function getKvDemo(kv: any) { + if (!kv) return null; - if (kv) { + try { + let value = await kv.get('demo-key'); + + // Set default value if none exists + if (!value) { + const defaultData = { + message: 'Hello from Cloudflare KV!', + timestamp: new Date().toISOString() + }; + await kv.put('demo-key', JSON.stringify(defaultData)); + return defaultData; + } + + // Try parsing as JSON, fall back to string wrapper try { - // Try to get a value from KV - kvData = await kv.get('demo-key'); - - // If no value exists, set one - if (!kvData) { - await kv.put('demo-key', JSON.stringify({ - message: 'Hello from Cloudflare KV!', - timestamp: new Date().toISOString() - })); - kvData = await kv.get('demo-key'); - } - } catch (error) { - console.error('KV error:', error); + return JSON.parse(value); + } catch { + return { message: value, type: 'string', timestamp: 'Set via API' }; } + } catch (error) { + console.error('KV error:', error); + return null; } +} - // Demonstrate R2 Storage info - const r2 = platform?.env.STORAGE; - let r2Info = null; +async function getR2Info(r2: any) { + if (!r2) return null; - if (r2) { - try { - // List objects in the bucket (up to 1000) - const objects = await r2.list({ limit: 10 }); - r2Info = { - bucketName: 'Storage bucket connected!', - objectCount: objects.objects?.length || 0, - objects: objects.objects?.map(obj => ({ - key: obj.key, - size: obj.size, - modified: obj.uploaded - })) || [] - }; - } catch (error) { - console.error('R2 error:', error); - r2Info = { error: 'Failed to access R2 bucket' }; - } + try { + const { objects = [] } = await r2.list({ limit: 10 }); + return { + bucketName: 'Storage bucket connected!', + objectCount: objects.length, + objects: objects.map((obj: any) => ({ + key: obj.key, + size: obj.size, + modified: obj.uploaded + })) + }; + } catch (error) { + console.error('R2 error:', error); + return { error: 'Failed to access R2 bucket' }; } +} +export const load: PageServerLoad = async ({ platform }) => { + const env = platform?.env; + + const [kv, r2] = await Promise.all([ + getKvDemo(env?.AUTH_STORE), + getR2Info(env?.STORAGE) + ]); + return { - kv: kvData ? JSON.parse(kvData) : null, - r2: r2Info, + kv, + r2, platform: { - hasKV: !!kv, - hasR2: !!r2, + hasKV: !!env?.AUTH_STORE, + hasR2: !!env?.STORAGE, hasContext: !!platform?.context } }; diff --git a/examples/cloudflare-sveltekit/src/routes/+page.svelte b/examples/cloudflare-sveltekit/src/routes/+page.svelte index b1ccd96f0..72a29c36a 100644 --- a/examples/cloudflare-sveltekit/src/routes/+page.svelte +++ b/examples/cloudflare-sveltekit/src/routes/+page.svelte @@ -1,153 +1,222 @@ - -
-

🚀 SvelteKit + Cloudflare + Alchemy

-

- This demo shows SvelteKit running on Cloudflare Workers with Alchemy-managed - resources. -

- -
-

🔧 Platform Status

-
    -
  • - KV Namespace: {data.platform.hasKV - ? "✅ Connected" - : "❌ Not available"} -
  • -
  • - R2 Bucket: {data.platform.hasR2 ? "✅ Connected" : "❌ Not available"} -
  • -
  • - Execution Context: {data.platform.hasContext - ? "✅ Available" - : "❌ Not available"} -
  • -
-
- - {#if data.kv} -
-

📦 KV Store Demo

-
-
{JSON.stringify(data.kv, null, 2)}
-
-
- {/if} - - {#if data.r2} -
-

🗄️ R2 Storage Demo

- {#if data.r2.error} -

Error: {data.r2.error}

- {:else} -

Bucket Status: {data.r2.bucketName}

-

Objects in bucket: {data.r2.objectCount}

- - {#if data.r2.objects && data.r2.objects.length > 0} -

Recent Objects:

-
- {#each data.r2.objects as obj} -
- {obj.key} - {obj.size} bytes - (modified: {new Date(obj.modified).toLocaleString()}) -
- {/each} -
- {:else} -

No objects in bucket yet

- {/if} - {/if} -
- {/if} - -
-

📚 Documentation

-

- Visit svelte.dev/docs/kit to read - the SvelteKit documentation -

-

- Visit alchemy.run to learn more about Alchemy -

-
-
- - + {#if data.r2.objects && data.r2.objects.length > 0} +
+
+ Recent Objects +
+
+ {#each data.r2.objects as obj} +
+ {obj.key} - {obj.size} + bytes +
+ {obj.modified} + {/each} +
+
+ {:else} +

No objects in bucket yet

+ {/if} + {/if} + + {/if} + + + + diff --git a/examples/cloudflare-sveltekit/src/routes/api/+server.ts b/examples/cloudflare-sveltekit/src/routes/api/+server.ts new file mode 100644 index 000000000..dc0154156 --- /dev/null +++ b/examples/cloudflare-sveltekit/src/routes/api/+server.ts @@ -0,0 +1,86 @@ +import type { RequestEvent } from '@sveltejs/kit'; + +export const GET = async ({ platform }: RequestEvent) => { + try { + // Demonstrate KV access + const kvValue = await platform?.env.AUTH_STORE.get('demo-key'); + + // Demonstrate R2 list operation + const r2List = await platform?.env.STORAGE.list({ limit: 5 }); + + return new Response(JSON.stringify({ + message: 'SvelteKit + Cloudflare API working!', + kv: { + key: 'demo-key', + value: kvValue || 'No value found' + }, + r2: { + objects: r2List?.objects.map((obj: any) => ({ + key: obj.key, + size: obj.size, + uploaded: obj.uploaded + })) || [] + } + }), { + headers: { + 'Content-Type': 'application/json' + } + }); + } catch (error) { + return new Response(JSON.stringify({ + error: 'API Error', + message: error instanceof Error ? error.message : 'Unknown error' + }), { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + }); + } +}; + +export const POST = async ({ request, platform }: RequestEvent) => { + try { + const body = await request.json() as { + key?: string; + value?: string; + fileContent?: string; + }; + + const { key, value, fileContent } = body; + + // Demonstrate KV write + if (key && value) { + await platform?.env.AUTH_STORE.put(key, value); + } + + // Demonstrate R2 write + if (fileContent) { + const fileName = `demo-${Date.now()}.txt`; + await platform?.env.STORAGE.put(fileName, fileContent); + } + + return new Response(JSON.stringify({ + success: true, + message: 'Data stored successfully', + stored: { + kv: key ? { key, value } : null, + r2: fileContent ? `demo-${Date.now()}.txt` : null + } + }), { + headers: { + 'Content-Type': 'application/json' + } + }); + } catch (error) { + return new Response(JSON.stringify({ + error: 'Storage Error', + message: error instanceof Error ? error.message : 'Unknown error' + }), { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + }); + } +}; \ No newline at end of file diff --git a/examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts b/examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts new file mode 100644 index 000000000..d6768fac3 --- /dev/null +++ b/examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts @@ -0,0 +1,42 @@ +import type { RequestEvent } from '@sveltejs/kit'; + +export const GET = async ({ url, platform }: RequestEvent) => { + if (!platform?.env?.STORAGE) { + return new Response('R2 bucket not available', { status: 500 }); + } + + // Extract filename from the URL path after /api/r2/ + const pathname = url.pathname; + const filename = pathname.split('/api/r2/')[1]; + + if (!filename) { + return new Response('Filename required', { status: 400 }); + } + + try { + const object = await platform.env.STORAGE.get(filename); + + if (!object) { + return new Response('File not found', { status: 404 }); + } + + // Get the file content + const content = await object.text(); + + // Determine content type based on file extension + const contentType = filename.endsWith('.json') ? 'application/json' : + filename.endsWith('.txt') ? 'text/plain' : + 'application/octet-stream'; + + return new Response(content, { + headers: { + 'Content-Type': contentType, + 'Content-Disposition': `inline; filename="${filename}"`, + 'Cache-Control': 'public, max-age=3600' + } + }); + } catch (error) { + console.error('Error serving R2 file:', error); + return new Response('Error retrieving file', { status: 500 }); + } +}; diff --git a/examples/cloudflare-sveltekit/static/alchemist.webp b/examples/cloudflare-sveltekit/static/alchemist.webp new file mode 100644 index 0000000000000000000000000000000000000000..979d404cbc36a19e09e1b7d07bce983ee19ea638 GIT binary patch literal 5304 zcmV;p6i4e)Nk&Gn6aWBMMM6+kP&il$0000G0001g004gg06|PpNMi;700EE$0FWU^ zwTOMTZQHhO+xF#b+qP}nwtca)$*LY1R`vcPVgi7B(3B|D@NFG%mBt2l9jIyeWM9`% zXiFk$ey+3&@Er6L_59scczF+^&dJk##n$9K>c2loV5adgNbx`_VR=lmBh9^Df=aX7 zfkd4X#gyhK(v7MoBJYDp`EhFTn1Ye^OyL+O+lYmi5lyEgq`uW8mInWkd_+;9JaT;OCl13rWSQJl9BmSK;8tN2{DMs1q!z_;>ZA`D9nSBEpWj1b2W6O+vEh4Ip#3G}gx z;(cl^ZnH#@+>s0T9TWt9>&OLCt+a~)6DN8z0b{@aVnE#N#{{6leL>)fTwEZ-f(S9- z!|GfBDqj=>hK)3F0pDKtg@E?0xw!xYO?qV$0OI=&W-b7!8!!GN0JQEZ%mx6;y%hlB zui&hFps_^+h##jG^)WKZ_D>9mkx$1l$tets>%=72e^K113{2};1#vbLa9xc;j9Hcr z^i11n(#Fjxw@%HZu3v%(zHP4ox|+A3!X;`9Llcm<{r~q{YdU5DoxPtanW?5_A~H>U zyl>Is#dAB<&zH#y^f%72%Qy~aRFUo_a$V>Hq}_Y{m3kNeD(52Ch$29Jr_?D>Stx|f zF*CVsQfCax46Bfm*n! zMh^!Qx~<1b8R&1NH;~XQJ^IPHAEGykP+z-tw#IU>l-?yTI(h$U_dADN*${e`+H_3& zwA*Er@i>kQ_n7rbYg7Q0W3{r>1lh((9HNTpQF{G{(FO%se`|HnT_)8XWI0D8vroPj z>mm>z`zXl%N2B)zKz1zhe3wq0clnWv&prupAR?|aD9G_wo2Z5$TRoJCbC{||SK?7V zvH~c~^i`XXUV$cwY>$xZ%XG^8=ES4C^;v6>%vmT9z|=b09hxzQ4(L77y$VCg2Dl$R>=Wm z8gMN_X1$r7@};EKD*^B-6dZzx_MK^g_d!xuJQM*yv6=hUb#?>ZKDS7{2v8%$jNeH` z6ckX_pQMuO38-o!DI7))09H^qASMm~0I)UyodGI<0Du5KZ7z{Uq#~iAEE)VTfDMUg z2yg+5NKLf7@cmbs^_Bd80};r!@B6=19$*~s{KS6Tdcyik^dJ7a{sXOV(L>9t*?0Tj z{x3>g)X(ePw12Gd*U7iLv*`Y-!(&Ebed8veUd-LS03V%qgzBf@?_drK$^ri_{~!AY z?3bShseOUJTl&g+C4R*>iZ|U&{QyY3c-pK*^eV_`62?mFT~gC-x33`!7k|^3AqB*OK*>L|&?l5x;pr9HbTzAri|L+^-Mr9g} z^&9Wz;q)%$@3a|yf8u%!4kx!oZaiZ?Tgl%){#|d8KnDFaNlj^kTKAJtNOTwMSmtoN#8p##K!oT>GFokeV7TaCS136=5+-n3GygOK>(oOJWiLc@Dhl}BNxnk!L zO7@++Vz?~As&FK}jO=J_zv^zInXpWnKhnhaN%--^lzrC+brZ&JvkrbwvO~FTNr)+I@gj~J zwlshM{^xfCU>bZzd!~#k;6-u%4Zdq%No%?aTYz>3SOEvC6a+@p>r)E0b2ZNVjy_>F zoTv$q@^>BBIetGZRzl`v#iQNa%2@iMB>bWPJwbAty0C^YRpfC}u+{0?*K2~7GrJHV ziHx+bUpu1kEWz&aBVO{BG(hG1G#AIlG!gJ1!Nb?+)$H0=P6egqT}EvBg0tBf@`3A#7K!?_KQAh-(s&H|QK?DAZCi^H#I@Ms zD&cS3;WjSUvGD(K29)3VQeWcmywp-PC9q$zSvd=fG^3sW;P-u5nIS89BS+b^;TX#R zobpXz^|k4jv}ed{1$oy|sfuiM__N*~0mri|$y7-QUrh6pSvl1X z?41IucMVCDG>*Q4MxOua1sHogyYp((FY9{IeT(q$~Fkf8ml!!Qd`5+mq0z zn~AGI42cfBSz;GX9k6Do!tz!Sw`)0lBdJ`s341h~B^}$D=!7L+FXT z(z0&YOT!sG`?_cU;$j)B(Pc8b606KOTgx|r*??V3B>fUY@-gXcrzlzj-*nB&; zJpPDG^GK-Ej^%%Aqd36vGfO_ZE9~pLm=yBCrA7^YiB-Tdcg>fLZ#=|uW9t$Nsgz7L zI{jaefJNhz*}YQ;!zz-$7O=vW4$Fw0k%JmT_1yPPLOQ*fjjN*kU0gXT>%)++oI1?R zAIPHKThl-cw&Vii`C`e?do|+sJtGjks(N#KX2eduAy1ASK@ zRzw7r`2xt|m`UUYU5z!LX%k9}R^xGk4xMyNfJQ#AGAE)Q>xJ)jF|&rNS+)ZtdVbaUJfdNc9_g`%99BcD@o+1d^2cP5dd%P_qmTY+EA z`srciNxcCSQ`RL&C7^15Bu@U#0CfOVJb`wV;m|hyf92AH9kckfLZPx-OTxXbV_s_1 zC%ij>YN^pWT~Hf^^I1L2pZBtSnh&r2Ex};i6UkVau&G$)Mn!WigHTs7ME{2bKoaM~ z?eTJvH3mTI{8t`Trs{5_Ljhq`P#uMR&&Mh4VyJoCM@z8x=j(7w|2yg6bQ<-oJ*ze7 z!fkOM%iSgi4ZXw7$a|DZt$0qDNP@Dl9vHa)01Wj%Bn?yNrW;z360E}2g3xJYxNopUv^3CfKv$xW1t`A7yrF+X@835+CS^dP+#mEm=xR%YCofi z8LINC5Uqi|C0CKS(dE)P06N_iGaXBWb1O9S<^xcTWikc*j#PJJNKa=2 zqE%70&E|_gkDX~0i4wcq7N}L{gt;_ySy7AjDX0yYJ`RGmMsmOEm%$y(uu&^5giOCa zNgOjhv;XCeI$+0O&3zo&#NOdo0&~A-p$M}*iE|AQQj$XdtH+&(vHpIJJUxA|N2X;u z9xI(>J(&(?ej5V6T%6Cr$Ro8@ANJzGfB{Cumc5c2*BXX?lwCk|BD@0*r#mAEm#Tf4 z={VFcx)@*;iLk*y{4n`;Vmh;0B`v>7yU#YkS38gOQq%l&y*`IBQ~URqL9Dp{1Fj?%gBJ3}erk$6&HX#FiYRF0x!R9UHqvXGHj}wG9$c$V zu|b}fue&Kno?T}Gl9mqG#u(`5%207L-qoL;)es}kzON^*N~SMEE$LX5E~w;ln{B5p z9_@>RGva(2uQn>~r)D68Kd*3>s5h)Qd8yD1e^D+t>pX7c%0ecWcsp&F)K$(q=|XJg zz!fc%?JkWDX9e=;43gnbq>)*c;2~ma@ZoRi0ezwiQB@931_NPb{|<0m-k$cdpO>&9 z{Mkd=(ZMcHI>XRkzfD{U?)lPhwC)%vso$nG(WXbV3Hm&V^ihALgOD>Fd zxzfR^oF-oeuVXRilAl@Tsyf|w$MYPUvnJVg>=$#U?6h{O)Im>S9m1(rcSy&+sG;^> zVC7|gj#zRR;tc;0^;_dR&!7A2C%TU23@$>p_~)KRcPOEt5fcBC58gu56ZRL2RA@Q( z;EAVt5wuSJtB5r1a{;$F*LNLN5v+pr!eHR4Xnj^$SnakpD_Yu@+<+x&M{+jJC_$ghX@_BLHvs%y15f6%&Fw11xQx7G z@9*m@Wh7~KSqY}A7-0XNDCRWGB^g(j^XTc5bWy&o?R!byslm{OG9(+=L2mVSVyBUvEKt3uon4` z9ekO8+*@o-26h1IrQp`l;CewIIt~pmy!}d+JH5l7IO1#vul77Ive&7}o8AkTuMi#p zO|G!S{(S{(g|n!6 z=madQr#BctY7OqVR^Dz}1vuK;P) \ No newline at end of file diff --git a/examples/cloudflare-sveltekit/static/svelte-logo.svg b/examples/cloudflare-sveltekit/static/svelte-logo.svg new file mode 100644 index 000000000..49492a83c --- /dev/null +++ b/examples/cloudflare-sveltekit/static/svelte-logo.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file From e431045f3fc5ae1c29ae2bc6ae8a704640258bd1 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Wed, 4 Jun 2025 08:03:45 -0400 Subject: [PATCH 07/11] docs(cloudflare): enhance README about CF binding and types --- examples/cloudflare-sveltekit/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/cloudflare-sveltekit/README.md b/examples/cloudflare-sveltekit/README.md index d17eaf7e9..c4817dc04 100644 --- a/examples/cloudflare-sveltekit/README.md +++ b/examples/cloudflare-sveltekit/README.md @@ -25,6 +25,8 @@ It includes: ``` You can also place these in a `.env` file in the repository root. + More information on how to get your Cloudflare API token and Account ID can be found in the [Cloudflare Auth Guide](https://alchemy.run/docs/guides/cloudflare-auth.html). + ## Running the Deployment 1. **Navigate to Example Directory:** @@ -75,6 +77,8 @@ export async function load({ platform }) { } ``` +The type definitions for the platform interface are defined in [`src/app.d.ts`](./src/app.d.ts), which provides type safety for the [Cloudflare bindings](https://svelte.dev/docs/kit/adapter-cloudflare#Runtime-APIs). You can learn more about the Cloudflare bindings [here](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/). + ## Cleanup To destroy the Cloudflare resources created by this example, run: From 7e8119231f27b9c2d2b1f070c6e7b55857606001 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Wed, 4 Jun 2025 08:41:49 -0400 Subject: [PATCH 08/11] docs(cloudflare): add guide for deploying SvelteKit apps --- .../docs/guides/cloudflare-sveltekit.md | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 alchemy-web/docs/guides/cloudflare-sveltekit.md diff --git a/alchemy-web/docs/guides/cloudflare-sveltekit.md b/alchemy-web/docs/guides/cloudflare-sveltekit.md new file mode 100644 index 000000000..70a6224cf --- /dev/null +++ b/alchemy-web/docs/guides/cloudflare-sveltekit.md @@ -0,0 +1,156 @@ +--- +order: 3 +title: SvelteKit +description: Step-by-step guide to deploying a SvelteKit application to Cloudflare Workers using Alchemy with KV storage and R2 buckets. +--- + +# SvelteKit + +This guide walks through how to deploy a SvelteKit application to Cloudflare Workers with Alchemy. + +## Create a new SvelteKit Project + +Start by creating a new SvelteKit project: + +```sh +bun create svelte@latest my-sveltekit-app +cd my-sveltekit-app +bun install +``` + +> [!NOTE] +> See Svelte's [Introduction](https://svelte.dev/docs/kit/introduction) guide for more details on SvelteKit applications. + +## Install Cloudflare Adapter and Dependencies + +Install the required dependencies: + +```sh +bun add @sveltejs/adapter-cloudflare alchemy cloudflare +bun add -D @cloudflare/workers-types +``` + +## Configure SvelteKit for Cloudflare + +Update your `svelte.config.js` to use the Cloudflare adapter: + +```js +import adapter from '@sveltejs/adapter-cloudflare'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter() + } +}; + +export default config; +``` + +## Create `alchemy.run.ts` + +Create an `alchemy.run.ts` file in the root of your project: + +```ts +import alchemy from "alchemy"; +import { KVNamespace, R2Bucket, SvelteKit } from "alchemy/cloudflare"; + +const app = await alchemy("my-sveltekit-app", { + stage: process.env.USER ?? "dev", + phase: process.argv.includes("--destroy") ? "destroy" : "up", +}); + +const website = await SvelteKit("sveltekit-website", { + bindings: { + AUTH_STORE: await KVNamespace("auth-store", { + title: "my-sveltekit-auth-store", + }), + STORAGE: await R2Bucket("storage", { + allowPublicAccess: false, + }), + }, + url: true, +}); + +console.log({ + url: website.url, +}); + +await app.finalize(); +``` + +## Configure SvelteKit Types + +Update `src/app.d.ts` for Cloudflare bindings: + +```ts +declare global { + namespace App { + interface Platform { + env: { + STORAGE: R2Bucket; + AUTH_STORE: KVNamespace; + }; + context: ExecutionContext; + caches: CacheStorage & { default: Cache }; + } + } +} + +export {}; +``` + +## Using Cloudflare Bindings + +In your SvelteKit routes, access Cloudflare resources via `platform.env`: + +```ts +// +page.server.ts +export const load = async ({ platform }) => { + const kvData = await platform?.env?.AUTH_STORE?.get('some-key'); + const r2Object = await platform?.env?.STORAGE?.get('some-file'); + return { kvData }; +}; +``` + +## Deploy Your Application + +Login to Cloudflare: + +```sh +wrangler login +``` + +Run your Alchemy script to deploy the application: + +```sh +bun ./alchemy.run +``` + +It should output the URL of your deployed site: + +```sh +{ + url: "https://your-site.your-sub-domain.workers.dev", +} +``` + +## Local Development + +To run your application locally: + +```sh +bun run dev +``` + +## Tear Down + +When you're finished experimenting, you can tear down the application: + +```sh +bun ./alchemy.run --destroy +``` + +This will remove all Cloudflare resources created by this deployment. \ No newline at end of file From 1098e370b3bcb5ff44438732e9898c472239b429 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Thu, 5 Jun 2025 07:28:15 -0400 Subject: [PATCH 09/11] chore(cloudflare): update dependencies and enhance SvelteKit guide for Cloudflare integration --- .../docs/guides/cloudflare-sveltekit.md | 197 ++++++++++++++++-- bun.lock | 10 +- examples/cloudflare-sveltekit/README.md | 17 +- examples/cloudflare-sveltekit/alchemy.run.ts | 4 +- examples/cloudflare-sveltekit/package.json | 4 +- examples/cloudflare-sveltekit/src/app.d.ts | 13 +- examples/cloudflare-sveltekit/src/env.ts | 17 ++ 7 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 examples/cloudflare-sveltekit/src/env.ts diff --git a/alchemy-web/docs/guides/cloudflare-sveltekit.md b/alchemy-web/docs/guides/cloudflare-sveltekit.md index 70a6224cf..f5d7834ff 100644 --- a/alchemy-web/docs/guides/cloudflare-sveltekit.md +++ b/alchemy-web/docs/guides/cloudflare-sveltekit.md @@ -12,12 +12,34 @@ This guide walks through how to deploy a SvelteKit application to Cloudflare Wor Start by creating a new SvelteKit project: -```sh +::: code-group + +```sh [bun] bun create svelte@latest my-sveltekit-app cd my-sveltekit-app bun install ``` +```sh [npm] +npm create svelte@latest my-sveltekit-app +cd my-sveltekit-app +npm install +``` + +```sh [pnpm] +pnpm create svelte@latest my-sveltekit-app +cd my-sveltekit-app +pnpm install +``` + +```sh [yarn] +yarn create svelte@latest my-sveltekit-app +cd my-sveltekit-app +yarn install +``` + +::: + > [!NOTE] > See Svelte's [Introduction](https://svelte.dev/docs/kit/introduction) guide for more details on SvelteKit applications. @@ -25,11 +47,30 @@ bun install Install the required dependencies: -```sh +::: code-group + +```sh [bun] bun add @sveltejs/adapter-cloudflare alchemy cloudflare bun add -D @cloudflare/workers-types ``` +```sh [npm] +npm install @sveltejs/adapter-cloudflare alchemy cloudflare +npm install --save-dev @cloudflare/workers-types +``` + +```sh [pnpm] +pnpm add @sveltejs/adapter-cloudflare alchemy cloudflare +pnpm add -D @cloudflare/workers-types +``` + +```sh [yarn] +yarn add @sveltejs/adapter-cloudflare alchemy cloudflare +yarn add -D @cloudflare/workers-types +``` + +::: + ## Configure SvelteKit for Cloudflare Update your `svelte.config.js` to use the Cloudflare adapter: @@ -49,6 +90,29 @@ const config = { export default config; ``` +Create or update your `vite.config.ts` to configure the `cloudflare:workers` module: + +```ts +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; +import { cloudflareWorkersDevEnvironmentShim, external } from 'alchemy/cloudflare'; + +export default defineConfig({ + plugins: [ + sveltekit(), + cloudflareWorkersDevEnvironmentShim() + ], + define: { + global: 'globalThis', + }, + build: { + rollupOptions: { + external + } + } +}); +``` + ## Create `alchemy.run.ts` Create an `alchemy.run.ts` file in the root of your project: @@ -83,7 +147,43 @@ await app.finalize(); ## Configure SvelteKit Types -Update `src/app.d.ts` for Cloudflare bindings: +Create `src/env.ts` to define your Cloudflare bindings with type safety: + +```ts +import type { website } from "../alchemy.run.js"; + +export interface CloudflarePlatform { + env: typeof website.Env; + context: ExecutionContext; + caches: CacheStorage & { default: Cache }; +} + +declare global { + export type CloudflareEnv = typeof website.Env; +} + +declare module "cloudflare:workers" { + namespace Cloudflare { + export interface Env extends CloudflareEnv {} + } +} +``` + +Then update `src/app.d.ts` to use these types: + +```ts +import type { CloudflarePlatform } from './env'; + +declare global { + namespace App { + interface Platform extends CloudflarePlatform {} + } +} + +export {}; +``` + +Alternatively, you can define types directly in `src/app.d.ts`: ```ts declare global { @@ -102,10 +202,26 @@ declare global { export {}; ``` +> [!NOTE] +> The `src/env.ts` approach provides better type safety since `.ts` files are type-checked, while `.d.ts` files are not. It also automatically derives types from your Alchemy configuration. The traditional `app.d.ts` approach is simpler but requires manual type definitions. Both approaches work with SvelteKit's adapter system by extending the `App.Platform` interface. + ## Using Cloudflare Bindings -In your SvelteKit routes, access Cloudflare resources via `platform.env`: +Both type configurations support two ways to access Cloudflare resources: + +**Option 1: Direct runtime import (recommended)** +```ts +// +page.server.ts +import { env } from "cloudflare:workers"; + +export const load = async () => { + const kvData = await env.AUTH_STORE?.get('some-key'); + const r2Object = await env.STORAGE?.get('some-file'); + return { kvData }; +}; +``` +**Option 2: Via platform parameter** ```ts // +page.server.ts export const load = async ({ platform }) => { @@ -115,20 +231,57 @@ export const load = async ({ platform }) => { }; ``` -## Deploy Your Application +## Log in to Cloudflare -Login to Cloudflare: +Before you can deploy, you need to authenticate by running `wrangler login`. -```sh -wrangler login +::: code-group + +```sh [bun] +bun wrangler login ``` -Run your Alchemy script to deploy the application: +```sh [npm] +npx wrangler login +``` -```sh +```sh [pnpm] +pnpm wrangler login +``` + +```sh [yarn] +yarn wrangler login +``` + +::: + +> [!TIP] +> Alchemy will by default try and use your wrangler OAuth token and Refresh Token to connect but see the [Cloudflare Auth](../guides/cloudflare-auth.md) for other methods. + +## Deploy + +Now we can run and deploy our Alchemy stack: + +::: code-group + +```sh [bun] bun ./alchemy.run ``` +```sh [npm] +npx tsx ./alchemy.run +``` + +```sh [pnpm] +pnpm tsx ./alchemy.run +``` + +```sh [yarn] +yarn tsx ./alchemy.run +``` + +::: + It should output the URL of your deployed site: ```sh @@ -141,16 +294,32 @@ It should output the URL of your deployed site: To run your application locally: -```sh +::: code-group + +```sh [bun] bun run dev ``` -## Tear Down +```sh [npm] +npm run dev +``` + +```sh [pnpm] +pnpm run dev +``` + +```sh [yarn] +yarn run dev +``` + +::: + +## Destroy -When you're finished experimenting, you can tear down the application: +For illustrative purposes, let's destroy the Alchemy stack: ```sh bun ./alchemy.run --destroy ``` -This will remove all Cloudflare resources created by this deployment. \ No newline at end of file +You're done! Happy SvelteKit'ing 🚀 \ No newline at end of file diff --git a/bun.lock b/bun.lock index f44d8bdfe..2f74f8a00 100644 --- a/bun.lock +++ b/bun.lock @@ -180,10 +180,10 @@ "name": "cloudflare-sveltekit", "version": "0.0.1", "devDependencies": { - "@cloudflare/workers-types": "^4.20250603.0", + "@cloudflare/workers-types": "^4.20250605.0", "@sveltejs/adapter-auto": "^6.0.1", "@sveltejs/adapter-cloudflare": "^7.0.3", - "@sveltejs/kit": "^2.21.1", + "@sveltejs/kit": "^2.21.2", "@sveltejs/vite-plugin-svelte": "^5.1.0", "alchemy": "workspace:*", "svelte": "^5.33.14", @@ -539,7 +539,7 @@ "@cloudflare/workers-shared": ["@cloudflare/workers-shared@0.17.5", "", { "dependencies": { "mime": "^3.0.0", "zod": "^3.22.3" } }, "sha512-e2tjozEy3/8JnPcddYFuMjW9As+aX0i7egciPE8b+mufS33QCtdFEzZKCK8utFzby0tx9TkxGFLJ+cmSrJ+tLw=="], - "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250603.0", "", {}, "sha512-62g5wVdXiRRu9sfC9fmwgZfE9W0rWy2txlgRKn1Xx1NWHshSdh4RWMX6gO4EBKPqgwlHKaMuSPZ1qM0hHsq7bA=="], + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250605.0", "", {}, "sha512-e3/ZCXcpmk3jUNfq/2gyVYqeOqUhuQ0hsSdohSGscCgTkUI37QraVeCAOQtciKPDFXKOkaGGkmxg+RLYunbKuw=="], "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], @@ -1105,7 +1105,7 @@ "@sveltejs/adapter-cloudflare": ["@sveltejs/adapter-cloudflare@7.0.3", "", { "dependencies": { "@cloudflare/workers-types": "^4.20250507.0", "worktop": "0.8.0-next.18" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0", "wrangler": "^4.0.0" } }, "sha512-hEbaEukbHuTUtq4a+CPb57YnyQdx+iWVuLZAmNzaJwGSsAgMafY+GY4UblsIACjSYpX5tZuhjBck+X5IoWLIWw=="], - "@sveltejs/kit": ["@sveltejs/kit@2.21.1", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-vLbtVwtDcK8LhJKnFkFYwM0uCdFmzioQnif0bjEYH1I24Arz22JPr/hLUiXGVYAwhu8INKx5qrdvr4tHgPwX6w=="], + "@sveltejs/kit": ["@sveltejs/kit@2.21.2", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-EMYTY4+rNa7TaRZYzCqhQslEkACEZzWc363jOYuc90oJrgvlWTcgqTxcGSIJim48hPaXwYlHyatRnnMmTFf5tA=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.0", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw=="], @@ -3807,6 +3807,8 @@ "@shikijs/vitepress-twoslash/shiki": ["shiki@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/engine-javascript": "3.4.2", "@shikijs/engine-oniguruma": "3.4.2", "@shikijs/langs": "3.4.2", "@shikijs/themes": "3.4.2", "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ=="], + "@sveltejs/adapter-cloudflare/@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250603.0", "", {}, "sha512-62g5wVdXiRRu9sfC9fmwgZfE9W0rWy2txlgRKn1Xx1NWHshSdh4RWMX6gO4EBKPqgwlHKaMuSPZ1qM0hHsq7bA=="], + "@sveltejs/kit/cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], diff --git a/examples/cloudflare-sveltekit/README.md b/examples/cloudflare-sveltekit/README.md index c4817dc04..49b384478 100644 --- a/examples/cloudflare-sveltekit/README.md +++ b/examples/cloudflare-sveltekit/README.md @@ -66,7 +66,20 @@ Note: The Cloudflare bindings will not function correctly in the local developme ## Accessing Cloudflare Resources -In your SvelteKit application, access the Cloudflare resources via `platform.env`: +In your SvelteKit application, access the Cloudflare resources using the runtime environment: + +```typescript +// In a +page.server.ts file +import { env } from "cloudflare:workers"; + +export async function load() { + const kvData = await env.AUTH_STORE?.get('some-key'); + const r2Object = await env.STORAGE?.get('some-file'); + return { kvData }; +} +``` + +Alternatively, you can access them via the platform parameter: ```typescript // In a +page.server.ts file @@ -77,7 +90,7 @@ export async function load({ platform }) { } ``` -The type definitions for the platform interface are defined in [`src/app.d.ts`](./src/app.d.ts), which provides type safety for the [Cloudflare bindings](https://svelte.dev/docs/kit/adapter-cloudflare#Runtime-APIs). You can learn more about the Cloudflare bindings [here](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/). +The type definitions for the Cloudflare bindings are configured in [`src/env.ts`](./src/env.ts) and [`src/app.d.ts`](./src/app.d.ts), which provide type safety for the [Cloudflare bindings](https://svelte.dev/docs/kit/adapter-cloudflare#Runtime-APIs). You can learn more about the Cloudflare bindings [here](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/). ## Cleanup diff --git a/examples/cloudflare-sveltekit/alchemy.run.ts b/examples/cloudflare-sveltekit/alchemy.run.ts index a626feaef..31cb90bf4 100644 --- a/examples/cloudflare-sveltekit/alchemy.run.ts +++ b/examples/cloudflare-sveltekit/alchemy.run.ts @@ -1,8 +1,8 @@ import alchemy from "alchemy"; import { + DOStateStore, KVNamespace, R2Bucket, - R2RestStateStore, SvelteKit, } from "alchemy/cloudflare"; @@ -14,7 +14,7 @@ const app = await alchemy("cloudflare-sveltekit", { password: process.env.ALCHEMY_PASSWORD, stateStore: process.env.ALCHEMY_STATE_STORE === "cloudflare" - ? (scope) => new R2RestStateStore(scope) + ? (scope) => new DOStateStore(scope) : undefined, }); diff --git a/examples/cloudflare-sveltekit/package.json b/examples/cloudflare-sveltekit/package.json index 5c6bf735e..ef3e629e0 100644 --- a/examples/cloudflare-sveltekit/package.json +++ b/examples/cloudflare-sveltekit/package.json @@ -14,10 +14,10 @@ "destroy": "bun run --env-file ../../.env ./alchemy.run.ts --destroy" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250603.0", + "@cloudflare/workers-types": "^4.20250605.0", "@sveltejs/adapter-auto": "^6.0.1", "@sveltejs/adapter-cloudflare": "^7.0.3", - "@sveltejs/kit": "^2.21.1", + "@sveltejs/kit": "^2.21.2", "@sveltejs/vite-plugin-svelte": "^5.1.0", "alchemy": "workspace:*", "svelte": "^5.33.14", diff --git a/examples/cloudflare-sveltekit/src/app.d.ts b/examples/cloudflare-sveltekit/src/app.d.ts index 030fc1040..2df636cfa 100644 --- a/examples/cloudflare-sveltekit/src/app.d.ts +++ b/examples/cloudflare-sveltekit/src/app.d.ts @@ -1,3 +1,5 @@ +import type { CloudflarePlatform } from './env'; + // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { @@ -6,16 +8,9 @@ declare global { // interface Locals {} // interface PageData {} // interface PageState {} - interface Platform { - env: { - STORAGE: R2Bucket; - AUTH_STORE: KVNamespace; - }; - context: ExecutionContext; - caches: CacheStorage & { default: Cache }; - } + interface Platform extends CloudflarePlatform {} } } -export { }; +// export { }; diff --git a/examples/cloudflare-sveltekit/src/env.ts b/examples/cloudflare-sveltekit/src/env.ts new file mode 100644 index 000000000..d6cba1cef --- /dev/null +++ b/examples/cloudflare-sveltekit/src/env.ts @@ -0,0 +1,17 @@ +import type { website } from "../alchemy.run.js"; + +export interface CloudflarePlatform { + env: typeof website.Env; + context: ExecutionContext; + caches: CacheStorage & { default: Cache }; +} + +declare global { + export type CloudflareEnv = typeof website.Env; +} + +declare module "cloudflare:workers" { + namespace Cloudflare { + export interface Env extends CloudflareEnv {} + } +} \ No newline at end of file From 6a68fa3f789eba499c13a90cb09af20df9c6f195 Mon Sep 17 00:00:00 2001 From: Jordan Coeyman Date: Fri, 6 Jun 2025 08:30:50 -0400 Subject: [PATCH 10/11] fix: resolve biome formatting and linting violations --- alchemy/src/cloudflare/index.ts | 1 - alchemy/src/cloudflare/sveltekit.ts | 5 +- examples/cloudflare-sveltekit/alchemy.run.ts | 29 +-- examples/cloudflare-sveltekit/package.json | 52 +++--- examples/cloudflare-sveltekit/src/app.d.ts | 2 +- examples/cloudflare-sveltekit/src/env.ts | 2 +- .../src/routes/+page.server.ts | 120 ++++++------- .../src/routes/api/+server.ts | 169 ++++++++++-------- .../src/routes/api/r2/[file]/+server.ts | 80 +++++---- examples/cloudflare-sveltekit/vite.config.ts | 6 +- 10 files changed, 241 insertions(+), 225 deletions(-) diff --git a/alchemy/src/cloudflare/index.ts b/alchemy/src/cloudflare/index.ts index 040815c0e..f2eff37d8 100644 --- a/alchemy/src/cloudflare/index.ts +++ b/alchemy/src/cloudflare/index.ts @@ -42,4 +42,3 @@ export * from "./worker.ts"; export { Workflow } from "./workflow.ts"; export * from "./wrangler.json.ts"; export * from "./zone.ts"; - diff --git a/alchemy/src/cloudflare/sveltekit.ts b/alchemy/src/cloudflare/sveltekit.ts index 6fdf898f6..407a2bc5e 100644 --- a/alchemy/src/cloudflare/sveltekit.ts +++ b/alchemy/src/cloudflare/sveltekit.ts @@ -51,13 +51,12 @@ export async function SvelteKit( id: string, props?: Partial>, ): Promise> { - if (props?.compatibilityDate) { const providedDate = new Date(props.compatibilityDate); const minDate = new Date("2024-09-23"); if (providedDate < minDate) { throw new Error( - `SvelteKit compatibility date must be >= 2024-09-23 for nodejs_compat support, got ${props.compatibilityDate}` + `SvelteKit compatibility date must be >= 2024-09-23 for nodejs_compat support, got ${props.compatibilityDate}`, ); } } @@ -70,4 +69,4 @@ export async function SvelteKit( compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])], compatibilityDate: props?.compatibilityDate, }); -} \ No newline at end of file +} diff --git a/examples/cloudflare-sveltekit/alchemy.run.ts b/examples/cloudflare-sveltekit/alchemy.run.ts index 31cb90bf4..370215f5a 100644 --- a/examples/cloudflare-sveltekit/alchemy.run.ts +++ b/examples/cloudflare-sveltekit/alchemy.run.ts @@ -18,20 +18,23 @@ const app = await alchemy("cloudflare-sveltekit", { : undefined, }); -export const website = await SvelteKit(`cloudflare-sveltekit-website${BRANCH_PREFIX}`, { - bindings: { - AUTH_STORE: await KVNamespace("AUTH_STORE", { - title: `cloudflare-sveltekit-auth-store${BRANCH_PREFIX}`, - adopt: true, - }), - STORAGE: await R2Bucket(`cloudflare-sveltekit-storage${BRANCH_PREFIX}`, { - allowPublicAccess: false, - adopt: true, - }), +export const website = await SvelteKit( + `cloudflare-sveltekit-website${BRANCH_PREFIX}`, + { + bindings: { + AUTH_STORE: await KVNamespace("AUTH_STORE", { + title: `cloudflare-sveltekit-auth-store${BRANCH_PREFIX}`, + adopt: true, + }), + STORAGE: await R2Bucket(`cloudflare-sveltekit-storage${BRANCH_PREFIX}`, { + allowPublicAccess: false, + adopt: true, + }), + }, + url: true, }, - url: true, -}); +); console.log(website.url); -await app.finalize(); \ No newline at end of file +await app.finalize(); diff --git a/examples/cloudflare-sveltekit/package.json b/examples/cloudflare-sveltekit/package.json index ef3e629e0..45cad1eff 100644 --- a/examples/cloudflare-sveltekit/package.json +++ b/examples/cloudflare-sveltekit/package.json @@ -1,28 +1,28 @@ { - "name": "cloudflare-sveltekit", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "prepare": "svelte-kit sync || echo ''", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "deploy": "bun run --env-file ../../.env ./alchemy.run.ts", - "destroy": "bun run --env-file ../../.env ./alchemy.run.ts --destroy" - }, - "devDependencies": { - "@cloudflare/workers-types": "^4.20250605.0", - "@sveltejs/adapter-auto": "^6.0.1", - "@sveltejs/adapter-cloudflare": "^7.0.3", - "@sveltejs/kit": "^2.21.2", - "@sveltejs/vite-plugin-svelte": "^5.1.0", - "alchemy": "workspace:*", - "svelte": "^5.33.14", - "svelte-check": "^4.2.1", - "typescript": "^5.8.3", - "vite": "^6.3.5" - } + "name": "cloudflare-sveltekit", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "deploy": "bun run --env-file ../../.env ./alchemy.run.ts", + "destroy": "bun run --env-file ../../.env ./alchemy.run.ts --destroy" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250605.0", + "@sveltejs/adapter-auto": "^6.0.1", + "@sveltejs/adapter-cloudflare": "^7.0.3", + "@sveltejs/kit": "^2.21.2", + "@sveltejs/vite-plugin-svelte": "^5.1.0", + "alchemy": "workspace:*", + "svelte": "^5.33.14", + "svelte-check": "^4.2.1", + "typescript": "^5.8.3", + "vite": "^6.3.5" + } } diff --git a/examples/cloudflare-sveltekit/src/app.d.ts b/examples/cloudflare-sveltekit/src/app.d.ts index 2df636cfa..07250430f 100644 --- a/examples/cloudflare-sveltekit/src/app.d.ts +++ b/examples/cloudflare-sveltekit/src/app.d.ts @@ -1,4 +1,4 @@ -import type { CloudflarePlatform } from './env'; +import type { CloudflarePlatform } from "./env.ts"; // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces diff --git a/examples/cloudflare-sveltekit/src/env.ts b/examples/cloudflare-sveltekit/src/env.ts index d6cba1cef..a1c017718 100644 --- a/examples/cloudflare-sveltekit/src/env.ts +++ b/examples/cloudflare-sveltekit/src/env.ts @@ -14,4 +14,4 @@ declare module "cloudflare:workers" { namespace Cloudflare { export interface Env extends CloudflareEnv {} } -} \ No newline at end of file +} diff --git a/examples/cloudflare-sveltekit/src/routes/+page.server.ts b/examples/cloudflare-sveltekit/src/routes/+page.server.ts index 50543aee0..656747de0 100644 --- a/examples/cloudflare-sveltekit/src/routes/+page.server.ts +++ b/examples/cloudflare-sveltekit/src/routes/+page.server.ts @@ -1,68 +1,68 @@ -import type { PageServerLoad } from './$types'; +import type { PageServerLoad } from "./$types"; async function getKvDemo(kv: any) { - if (!kv) return null; - - try { - let value = await kv.get('demo-key'); - - // Set default value if none exists - if (!value) { - const defaultData = { - message: 'Hello from Cloudflare KV!', - timestamp: new Date().toISOString() - }; - await kv.put('demo-key', JSON.stringify(defaultData)); - return defaultData; - } - - // Try parsing as JSON, fall back to string wrapper - try { - return JSON.parse(value); - } catch { - return { message: value, type: 'string', timestamp: 'Set via API' }; - } - } catch (error) { - console.error('KV error:', error); - return null; - } + if (!kv) return null; + + try { + let value = await kv.get("demo-key"); + + // Set default value if none exists + if (!value) { + const defaultData = { + message: "Hello from Cloudflare KV!", + timestamp: new Date().toISOString(), + }; + await kv.put("demo-key", JSON.stringify(defaultData)); + return defaultData; + } + + // Try parsing as JSON, fall back to string wrapper + try { + return JSON.parse(value); + } catch { + return { message: value, type: "string", timestamp: "Set via API" }; + } + } catch (error) { + console.error("KV error:", error); + return null; + } } async function getR2Info(r2: any) { - if (!r2) return null; - - try { - const { objects = [] } = await r2.list({ limit: 10 }); - return { - bucketName: 'Storage bucket connected!', - objectCount: objects.length, - objects: objects.map((obj: any) => ({ - key: obj.key, - size: obj.size, - modified: obj.uploaded - })) - }; - } catch (error) { - console.error('R2 error:', error); - return { error: 'Failed to access R2 bucket' }; - } + if (!r2) return null; + + try { + const { objects = [] } = await r2.list({ limit: 10 }); + return { + bucketName: "Storage bucket connected!", + objectCount: objects.length, + objects: objects.map((obj: any) => ({ + key: obj.key, + size: obj.size, + modified: obj.uploaded, + })), + }; + } catch (error) { + console.error("R2 error:", error); + return { error: "Failed to access R2 bucket" }; + } } export const load: PageServerLoad = async ({ platform }) => { - const env = platform?.env; - - const [kv, r2] = await Promise.all([ - getKvDemo(env?.AUTH_STORE), - getR2Info(env?.STORAGE) - ]); - - return { - kv, - r2, - platform: { - hasKV: !!env?.AUTH_STORE, - hasR2: !!env?.STORAGE, - hasContext: !!platform?.context - } - }; -}; \ No newline at end of file + const env = platform?.env; + + const [kv, r2] = await Promise.all([ + getKvDemo(env?.AUTH_STORE), + getR2Info(env?.STORAGE), + ]); + + return { + kv, + r2, + platform: { + hasKV: !!env?.AUTH_STORE, + hasR2: !!env?.STORAGE, + hasContext: !!platform?.context, + }, + }; +}; diff --git a/examples/cloudflare-sveltekit/src/routes/api/+server.ts b/examples/cloudflare-sveltekit/src/routes/api/+server.ts index dc0154156..f3b6a4c71 100644 --- a/examples/cloudflare-sveltekit/src/routes/api/+server.ts +++ b/examples/cloudflare-sveltekit/src/routes/api/+server.ts @@ -1,86 +1,99 @@ -import type { RequestEvent } from '@sveltejs/kit'; +import type { RequestEvent } from "@sveltejs/kit"; export const GET = async ({ platform }: RequestEvent) => { - try { - // Demonstrate KV access - const kvValue = await platform?.env.AUTH_STORE.get('demo-key'); - - // Demonstrate R2 list operation - const r2List = await platform?.env.STORAGE.list({ limit: 5 }); - - return new Response(JSON.stringify({ - message: 'SvelteKit + Cloudflare API working!', - kv: { - key: 'demo-key', - value: kvValue || 'No value found' - }, - r2: { - objects: r2List?.objects.map((obj: any) => ({ - key: obj.key, - size: obj.size, - uploaded: obj.uploaded - })) || [] - } - }), { - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - return new Response(JSON.stringify({ - error: 'API Error', - message: error instanceof Error ? error.message : 'Unknown error' - }), { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - }); - } + try { + // Demonstrate KV access + const kvValue = await platform?.env.AUTH_STORE.get("demo-key"); + + // Demonstrate R2 list operation + const r2List = await platform?.env.STORAGE.list({ limit: 5 }); + + return new Response( + JSON.stringify({ + message: "SvelteKit + Cloudflare API working!", + kv: { + key: "demo-key", + value: kvValue || "No value found", + }, + r2: { + objects: + r2List?.objects.map((obj: any) => ({ + key: obj.key, + size: obj.size, + uploaded: obj.uploaded, + })) || [], + }, + }), + { + headers: { + "Content-Type": "application/json", + }, + }, + ); + } catch (error) { + return new Response( + JSON.stringify({ + error: "API Error", + message: error instanceof Error ? error.message : "Unknown error", + }), + { + status: 500, + headers: { + "Content-Type": "application/json", + }, + }, + ); + } }; export const POST = async ({ request, platform }: RequestEvent) => { - try { - const body = await request.json() as { - key?: string; + try { + const body = (await request.json()) as { + key?: string; value?: string; fileContent?: string; }; - - const { key, value, fileContent } = body; - - // Demonstrate KV write - if (key && value) { - await platform?.env.AUTH_STORE.put(key, value); - } - - // Demonstrate R2 write - if (fileContent) { - const fileName = `demo-${Date.now()}.txt`; - await platform?.env.STORAGE.put(fileName, fileContent); - } - - return new Response(JSON.stringify({ - success: true, - message: 'Data stored successfully', - stored: { - kv: key ? { key, value } : null, - r2: fileContent ? `demo-${Date.now()}.txt` : null - } - }), { - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - return new Response(JSON.stringify({ - error: 'Storage Error', - message: error instanceof Error ? error.message : 'Unknown error' - }), { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - }); - } -}; \ No newline at end of file + + const { key, value, fileContent } = body; + + // Demonstrate KV write + if (key && value) { + await platform?.env.AUTH_STORE.put(key, value); + } + + // Demonstrate R2 write + if (fileContent) { + const fileName = `demo-${Date.now()}.txt`; + await platform?.env.STORAGE.put(fileName, fileContent); + } + + return new Response( + JSON.stringify({ + success: true, + message: "Data stored successfully", + stored: { + kv: key ? { key, value } : null, + r2: fileContent ? `demo-${Date.now()}.txt` : null, + }, + }), + { + headers: { + "Content-Type": "application/json", + }, + }, + ); + } catch (error) { + return new Response( + JSON.stringify({ + error: "Storage Error", + message: error instanceof Error ? error.message : "Unknown error", + }), + { + status: 500, + headers: { + "Content-Type": "application/json", + }, + }, + ); + } +}; diff --git a/examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts b/examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts index d6768fac3..296607df1 100644 --- a/examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts +++ b/examples/cloudflare-sveltekit/src/routes/api/r2/[file]/+server.ts @@ -1,42 +1,44 @@ -import type { RequestEvent } from '@sveltejs/kit'; +import type { RequestEvent } from "@sveltejs/kit"; export const GET = async ({ url, platform }: RequestEvent) => { - if (!platform?.env?.STORAGE) { - return new Response('R2 bucket not available', { status: 500 }); - } - - // Extract filename from the URL path after /api/r2/ - const pathname = url.pathname; - const filename = pathname.split('/api/r2/')[1]; - - if (!filename) { - return new Response('Filename required', { status: 400 }); - } - - try { - const object = await platform.env.STORAGE.get(filename); - - if (!object) { - return new Response('File not found', { status: 404 }); - } - - // Get the file content - const content = await object.text(); - - // Determine content type based on file extension - const contentType = filename.endsWith('.json') ? 'application/json' : - filename.endsWith('.txt') ? 'text/plain' : - 'application/octet-stream'; - - return new Response(content, { - headers: { - 'Content-Type': contentType, - 'Content-Disposition': `inline; filename="${filename}"`, - 'Cache-Control': 'public, max-age=3600' - } - }); - } catch (error) { - console.error('Error serving R2 file:', error); - return new Response('Error retrieving file', { status: 500 }); - } + if (!platform?.env?.STORAGE) { + return new Response("R2 bucket not available", { status: 500 }); + } + + // Extract filename from the URL path after /api/r2/ + const pathname = url.pathname; + const filename = pathname.split("/api/r2/")[1]; + + if (!filename) { + return new Response("Filename required", { status: 400 }); + } + + try { + const object = await platform.env.STORAGE.get(filename); + + if (!object) { + return new Response("File not found", { status: 404 }); + } + + // Get the file content + const content = await object.text(); + + // Determine content type based on file extension + const contentType = filename.endsWith(".json") + ? "application/json" + : filename.endsWith(".txt") + ? "text/plain" + : "application/octet-stream"; + + return new Response(content, { + headers: { + "Content-Type": contentType, + "Content-Disposition": `inline; filename="${filename}"`, + "Cache-Control": "public, max-age=3600", + }, + }); + } catch (error) { + console.error("Error serving R2 file:", error); + return new Response("Error retrieving file", { status: 500 }); + } }; diff --git a/examples/cloudflare-sveltekit/vite.config.ts b/examples/cloudflare-sveltekit/vite.config.ts index bbf8c7da4..80864b9de 100644 --- a/examples/cloudflare-sveltekit/vite.config.ts +++ b/examples/cloudflare-sveltekit/vite.config.ts @@ -1,6 +1,6 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vite"; export default defineConfig({ - plugins: [sveltekit()] + plugins: [sveltekit()], }); From 1c7ef1a1d41dc758b6e61deed2ae140878fff7ab Mon Sep 17 00:00:00 2001 From: Sam Goodwin Date: Sun, 8 Jun 2025 23:12:03 -0700 Subject: [PATCH 11/11] fix r2 adoption --- alchemy/src/cloudflare/pipeline.ts | 28 +++++++++++-------- bun.lock | 6 ++-- .../cloudflare-nuxt-pipeline/alchemy.run.ts | 5 ++++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/alchemy/src/cloudflare/pipeline.ts b/alchemy/src/cloudflare/pipeline.ts index ac47d9c7b..8b7b4c1a1 100644 --- a/alchemy/src/cloudflare/pipeline.ts +++ b/alchemy/src/cloudflare/pipeline.ts @@ -368,27 +368,33 @@ const PipelineResource = Resource("cloudflare::Pipeline", async function < let pipelineData: CloudflarePipelineResponse; if (this.phase === "create") { + console.log(props); // Check if we should adopt an existing pipeline - if (props.adopt) { - try { - // Try to create pipeline first - console.log("Creating new Cloudflare Pipeline:", pipelineName); - pipelineData = await createPipeline(api, pipelineName, props); - } catch (error) { - // If creation fails with 409 (conflict), adopt existing pipeline - if (error instanceof CloudflareApiError && error.status === 409) { + try { + // Try to create pipeline first + console.log("Creating new Cloudflare Pipeline:", pipelineName); + pipelineData = await createPipeline(api, pipelineName, props); + } catch (error) { + // If creation fails with 409 (conflict), adopt existing pipeline + if ( + error instanceof CloudflareApiError && + (error.status === 409 || + (error.status === 400 && + error.message.includes("Pipeline with this name already exists"))) + ) { + if (props.adopt) { console.log( "Pipeline already exists, adopting existing Cloudflare Pipeline:", pipelineName, ); pipelineData = await getPipeline(api, pipelineName); } else { - // For any other error, rethrow throw error; } + } else { + // For any other error, rethrow + throw error; } - } else { - pipelineData = await createPipeline(api, pipelineName, props); } } else { // Update operation diff --git a/bun.lock b/bun.lock index 884471c47..0d1f33539 100644 --- a/bun.lock +++ b/bun.lock @@ -1712,7 +1712,7 @@ "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], - "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "common-path-prefix": ["common-path-prefix@3.0.0", "", {}, "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="], @@ -4124,8 +4124,6 @@ "tar/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], - "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], - "unixify/normalize-path": ["normalize-path@2.1.1", "", { "dependencies": { "remove-trailing-separator": "^1.0.1" } }, "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w=="], "unplugin-vue-router/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], @@ -4146,6 +4144,8 @@ "vibe-rules/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "vibe-rules/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + "vinxi/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "vinxi/esbuild": ["esbuild@0.20.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.20.2", "@esbuild/android-arm": "0.20.2", "@esbuild/android-arm64": "0.20.2", "@esbuild/android-x64": "0.20.2", "@esbuild/darwin-arm64": "0.20.2", "@esbuild/darwin-x64": "0.20.2", "@esbuild/freebsd-arm64": "0.20.2", "@esbuild/freebsd-x64": "0.20.2", "@esbuild/linux-arm": "0.20.2", "@esbuild/linux-arm64": "0.20.2", "@esbuild/linux-ia32": "0.20.2", "@esbuild/linux-loong64": "0.20.2", "@esbuild/linux-mips64el": "0.20.2", "@esbuild/linux-ppc64": "0.20.2", "@esbuild/linux-riscv64": "0.20.2", "@esbuild/linux-s390x": "0.20.2", "@esbuild/linux-x64": "0.20.2", "@esbuild/netbsd-x64": "0.20.2", "@esbuild/openbsd-x64": "0.20.2", "@esbuild/sunos-x64": "0.20.2", "@esbuild/win32-arm64": "0.20.2", "@esbuild/win32-ia32": "0.20.2", "@esbuild/win32-x64": "0.20.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g=="], diff --git a/examples/cloudflare-nuxt-pipeline/alchemy.run.ts b/examples/cloudflare-nuxt-pipeline/alchemy.run.ts index d6e8206c7..f8529d0f5 100644 --- a/examples/cloudflare-nuxt-pipeline/alchemy.run.ts +++ b/examples/cloudflare-nuxt-pipeline/alchemy.run.ts @@ -16,11 +16,15 @@ const app = await alchemy("cloudflare-nuxt-pipeline", { const bucket = await R2Bucket( `cloudflare-nuxt-pipeline-bucket${BRANCH_PREFIX}`, + { + adopt: true, + }, ); const pipeline = await Pipeline( `cloudflare-nuxt-pipeline-pipeline${BRANCH_PREFIX}`, { + adopt: true, source: [{ type: "binding", format: "json" }], destination: { type: "r2", @@ -45,6 +49,7 @@ const pipeline = await Pipeline( export const website = await Nuxt( `cloudflare-nuxt-pipeline-website${BRANCH_PREFIX}`, { + adopt: true, bindings: { R2_BUCKET: bucket, PIPELINE: pipeline,