Skip to content

Commit fd05217

Browse files
Brooooooklynclaude
andcommitted
fix(void): give Nuxt, Analog and deploy cron bundles the app's void/db
Nitro builds a Nuxt or Analog server without Void's Vite plugins, so every server route got the static D1 client from `void/db`. A PostgreSQL or MySQL app failed each database request with a missing `DB` binding, and `db.query` was undefined even on D1 because the client had no schema. The deploy build of hosted framework cron and queue handlers had the same gap: it built `void/db` as the D1 client and could not resolve `@schema`. Nuxt and Analog apps now add `void/nitro` to their Nitro modules. The module and the cron/queue handler build share the plugin that serves the app's own `void/db`, with its dialect, schema and driver, and resolve `@schema` through the bundler so a schema folder works. Their Worker builds import a workerd build of pg or mysql2, made the same way as the migration handler, because Nitro's CommonJS handling and Node stubs break both drivers in workerd. The pg build loads no `node:fs`, which workerd provides only from the 2025-09-15 compatibility date. For PostgreSQL and MySQL the module also runs each request in Void's request scope and keeps Void's `node:async_hooks` import native, so a request reuses one pool across its queries instead of opening a connection per query. The cron/queue handler build does the same for each cron event and queue batch, as Void apps do. D1 apps keep their current request and event handling. A PostgreSQL or MySQL app with no schema file now gets its dialect's `void/db` in Vite builds too. An app whose bundle still loads the D1 client while it has HYPERDRIVE or DATABASE_URL gets an error that says to add `void/nitro`. `void init` prints the module step for Nuxt and Analog. Nuxt apps keep the Nitro `@schema` alias, because Nuxt types server routes from Nitro's aliases. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
1 parent bd1705f commit fd05217

25 files changed

Lines changed: 922 additions & 153 deletions

File tree

‎docs/guide/database.md‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,6 @@ const usersWithPosts = await db.query.users.findMany({
234234
});
235235
```
236236

237-
::: warning ⚠️ Nuxt and Analog limitations
238-
In Nuxt and Analog, use the query builder (`db.select().from(table)`). The `db.query.*` relational API is not available in these frameworks.
239-
:::
240-
241237
## Seeding
242238

243239
Use `void db seed` to reset your local database, re-apply migrations, and then run a seed file.

‎docs/integrations/frameworks/analog.md‎

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ Void provides the local Cloudflare binding runtime. `nitro-cloudflare-dev` creat
2626
### 3. Configure `vite.config.ts`
2727

2828
```ts
29-
import { resolve } from 'node:path';
3029
import analog from '@analogjs/platform';
3130
import { defineConfig } from 'vite';
3231
import { voidPlugin } from 'void';
@@ -41,19 +40,16 @@ export default defineConfig({
4140
nitro: {
4241
preset: 'cloudflare-module',
4342
cloudflareDev: { configPath: './.void-wrangler.jsonc' },
44-
modules: ['nitro-cloudflare-dev'],
45-
alias: {
46-
// Nitro has its own bundler that doesn't use Vite aliases.
47-
// Duplicate @schema here so Nitro can resolve it.
48-
'@schema': resolve(__dirname, 'db/schema.ts'),
49-
},
43+
modules: ['nitro-cloudflare-dev', 'void/nitro'],
5044
},
5145
}),
5246
voidPlugin(),
5347
],
5448
});
5549
```
5650

51+
The `void/nitro` module gives Analog API routes and page server loads your app's `void/db` and `@schema`.
52+
5753
### 4. Create `void.config.ts`
5854

5955
Void generates the Cloudflare config used by Analog's development runtime:
@@ -98,10 +94,6 @@ export const load = async ({ event }: PageServerLoad) => {
9894
};
9995
```
10096

101-
::: warning ⚠️ Analog limitation
102-
Use `db.select().from(table)` for database queries in Analog. The `db.query.*` relational API is not available.
103-
:::
104-
10597
### Other resources
10698

10799
Use [KV](../../guide/kv.md), [object storage](../../guide/storage.md), and [AI](../../guide/ai.md) from server-side code with the same imports as a Void app. Add [cron jobs](../../guide/jobs.md) in `crons/` and [queue consumers](../../guide/queues.md) in `queues/`.

