Skip to content

fix(void): make PostgreSQL and MySQL void/db work across frameworks, builds and deploys - #413

Merged
Brooooooklyn merged 17 commits into
mainfrom
fix/mysql-db-high-bugs
Oct 14, 2026
Merged

Brooooooklyn merged 17 commits into
mainfrom
fix/mysql-db-high-bugs

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Problem

PostgreSQL and MySQL void/db failed in most places outside a plain Void app on main. These failures were found while we verified #410, #411 and #412, and in the follow-up issues #414 to #420:

                         vp dev                         built Worker / deploy
Void app, MySQL          "require is not defined"       "Code generation from strings disallowed"
                                                        hosted migration Worker fails at startup
React Router, TanStack   "Request-scoped resource is unavailable outside a runtime handler"
 Start, vinext           a cron with `import … from 'void'` → every dev trigger 500
Astro                    same error; with a scope fix: "sorry, too many clients already" under load
                         `astro preview` after `astro build`: "No build output found"
                         dev restart → /__void/scheduled 404
SvelteKit                installed void → D1 runtime    createRequire(import.meta.url) crash → 500
                         a package's void/db → D1       a package's workerd code breaks Kit's analyse
Nuxt, Analog             always the D1 client: missing DB binding, db.query undefined (D1 too)
                         native deploy: no nodejs_compat → Node stubs; init notes do not deploy
framework crons/queues   hosted bundle built void/db as the D1 client
module-level query       const q = db.select()…  works on request 1, hangs on every later request
MySQL idle connections   never closed: one open connection per request on Node, per WebSocket message
MySQL migrations         two runs (deploy + deploy, deploy + CLI) apply one file twice
                         Better Auth, `void db reset` and `seed` ran outside the lock
                         a file without breakpoints → one multi-statement query (Hyperdrive rejects it)
installed void           Node target: void/kv loads instead of the guard error
                         Node target build: a page action's ValidationError → 500; API-only build crash

What this PR changes

One commit per fix. Each commit message describes the bug, the cause and the fix.

Request scope for every runtime that runs app code

request / cron / queue
   │
   ▼
withRuntimeEnv(env)  ← class A wrappers, Astro entry, SvelteKit entry,
   │                   void/nitro plugin, framework cron/queue bundle
   ▼                   (pg/MySQL apps only; D1 output is unchanged)
void/db  ── one drizzle instance ──► client proxy ──► this request's pool
                                       │
                                       └─ outside a scope: one pool per use
  • A query built at module scope now runs on the pool of the request that executes it.
  • MySQL connections close after 1 s idle (createDb pools: 60 s), timed from mysql2's release event. No pool timer runs in global scope.
  • The env and request-scope stores live on globalThis under versioned Symbol.for keys, as authContext does, so every copy of void's env module sees the current request.

Builds

SvelteKit pg/MySQL   ssr env: workerd conditions; inline void, drizzle-orm, the driver
                     and installed packages that depend on void; others follow Kit
Nuxt / Analog        modules: ['void/nitro'] → app's void/db, @schema, nodeCompat,
                     one workerd build per driver copy the bundler resolves
                     (mysql2 and mysql2/promise share it)
