Repository navigation
Commit b89a733
fix(db): keep migrations, reset, seed and auth schema upgrades from damaging databases (#435)
## Problem
Some `void db` commands, the dev migration step, and the auth schema
upgrade could change the wrong database, apply SQL that was never
checked, or leave a database that no command can repair.
```
command / step what happened on main
───────────────────────────────────── ─────────────────────────────────────────────────────────────
vp dev, pending DROP without pragma ──▶ warning, then applied anyway (also to DATABASE_URL in .env)
db migrate --remote, D1 binding ──▶ checks db/migrations, Cloudflare applies ./migrations
without migrations_dir (unchecked SQL on production D1)
db rename-migrations, direct CF ──▶ files renamed, deployed records not → next deploy runs them again
db rename-migrations, MySQL ──▶ half of the tracking UPDATEs committed, files rolled back
db reset / seed, PostgreSQL enum ──▶ tables dropped, enum kept → migration fails → empty database
db reset / seed, MySQL view ──▶ same, with "Table already exists"
db seed --file typo ──▶ database reset first, then "file not found"
db reset --remote ──▶ flag ignored, local database reset, success printed
db status, PG/MySQL down ──▶ every migration "pending", exit 0
db export, MySQL ──▶ DATE/DATETIME as ISO-Z (restore fails), JSON as [object Object],
tables named _x left out
auth field added to a table w/ rows ──▶ ADD COLUMN NOT NULL fails (SQLite/D1, PG), even with a default
generateId: 'serial' / 'uuid' (PG) ──▶ generated auth schema has no id default → sign-up fails
first vp dev, D1 + auth ──▶ "unable to open database file"
PG auth, no DATABASE_URL ──▶ nothing created, "Applied Better Auth migrations" printed
hosted PG/MySQL, auth step fails ──▶ 500 with migrations: [] → platform loses the applied files
```
## What this PR changes
```
destructive SQL ──▶ vp dev applies none of the pending files, prints the validator error, keeps running
(same rule as void db migrate and deploy)
remote D1 ──▶ db migrate --remote stops when the binding has no migrations_dir
rename ──▶ stops for direct Cloudflare (saved, or a real D1/Hyperdrive id in checked-in config)
and for an unlinked Void project; MySQL tracking renames run in one transaction
reset / seed ──▶ PG drops public tables, views, standalone sequences and custom types (public.<name>);
stops first if an object in another schema uses one of those views, sequences or types
MySQL drops tables and views
seed: check file → confirm non-local DB → import module → reset → seed
flags ──▶ each void db action accepts only the flags it reads
status ──▶ unreadable local PG/MySQL: `unknown` + warning, or exit 1 when remote is unknown too
export ──▶ MySQL dates, JSON, BIGINT as MySQL text; only Void's own tables are skipped
auth schema ──▶ fixed defaults on ADD COLUMN; clear refusal on a populated SQLite/PG table
PG serial → identity, uuid → gen_random_uuid(); D1 folder created; PG URL check
handlers ──▶ 500 body lists the files that committed before the auth step failed
messages ──▶ paths follow sourceDir; "Start the dev server" (no `void dev` command exists)
codegen / init ──▶ .void/tsconfig.json lists only files that exist; init demo follows the chosen dialect
```
## Decisions
| Topic | Decision |
| --- | --- |
| Destructive file in dev | Refuse every pending file for that start,
not only the bad one, so files never apply out of order. The dev server
still starts. |
| `rename-migrations` on direct Cloudflare | Refuse. Renaming is
cosmetic: old numeric names and new timestamp names sort the same for
Void and for D1. A remote rename needs credentials and an undo across
two systems. |
| Auth fields on MySQL | No refusal. MySQL fills old rows on its own,
and that path works today. MySQL now gets the declared default. |
| Auth defaults in the Drizzle schema | Not added. `.default(false)` on
`user.emailVerified` makes drizzle-kit rebuild `user` on SQLite; on D1
the `DROP TABLE user` cascades and deletes every session and account. |
| Unknown `void db` flags | Error. Every flag in help, docs and skills
stays accepted. |
| PG reset and other schemas | Before any drop, reset stops and lists
the objects in other schemas that use a `public` view, sequence or type
it drops (`DROP TYPE … CASCADE` would delete their columns). Table drops
keep the existing `CASCADE` behavior. |
## Behavior changes to note in the release
- `vp dev` no longer applies a pending destructive migration without `--
void:allow-destructive`. It applies no pending file for that start.
- `void db migrate --remote` needs `migrations_dir` on the D1 binding.
Void writes it on every provision.
- `void db rename-migrations` stops for direct Cloudflare projects and
for Void projects that are not linked.
- `void db <action>` rejects flags that the action does not use, for
example `void db reset --remote`.
- `void db status` exits 1 when it can read neither the local nor the
remote PostgreSQL/MySQL database.
- `void db seed` asks about a non-local database before it loads the
seed module.
- Adding a required auth field with no fixed default to a SQLite/D1 or
PostgreSQL table that has rows fails with a message that says how to fix
it.
- Adding a required `unique` auth field to a table with two or more rows
fails before any change, on every dialect.
- PostgreSQL apps with `generateId: 'serial'` or `'uuid'`: deploy
reports schema drift until `void db generate` writes the migration that
adds the id default.
## Known limits
- Direct Cloudflare auth upgrades come from `void db generate`; a
required field with a default still has no backfill there.
- A fresh clone of a hosted project has no `.void/project.json`, so
`rename-migrations` cannot see the deployment. After `void connect` it
stops as expected.
- PostgreSQL reset drops `public` tables with `CASCADE`, as before: a
view, a foreign key, an inheriting table or a partition in another
schema that depends on a `public` table goes with it.
- PostgreSQL reset keeps plain functions and schemas other than
`public`. A migration with `CREATE FUNCTION` or `CREATE SCHEMA` still
fails on reset.
- A MySQL dump restored with `NO_BACKSLASH_ESCAPES` changes backslashes
in strings (same as main). MySQL export still skips views (same as
main).
- MySQL DDL commits on its own, so a failed MySQL migration can stay
partly applied. The error and the docs explain the recovery.
## Testing
| Check | Result |
| --- | --- |
| New and changed tests | Each fails on main (20 test files, 40 failures
on main), for the right reason |
| PostgreSQL reset | postgres:17 and PGlite: enums, domains, ranges,
composite types, sequences, views, matviews, extension objects,
`search_path`, objects in other schemas |
| MySQL reset, export, restore | mysql:8.4: views, FKs, dates in a
non-UTC zone, JSON, BIGINT limits, DECIMAL, BLOB, BIT, round trip
through `void db execute --file` |
| Auth migrations | node:sqlite, Miniflare D1, postgres:17, mysql:8.4:
defaults with quotes and backslashes, refusal, serial and uuid ids with
Better Auth sign-up |
| Dev plugin | SQLite, PG, MySQL: harmless start, pragma'd DROP, refused
DROP |
| CLI | Built CLI against real databases: every documented flag, menu
path, status, seed, rename, absolute paths |
| `vp run build:all`, `vp run check`, `vp lint`, `vp fmt --check` | pass
|
| `vp run test` | 711 files pass, 1 skipped |
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>1 parent 7b17057 commit b89a733
53 files changed
Lines changed: 2271 additions & 227 deletions
File tree
- docs
- guide/database
- reference/cli
- packages/void
- src
- auth
- cli
- codegen
- migrations
- runtime
- test
- integration
- unit
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
76 | | - | |
| 76 | + | |
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
95 | 97 | | |
96 | 98 | | |
97 | 99 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
33 | 35 | | |
34 | 36 | | |
35 | 37 | | |
| |||
177 | 179 | | |
178 | 180 | | |
179 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
180 | 200 | | |
181 | 201 | | |
182 | 202 | | |
| |||
307 | 327 | | |
308 | 328 | | |
309 | 329 | | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
310 | 337 | | |
311 | 338 | | |
312 | 339 | | |
| |||
358 | 385 | | |
359 | 386 | | |
360 | 387 | | |
361 | | - | |
362 | | - | |
363 | | - | |
364 | 388 | | |
365 | 389 | | |
366 | 390 | | |
| |||
536 | 560 | | |
537 | 561 | | |
538 | 562 | | |
| 563 | + | |
539 | 564 | | |
540 | 565 | | |
541 | 566 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
1 | 3 | | |
2 | 4 | | |
3 | 5 | | |
| |||
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
21 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
| |||
27 | 32 | | |
28 | 33 | | |
29 | 34 | | |
30 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
31 | 44 | | |
32 | 45 | | |
33 | 46 | | |
34 | 47 | | |
35 | 48 | | |
| 49 | + | |
| 50 | + | |
36 | 51 | | |
37 | 52 | | |
38 | 53 | | |
| |||
52 | 67 | | |
53 | 68 | | |
54 | 69 | | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | 70 | | |
61 | 71 | | |
62 | 72 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
| 67 | + | |
67 | 68 | | |
68 | 69 | | |
69 | 70 | | |
| |||
206 | 207 | | |
207 | 208 | | |
208 | 209 | | |
209 | | - | |
| 210 | + | |
210 | 211 | | |
211 | 212 | | |
212 | 213 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
99 | 116 | | |
100 | 117 | | |
101 | 118 | | |
| |||
131 | 148 | | |
132 | 149 | | |
133 | 150 | | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
134 | 155 | | |
135 | 156 | | |
136 | 157 | | |
| |||
0 commit comments