diff --git a/README.md b/README.md index ea5eb66..7e8573b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Cloudflare Bindings for Nitro and Nuxt +# Cloudflare Platform for Nitro and Nuxt -This proof of concept module enables access to the Cloudflare runtime bindings in the development server of [Nitro](https://nitro.unjs.io) and [Nuxt](https://nuxt.com) using the [new `getBindingsProxy` API](https://github.com/cloudflare/workers-sdk/pull/4523) exposed by [wrangler](https://developers.cloudflare.com/workers/wrangler/) and [miniflare](https://miniflare.dev/) +This proof of concept module enables access to the Cloudflare runtime platform in the development server of [Nitro](https://nitro.unjs.io) and [Nuxt](https://nuxt.com) using the [new `getPlatformProxy` API](https://github.com/cloudflare/workers-sdk/pull/5002) exposed by [wrangler](https://developers.cloudflare.com/workers/wrangler/) and [miniflare](https://miniflare.dev/) > [!NOTE] > Nitro plans to introduce a new method to allow native dev presets, meaning you can natively run [miniflare](https://miniflare.dev/) as your development server without this module or a proxy in the future! diff --git a/examples/nitro/routes/counter.ts b/examples/nitro/routes/counter.ts index 2987f48..37254a7 100644 --- a/examples/nitro/routes/counter.ts +++ b/examples/nitro/routes/counter.ts @@ -2,7 +2,7 @@ export default eventHandler(async (event) => { const { KV } = event.context.cloudflare.env; let ctr = (await KV.get("counter")) || 0; - await KV.put("counter", ++ctr); + await KV.put("counter", ++ctr % 100); - return { counter: ctr }; + return `counter: ${(await KV.get("counter")) || 0}`; }); diff --git a/examples/nitro/routes/index.ts b/examples/nitro/routes/index.ts index af1b7d2..2aa3f43 100644 --- a/examples/nitro/routes/index.ts +++ b/examples/nitro/routes/index.ts @@ -1,4 +1,18 @@ -export default eventHandler(async () => { - const { counter } = (await $fetch("/counter")) as { counter: number }; - return `
Counter: ${counter}
`; +export default eventHandler(async (event) => { + const cloudflare = await event.context.cloudflare; + + const logs: string[] = []; + const log = (str: string) => logs.push(str); + + log(`Keys of cloudflare: ${Object.keys(cloudflare).join(", ")}`); + + log(`Keys of cloudflare.env: ${Object.keys(cloudflare.env).join(", ")}`); + + log(`Colo: ${cloudflare.request.cf.colo}`); + + log( + `typeof cloudflare.context.waitUntil: ${typeof cloudflare.context.waitUntil}`, + ); + + return `
`; }); diff --git a/package.json b/package.json index 3610ef2..bb9b6a8 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "prettier": "^3.2.4", "typescript": "^5.2.2", "unbuild": "^2.0.0", - "wrangler": "^3.24.0" + "wrangler": "^3.28.2" }, "packageManager": "pnpm@8.10.0" } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9e4fa6..498d221 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,8 +55,8 @@ importers: specifier: ^2.0.0 version: 2.0.0(typescript@5.3.3) wrangler: - specifier: ^3.24.0 - version: 3.24.0 + specifier: ^3.28.2 + version: 3.28.2 examples/nitro: devDependencies: @@ -435,12 +435,6 @@ packages: to-fast-properties: 2.0.0 dev: true - /@cloudflare/kv-asset-handler@0.2.0: - resolution: {integrity: sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==} - dependencies: - mime: 3.0.0 - dev: true - /@cloudflare/kv-asset-handler@0.3.1: resolution: {integrity: sha512-lKN2XCfKCmpKb86a1tl4GIwsJYDy9TGuwjhDELLmpKygQhw8X2xR4dusgpC5Tg7q1pB96Eb0rBo81kxSILQMwA==} dependencies: @@ -456,6 +450,15 @@ packages: dev: true optional: true + /@cloudflare/workerd-darwin-64@1.20240129.0: + resolution: {integrity: sha512-DfVVB5IsQLVcWPJwV019vY3nEtU88c2Qu2ST5SQxqcGivZ52imagLRK0RHCIP8PK4piSiq90qUC6ybppUsw8eg==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@cloudflare/workerd-darwin-arm64@1.20231218.0: resolution: {integrity: sha512-b39qrU1bKolCfmKFDAnX4vXcqzISkEUVE/V8sMBsFzxrIpNAbcUHBZAQPYmS/OHIGB94KjOVokvDi7J6UNurPw==} engines: {node: '>=16'} @@ -465,6 +468,15 @@ packages: dev: true optional: true + /@cloudflare/workerd-darwin-arm64@1.20240129.0: + resolution: {integrity: sha512-t0q8ABkmumG1zRM/MZ/vIv/Ysx0vTAXnQAPy/JW5aeQi/tqrypXkO9/NhPc0jbF/g/hIPrWEqpDgEp3CB7Da7Q==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@cloudflare/workerd-linux-64@1.20231218.0: resolution: {integrity: sha512-dMUF1wA+0mybm6hHNOCgY/WMNMwomPPs4I7vvYCgwHSkch0Q2Wb7TnxQZSt8d1PK/myibaBwadrlIxpjxmpz3w==} engines: {node: '>=16'} @@ -474,6 +486,15 @@ packages: dev: true optional: true + /@cloudflare/workerd-linux-64@1.20240129.0: + resolution: {integrity: sha512-sFV1uobHgDI+6CKBS/ZshQvOvajgwl6BtiYaH4PSFSpvXTmRx+A9bcug+6BnD+V4WgwxTiEO2iR97E1XuwDAVw==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@cloudflare/workerd-linux-arm64@1.20231218.0: resolution: {integrity: sha512-2s5uc8IHt0QmWyKxAr1Fy+4b8Xy0b/oUtlPnm5MrKi2gDRlZzR7JvxENPJCpCnYENydS8lzvkMiAFECPBccmyQ==} engines: {node: '>=16'} @@ -483,6 +504,15 @@ packages: dev: true optional: true + /@cloudflare/workerd-linux-arm64@1.20240129.0: + resolution: {integrity: sha512-O7q7htHaFRp8PgTqNJx1/fYc3+LnvAo6kWWB9a14C5OWak6AAZk42PNpKPx+DXTmGvI+8S1+futBGUeJ8NPDXg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@cloudflare/workerd-windows-64@1.20231218.0: resolution: {integrity: sha512-oN5hz6TXUDB5YKUN5N3QWAv6cYz9JjTZ9g16HVyoegVFEL6/zXU3tV19MBX2IvlE11ab/mRogEv9KXVIrHfKmA==} engines: {node: '>=16'} @@ -492,6 +522,15 @@ packages: dev: true optional: true + /@cloudflare/workerd-windows-64@1.20240129.0: + resolution: {integrity: sha512-YqGno0XSqqqkDmNoGEX6M8kJlI2lEfWntbTPVtHaZlaXVR9sWfoD7TEno0NKC95cXFz+ioyFLbgbOdnfWwmVAA==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -5343,8 +5382,8 @@ packages: - utf-8-validate dev: true - /miniflare@3.20231218.3: - resolution: {integrity: sha512-OrPBYWO0WnFv6DrxZ7hF8f5agZ4+xo/2qSLE0wwCJSqlFhr91dfSJautxfCOBD896nAA7Jqr5LBPEnqq3/k/JQ==} + /miniflare@3.20240129.2: + resolution: {integrity: sha512-BPUg8HsPmWQlRFUeiQk274i8M9L0gOvzbkjryuTvCX+M53EwBpP0gM2wyrRr/HokQoJcxWGh3InBu6L8+0bbPw==} engines: {node: '>=16.13'} hasBin: true dependencies: @@ -5356,7 +5395,7 @@ packages: glob-to-regexp: 0.4.1 stoppable: 1.1.0 undici: 5.28.2 - workerd: 1.20231218.0 + workerd: 1.20240129.0 ws: 8.16.0 youch: 3.3.3 zod: 3.22.4 @@ -8334,18 +8373,36 @@ packages: '@cloudflare/workerd-windows-64': 1.20231218.0 dev: true - /wrangler@3.24.0: - resolution: {integrity: sha512-jEnqpY+9/J4VPjtuEnS2lhCPXkvbDClnMalSWaRxSx+1tiTWMJhMjtK9oyXLdO+ZUf9Q4LvFTYSPm8O1uwmnxQ==} + /workerd@1.20240129.0: + resolution: {integrity: sha512-t4pnsmjjk/u+GdVDgH2M1AFmJaBUABshYK/vT/HNrAXsHSwN6VR8Yqw0JQ845OokO34VLkuUtYQYyxHHKpdtsw==} + engines: {node: '>=16'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20240129.0 + '@cloudflare/workerd-darwin-arm64': 1.20240129.0 + '@cloudflare/workerd-linux-64': 1.20240129.0 + '@cloudflare/workerd-linux-arm64': 1.20240129.0 + '@cloudflare/workerd-windows-64': 1.20240129.0 + dev: true + + /wrangler@3.28.2: + resolution: {integrity: sha512-hlD4f2avBZuR1+qo9Um6D1prdWrSRtGTo9h6o/AKce+bHQEJWoJgJKHeLmrpZlLtHg/gGR1Xa1xzrexhuIzeJw==} engines: {node: '>=16.17.0'} hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20230914.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true dependencies: - '@cloudflare/kv-asset-handler': 0.2.0 + '@cloudflare/kv-asset-handler': 0.3.1 '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) blake3-wasm: 2.1.5 chokidar: 3.5.3 esbuild: 0.17.19 - miniflare: 3.20231218.3 + miniflare: 3.20240129.2 nanoid: 3.3.7 path-to-regexp: 6.2.1 resolve: 1.22.8 diff --git a/src/runtime/plugin.dev.ts b/src/runtime/plugin.dev.ts index 67b3102..bffc8fc 100644 --- a/src/runtime/plugin.dev.ts +++ b/src/runtime/plugin.dev.ts @@ -1,23 +1,52 @@ import type { NitroAppPlugin } from "nitropack"; // @ts-ignore -import { useRuntimeConfig } from "#imports"; +import { useRuntimeConfig, getRequestURL } from "#imports"; export default function (nitroApp) { - let _proxy: ReturnType; + let _proxy: ReturnType; nitroApp.hooks.hook("request", async (event) => { // Lazy initialize proxy when first request comes in if (!_proxy) { - _proxy = getBindingsProxy().catch((error) => { + _proxy = getPlatformProxy().catch((error) => { console.error("Failed to initialize wrangler bindings proxy", error); - return { bindings: {}, dispose: () => Promise.resolve() }; + return { + env: {}, + cf: {}, + ctx: { + waitUntil() {}, + passThroughOnException() {}, + }, + caches: { + open(): Promise { + const result = Promise.resolve(new Cache()); + return result; + }, + get default(): Cache { + return new Cache(); + }, + }, + dispose: () => Promise.resolve(), + } as unknown as ReturnType; }); } - // Inject proxy bindings to the request context + const proxy = await _proxy; + + // Inject the various cf values from the proxy in event and event.context + + event.waitUntil = proxy.ctx.waitUntil; + (event.context as any).cf = proxy.cf; + (event.context as any).waitUntil = proxy.ctx.waitUntil; + + const request = new Request(getRequestURL(event)); + (request as any).cf = proxy.cf; + event.context.cloudflare = { ...event.context.cloudflare, - env: (await _proxy).bindings, + request, + env: proxy.env, + context: proxy.ctx, }; }); @@ -27,9 +56,9 @@ export default function (nitroApp) { }); }; -async function getBindingsProxy() { +async function getPlatformProxy() { const _pkg = "wrangler"; // Bypass bundling! - const { getBindingsProxy } = (await import( + const { getPlatformProxy } = (await import( _pkg )) as typeof import("wrangler"); @@ -37,10 +66,27 @@ async function getBindingsProxy() { wrangler: { configPath: string; persistDir: string }; } = useRuntimeConfig(); - const proxy = await getBindingsProxy({ + const proxy = await getPlatformProxy({ configPath: runtimeConfig.wrangler.configPath, persist: { path: runtimeConfig.wrangler.persistDir }, }); return proxy; } + +class Cache { + delete(): Promise { + const result = Promise.resolve(false); + return result; + } + + match() { + const result = Promise.resolve(undefined); + return result; + } + + put(): Promise { + const result = Promise.resolve(); + return result; + } +}