‎docs/integrations/frameworks/nuxt.md‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,23 @@ import { voidPlugin } from 'void';
3232
export default defineNuxtConfig({
3333
nitro: {
3434
preset: 'cloudflare-module',
35+
modules: ['void/nitro'],
3536
cloudflareDev: { configPath: './.void-wrangler.jsonc' },
3637
// Required for Nuxt 4.6.0 on Windows.
3738
externals: {
3839
inline: [/[\\/]node_modules[\\/]nuxt[\\/]dist[\\/]/],
3940
},
4041
alias: {
41-
// Nitro has its own bundler that doesn't use Vite aliases.
42-
// Duplicate @schema here so Nitro can resolve it.
42+
// Nuxt types server routes from Nitro's aliases.
4343
'@schema': resolve(import.meta.dirname, 'db/schema.ts'),
4444
},
4545
},
4646
vite: { plugins: [voidPlugin()] },
4747
});
4848
```
4949

50+
The `void/nitro` module gives Nuxt server routes your app's `void/db` and `@schema`.
51+
5052
### 4. Create `void.config.ts`
5153

5254
Void generates the Cloudflare config used by Nuxt's development runtime:
@@ -88,10 +90,6 @@ export default defineEventHandler(async () => {
8890
});
8991
```
9092

91-
::: warning ⚠️ Nuxt limitation
92-
Use `db.select().from(table)` for database queries in Nuxt. The `db.query.*` relational API is not available.
93-
:::
94-
9593
### Other resources
9694

9795
Use [KV](../../guide/kv.md), [object storage](../../guide/storage.md), and [AI](../../guide/ai.md) from server-side code with the same imports as a Void app. Add [cron jobs](../../guide/jobs.md) in `crons/` and [queue consumers](../../guide/queues.md) in `queues/`.

‎docs/reference/api.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,5 @@ Import Void's app-facing APIs from these paths:
6868
| `void/queues` | `queues` proxy for sending messages to typed queues |
6969
| `void/sandbox` | `getSandbox`, `sandbox`, `SandboxLimitError`, `SandboxUnavailableError`, and the SDK 1.0 file, backup, and mount helpers. `getSandbox(id)` resolves a client. Commands, files, port requests, and process completion return promises; limits reject with `SandboxLimitError`, which extends `UsageLimit`. Container state lasts until inactivity (default ten minutes), a crash, or a restart. See [Sandboxes](../guide/sandboxes.md) for the API and deployment requirements. |
7070
| `void/env` | `defineEnv`, `env`, schema helpers (`string`, `number`, `boolean`, `url`, `email`, `oneOf`, `json`), and global Cloudflare env types |
71+
| `void/nitro` | Nitro module for Nuxt and Analog. Add `'void/nitro'` to the Nitro `modules` so server routes get the app's `void/db` and `@schema` |
7172
| `void/sveltekit` | SvelteKit integration helpers: `withVoidTSConfig()` and `mergeVoidSvelteKitTsconfig()` |

