Describe the Bug
When upgrading to Drizzle v1 (Beta) and utilizing its new "Folder V3" architecture alongside tauri-plugin-libsql-api in a Nuxt/Vite environment, the migrate() helper fails with a SQLite error: UNIQUE constraint failed: __drizzle_migrations.hash.
This happens because Drizzle v1 removes the flat file system and journal.json approach, grouping each individual migration inside its own subdirectory where the SQL payload file is strictly named migration.sql. The runner fails to cleanly isolate the unique folder names from the raw file paths.
Environment
- Framework: Nuxt 4 / Vite
- Runtime: Tauri
- Library: tauri-plugin-libsql-api
- ORM: Drizzle ORM / Drizzle Kit
v1.0.0-beta.x (Folder V3 layout)
Steps to Reproduce
- Generate migrations using Drizzle v1 Beta, which yields a directory structure like this:
db/migrations/
├── 20260508135710_init/
│ └── migration.sql
└── 20260605110000_add_users/
└── migration.sql
- Collect the .sql modules via Vite's import.meta.glob inside a Nuxt plugin:
const migrations = import.meta.glob<string>('~/db/migrations/**/*.sql', {
eager: true,
query: '?raw',
import: 'default',
})
await Database.load('sqlite:app.db')
await migrate('sqlite:app.db', migrations)
- Run the application. The migration runner crashes when attempting to parse the second migration folder:
SQLite failure: UNIQUE constraint failed: __drizzle_migrations.hash
Root Cause & Findings
Under the hood, Vite's import.meta.glob spits out an object where every key maps to the absolute/relative file path, meaning all keys terminate uniformly with /migration.sql:
"/src/db/migrations/20260508135710_init/migration.sql"
"/src/db/migrations/20260605110000_add_users/migration.sql"
The current implementation of migrate() in tauri-plugin-libsql-api appears to strip the preceding path logic and reads only the filename ("migration.sql"). Because it interprets both files as having an identical name string, the internal loop breaks. It doesn't treat them as separate chronological ledger records, attempting to duplicate or rewrite metadata hashes into the __drizzle_migrations tracking table.
Expected Behavior
The migrate() logic should be adapted to handle Drizzle v1 migration folders by using the second-to-last path segment (the parent directory name) or the full relative path string to ensure distinct uniqueness across the ledger.
Temporary Workaround
Manually intercepting the glob keys and mapping them to their containing Drizzle v1 folder names before throwing them into migrate():
import { Database, migrate } from 'tauri-plugin-libsql-api'
export default defineNuxtPlugin(async (_nuxtApp) => {
const rawMigrations = import.meta.glob<string>('~/db/migrations/**/*.sql', {
eager: true,
query: '?raw',
import: 'default',
})
const migrations = Object.keys(rawMigrations).reduce(
(acc, path) => {
const segments = path.split('/')
const folderName = segments[segments.length - 2]
if (folderName) {
const uniqueKey = `${folderName}.sql`
acc[uniqueKey] = rawMigrations[path] as string
}
return acc
},
{} as Record<string, string>,
)
await Database.load('sqlite:app.db')
await migrate('sqlite:app.db', migrations)
})
Describe the Bug
When upgrading to Drizzle v1 (Beta) and utilizing its new "Folder V3" architecture alongside
tauri-plugin-libsql-apiin a Nuxt/Vite environment, themigrate()helper fails with a SQLite error:UNIQUE constraint failed: __drizzle_migrations.hash.This happens because Drizzle v1 removes the flat file system and
journal.jsonapproach, grouping each individual migration inside its own subdirectory where the SQL payload file is strictly namedmigration.sql. The runner fails to cleanly isolate the unique folder names from the raw file paths.Environment
v1.0.0-beta.x(Folder V3 layout)Steps to Reproduce
Root Cause & Findings
Under the hood, Vite's import.meta.glob spits out an object where every key maps to the absolute/relative file path, meaning all keys terminate uniformly with /migration.sql:
"/src/db/migrations/20260508135710_init/migration.sql""/src/db/migrations/20260605110000_add_users/migration.sql"The current implementation of
migrate()intauri-plugin-libsql-apiappears to strip the preceding path logic and reads only the filename("migration.sql"). Because it interprets both files as having an identical name string, the internal loop breaks. It doesn't treat them as separate chronological ledger records, attempting to duplicate or rewrite metadata hashes into the__drizzle_migrationstracking table.Expected Behavior
The
migrate()logic should be adapted to handle Drizzle v1 migration folders by using the second-to-last path segment (the parent directory name) or the full relative path string to ensure distinct uniqueness across the ledger.Temporary Workaround
Manually intercepting the glob keys and mapping them to their containing Drizzle v1 folder names before throwing them into
migrate():