From 47b185c3414b2cdea82767f0be9a847fe24c757a Mon Sep 17 00:00:00 2001 From: meganemura <290611+meganemura@users.noreply.github.com> Date: Sun, 18 Oct 2026 06:42:33 +0000 Subject: [PATCH 1/3] fix(types): add ASSETS to the env types Void always binds ASSETS, but CloudBindings and Cloudflare.Env do not declare it, so c.env.ASSETS has type unknown. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01CZxFCFRcYc9ojkUsSvuz5j --- packages/void/src/runtime/env-public.ts | 1 + packages/void/src/runtime/handler.ts | 1 + packages/void/test/integration/env-types-typecheck.test.ts | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/void/src/runtime/env-public.ts b/packages/void/src/runtime/env-public.ts index 058c88380..dcb57b1f8 100644 --- a/packages/void/src/runtime/env-public.ts +++ b/packages/void/src/runtime/env-public.ts @@ -400,6 +400,7 @@ declare global { STORAGE: R2Bucket; AI: Ai; SANDBOX: DurableObjectNamespace; + ASSETS: Fetcher; /** @internal Service binding to proxy worker (AI, revalidation, future features) */ __VOID_PROXY?: Fetcher; /** @internal Custom proxy URL override */ diff --git a/packages/void/src/runtime/handler.ts b/packages/void/src/runtime/handler.ts index 9804e091b..6d43ab7e1 100644 --- a/packages/void/src/runtime/handler.ts +++ b/packages/void/src/runtime/handler.ts @@ -55,6 +55,7 @@ export interface CloudBindings extends VoidGeneratedEnvBindings { STORAGE: R2Bucket; AI: Ai; SANDBOX: DurableObjectNamespace; + ASSETS: Fetcher; [key: string]: unknown; } diff --git a/packages/void/test/integration/env-types-typecheck.test.ts b/packages/void/test/integration/env-types-typecheck.test.ts index 298b12b76..2cc5250b0 100644 --- a/packages/void/test/integration/env-types-typecheck.test.ts +++ b/packages/void/test/integration/env-types-typecheck.test.ts @@ -33,7 +33,8 @@ describe('generated env types in a consumer project', () => { 'import { defineHandler } from "void";\n' + 'import { env } from "void/env";\n' + 'const direct: string = env.API_SECRET;\n' + - 'export const GET = defineHandler((c) => c.text(direct + c.env.API_SECRET.toUpperCase()));\n', + 'export const GET = defineHandler((c) => c.text(direct + c.env.API_SECRET.toUpperCase()));\n' + + 'export const POST = defineHandler((c) => c.env.ASSETS.fetch(c.req.raw));\n', ); writeFileSync( join(root, 'tsconfig.json'), From f49841a78cde440b7e4ada5ed9927348c82f5a19 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 20 Oct 2026 15:11:42 +0800 Subject: [PATCH 2/3] test(types): check ASSETS on Cloudflare.Env next to wrangler types output Also list ASSETS with the other binding types in the docs. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/integrations/cloudflare.md | 1 + docs/reference/api/env.md | 2 +- docs/reference/api/types.md | 1 + .../test/integration/env-types-typecheck.test.ts | 13 +++++++++++-- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/integrations/cloudflare.md b/docs/integrations/cloudflare.md index 3acb16822..5b7a85030 100644 --- a/docs/integrations/cloudflare.md +++ b/docs/integrations/cloudflare.md @@ -52,6 +52,7 @@ This provides binding types and Workers runtime types. | `KV` | KV | `c.env.KV` or `void/kv` | | `STORAGE` | R2 | `c.env.STORAGE` or `void/storage` | | `AI` | Workers AI | `c.env.AI` or `void/ai` | +| `ASSETS` | Static assets | `c.env.ASSETS.fetch(request)` | | `QUEUE_*` | Queues | `defineQueue()` or `void/queues` | | Filename-derived | Durable Objects | A module in `durable-objects/` | diff --git a/docs/reference/api/env.md b/docs/reference/api/env.md index a329354eb..ba26e9f3d 100644 --- a/docs/reference/api/env.md +++ b/docs/reference/api/env.md @@ -45,6 +45,6 @@ Non-client keys are server values and use remote secret storage in production. ` /// ``` -Declares the `Cloudflare.Env` namespace with `DB`, `KV`, `STORAGE`, `AI`, and `SANDBOX` binding types. +Declares the `Cloudflare.Env` namespace with `DB`, `KV`, `STORAGE`, `AI`, `SANDBOX`, and `ASSETS` binding types. For handler context variables such as `c.set()` and `c.get()`, augment [`CloudContextVariables`](./types.md#cloudcontextvariables). This is separate from `Cloudflare.Env`, which types worker bindings. Framework adapters such as `@void/vue`, `@void/react`, and `@void/svelte` also augment `CloudContextVariables` to add the `shared` key used by `useShared()`. diff --git a/docs/reference/api/types.md b/docs/reference/api/types.md index b20f7691d..5a31628a6 100644 --- a/docs/reference/api/types.md +++ b/docs/reference/api/types.md @@ -24,6 +24,7 @@ interface CloudEnv extends Env { STORAGE: R2Bucket; AI: Ai; SANDBOX: DurableObjectNamespace; + ASSETS: Fetcher; [key: string]: unknown; }; Variables: CloudContextVariables; diff --git a/packages/void/test/integration/env-types-typecheck.test.ts b/packages/void/test/integration/env-types-typecheck.test.ts index 2cc5250b0..a779f713f 100644 --- a/packages/void/test/integration/env-types-typecheck.test.ts +++ b/packages/void/test/integration/env-types-typecheck.test.ts @@ -14,7 +14,14 @@ afterEach(() => { }); describe('generated env types in a consumer project', () => { - it('types schema keys on both env and handler c.env', () => { + // `wrangler types` declares an assets binding as `ASSETS: Fetcher`, which must merge with Void's. + it.each([ + ['', ''], + [ + ' next to wrangler types output', + 'declare global { namespace Cloudflare { interface Env { ASSETS: Fetcher } } }\n', + ], + ])('types schema keys and ASSETS on both env and handler c.env%s', (_, wranglerTypes) => { // This nested fixture resolves the workspace's installed void package just // as a consumer does, while keeping its source out of the playground routes. const root = mkdtempSync(join(workspaceRoot, 'playground/kitchen-sink/.tmp-env-types-')); @@ -34,7 +41,9 @@ describe('generated env types in a consumer project', () => { 'import { env } from "void/env";\n' + 'const direct: string = env.API_SECRET;\n' + 'export const GET = defineHandler((c) => c.text(direct + c.env.API_SECRET.toUpperCase()));\n' + - 'export const POST = defineHandler((c) => c.env.ASSETS.fetch(c.req.raw));\n', + 'export const POST = defineHandler((c) => c.env.ASSETS.fetch(c.req.raw));\n' + + 'export const fetchAsset = (bindings: Cloudflare.Env) => bindings.ASSETS.fetch("https://app.test/");\n' + + wranglerTypes, ); writeFileSync( join(root, 'tsconfig.json'), From d11b2718a2ad07811856deab24f9b6e1f2d23088 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 20 Oct 2026 15:21:12 +0800 Subject: [PATCH 3/3] fix(types): type c.env.ASSETS as the fetch(Request) provider every target has Node, Bun and Deno targets inject an ASSETS object whose fetch takes only a Request, so the full Fetcher let handlers type-check calls that fail there. Cloudflare.Env keeps Fetcher to merge with wrangler types output. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/reference/api/types.md | 2 +- packages/void/src/runtime/handler.ts | 4 +++- packages/void/test/integration/env-types-typecheck.test.ts | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/reference/api/types.md b/docs/reference/api/types.md index 5a31628a6..6444104f9 100644 --- a/docs/reference/api/types.md +++ b/docs/reference/api/types.md @@ -24,7 +24,7 @@ interface CloudEnv extends Env { STORAGE: R2Bucket; AI: Ai; SANDBOX: DurableObjectNamespace; - ASSETS: Fetcher; + ASSETS: { fetch(request: Request): Promise }; [key: string]: unknown; }; Variables: CloudContextVariables; diff --git a/packages/void/src/runtime/handler.ts b/packages/void/src/runtime/handler.ts index 6d43ab7e1..01006ffc7 100644 --- a/packages/void/src/runtime/handler.ts +++ b/packages/void/src/runtime/handler.ts @@ -4,6 +4,7 @@ import type { HeadDescriptor } from '../pages/head.ts'; import type { AuthSession, AuthUser } from './auth.ts'; import { base64ToBytes } from './base64.ts'; import { convertReturnValue } from './response.ts'; +import type { AssetBinding } from './routing-worker.ts'; import { runValidation, type ValidatorSlots, type HandlerInput } from './validator.ts'; /** @@ -55,7 +56,8 @@ export interface CloudBindings extends VoidGeneratedEnvBindings { STORAGE: R2Bucket; AI: Ai; SANDBOX: DurableObjectNamespace; - ASSETS: Fetcher; + /** Node, Bun and Deno targets serve static files through a provider that takes only a `Request`. */ + ASSETS: AssetBinding; [key: string]: unknown; } diff --git a/packages/void/test/integration/env-types-typecheck.test.ts b/packages/void/test/integration/env-types-typecheck.test.ts index a779f713f..ddfa411b2 100644 --- a/packages/void/test/integration/env-types-typecheck.test.ts +++ b/packages/void/test/integration/env-types-typecheck.test.ts @@ -42,6 +42,8 @@ describe('generated env types in a consumer project', () => { 'const direct: string = env.API_SECRET;\n' + 'export const GET = defineHandler((c) => c.text(direct + c.env.API_SECRET.toUpperCase()));\n' + 'export const POST = defineHandler((c) => c.env.ASSETS.fetch(c.req.raw));\n' + + '// @ts-expect-error Node, Bun and Deno targets fetch assets only by Request.\n' + + 'export const PUT = defineHandler((c) => c.env.ASSETS.fetch("/logo.png"));\n' + 'export const fetchAsset = (bindings: Cloudflare.Env) => bindings.ASSETS.fetch("https://app.test/");\n' + wranglerTypes, );