From f826a8190c70de72b2a5dd1465f85872b57c69f6 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:01:14 -0700 Subject: [PATCH 01/19] remove cloudflare `platform`, emulate the `cloudflare:workers` module instead --- .changeset/fuzzy-kings-sit.md | 6 + packages/adapter-cloudflare/ambient.d.ts | 17 -- packages/adapter-cloudflare/globals.d.ts | 6 + packages/adapter-cloudflare/index.d.ts | 1 - packages/adapter-cloudflare/index.js | 156 ++++++++++++------ packages/adapter-cloudflare/package.json | 6 +- .../src/virtual-cloudflare-workers.js | 149 +++++++++++++++++ .../test/apps/workers/.gitignore | 1 + .../test/apps/workers/config/wrangler.jsonc | 3 + .../test/apps/workers/package.json | 1 + .../apps/workers/src/routes/ctx/+server.js | 6 +- .../workers/src/routes/prerender/+server.js | 12 ++ .../test/apps/workers/test/test.js | 10 +- packages/adapter-cloudflare/tsconfig.json | 10 +- pnpm-lock.yaml | 6 + 15 files changed, 310 insertions(+), 80 deletions(-) create mode 100644 .changeset/fuzzy-kings-sit.md delete mode 100644 packages/adapter-cloudflare/ambient.d.ts create mode 100644 packages/adapter-cloudflare/globals.d.ts create mode 100644 packages/adapter-cloudflare/src/virtual-cloudflare-workers.js create mode 100644 packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js diff --git a/.changeset/fuzzy-kings-sit.md b/.changeset/fuzzy-kings-sit.md new file mode 100644 index 000000000000..e08a9fad8bce --- /dev/null +++ b/.changeset/fuzzy-kings-sit.md @@ -0,0 +1,6 @@ +--- +"@sveltejs/adapter-cloudflare": major +--- + +breaking: remove cloudflare `platform`, emulate the `cloudflare:workers` module instead + \ No newline at end of file diff --git a/packages/adapter-cloudflare/ambient.d.ts b/packages/adapter-cloudflare/ambient.d.ts deleted file mode 100644 index d3c19971446c..000000000000 --- a/packages/adapter-cloudflare/ambient.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - CacheStorage, - IncomingRequestCfProperties, - ExecutionContext -} from '@cloudflare/workers-types'; - -declare global { - namespace App { - export interface Platform { - // we should not type `env` here because it will override any type set by - // the user in `src/app.d.ts` - ctx: ExecutionContext; - caches: CacheStorage; - cf?: IncomingRequestCfProperties; - } - } -} diff --git a/packages/adapter-cloudflare/globals.d.ts b/packages/adapter-cloudflare/globals.d.ts new file mode 100644 index 000000000000..a3ffd17c0f3a --- /dev/null +++ b/packages/adapter-cloudflare/globals.d.ts @@ -0,0 +1,6 @@ +import type { PlatformProxy, CacheStorage } from 'wrangler'; + +declare global { + var __platform_proxy: PlatformProxy; + var caches: CacheStorage; +} diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index b9062094bd08..9c7266f124e0 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -1,5 +1,4 @@ import { Adapter } from '@sveltejs/kit'; -import './ambient.js'; import { GetPlatformProxyOptions } from 'wrangler'; export default function plugin(options?: AdapterOptions): Adapter; diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index d137538e7194..21e94e058cf3 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -1,4 +1,7 @@ -import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +/** @import { Plugin } from 'vite' */ +/** @import { GetPlatformProxyOptions } from 'wrangler' */ + +import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; @@ -10,27 +13,34 @@ import { parse_redirects, append_headers } from './utils.js'; +import { exactRegex } from '@rolldown/pluginutils'; const name = '@sveltejs/adapter-cloudflare'; /** @type {typeof import('./index.js').default} */ export default function (options = {}) { + // Add a random query so we can reliably string-replace the stub + const stub_import = + import.meta.resolve('./src/virtual-cloudflare-workers.js') + '?' + crypto.randomUUID(); return { name, async adapt(builder) { - if (existsSync('_routes.json') || existsSync(`${builder.config.files.assets}/_routes.json`)) { + if ( + fs.existsSync('_routes.json') || + fs.existsSync(`${builder.config.files.assets}/_routes.json`) + ) { throw new Error( "Cloudflare Pages' _routes.json should be configured from the adapter option of the SvelteKit plugin in your vite.config.js. See https://svelte.dev/docs/kit/adapter-cloudflare#Options-routes" ); } - if (existsSync(`${builder.config.files.assets}/_headers`)) { + if (fs.existsSync(`${builder.config.files.assets}/_headers`)) { throw new Error( `The _headers file should be placed in the project root rather than the ${builder.config.files.assets} directory` ); } - if (existsSync(`${builder.config.files.assets}/_redirects`)) { + if (fs.existsSync(`${builder.config.files.assets}/_redirects`)) { throw new Error( `The _redirects file should be placed in the project root rather than the ${builder.config.files.assets} directory` ); @@ -69,15 +79,17 @@ export default function (options = {}) { const files = fileURLToPath(new URL('./files', import.meta.url).href); const tmp = builder.getBuildDirectory('cloudflare-tmp'); - rmSync(dest, { force: true, recursive: true }); - rmSync(worker_dest, { force: true, recursive: true }); + fs.rmSync(dest, { force: true, recursive: true }); + fs.rmSync(worker_dest, { force: true, recursive: true }); + + fs.mkdirSync(dest, { recursive: true }); + fs.mkdirSync(tmp, { recursive: true }); - mkdirSync(dest, { recursive: true }); - mkdirSync(tmp, { recursive: true }); + replace_stub(builder.getServerDirectory(), stub_import); // client assets and prerendered pages const assets_dest = `${dest}${builder.config.paths.base}`; - mkdirSync(assets_dest, { recursive: true }); + fs.mkdirSync(assets_dest, { recursive: true }); if ( building_for_cloudflare_pages || wrangler_config.assets?.not_found_handling === '404-page' @@ -89,7 +101,7 @@ export default function (options = {}) { if (options.fallback === 'spa') { await builder.generateFallback(fallback); } else { - writeFileSync(fallback, 'Not Found'); + fs.writeFileSync(fallback, 'Not Found'); } } const client_assets = builder.writeClient(assets_dest); @@ -103,7 +115,7 @@ export default function (options = {}) { // worker const worker_dest_dir = path.dirname(worker_dest); - writeFileSync( + fs.writeFileSync( `${tmp}/manifest.js`, `export const manifest = ${builder.generateManifest({ relativePath: path.posix.relative(tmp, builder.getServerDirectory()) })};\n\n` + `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n\n` + @@ -133,19 +145,19 @@ export default function (options = {}) { const headers_dest = `${dest}/_headers`; /** @type {string | undefined} */ let headers; - if (existsSync(headers_src)) { - headers = readFileSync(headers_src, 'utf-8'); + if (fs.existsSync(headers_src)) { + headers = fs.readFileSync(headers_src, 'utf-8'); } - writeFileSync(headers_dest, generate_headers(builder.getAppPath(), headers)); + fs.writeFileSync(headers_dest, generate_headers(builder.getAppPath(), headers)); // _redirects const redirects_src = '_redirects'; const redirects_dest = `${dest}/_redirects`; - if (existsSync(redirects_src)) { - copyFileSync(redirects_src, redirects_dest); + if (fs.existsSync(redirects_src)) { + fs.copyFileSync(redirects_src, redirects_dest); } if (builder.prerendered.redirects.size > 0) { - writeFileSync(redirects_dest, generate_redirects(builder.prerendered.redirects), { + fs.writeFileSync(redirects_dest, generate_redirects(builder.prerendered.redirects), { flag: 'a' }); } @@ -158,12 +170,12 @@ export default function (options = {}) { // Worker but instead let the rules in the `_redirects` file take over. /** @type {string[]} */ let redirects = []; - if (existsSync(redirects_dest)) { - const redirect_rules = readFileSync(redirects_dest, 'utf8'); + if (fs.existsSync(redirects_dest)) { + const redirect_rules = fs.readFileSync(redirects_dest, 'utf8'); redirects = parse_redirects(redirect_rules); } - writeFileSync( + fs.writeFileSync( `${dest}/_routes.json`, JSON.stringify( get_routes_json(builder, client_assets, redirects, options.routes ?? {}), @@ -172,46 +184,59 @@ export default function (options = {}) { ) ); } else { - writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); + fs.writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); } }, - emulate() { - // we want to invoke `getPlatformProxy` only once, but await it only when it is accessed. - // If we would await it here, it would hang indefinitely because the platform proxy only resolves once a request happens - const get_emulated = async () => { - const proxy = await getPlatformProxy(options.platformProxy); - const platform = { - env: proxy.env, - ctx: proxy.ctx, - context: proxy.ctx, // deprecated in favor of ctx - caches: proxy.caches, - cf: proxy.cf - }; - /** @type {Record} */ - const env = {}; - const prerender_platform = /** @type {App.Platform} */ (/** @type {unknown} */ ({ env })); - for (const key in proxy.env) { - Object.defineProperty(env, key, { - get: () => { - throw new Error(`Cannot access platform.env.${key} in a prerenderable route`); - } - }); - } - return { platform, prerender_platform }; - }; - - let emulated; - - return { - platform: async ({ prerender }) => { - emulated ??= await get_emulated(); - return prerender ? emulated.prerender_platform : emulated.platform; - } - }; - }, supports: { read: () => true, instrumentation: () => true + }, + vite: { + plugins: { + pre: [ + virtual_workers_module( + { + configPath: options.config, + ...options.platformProxy + }, + stub_import + ) + ] + } + } + }; +} + +/** + * @param {GetPlatformProxyOptions} options + * @param {string} stub_import + * @returns {Plugin} + */ +function virtual_workers_module(options, stub_import) { + const setup = async () => { + if (globalThis.__platform_proxy) return; + const proxy = await getPlatformProxy(options); + globalThis.__platform_proxy = proxy; + globalThis.caches = proxy.caches; + }; + return { + name: 'vite-plugin-adapter-cloudflare-virtual-workers-module', + buildStart: setup, + buildEnd: () => { + void globalThis.__platform_proxy?.dispose(); + // @ts-expect-error + globalThis.__platform_proxy = undefined; + }, + configureServer: setup, + configurePreviewServer: setup, + resolveId: { + filter: { id: exactRegex('cloudflare:workers') }, + handler() { + return { + id: stub_import, + external: true + }; + } } }; } @@ -295,3 +320,24 @@ function validate_wrangler_config(config_file = undefined) { function posixify(str) { return str.replace(/\\/g, '/'); } + +/** + * + * @param {string} directory + * @param {string} stub_import + */ +function replace_stub(directory, stub_import) { + // recurse, find stub_import, replace with "cloudflare:workers" + const files = fs.readdirSync(directory); + for (const file of files) { + const file_path = path.join(directory, file); + if (fs.statSync(file_path).isDirectory()) { + replace_stub(file_path, stub_import); + } else { + const contents = fs.readFileSync(file_path, 'utf8'); + if (contents.includes(stub_import)) { + fs.writeFileSync(file_path, contents.replaceAll(stub_import, 'cloudflare:workers')); + } + } + } +} diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 4ad741a03261..1188f9b4528d 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -27,11 +27,11 @@ }, "types": "index.d.ts", "files": [ + "src/virtual-cloudflare-workers.js", "files/worker.js", "index.js", "utils.js", - "index.d.ts", - "ambient.d.ts" + "index.d.ts" ], "scripts": { "lint": "oxfmt --check .", @@ -46,9 +46,11 @@ }, "devDependencies": { "@playwright/test": "catalog:", + "@rolldown/pluginutils": "^1.0.1", "@sveltejs/kit": "workspace:^", "@types/node": "catalog:", "typescript": "catalog:typescript-native", + "vite": "catalog:", "vitest": "catalog:" }, "peerDependencies": { diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js new file mode 100644 index 000000000000..7be779d9be34 --- /dev/null +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -0,0 +1,149 @@ +import { AsyncLocalStorage } from 'node:async_hooks'; +/** @type {boolean} */ +let prerendering; +try { + const { prerendering: prerendering_value } = await import('$app/env/internal'); + console.log(prerendering_value); + prerendering = prerendering_value; +} catch { + // this will throw during analysis, which is when prerendering happens + prerendering = true; +} + +const als = new AsyncLocalStorage(); + +const proxy = globalThis.__platform_proxy; + +function get_current_env() { + return als.getStore() ?? proxy.env; +} + +export const env = new Proxy( + {}, + { + get(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.get(inner, prop); + } + return undefined; + }, + + set(_, prop, newValue) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.set(inner, prop, newValue); + } + return true; + }, + + has(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.has(inner, prop); + } + return false; + }, + + ownKeys(_) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.ownKeys(inner); + } + return []; + }, + + deleteProperty(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.deleteProperty(inner, prop); + } + return true; + }, + + defineProperty(_, prop, attr) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.defineProperty(inner, prop, attr); + } + return true; + }, + + getOwnPropertyDescriptor(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.getOwnPropertyDescriptor(inner, prop); + } + return undefined; + } + } +); + +/** + * @param {unknown} newEnv + * @param {() => unknown} fn + */ +export function withEnv(newEnv, fn) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + return als.run(newEnv, fn); +} +/** + * @param {unknown} newEnv + * @param {unknown} newExports + * @param {() => unknown} fn + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function withEnvAndExports(newEnv, newExports, fn) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + return als.run(newEnv, fn); +} + +// no-ops +export const exports = new Proxy( + {}, + { + get() { + throw new Error('exports is not available in dev mode'); + }, + has() { + throw new Error('exports is not available in dev mode'); + } + } +); +export function waitUntil() {} +export const cache = { + purge() {} +}; +export const tracing = { + enterSpan() { + throw new Error('tracing is not available in dev mode'); + }, + startActiveSpan() { + throw new Error('tracing is not available in dev mode'); + } +}; diff --git a/packages/adapter-cloudflare/test/apps/workers/.gitignore b/packages/adapter-cloudflare/test/apps/workers/.gitignore index de2e15da78f4..63d9a515f164 100644 --- a/packages/adapter-cloudflare/test/apps/workers/.gitignore +++ b/packages/adapter-cloudflare/test/apps/workers/.gitignore @@ -5,3 +5,4 @@ node_modules # Cloudflare .wrangler /dist +worker-configuration.d.ts diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc index 8c2d83f8c21a..372a381e9b61 100644 --- a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -7,5 +7,8 @@ "assets": { "directory": "../dist/public", "binding": "ASSETS" + }, + "vars": { + "FOO": "from wrangler.jsonc" } } diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index c59a5adc2774..51b3622abe70 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -6,6 +6,7 @@ "dev": "vite dev", "build": "vite build", "preview": "wrangler dev dist/index.js --config config/wrangler.jsonc", + "prepare": "wrangler types --config config/wrangler.jsonc || echo ''", "test:dev": "DEV=true playwright test", "test:build": "playwright test", "test": "pnpm test:dev && pnpm test:build" diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js index e26ae2cbbd71..b21afacc72d4 100644 --- a/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js @@ -1,3 +1,5 @@ -export function GET({ platform }) { - return new Response(platform?.ctx.waitUntil ? 'ctx works' : 'ctx does not work'); +import { env } from 'cloudflare:workers'; + +export function GET() { + return new Response(env.FOO); } diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js new file mode 100644 index 000000000000..0bc773d04e72 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js @@ -0,0 +1,12 @@ +import { env } from 'cloudflare:workers'; + +export function GET() { + try { + env.FOO; + return new Response('no error'); + } catch (e) { + return new Response(String(e)); + } +} + +export const prerender = true; diff --git a/packages/adapter-cloudflare/test/apps/workers/test/test.js b/packages/adapter-cloudflare/test/apps/workers/test/test.js index 44832c70890f..c692d2d7ba98 100644 --- a/packages/adapter-cloudflare/test/apps/workers/test/test.js +++ b/packages/adapter-cloudflare/test/apps/workers/test/test.js @@ -7,9 +7,9 @@ test('worker', async ({ page }) => { await expect(page.locator('h1')).toContainText('Sum: 3'); }); -test('ctx', async ({ request }) => { +test('cloudflare:workers', async ({ request }) => { const res = await request.get('/ctx'); - expect(await res.text()).toBe('ctx works'); + expect(await res.text()).toBe('from wrangler.jsonc'); }); test('read from $app/server works', async ({ request }) => { @@ -20,3 +20,9 @@ test('read from $app/server works', async ({ request }) => { const response = await request.get('/read'); expect(await response.text()).toBe(content); }); + +test('prerendering throws', async ({ request }) => { + if (process.env.DEV) return; + const res = await request.get('/prerender'); + expect(await res.text()).toContain('Cannot access cloudflare:workers in a prerenderable route'); +}); diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index ce628354cbad..2e16130e2b76 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -15,5 +15,13 @@ "lib": ["es2024"], "types": ["node"] }, - "include": ["index.js", "utils.js", "utils.spec.js", "vitest.config.js", "test/utils.js"] + "include": [ + "index.js", + "src", + "utils.js", + "utils.spec.js", + "vitest.config.js", + "globals.d.ts", + "test/utils.js" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb600534f2a7..f6f1bfe53dff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,6 +154,9 @@ importers: '@playwright/test': specifier: 'catalog:' version: 1.62.1 + '@rolldown/pluginutils': + specifier: ^1.0.1 + version: 1.0.1 '@sveltejs/kit': specifier: workspace:^ version: link:../kit @@ -163,6 +166,9 @@ importers: typescript: specifier: catalog:typescript-native version: 7.0.2 + vite: + specifier: 'catalog:' + version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) vitest: specifier: 'catalog:' version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@4.1.10)(jsdom@30.0.1)(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)) From d30f563601de500184a458492aef3afada280284 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:31:03 -0700 Subject: [PATCH 02/19] support Request.cf --- packages/adapter-cloudflare/index.js | 8 ++++++++ .../test/apps/workers/src/routes/cf/+server.js | 3 +++ .../test/apps/workers/src/routes/{ctx => env}/+server.js | 0 .../adapter-cloudflare/test/apps/workers/test/test.js | 8 +++++++- 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js rename packages/adapter-cloudflare/test/apps/workers/src/routes/{ctx => env}/+server.js (100%) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 21e94e058cf3..9cd9f752c8eb 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -14,6 +14,7 @@ import { append_headers } from './utils.js'; import { exactRegex } from '@rolldown/pluginutils'; +import { getRequest } from '@sveltejs/kit/node'; const name = '@sveltejs/adapter-cloudflare'; @@ -191,6 +192,13 @@ export default function (options = {}) { read: () => true, instrumentation: () => true }, + getRequest(options) { + const request = getRequest(options); + /** @type {import('@cloudflare/workers-types').Request} */ ( + /** @type {unknown} */ (request) + ).cf = globalThis.__platform_proxy?.cf; + return request; + }, vite: { plugins: { pre: [ diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js new file mode 100644 index 000000000000..677e42171f0e --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js @@ -0,0 +1,3 @@ +export function GET({ request }) { + return Response.json(request.cf); +} diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/env/+server.js similarity index 100% rename from packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js rename to packages/adapter-cloudflare/test/apps/workers/src/routes/env/+server.js diff --git a/packages/adapter-cloudflare/test/apps/workers/test/test.js b/packages/adapter-cloudflare/test/apps/workers/test/test.js index c692d2d7ba98..8b3ba355fb55 100644 --- a/packages/adapter-cloudflare/test/apps/workers/test/test.js +++ b/packages/adapter-cloudflare/test/apps/workers/test/test.js @@ -8,10 +8,16 @@ test('worker', async ({ page }) => { }); test('cloudflare:workers', async ({ request }) => { - const res = await request.get('/ctx'); + const res = await request.get('/env'); expect(await res.text()).toBe('from wrangler.jsonc'); }); +test('Request.cf', async ({ request }) => { + const res = await request.get('cf'); + const cf = await res.json(); + expect(cf.colo).toBeDefined(); +}); + test('read from $app/server works', async ({ request }) => { const content = fs.readFileSync( path.resolve(import.meta.dirname, '../src/routes/read/file.txt'), From 0fe88311856d005506414ef5786f0e2682698253 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:46:45 -0700 Subject: [PATCH 03/19] fix ai stuff --- packages/adapter-cloudflare/package.json | 4 ++-- .../adapter-cloudflare/src/virtual-cloudflare-workers.js | 2 +- pnpm-lock.yaml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 1188f9b4528d..44a952913abc 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -42,11 +42,11 @@ "test": "pnpm test:unit && pnpm test:e2e" }, "dependencies": { - "@cloudflare/workers-types": "^5.20260809.1" + "@cloudflare/workers-types": "^5.20260809.1", + "@rolldown/pluginutils": "^1.0.1" }, "devDependencies": { "@playwright/test": "catalog:", - "@rolldown/pluginutils": "^1.0.1", "@sveltejs/kit": "workspace:^", "@types/node": "catalog:", "typescript": "catalog:typescript-native", diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index 7be779d9be34..f1cbb1f53c5d 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -2,8 +2,8 @@ import { AsyncLocalStorage } from 'node:async_hooks'; /** @type {boolean} */ let prerendering; try { + // @ts-expect-error internal types are not published const { prerendering: prerendering_value } = await import('$app/env/internal'); - console.log(prerendering_value); prerendering = prerendering_value; } catch { // this will throw during analysis, which is when prerendering happens diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6f1bfe53dff..80cf0c2312ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,6 +147,9 @@ importers: '@cloudflare/workers-types': specifier: ^5.20260809.1 version: 5.20260809.1 + '@rolldown/pluginutils': + specifier: ^1.0.1 + version: 1.0.1 wrangler: specifier: ^4.118.0 version: 4.120.1(@cloudflare/workers-types@5.20260809.1) @@ -154,9 +157,6 @@ importers: '@playwright/test': specifier: 'catalog:' version: 1.62.1 - '@rolldown/pluginutils': - specifier: ^1.0.1 - version: 1.0.1 '@sveltejs/kit': specifier: workspace:^ version: link:../kit From 027905567cad1f9d5aa8904a6a7eea5a5286c3a6 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:30:34 -0700 Subject: [PATCH 04/19] don't run proxy during build --- packages/adapter-cloudflare/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 9cd9f752c8eb..3498a3f43990 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -229,12 +229,6 @@ function virtual_workers_module(options, stub_import) { }; return { name: 'vite-plugin-adapter-cloudflare-virtual-workers-module', - buildStart: setup, - buildEnd: () => { - void globalThis.__platform_proxy?.dispose(); - // @ts-expect-error - globalThis.__platform_proxy = undefined; - }, configureServer: setup, configurePreviewServer: setup, resolveId: { From 89b459c3e2cd055c7066d7588afdbcb65509330f Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:54:32 -0700 Subject: [PATCH 05/19] remove platform from built worker --- packages/adapter-cloudflare/files/worker.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/adapter-cloudflare/files/worker.js b/packages/adapter-cloudflare/files/worker.js index 911af0cfb971..2f14adde2038 100644 --- a/packages/adapter-cloudflare/files/worker.js +++ b/packages/adapter-cloudflare/files/worker.js @@ -37,10 +37,9 @@ export default { /** * @param {Request} req * @param {{ ASSETS: { fetch: typeof fetch } }} env - * @param {ExecutionContext} ctx * @returns {Promise} */ - async fetch(req, env, ctx) { + async fetch(req, env) { if (!origin) { origin = new URL(req.url).origin; } @@ -96,12 +95,6 @@ export default { // dynamically-generated pages return await server.respond(req, { - platform: { - env, - ctx, - caches, - cf: req.cf - }, getClientAddress() { return /** @type {string} */ (req.headers.get('cf-connecting-ip')); } From db0486165c62598513959fd72ff631ce4bec2653 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:54:37 -0700 Subject: [PATCH 06/19] update docs --- .../60-adapter-cloudflare.md | 48 +++++-------------- .../35-migrating-to-sveltekit-3.md | 36 +++++++++++++- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index a7f16b855eae..c607808005d4 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -130,62 +130,36 @@ Functions contained in the [`/functions` directory](https://developers.cloudflar ## Runtime APIs -The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties), meaning that you can access it in hooks and endpoints: +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is available on the [`cloudflare:workers`](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) module. ```js // @filename: ambient.d.ts import { DurableObjectNamespace } from '@cloudflare/workers-types'; -declare global { - namespace App { - interface Platform { - env: { - YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; - }; - } - } +declare module 'cloudflare:workers' { + export const env: { + YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + }; } // @filename: +server.js // ---cut--- // @errors: 2355 2322 /// file: +server.js +import { env } from 'cloudflare:workers'; + /** @type {import('./$types').RequestHandler} */ -export async function POST({ request, platform }) { - const x = platform?.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); +export async function POST() { + const x = env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` > [!NOTE] SvelteKit's built-in [`$app/env/*` modules](environment-variables) should be preferred for environment variables. -To make these types available to your app, install [`wrangler`](https://www.npmjs.com/package/wrangler), run [`wrangler types`](https://developers.cloudflare.com/workers/languages/typescript/), and reference them in your `src/app.d.ts`: - -```ts -// @filename: ambient.d.ts -import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types'; - -namespace Cloudflare { - export interface Env { - YOUR_KV_NAMESPACE: KVNamespace; - YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; - } -} -/// file: src/app.d.ts -// ---cut--- - -declare global { - namespace App { - interface Platform { -+++ env: Cloudflare.Env;+++ - } - } -} - -export {}; -``` +To make these types available to your app, install [`wrangler`](https://www.npmjs.com/package/wrangler) and run [`wrangler types`](https://developers.cloudflare.com/workers/languages/typescript/). ### Testing locally -Cloudflare specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. +Cloudflare specific values are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. > [!NOTE] [Durable Objects](https://developers.cloudflare.com/durable-objects/) and [Workflows](https://developers.cloudflare.com/workflows/), which require custom classes to be exported from your worker, are not currently supported. diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index 8751bc616187..3b2abd24d3a0 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -453,9 +453,43 @@ All first-party adapters now require SvelteKit 3, alongside these adapter-specif ### `adapter-cloudflare` +Cloudflare-specific APIs are no longer available on `platform`. Instead, find them where you would expect on a Cloudflare worker: + +- `env`, `ctx.waitUntil`, and other `ctx` properties should be imported from `cloudflare:workers`: +```js +// @filename: ambient.d.ts +declare module 'cloudflare:workers' { + export const env: { KV: { get(): Promise } }; + export function waitUntil(promise: Promise): void; +} +// ---cut--- +import { env, waitUntil } from 'cloudflare:workers'; + +const value = await env.KV.get('key'); +``` +- `cf` is now a property of the `Request` object: +```js +/// file: src/routes/cf/+server.js +export async function GET({ request }) { + const { country } = request.cf; +} +``` +- `caches` is now a global variable: +```js +/// file: src/routes/cache/+server.js +const caches = { + async open(name) { + return { match(request) {} } + } +}; +let request = new Request(); +// ---cut--- +const cache = caches.open('foo'); +await myCache.match(request); +``` + - minimum `wrangler` is now `^4.67.0` - `@cloudflare/workers-types` upgraded -- `platform.context` removed in favour of `platform.ctx` ### `adapter-node` From 3e4f9279492641004fcf7143c0c26a51b0a6266d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 13 Aug 2026 14:23:07 -0700 Subject: [PATCH 07/19] fix lockfile? --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80cf0c2312ab..44ef54d07a38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,7 +168,7 @@ importers: version: 7.0.2 vite: specifier: 'catalog:' - version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) + version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) vitest: specifier: 'catalog:' version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@4.1.10)(jsdom@30.0.1)(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)) From b57537ae61102998bb3e9ccb0de26c376a817597 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 13 Aug 2026 14:23:45 -0700 Subject: [PATCH 08/19] typo in docs --- documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index 3b2abd24d3a0..0af2d2512f9d 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -484,7 +484,7 @@ const caches = { }; let request = new Request(); // ---cut--- -const cache = caches.open('foo'); +const myCache = caches.open('foo'); await myCache.match(request); ``` From 631670b467a7b904e6fe787289776e3146a93a4d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 13:31:35 -0700 Subject: [PATCH 09/19] add dev/preview comment --- packages/kit/src/exports/public.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index eecdde2b0856..e67156d1acbe 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -54,14 +54,14 @@ export interface Adapter { instrumentation?: () => boolean; }; /** - * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. - * To call the original setRequest function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to convert an `http.IncomingMessage` + * to a `Request` object. To call the original setRequest function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior to write a `Response` object to an `http.ServerResponse`. - * To call the original setResponse function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ setResponse?: typeof setResponse; From a4435f7208c764aad0879c24093fef237402fe02 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 13:44:42 -0700 Subject: [PATCH 10/19] types --- packages/kit/types/index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index a1c01f07ef32..a5543fe29dcc 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -40,14 +40,14 @@ declare module '@sveltejs/kit' { instrumentation?: () => boolean; }; /** - * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. - * To call the original setRequest function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to convert an `http.IncomingMessage` + * to a `Request` object. To call the original setRequest function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior to write a `Response` object to an `http.ServerResponse`. - * To call the original setResponse function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ setResponse?: typeof setResponse; From 834de965c80bc44f2b116bcc74b22a0d73f8d232 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 16:38:05 -0700 Subject: [PATCH 11/19] apply comments --- .../25-build-and-deploy/60-adapter-cloudflare.md | 6 +++--- .../60-appendix/35-migrating-to-sveltekit-3.md | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index c607808005d4..5e745a04fe85 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -4,7 +4,7 @@ title: Cloudflare To deploy to [Cloudflare Workers](https://workers.cloudflare.com/) or [Cloudflare Pages](https://pages.cloudflare.com/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare). -This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that `event.platform` is emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. +This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that `cloudflare:workers` is emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. ## Comparisons @@ -53,7 +53,7 @@ Path to your [Wrangler configuration file](https://developers.cloudflare.com/wor ### platformProxy -Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. +Preferences for the emulated `env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. ### fallback @@ -159,7 +159,7 @@ To make these types available to your app, install [`wrangler`](https://www.npmj ### Testing locally -Cloudflare specific values are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. +Cloudflare specific values are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. > [!NOTE] [Durable Objects](https://developers.cloudflare.com/durable-objects/) and [Workflows](https://developers.cloudflare.com/workflows/), which require custom classes to be exported from your worker, are not currently supported. diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index 0af2d2512f9d..39cc4b758aa8 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -470,6 +470,12 @@ const value = await env.KV.get('key'); - `cf` is now a property of the `Request` object: ```js /// file: src/routes/cf/+server.js +// @filename: ambient.d.ts +interface Request { + cf: import('@cloudflare/workers-types').IncomingRequestCfProperties; +} +// @filename: index.js +// ---cut--- export async function GET({ request }) { const { country } = request.cf; } @@ -477,14 +483,9 @@ export async function GET({ request }) { - `caches` is now a global variable: ```js /// file: src/routes/cache/+server.js -const caches = { - async open(name) { - return { match(request) {} } - } -}; -let request = new Request(); +let request = new Request(''); // ---cut--- -const myCache = caches.open('foo'); +const myCache = await caches.open('foo'); await myCache.match(request); ``` From bdab7bf093b5adcdf7c1a981f15828d3530f6ecc Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 16:38:14 -0700 Subject: [PATCH 12/19] mock tracing --- .../src/virtual-cloudflare-workers.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index f1cbb1f53c5d..a87f9abeb454 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -139,11 +139,20 @@ export function waitUntil() {} export const cache = { purge() {} }; +class Span { + get isTraced() { return false; } + setAttribute() {} + end() {} + } +/** @type {import('@cloudflare/workers-types').Tracing} */ export const tracing = { - enterSpan() { - throw new Error('tracing is not available in dev mode'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + enterSpan(name, callback, ...args) { + return callback(new Span(), ...args); }, - startActiveSpan() { - throw new Error('tracing is not available in dev mode'); - } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + startActiveSpan(name, callback, ...args) { + return callback(new Span(), ...args); + }, + Span, }; From 5c09724ba5d48fd8cb60d0420ff411d107e591d5 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 21:15:45 -0700 Subject: [PATCH 13/19] type safety for virtual module --- .../src/virtual-cloudflare-workers.js | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index a87f9abeb454..64b0d8a91d4a 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -18,6 +18,8 @@ function get_current_env() { return als.getStore() ?? proxy.env; } +/** @typedef {import('@cloudflare/workers-types').CloudflareWorkersModule} Module */ + export const env = new Proxy( {}, { @@ -100,23 +102,15 @@ export const env = new Proxy( } ); -/** - * @param {unknown} newEnv - * @param {() => unknown} fn - */ +/** @type {Module['withEnv']} */ export function withEnv(newEnv, fn) { if (prerendering) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } return als.run(newEnv, fn); } -/** - * @param {unknown} newEnv - * @param {unknown} newExports - * @param {() => unknown} fn - */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export function withEnvAndExports(newEnv, newExports, fn) { +/** @type {Module['withEnvAndExports']} */ +export function withEnvAndExports(newEnv, _, fn) { if (prerendering) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } @@ -135,24 +129,36 @@ export const exports = new Proxy( } } ); +/** @type {Module['waitUntil']} */ export function waitUntil() {} +/** @type {Module['cache']} */ export const cache = { - purge() {} + purge() { + return Promise.resolve({ success: true, errors: [] }); + } }; class Span { - get isTraced() { return false; } - setAttribute() {} - end() {} + get isTraced() { + return false; + } + setAttribute() { + return this; } + setAttributes() { + return this; + } + end() {} +} /** @type {import('@cloudflare/workers-types').Tracing} */ export const tracing = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - enterSpan(name, callback, ...args) { + enterSpan(_, callback, ...args) { return callback(new Span(), ...args); }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - startActiveSpan(name, callback, ...args) { + startActiveSpan(_, callback, ...args) { return callback(new Span(), ...args); }, - Span, + startSpan(_) { + return new Span(); + }, + Span }; From f57f0420021241f9e926f76d322fa28b1238f78b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 21:16:26 -0700 Subject: [PATCH 14/19] use global proxy's existance as prerendering signal --- .../src/virtual-cloudflare-workers.js | 28 ++++++------------- packages/kit/src/exports/public.d.ts | 2 +- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index 64b0d8a91d4a..dba4342df17e 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -1,14 +1,4 @@ import { AsyncLocalStorage } from 'node:async_hooks'; -/** @type {boolean} */ -let prerendering; -try { - // @ts-expect-error internal types are not published - const { prerendering: prerendering_value } = await import('$app/env/internal'); - prerendering = prerendering_value; -} catch { - // this will throw during analysis, which is when prerendering happens - prerendering = true; -} const als = new AsyncLocalStorage(); @@ -24,7 +14,7 @@ export const env = new Proxy( {}, { get(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -35,7 +25,7 @@ export const env = new Proxy( }, set(_, prop, newValue) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -46,7 +36,7 @@ export const env = new Proxy( }, has(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -57,7 +47,7 @@ export const env = new Proxy( }, ownKeys(_) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -68,7 +58,7 @@ export const env = new Proxy( }, deleteProperty(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -79,7 +69,7 @@ export const env = new Proxy( }, defineProperty(_, prop, attr) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -90,7 +80,7 @@ export const env = new Proxy( }, getOwnPropertyDescriptor(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -104,14 +94,14 @@ export const env = new Proxy( /** @type {Module['withEnv']} */ export function withEnv(newEnv, fn) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } return als.run(newEnv, fn); } /** @type {Module['withEnvAndExports']} */ export function withEnvAndExports(newEnv, _, fn) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } return als.run(newEnv, fn); diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index e67156d1acbe..fbf84c2134c1 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -60,7 +60,7 @@ export interface Adapter { */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ From 6c2d41f74f74e322e8b544132ab7e05297c9720b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 21:20:31 -0700 Subject: [PATCH 15/19] types!! --- packages/kit/types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index a5543fe29dcc..f3adb14a4a80 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -46,7 +46,7 @@ declare module '@sveltejs/kit' { */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ From 0a289ea6caf8d6ab443b9cd9ea38f5aba01d01ec Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 22:53:42 -0700 Subject: [PATCH 16/19] fix types --- packages/adapter-cloudflare/globals.d.ts | 3 +-- packages/adapter-cloudflare/index.js | 2 +- packages/adapter-cloudflare/package.json | 2 +- .../src/virtual-cloudflare-workers.js | 4 ++-- packages/adapter-cloudflare/tsconfig.json | 2 +- pnpm-lock.yaml | 20 +++++++++---------- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/adapter-cloudflare/globals.d.ts b/packages/adapter-cloudflare/globals.d.ts index a3ffd17c0f3a..a64ccbc1392f 100644 --- a/packages/adapter-cloudflare/globals.d.ts +++ b/packages/adapter-cloudflare/globals.d.ts @@ -1,6 +1,5 @@ -import type { PlatformProxy, CacheStorage } from 'wrangler'; +import type { PlatformProxy } from 'wrangler'; declare global { var __platform_proxy: PlatformProxy; - var caches: CacheStorage; } diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 3498a3f43990..f57cfd331faa 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -225,7 +225,7 @@ function virtual_workers_module(options, stub_import) { if (globalThis.__platform_proxy) return; const proxy = await getPlatformProxy(options); globalThis.__platform_proxy = proxy; - globalThis.caches = proxy.caches; + /** @type {any} */ (globalThis).caches = proxy.caches; }; return { name: 'vite-plugin-adapter-cloudflare-virtual-workers-module', diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 44a952913abc..3cbbc57b78c0 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -42,7 +42,7 @@ "test": "pnpm test:unit && pnpm test:e2e" }, "dependencies": { - "@cloudflare/workers-types": "^5.20260809.1", + "@cloudflare/workers-types": "^5.20260813.1", "@rolldown/pluginutils": "^1.0.1" }, "devDependencies": { diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index dba4342df17e..35721552a1c7 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -8,7 +8,7 @@ function get_current_env() { return als.getStore() ?? proxy.env; } -/** @typedef {import('@cloudflare/workers-types').CloudflareWorkersModule} Module */ +/** @typedef {typeof import('cloudflare:workers')} Module */ export const env = new Proxy( {}, @@ -139,7 +139,7 @@ class Span { } end() {} } -/** @type {import('@cloudflare/workers-types').Tracing} */ +/** @type {Module['tracing']} */ export const tracing = { enterSpan(_, callback, ...args) { return callback(new Span(), ...args); diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index 2e16130e2b76..700eb7ca2df9 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -13,7 +13,7 @@ // taken from the Cloudflare Workers TypeScript template https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates/hello-world/ts/tsconfig.json "target": "es2024", "lib": ["es2024"], - "types": ["node"] + "types": ["node", "@cloudflare/workers-types"] }, "include": [ "index.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44ef54d07a38..bbd54ea48e23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,14 +145,14 @@ importers: packages/adapter-cloudflare: dependencies: '@cloudflare/workers-types': - specifier: ^5.20260809.1 - version: 5.20260809.1 + specifier: ^5.20260813.1 + version: 5.20260813.1 '@rolldown/pluginutils': specifier: ^1.0.1 version: 1.0.1 wrangler: specifier: ^4.118.0 - version: 4.120.1(@cloudflare/workers-types@5.20260809.1) + version: 4.120.1(@cloudflare/workers-types@5.20260813.1) devDependencies: '@playwright/test': specifier: 'catalog:' @@ -192,7 +192,7 @@ importers: version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) wrangler: specifier: 'catalog:' - version: 4.120.1(@cloudflare/workers-types@5.20260809.1) + version: 4.120.1(@cloudflare/workers-types@5.20260813.1) packages/adapter-cloudflare/test/apps/workers: devDependencies: @@ -213,7 +213,7 @@ importers: version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) wrangler: specifier: 'catalog:' - version: 4.120.1(@cloudflare/workers-types@5.20260809.1) + version: 4.120.1(@cloudflare/workers-types@5.20260813.1) packages/adapter-netlify: dependencies: @@ -1825,8 +1825,8 @@ packages: cpu: [x64] os: [win32] - '@cloudflare/workers-types@5.20260809.1': - resolution: {integrity: sha512-sBM+0I5lCY9LgTnorn/N2UyrA6KVbUzj9tncxwH8v6sH8tVeAyJj+i0z3xXWY4l4fd1oDcwt8CGf50vGwVISYQ==} + '@cloudflare/workers-types@5.20260813.1': + resolution: {integrity: sha512-RQNfm7xD10hNHEQZFxQPmyGMJ9+aDGPcdFZ0x1LtmjRoLFcgZkGvfaqJAbOMQBAUFSESO3bYJS4p9mOLv28Ihg==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -4766,7 +4766,7 @@ snapshots: '@cloudflare/workerd-windows-64@1.20260804.1': optional: true - '@cloudflare/workers-types@5.20260809.1': {} + '@cloudflare/workers-types@5.20260813.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: @@ -7154,7 +7154,7 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260804.1 '@cloudflare/workerd-windows-64': 1.20260804.1 - wrangler@4.120.1(@cloudflare/workers-types@5.20260809.1): + wrangler@4.120.1(@cloudflare/workers-types@5.20260813.1): dependencies: '@cloudflare/kv-asset-handler': 0.5.0 '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260804.1) @@ -7165,7 +7165,7 @@ snapshots: unenv: 2.0.0-rc.24 workerd: 1.20260804.1 optionalDependencies: - '@cloudflare/workers-types': 5.20260809.1 + '@cloudflare/workers-types': 5.20260813.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil From ee4c9b1f8731a4b0fa0230c3d9122025003d6a72 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 23:02:36 -0700 Subject: [PATCH 17/19] remove prepare script --- packages/adapter-cloudflare/test/apps/workers/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index 51b3622abe70..816115baeaf1 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -6,7 +6,7 @@ "dev": "vite dev", "build": "vite build", "preview": "wrangler dev dist/index.js --config config/wrangler.jsonc", - "prepare": "wrangler types --config config/wrangler.jsonc || echo ''", + "generate:types": "wrangler types --config config/wrangler.jsonc", "test:dev": "DEV=true playwright test", "test:build": "playwright test", "test": "pnpm test:dev && pnpm test:build" From cbe27f267eafdd8d515ab8398afad0ccb62dc0f0 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sat, 15 Aug 2026 09:26:50 -0400 Subject: [PATCH 18/19] doc types --- .../docs/25-build-and-deploy/60-adapter-cloudflare.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index 5e745a04fe85..5271950a043e 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -133,18 +133,16 @@ Functions contained in the [`/functions` directory](https://developers.cloudflar The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is available on the [`cloudflare:workers`](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) module. ```js +/// file: +server.js // @filename: ambient.d.ts -import { DurableObjectNamespace } from '@cloudflare/workers-types'; - declare module 'cloudflare:workers' { export const env: { - YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + YOUR_DURABLE_OBJECT_NAMESPACE: import('@cloudflare/workers-types').DurableObjectNamespace; }; } // @filename: +server.js // ---cut--- // @errors: 2355 2322 -/// file: +server.js import { env } from 'cloudflare:workers'; /** @type {import('./$types').RequestHandler} */ From 0f6072c6b23b162fd2e8bf94290bea14a8104194 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sat, 15 Aug 2026 09:28:11 -0400 Subject: [PATCH 19/19] doc types 2 --- documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index 39cc4b758aa8..bbb839906cbd 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -475,6 +475,7 @@ interface Request { cf: import('@cloudflare/workers-types').IncomingRequestCfProperties; } // @filename: index.js +// @errors: 7031 // ---cut--- export async function GET({ request }) { const { country } = request.cf;