‎packages/void/AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ generated entries must preserve the module-boundary rules below.
102102
- **pnpm scaffold build policy**: Keep `create-void` and `void init` aligned with the repository: `better-sqlite3: false` uses version 13's bundled Node-API binaries; `esbuild`, `sharp`, and `workerd` have approved builds. Do not require an unnecessary node-gyp rebuild in fresh applications.
103103
- **Scaffold dependency installation**: Vite+ projects install through local/global `vp`, bootstrapping with `npx` when neither is available. Preserve existing package-manager pins and lockfiles; fresh Vite+ projects select pnpm through the build-policy YAML without adding a version pin. Plain Vite projects use their selected package manager. Do not run a global package manager directly for Vite+ installs.
104104
- **Prerender discovery and environment validation**: Only `POST /__void/prerender-paths` bypasses whole-schema boot validation, because path discovery runs without remote production secrets. Discovery answers only when the build-time runner marks the request (the `__VOID_PRERENDER` Miniflare binding on Cloudflare, the Hono env argument on Node/Bun/Deno), never on a deployed app or dev server. Keep validation on readiness, application requests, page rendering, and event handlers.
105-
- **Pre-resolved paths in codegen**: Generated virtual module code uses absolute paths for `hono` and `void/response` (resolved via `import.meta.resolve()`). Virtual modules have no filesystem location, so bare specifiers can't resolve from them. void/db never imports its pg or mysql2 driver itself: drizzle builds the pool from `connection`, and the worker prebundle serves drizzle's bare driver import.
105+
- **Pre-resolved paths in codegen**: Generated virtual module code uses absolute paths for `hono` and `void/response` (resolved via `import.meta.resolve()`). Virtual modules have no filesystem location, so bare specifiers can't resolve from them. void/db never imports its pg or mysql2 driver itself: drizzle builds the pool from `connection`, and the worker prebundle serves drizzle's bare driver import. Bundles that never run Void's Vite plugins, Nitro (through `void/nitro`) and the deploy cron/queue handler build, get the same module from `voidDbBundlerPlugin`, which resolves `@schema` through the bundler so a schema folder works. Their Worker builds import the driver that `bundleWorkerSqlDriver` builds like the migration handler: Nitro's CommonJS handling and Node stubs break pg and mysql2 in workerd. For pg and MySQL, `void/nitro` also runs each request in `withRuntimeEnv` (as the Class A wrapper does) and keeps Void's `node:async_hooks` native in workerd builds, because Nitro's unenv AsyncLocalStorage drops the store after the first await. The deploy handler build likewise runs each cron event and queue batch in `withRuntimeEnv`.
106106
- **CF plugin API**: Worker config (`main`, `d1_databases`, `kv_namespaces`, `r2_buckets`) goes inside the `config` option of `cloudflare()`, NOT as top-level properties. `persistState` takes `{ path: string }`, not a raw string. The `config` option uses a **function form** `(resolved: WorkerConfig) => Partial<WorkerConfig>` so that bindings from the user's `wrangler.json` (if present) take precedence over inferred placeholder bindings.
107107
- **Direct deploy package boundary**: target-neutral intents, artifacts, resources, migrations, and Durable Object plans live in public `@void/deploy-core`; root Wrangler config parsing and verified Cloudflare upload primitives live in public `@void/deploy-cloudflare`. The CLI consumes both packages directly. Hosted platform packages consume `@void/deploy-core` and must not be imported by public SDK packages.
108108
- **Direct activation invariant**: native Cloudflare deploy uploads a Worker Version without traffic, records the validated migration filenames in its annotation, probes that version directly, activates it at 100%, and only then synchronizes triggers. A trigger-sync failure restores the prior active version. Database migrations are forward-only; rollback warns before activating code whose recorded migration set is older or unknown.

‎packages/void/package.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@
9090
"./routes": {
9191
"types": "./dist/routes-stub.d.mts"
9292
},
93+
"./nitro": {
94+
"types": "./dist/nitro.d.mts",
95+
"import": "./dist/nitro.mjs",
96+
"require": "./dist/nitro.mjs"
97+
},
9398
"./sveltekit": {
9499
"types": "./dist/sveltekit.d.mts",
95100
"import": "./dist/sveltekit.mjs",

‎packages/void/src/cli/init.ts‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2373,16 +2373,34 @@ function noteManualFrameworkConfigSetup(framework: DetectedFramework): void {
23732373
if (framework.name === 'nuxt') {
23742374
note(
23752375
`Detected Nuxt. Void init does not patch nuxt.config.* automatically.\n\n` +
2376-
`Add \`voidPlugin()\` to the Nuxt Vite plugins option:\n\n` +
2376+
`Add \`voidPlugin()\` to the Nuxt Vite plugins option and \`void/nitro\` to the Nitro modules:\n\n` +
23772377
`import { voidPlugin } from "void";\n\n` +
23782378
`export default defineNuxtConfig({\n` +
2379+
` nitro: { modules: ["void/nitro"] },\n` +
23792380
` vite: { plugins: [voidPlugin()] },\n` +
23802381
`});`,
23812382
'Manual Nuxt config step',
23822383
);
23832384
return;
23842385
}
23852386

2387+
if (framework.name === 'analog') {
2388+
note(
2389+
`Detected Analog. Void init does not patch vite.config.* automatically.\n\n` +
2390+
`Add \`voidPlugin()\` to the Vite plugins and \`void/nitro\` to Analog's Nitro modules:\n\n` +
2391+
`import analog from "@analogjs/platform";\n` +
2392+
`import { voidPlugin } from "void";\n\n` +
2393+
`export default defineConfig({\n` +
2394+
` plugins: [\n` +
2395+
` analog({ nitro: { modules: ["nitro-cloudflare-dev", "void/nitro"] } }),\n` +
2396+
` voidPlugin(),\n` +
2397+
` ],\n` +
2398+
`});`,
2399+
'Manual Analog config step',
2400+
);
2401+
return;
2402+
}
2403+
23862404
noteManualViteConfigSetup();
23872405
}
23882406

0 commit comments

Comments
 (0)