Repository navigation
fix(void): make PostgreSQL and MySQL void/db work across frameworks, builds and deploys - #413
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🚀 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 deploymentPreview Deployments by commit
|
There was a problem hiding this comment.
💡 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".
fd05217 to
12ee4c1
Compare
There was a problem hiding this comment.
💡 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".
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
3009112 to
cd26816
Compare
There was a problem hiding this comment.
💡 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".
cd26816 to
7e567da
Compare
There was a problem hiding this comment.
💡 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".
7e567da to
76214c3
Compare
There was a problem hiding this comment.
💡 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".
a3f1768 to
870b9cb
Compare
There was a problem hiding this comment.
💡 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".
870b9cb to
c2d041d
Compare
There was a problem hiding this comment.
💡 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".
…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
c2d041d to
0f43659
Compare
There was a problem hiding this comment.
💡 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".
Problem
PostgreSQL and MySQL
void/dbfailed in most places outside a plain Void app onmain. These failures were found while we verified #410, #411 and #412, and in the follow-up issues #414 to #420: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
createDbpools: 60 s), timed from mysql2's release event. No pool timer runs in global scope.globalThisunder versionedSymbol.forkeys, asauthContextdoes, so every copy of void's env module sees the current request.Builds
nodejs_compatfor pg/MySQL, as managed deploys do.requireof 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.voidimport 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
vp dev, each step locks the database it changes.void db reset, and the reset thatvoid db seeddoes, hold the lock from the first drop until the files are applied again. They keep the lease table.Decisions for review
void/nitromodules: ['void/nitro']. Nitro bundles server routes without Void's Vite plugin, so this is the only way to give them the app'svoid/db. A pg/MySQL app without it gets an error that says to add it._void_migration_lock(MySQL)GET_LOCK.void db seedreleases the lock after its reset. Seed data is ordinary writes, like app traffic, and a long seed must not block deploys.pg/mysql2import 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.void/dbneeds. Inlining every dependency broke packages whose workerd build Kit's Node analyse step cannot load.voidare 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)
void db resetfails when the migrations create a view (mainhas the same behavior).@sentry/sveltekitfails in deploy packaging, because Void's framework Worker packager resolves external packages with browser conditions. D1 apps onmainfail the same way.astro devonce after "ready" on a fresh.astro/folder (Astro's update check). Void's triggers keep working after the restart.Rollout
Workers deployed before this PR, and the released 0.28.0 CLI, take no migration lock or lease.
Testing
vp run build:all+node test/check-playground-build-artifacts.mjsvp run check,vp lint,vp fmtvp test run(full suite)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