Node target          inline void, @void/* and installed ES module packages that depend
                     on void (dev, Vitest, build) → one runtime copy, guard applies
hosted handlers      cron/queue bundle and migration handler bundle every dependency
                     (npm, Yarn and Bun hoisted layouts)
  • A driver copy that cannot run in Workers (pg below 8.11, mysql2 below 3.13) fails the build with an error that names the version and the importing file.
  • Native deploys add nodejs_compat for pg/MySQL, as managed deploys do.
  • In pg/MySQL Worker builds, Void itself turns a CommonJS require of a Node built-in into an import. The Cloudflare plugin does the same, but it skips that step when the app has two Vite copies.
  • In framework dev servers, a bare void import in server code gets the handler exports (defineScheduled, defineQueue, …), as in Void apps.

MySQL migrations: one lock across deploys, the CLI, dev and Better Auth

void db migrate / reset / seed, vp dev    hosted deploy (through Hyperdrive)
GET_LOCK('void_migrations:<db hash>')     lease row in _void_migration_lock
then: wait while a lease is live          then: wait while IS_USED_LOCK is held
                                          renew the lease before each statement,
                                          and every 30 s during Better Auth
release with the session                  release = expire the row (no DELETE)

each file:  breakpoints ─► statements (split outside strings, names, comments)
            a trigger/procedure/function/event keeps the rest of its part
  • Better Auth's migrations run inside the same locked run. In vp dev, each step locks the database it changes.
  • void db reset, and the reset that void db seed does, hold the lock from the first drop until the files are applied again. They keep the lease table.
  • A failed file without breakpoints now gets the partial-apply note, because the runner knows which statements ran.

Decisions for review

Topic Decision
New public entry void/nitro Opt-in Nitro module: modules: ['void/nitro']. Nitro bundles server routes without Void's Vite plugin, so this is the only way to give them the app's void/db. A pg/MySQL app without it gets an error that says to add it.
New table _void_migration_lock (MySQL) One row per database. It holds the deploy lease. Hyperdrive pools per transaction, so a hosted run cannot hold GET_LOCK.
Scope only for pg/MySQL D1 and no-database apps keep their exact current output. A request scope moves D1 reads to a per-request session.
Lock wait times Deploys wait up to about 2 minutes (a stopped run's lease expires after 120 s). The CLI waits up to about 20 seconds.
Seed data outside the lock void db seed releases the lock after its reset. Seed data is ordinary writes, like app traffic, and a long seed must not block deploys.
One query per statement The CLI and the hosted handler send each statement alone. A file with 300 statements over a 20 ms link takes about 8 s instead of 3 s. In return the hosted path works through Hyperdrive and a failed file shows what ran.
Driver copy per import Each pg/mysql2 import gets the copy the bundler resolves, as Node dev and Vite Worker builds do. In a hoisted install, Void's client then uses the app's driver; an old one fails the build with a clear error.
SvelteKit pg/MySQL inline list Inline only what void/db needs. Inlining every dependency broke packages whose workerd build Kit's Node analyse step cannot load.
Node target build Installed packages that depend on void are bundled with their dependencies. The docs say to list a dependency that uses a native addon in the app.

Known limits (not in this PR)

  • A MySQL stored program followed by other statements in one breakpoint part fails on Void platform deploys. The server rejects the whole part, and the error asks for a breakpoint after the stored program, as the docs do.
  • void db reset fails when the migrations create a view (main has the same behavior).
  • SvelteKit: @sentry/sveltekit fails in deploy packaging, because Void's framework Worker packager resolves external packages with browser conditions. D1 apps on main fail the same way.
  • An installed package that depends on void, but is CommonJS, loads CommonJS through its relative imports, or is reached only through a package that does not depend on void, stays with Node (no guard on the Node target, the D1 client in SvelteKit dev).
  • Astro restarts astro dev once after "ready" on a fresh .astro/ folder (Astro's update check). Void's triggers keep working after the restart.
  • The deploy cron/queue bundle and the hosted migration handler carry their own driver copy.

Rollout

main ── this PR

Workers deployed before this PR, and the released 0.28.0 CLI, take no migration lock or lease.

Testing

Check Result
vp run build:all + node test/check-playground-build-artifacts.mjs pass
vp run check, vp lint, vp fmt pass
vp test run (full suite) pass (704 files, 13,286 tests; 1 file and 30 tests skipped by design)
Packed consumer smoke (npm, pnpm), with a new Node-target step pass
Live: MySQL 8.4 and Vitess, race shapes, reset during a deploy, Better Auth races (hosted and dev), 30 splitter cases incl. drizzle-kit and mysqldump output each file applied once, CLI and hosted results match
Live: React Router, Astro, SvelteKit, Nuxt, Analog with pg/MySQL; npm, pnpm, Yarn, Bun installs; dev and built Workers in Miniflare pass
Mutation runs: each fix part removed → a CI test fails pass

Fixes #410
Fixes #411
Fixes #412
Fixes #414
Fixes #415
Fixes #416
Fixes #417
Fixes #418
Fixes #419
Fixes #420

🤖 Generated with Claude Code

https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH

@Brooooooklyn
Brooooooklyn requested a review from a team as a code owner October 13, 2026 22:24
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T10:43:37.860390Z 0f43659 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-mysql-db-high-bugs-void-cloud.voidzero-docs.workers.dev (commit 12ee4c1)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
0f43659 2026-10-14T10:37:38.705Z View logs ↗
  • Build: Failed ❌

View logs ↗
c2d041d 2026-10-14T10:08:11.031Z View logs ↗
  • Build: Failed ❌

View logs ↗
870b9cb 2026-10-14T09:57:34.008Z View logs ↗
  • Build: Failed ❌

View logs ↗
a3f1768 2026-10-14T09:52:36.336Z View logs ↗
  • Build: Failed ❌

View logs ↗
76214c3 2026-10-14T09:40:20.450Z View logs ↗
  • Build: Failed ❌

View logs ↗
7e567da 2026-10-14T09:03:00.338Z View logs ↗
  • Build: Failed ❌

View logs ↗
cd26816 2026-10-14T08:43:38.851Z View logs ↗
  • Build: Failed ❌

View logs ↗
3009112 2026-10-14T08:05:51.530Z View logs ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://f9c0b364-void-cloud.voidzero-docs.workers.dev 12ee4c1 2026-10-13T22:37:47.546Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://1a8af065-void-cloud.voidzero-docs.workers.dev fd05217 2026-10-13T22:25:19.014Z Visit the dashboard ↗

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd05217ee6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/plugin-drizzle.ts Outdated
@Brooooooklyn
Brooooooklyn force-pushed the fix/mysql-db-high-bugs branch from fd05217 to 12ee4c1 Compare October 13, 2026 22:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12ee4c1477

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/runtime/migration-handler-mysql.ts
Brooooooklyn and others added 13 commits October 14, 2026 15:38
MySQL apps failed in workerd in three ways:

- vp dev: void/db imported mysql2 by file path. Vite only sends bare
  imports to the dep optimizer, so workerd got raw CommonJS and every
  void/db route returned "require is not defined". With Void auth on,
  the dev server did not start.
- dev and built Workers: no mysql2 connection set disableEval, so
  row-returning queries failed with "Code generation from strings
  disallowed for this context". This also broke the hosted
  /__void/migrate and auth migration routes.

void/db now lets drizzle build the mysql2 pool, so the only mysql2
import is drizzle's bare one. Void-owned worker runtimes prebundle
'void > mysql2' for MySQL apps. Every mysql2 connection that runs in
workerd sets disableEval: true, as Cloudflare's Hyperdrive docs
require.

Also correct the deploy migration comment: a failed MySQL file can
leave earlier statements applied, so reruns are not idempotent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
Void picked the workerd prebundle list by framework class, not by
where code runs:

- Astro's Cloudflare adapter runs its ssr environment in workerd, but
  got no include. An installed void/email failed with "exports is not
  defined" (#411) and MySQL void/db with "require is not defined".
- Default-mode apps also prebundled into Vite's unused Node ssr
  environment (#412).
- PostgreSQL void/db failed in dev with "require is not defined" when
  pg was not reachable from the app root (strict pnpm, no migrations).

The include now goes to every environment whose resolve conditions
contain workerd: Void's worker, class A frameworks and Astro. The
Cloudflare plugin sets them from its config hook, so plugin order does
not matter. The pg template lets drizzle build the pool, like MySQL,
and 'void > pg' serves drizzle's bare import.

Fixes #411
Fixes #412

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
Hosted framework deploys of MySQL apps with migrations could not start.
The migration handler bundle kept the bundler's
createRequire(import.meta.url), and workerd gives modules no
import.meta.url, so the whole Worker failed at startup. With npm, Yarn
or Bun, mysql2's dependencies also stayed bare imports.

The MySQL bundle now gets the same require shim as PostgreSQL. It uses
default imports, because safer-buffer needs the CommonJS shape of
node:buffer. It is built with noExternal: true, so hoisted
dependencies are bundled too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
Two MySQL runs at the same time could apply a data migration twice, and
a failed file left its earlier data changes applied, so a rerun applied
them again. Failed migrations also printed only the database error on
the CLI paths, for every dialect.

The MySQL runner now takes a per-database named lock and runs each file
in a transaction with its tracking row. The hosted handler uses the
transaction too; the platform already runs one migration per project.
MySQL still commits DDL on its own. When that leaves earlier statements
applied, the error says so and links the new docs section.

Every SQL runner now reports "migration <file> failed: <error>" and
keeps the database error as the cause.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
React Router, TanStack Start and vinext with PostgreSQL or MySQL threw
"Request-scoped resource is unavailable outside a runtime handler" on the
first void/db query, in dev and in built Workers. Astro failed the same way.

The class A worker wrappers now run each fetch inside withRuntimeEnv when
the app uses PostgreSQL or MySQL, so one request reuses one client. D1 apps
and apps without a database keep the same wrapper output.

Outside a Void request scope, the pg and MySQL db export now builds a client
for each use, like D1 does. That MySQL client closes idle connections, and
one query chain uses one client instead of one per property read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
With void installed from npm, SvelteKit's dev server externalized void
before any plugin ran. void/db then loaded the package's D1 runtime, so
PostgreSQL and MySQL apps failed and the hooks' env was a second copy.

The SvelteKit plugin now keeps void in Vite's SSR graph during serve. Bare
'void' imports in server code go to the handler entry, as they do in Void
apps, so the full plugin does not load into Kit's module runner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
…alls

A build decides what is external by resolving from the app root. With npm,
Yarn or Bun, pg, pg-types and @void/deploy-core are hoisted there, so the
hosted pg and sqlite migration handlers kept bare imports that workerd
cannot load ("No such module", "Unsupported require").

The migration handler build now bundles every package for each dialect.
Node built-ins stay external for the require shims.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
… the CLI

Two deploys of different projects that use one MySQL database, or a
deploy and `void db migrate`, could both read a file as pending and
apply it twice. The platform only locks one project's deploys, and the
hosted handler took no lock: it reaches MySQL through Hyperdrive, which
pools connections per transaction, so it cannot hold the CLI's
GET_LOCK, and it never saw that lock either.

The hosted handler now holds a lease row in `_void_migration_lock`,
written one autocommit statement at a time on its own connection and
renewed before each migration statement. After taking it, the handler
checks the CLI's named lock with IS_USED_LOCK. After GET_LOCK, the CLI
runner waits for a live lease. Each run publishes its own lock before
it checks for the other kind, so runs on one database never overlap.
Lease times are UTC, so session time zones and DST changes do not move
them. A Vitess connection target such as `app@primary` uses the lock
of its keyspace.

A run releases its lease by expiring the row rather than deleting it,
because InnoDB ends one of two waiting inserts with a deadlock when the
row is deleted under them. A lease statement that still loses a
deadlock polls again. Deploys wait up to about 2 minutes, enough for
the lease of a run that stopped early to expire; the CLI waits up to
about 20 seconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
…ed void on Node

With an installed void (npm or pnpm, not a workspace link), Vite can load
void outside the plugin path, which broke two things in dev:

- Node target: void/kv, void/storage and void/db loaded and returned 200
  instead of failing with "target: 'void/kv' is not available with target
  'node'". Vite's SSR import analysis externalizes a bare import that
  resolves into node_modules before any plugin resolveId runs, so the
  guard never saw it. A workspace link resolves outside node_modules and
  already threw. Builds already enforced the guard.
- SvelteKit: an installed package that imports void/* stays external and
  loads its own copy of void's env modules. Each copy made its own
  AsyncLocalStorage, so the package saw no request env and failed with
  "env: Cloudflare env is unavailable".

The Node-target plugin now inlines void (ssr.noExternal) outside builds,
for the dev server and Vitest, as SvelteKit dev already does, so every
void import reaches the guard. The request env and request scope stores
now live on globalThis under versioned Symbol.for keys, like authContext,
so every copy of the module reads the current request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
…ope Astro requests

A PostgreSQL or MySQL query built or prepared at module scope, such as
`const q = db.select().from(users)` or a cached `.prepare()`, worked on
the first request and then failed on every later one: pg hung until
workerd cancelled the request, MySQL failed with "Cannot perform I/O on
behalf of a different request". drizzle keeps the client it was built
with, and that client was the first request's pool, whose socket workerd
ties to that request.

Astro apps also had no Void request scope, so every query built its own
client. Twenty concurrent requests with eight queries each exhausted
Postgres ("sorry, too many clients already") and failed about half of
them, in dev and in the built Worker.

void/db for PostgreSQL and MySQL is now one drizzle instance. Its client
looks up the current request's pool on each call, so a query runs on the
pool of the request that executes it, and building a query creates no
pool. drizzle's pool checks still see a pool, so a transaction holds one
connection; their answers are kept per isolate, so outside a request
scope they build no extra pools. Inside a request, reading `db` builds
the request's pool first, so a missing DATABASE_URL or Hyperdrive still
reports its own error rather than drizzle's "Failed query".

For PostgreSQL and MySQL, the Astro Cloudflare adapter's default entry
now runs each request inside Void's request scope, in dev, the build and
its prerender worker. D1 Astro apps keep their current entry.

The Astro PostgreSQL integration test also failed now and then under
parallel load: Astro's update check writes .astro/settings.json in the
fresh test copy, the watcher can report that one write twice, and the
second report restarts the dev server mid-request. The test turns the
update check off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
void/db's MySQL pools never closed an idle connection. mysql2 closes
idle connections only from a timer that it starts when it builds a pool
with maxIdle below connectionLimit. The request pools set both to 1 and
createDb kept mysql2's defaults (both 10), so no timer ran: on Node, as
in SvelteKit dev, every request left its connection open, and in workerd
a WebSocket left one open per message. The pool built outside a request
scope used maxIdle 0, which starts the timer when the pool is built:
workerd rejects a timer in global scope, and on Node it fires every
second until the pool ends, which nothing does, so the process stays
alive.

The pools now set no maxIdle, so mysql2 starts no timer. void/db closes
a connection once it has been idle, timed from the pool's release event,
which runs in the request that ran the query, and stops the timer when
the connection is acquired again. Request and outside-scope pools close
after 1 second, createDb pools after 60 seconds (mysql2's default). The
timer is unref'd, so a Node script that ends its pool exits at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
…cope

A built SvelteKit app with a PostgreSQL or MySQL database failed every
database request in workerd. Kit builds its server in Vite's Node `ssr`
environment, and the adapter's Worker runs that output: the CommonJS
requires of Node built-ins in pg and mysql2 became
`createRequire(import.meta.url)`, which throws in workerd, and pg loaded
pg-cloudflare's empty Node stub. When the driver resolves from the app
root (npm, Yarn and Bun installs, or an app that lists pg or mysql2),
Kit left it external, and Void's browser-target packaging bundled it
with browser stubs instead. The built app also ran outside Void's
request scope, so each query opened its own connection.

For SvelteKit apps on PostgreSQL or MySQL, Void now builds that
environment the way the Cloudflare plugin builds Workers: `workerd`
conditions, every dependency bundled, and neutral output. A required
Node built-in that workerd provides for the app's compatibility date and
flags, as Cloudflare's own preset lists them, is served from a `node:`
import. Any other require stays as it is and throws only if it runs, so
a module that requires `tty` without calling it still loads. Each
request runs in Void's request scope with Kit 2's `platform.env` or
Kit 3's `cloudflare:workers` env; Kit's prerender in Node runs outside
it. D1 apps keep Kit's output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
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
@Brooooooklyn
Brooooooklyn force-pushed the fix/mysql-db-high-bugs branch 2 times, most recently from 3009112 to cd26816 Compare October 14, 2026 08:42

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd2681649f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/installed-packages.ts
@Brooooooklyn
Brooooooklyn force-pushed the fix/mysql-db-high-bugs branch from cd26816 to 7e567da Compare October 14, 2026 09:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e567dab5e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/plugin-drizzle.ts Outdated
Comment thread packages/void/src/installed-packages.ts
@Brooooooklyn
Brooooooklyn force-pushed the fix/mysql-db-high-bugs branch from 7e567da to 76214c3 Compare October 14, 2026 09:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76214c3db2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/installed-packages.ts Outdated
@Brooooooklyn
Brooooooklyn force-pushed the fix/mysql-db-high-bugs branch 2 times, most recently from a3f1768 to 870b9cb Compare October 14, 2026 09:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 870b9cb577

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/installed-packages.ts Outdated
@Brooooooklyn
Brooooooklyn force-pushed the fix/mysql-db-high-bugs branch from 870b9cb to c2d041d Compare October 14, 2026 10:06

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2d041df3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/runtime/mysql-migration-sql.ts
Brooooooklyn and others added 4 commits October 14, 2026 18:36
…rough auth and reset

A MySQL migration file without statement breakpoints ran as one
query. Void platform deploys sent it to Hyperdrive as a
multi-statement query, which Hyperdrive does not support. The CLI
could not tell that earlier statements of a failed file had run, so
the error had no partial-apply note.

The migration lock also ended too early. The hosted handler released
its lease before Better Auth's migrations ran, and `vp dev` ran them
after the lock was released (auth-only apps took no lock at all). Two
runs could then add the same column and fail. `void db reset` and
`void db seed` dropped every table, including the deploy lease table,
without the lock, so a deploy in progress failed.

Both MySQL runners now split each file into single statements, at
breakpoints and then at semicolons outside strings, quoted names and
comments. A statement that starts a trigger, procedure, function or
event keeps the rest of its part with it. Words such as `event` or a
`BEGIN;` transaction still split. The hosted connection no longer
sets multipleStatements.

Better Auth's MySQL migrations now run inside the lock. The hosted
handler renews its lease on a timer while they run. `vp dev` holds
the lock of the database each step changes: the files use .env's
DATABASE_URL, and Better Auth uses the shell's before .env. When both
name one database, auth runs inside the same locked run, also for an
app without migration files.
`void db reset` and `seed` hold the lock from the first drop until
the files are applied again, and keep the lease table.

The docs name reset and seed in the lock note, say that a sharded
Vitess commit is not atomic across shards, and ask for breakpoints
around MySQL stored programs.

Fixes #414
Fixes #415

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
…lteKit

An installed void (npm or pnpm, not a workspace link) broke the Node
target. The build left the app's void/handler to Node, while the
generated routes bundle their own copy of Void's runtime. A
ValidationError from a page action then failed the routes' instanceof
check, so the request returned 500. An API-only Node build crashed in
buildApp: Vite 8 runs that hook for the single-environment builder
too, which has no client environment. An installed package that
imports void/kv skipped the Cloudflare-only guard, because Vite leaves
a package in node_modules to Node before any plugin sees its imports.
The maintainer notes listed void/ws as blocked and left out
void/sandbox.

SvelteKit had the same gap in dev: in a PostgreSQL or MySQL app, an
installed package's void/db got the D1 client. The PostgreSQL and
MySQL build inlined every dependency with workerd conditions, so a
package whose workerd build imports cloudflare:workers (capnweb) broke
Kit's analyse step, which loads that output in Node.

A new helper finds installed ES module packages that depend on void
or @void/*. It reads the app's dependencies and recurses only into
those packages. CommonJS packages, including one whose import entry
wraps its require build, stay with Node.

The Node target now inlines void, @void/* and those packages in dev,
Vitest and the build. The build keeps a dynamic cloudflare: import
external, as Void's runtime loads one only in workerd, and fails on a
static one, which Node cannot load. The guard error names the
importing file. buildApp builds the client only when it exists.

In PostgreSQL and MySQL apps, SvelteKit dev inlines the same
packages, so they get the app's void/db. The PostgreSQL and MySQL build inlines only what void/db
needs: void, drizzle-orm, the driver with its dependencies, and those
packages. Other dependencies follow Kit's own rules, as in D1 builds.
The D1 client error now says when a package gets the app's void/db.

Fixes #416
Fixes #420

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
… and fix Astro preview and restarts

React Router, TanStack Start, vinext and Astro dev servers import every
cron and queue file into workerd for the dev triggers. A cron that
imports defineScheduled from 'void' loaded the package entry, which is
the Vite plugin. The dependency optimizer then tried to bundle Wrangler,
so every dev trigger returned 500. In Astro every request failed.

`astro preview` after `astro build` failed with "No build output
found". The build moves Wrangler's deploy config pointer aside, and only
a Vite preview put it back. `astro preview` loads the config but runs
none of Void's Vite hooks.

When Astro restarts the dev server, the new server starts before the old
one closes. The new plugin saw Void's middleware file and left it alone,
and the old one then deleted it, so /__void/scheduled returned 404.

The fix:
- A shared plugin sends bare 'void' to void/handler in the server
  environments of class A frameworks and Astro, in dev and build.
  Client code keeps the package entry.
- voidPlugin() puts the deploy config pointer back when it runs for
  `astro preview`, and removes it when the process exits, as a Vite
  preview does.
- The Astro dev middleware keeps a per-process count of the dev servers
  that use it. A new server joins the count, and the last one to close
  removes the file and its .gitignore line.

Astro's own restart right after "ready" comes from its update check,
not from Void code.

Fixes #417
Fixes #419

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
…ach import's own driver

Nuxt and Analog apps with a PostgreSQL or MySQL database did not deploy
as working Workers. A native Cloudflare deploy did not add
nodejs_compat. Nitro reads that flag only from a wrangler.* file, so it
bundled Node stubs, and node:crypto calls failed in the Worker. The
init notes for Nuxt and Analog left out the Cloudflare preset and the
dev config, so Nitro built a Node server and dev had no bindings.
Analog's Vite server build compiled its own void/db, drizzle and driver
into a page chunk that Nitro then bundled. With a hoisted pg this
failed on pg-native; otherwise it added unused driver copies.
nuxi typecheck could not find @Schema and rejected cloudflareDev.
Nitro and the deploy cron and queue bundles also gave every pg or
mysql2 import the driver from Void's own dependencies, even an app's
own import of another version. No test ran a Nitro-built pg or MySQL
Worker in workerd.

Native deploys now add nodejs_compat for pg and MySQL, as managed
deploys do. void/nitro turns on Nitro's cloudflare.nodeCompat for pg
and MySQL Worker builds. For D1 it does so only when the flags both
deploy paths give the Worker list nodejs_compat. The Nuxt and Analog
notes print a config that builds for Cloudflare and runs dev with
bindings. In a Nuxt or Analog build, Void's Vite plugin leaves a pg or
MySQL void/db import to Nitro, so the app has one client and one
driver copy, or the void/nitro hint when the module is missing. The
Nuxt docs and playground use a top-level alias and cloudflare.dev.

Each pg or mysql2 import now gets a workerd build of the copy the
bundler resolves for it. The lookup runs as a require, so Rollup's
CommonJS plugin does not wrap the raw driver. Workers need pg 8.11
or later (for its Cloudflare socket) and mysql2 3.13 or later (for
disableEval). The build now fails on an older copy with an error that
names the driver version and the importer.

A new test builds real Nitro Workers for pg and MySQL and runs them in
Miniflare.

Fixes #418

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
@Brooooooklyn
Brooooooklyn force-pushed the fix/mysql-db-high-bugs branch from c2d041d to 0f43659 Compare October 14, 2026 10:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f436597e5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/plugin-migration.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment