You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 293ea34
Browse filesBrowse the repository at this point in the historyBrowse files
feat(db): support Drizzle's casing option in void/db and void db (#494)
Fixes#466
## What
```ts
// void.config.ts
database: 'pg' // unchanged shorthand
database: { dialect: 'pg', casing: 'snake_case' } // new object form
database: { casing: 'snake_case' } // D1 (default dialect)
```
With `casing: 'snake_case'`, a column declared without a name
(`createdAt: timestamp()`) is stored as `created_at`, both in `void/db`
queries and in what `void db generate` writes.
```
database.casing ──► runtime void/db (D1, pg, mysql: db, createDb, createPrimaryDb)
├─► void db seed
└─► .void/drizzle.config.json "casing" → void db generate / push / deploy drift check
```
## Design
- **Why the object form:** all database options stay under one key, and
the string shorthand keeps working. `dialect` defaults to `"sqlite"`
(D1).
- **Validation:** unknown keys, a bad `dialect` or a bad `casing` each
give a clear config error. `schema.json` accepts both forms.
- **One way to read it:** every reader of `database` goes through
`getDatabaseDialect` / `getDatabaseCasing`. That covers application
analysis (deploy, Hyperdrive, manifest dialect), the CLI (`db`, `gen`,
`init`, wrapper), nitro, routing, the drizzle plugin and the framework
plugin. `void init` keeps the casing when it changes the dialect.
- **No casing means no change:** without `casing`, the generated
`void/db` module and the drizzle-kit config are byte-identical to
`main`. This was checked in all 12 dialect × schema × forced-virtual
combinations.
- **Introspection** (drizzle-kit's separate `introspect.casing`) is not
touched.
- **Auth:** the built-in auth tables name every column explicitly, so
`casing` doesn't change them. A real sign-up worked with snake_case on.
## Tests
| Area | Test |
|---|---|
| Runtime, real queries | `db-casing.test.ts`: D1 (Miniflare, inside a
request and outside it via `createPrimaryDb`), PGlite, and fake MySQL
hit snake_case columns |
| drizzle-kit | real drizzle-kit generate writes `created_at`; config
writer emits `casing` |
| Plugins | `plugin-vite-environment.test.ts`: Pages, class A (TanStack
Start) and class B (SvelteKit) apps read casing from the config |
| Config / init / seed / prepare | object-form parsing and errors; init
keeps casing; seed uses casing; `void prepare` agrees with the plugin on
`db.d.ts` |
- On `main`'s source, 36 of these tests fail; with the change, all 482
pass.
- Undoing each wiring point fails at least one test.
- `check:sdk` (12/12), `check:platform` (7/7), `vp fmt --check`, `vp
lint`, and 38 db/deploy/config/inference unit files (642 tests) all
pass.
- A scratch app (dev, build + preview, `void db
generate`/`migrate`/`seed`/`push`, deploy drift check) showed snake_case
columns in D1 (`PRAGMA table_info`).
Docs: `docs/guide/database.md` (Column casing) and
`docs/reference/config.md`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/guide/database.md
+26Lines changed: 26 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -114,6 +114,32 @@ export * from "./schema/users";
114
114
export*from"./schema/posts";
115
115
```
116
116
117
+
### Column casing
118
+
119
+
By default, a column declared without a name, such as `createdAt: timestamp()`, uses its key as the column name. Set `database.casing` to `'snake_case'` to store these columns as `created_at` instead. It applies to `void/db` queries and to `void db` commands such as `generate`, `push`, and `seed`. Columns with an explicit name, such as `text('created_at')`, keep that name.
Omit `dialect` for D1. If your database already has tables, run `void db generate` after you change `casing` and review the migration before you apply it.
142
+
117
143
## Querying
118
144
119
145
Void re-exports all common [Drizzle query operators](https://orm.drizzle.team/docs/select) from `void/db` so you don't need to install `drizzle-orm` separately. Import `db` from `void/db` and your tables from `@schema`:
|`dialect`|`"sqlite"` (default), `"pg"`, `"mysql"`| Database backend. `"sqlite"` is D1 |
155
+
|`casing`|`"snake_case"`, `"camelCase"`|[Drizzle casing](https://orm.drizzle.team/docs/sql-schema-declaration#camel-and-snake-casing) for `void/db` queries and `void db` commands. Omit to use each key as its column name |
156
+
144
157
When using an external database:
145
158
146
159
- Add `DATABASE_URL` to `.env` for local development
Copy file name to clipboardExpand all lines: packages/void/schema.json
+23-3Lines changed: 23 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -392,9 +392,29 @@
392
392
"additionalProperties": false
393
393
},
394
394
"database": {
395
-
"type": "string",
396
-
"enum": ["pg", "mysql"],
397
-
"description": "Database backend. Omit for D1 (SQLite, default). Set to \"pg\" for PostgreSQL or \"mysql\" for MySQL via Hyperdrive."
395
+
"description": "Database backend. Omit for D1 (SQLite, default). Set to \"pg\" for PostgreSQL or \"mysql\" for MySQL via Hyperdrive, or use an object to also set the column casing.",
396
+
"oneOf": [
397
+
{
398
+
"type": "string",
399
+
"enum": ["pg", "mysql"]
400
+
},
401
+
{
402
+
"type": "object",
403
+
"properties": {
404
+
"dialect": {
405
+
"type": "string",
406
+
"enum": ["sqlite", "pg", "mysql"],
407
+
"description": "Database backend. Defaults to \"sqlite\" (D1)."
408
+
},
409
+
"casing": {
410
+
"type": "string",
411
+
"enum": ["snake_case", "camelCase"],
412
+
"description": "Drizzle casing: how columns declared without a name are named in the database, for queries and generated migrations."
0 commit comments