drizzle-team / drizzle-team/drizzle-orm
[BUG]: drizzle-kit pull generates incorrect schemas.ts for 'auth' Supabase schema
- Dominant language
- TypeScript
- Stars
- 35.8k
- Forks
- 1.6k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 4
Description
### Report hasn't been filed before.
- [x] I have verified that the bug I'm about to report hasn't been filed before.
### What version of `drizzle-orm` are you using?
0.44.2
### What version of `drizzle-kit` are you using?
0.31.1
### Other packages
"@supabase/supabase-js": "^2.49.10",
### Describe the Bug
**What is the undesired behavior?** `drizzle-kit pull` generates TypeScript schema files with malformed string literals containing unclosed single quotes in `.default()` method calls, making the generated code syntactically invalid and preventing TypeScript compilation.
**What are the steps to reproduce it?**
1. Set up a Supabase PostgreSQL database with auth schema enabled
2. Configure to connect to the Supabase database `drizzle.config.ts`
3. Run `npx drizzle-kit pull` to introspect the database schema
4. Attempt to compile the generated TypeScript files
**What is the desired result?** Generated schema should contain valid TypeScript syntax with properly closed string literals, allowing the code to compile successfully.
**Database Engine & Cloud Provider:**
- PostgreSQL via Supabase cloud platform
- Using `@supabase/supabase-js: ^2.49.10`
**TypeScript Version:** TypeScript 5.8.3
**Generated Code Example:**
```typescript
// ❌ Generated with syntax errors
phoneChange: text("phone_change").default('),
phoneChangeToken: varchar("phone_change_token", { length: 255 }).default('),
emailChangeTokenCurrent: varchar("email_change_token_current", { length: 255 }).default('),
ipAddress: varchar("ip_address", { length: 64 }).default(').notNull(),
// ✅ Should be generated as
phoneChange: text("phone_change").default(''),
phoneChangeToken: varchar("phone_change_token", { length: 255 }).default(''),
emailChangeTokenCurrent: varchar("email_change_token_current", { length: 255 }).default(''),
ipAddress: varchar("ip_address", { length: 64 }).default('').notNull(),
```
**Impact:**
- TypeScript compilation fails with "Unterminated string literal" errors
- Generated schema files are completely unusable
- Requires manual fixing after every `drizzle-kit pull` operation
- Breaks automated CI/CD pipelines that depend on schema generation
This appears to specifically affect string fields with empty string defaults when introspecting Supabase's auth schema tables.
Providing full usersInAuth with errors
```typescript
export const usersInAuth = auth.table("users", {
instanceId: uuid("instance_id"),
id: uuid().notNull(),
aud: varchar({ length: 255 }),
role: varchar({ length: 255 }),
email: varchar({ length: 255 }),
encryptedPassword: varchar("encrypted_password", { length: 255 }),
emailConfirmedAt: timestamp("email_confirmed_at", { withTimezone: true, mode: 'string' }),
invitedAt: timestamp("invited_at", { withTimezone: true, mode: 'string' }),
confirmationToken: varchar("confirmation_token", { length: 255 }),
confirmationSentAt: timestamp("confirmation_sent_at", { withTimezone: true, mode: 'string' }),
recoveryToken: varchar("recovery_token", { length: 255 }),
recoverySentAt: timestamp("recovery_sent_at", { withTimezone: true, mode: 'string' }),
emailChangeTokenNew: varchar("email_change_token_new", { length: 255 }),
emailChange: varchar("email_change", { length: 255 }),
emailChangeSentAt: timestamp("email_change_sent_at", { withTimezone: true, mode: 'string' }),
lastSignInAt: timestamp("last_sign_in_at", { withTimezone: true, mode: 'string' }),
rawAppMetaData: jsonb("raw_app_meta_data"),
rawUserMetaData: jsonb("raw_user_meta_data"),
isSuperAdmin: boolean("is_super_admin"),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }),
phone: text().default(sql`NULL`),
phoneConfirmedAt: timestamp("phone_confirmed_at", { withTimezone: true, mode: 'string' }),
phoneChange: text("phone_change").default('),
phoneChangeToken: varchar("phone_change_token", { length: 255 }).default('),
phoneChangeSentAt: timestamp("phone_change_sent_at", { withTimezone: true, mode: 'string' }),
confirmedAt: timestamp("confirmed_at", { withTimezone: true, mode: 'string' }).generatedAlwaysAs(sql`LEAST(email_confirmed_at, phone_confirmed_at)`),
emailChangeTokenCurrent: varchar("email_change_token_current", { length: 255 }).default('),
emailChangeConfirmStatus: smallint("email_change_confirm_status").default(0),
bannedUntil: timestamp("banned_until", { withTimezone: true, mode: 'string' }),
reauthenticationToken: varchar("reauthentication_token", { length: 255 }).default('),
reauthenticationSentAt: timestamp("reauthentication_sent_at", { withTimezone: true, mode: 'string' }),
isSsoUser: boolean("is_sso_user").default(false).notNull(),
deletedAt: timestamp("deleted_at", { withTimezone: true, mode: 'string' }),
isAnonymous: boolean("is_anonymous").default(false).notNull(),
}, (table) => [
uniqueIndex("confirmation_token_idx").using("btree", table.confirmationToken.asc().nullsLast().op("text_ops")).where(sql`((confirmation_token)::text !~ '^[0-9 ]*$'::text)`),
uniqueIndex("email_change_token_current_idx").using("btree", table.emailChangeTokenCurrent.asc().nullsLast().op("text_ops")).where(sql`((email_change_token_current)::text !~ '^[0-9 ]*$'::text)`),
uniqueIndex("email_change_token_new_idx").using("btree", table.emailChangeTokenNew.asc().nullsLast().op("text_ops")).where(sql`((email_change_token_new)::text !~ '^[0-9 ]*$'::text)`),
uniqueIndex("reauthentication_token_idx").using("btree", table.reauthenticationToken.asc().nullsLast().op("text_ops")).where(sql`((reauthentication_token)::text !~ '^[0-9 ]*$'::text)`),
uniqueIndex("recovery_token_idx").using("btree", table.recoveryToken.asc().nullsLast().op("text_ops")).where(sql`((recovery_token)::text !~ '^[0-9 ]*$'::text)`),
uniqueIndex("users_email_partial_key").using("btree", table.email.asc().nullsLast().op("text_ops")).where(sql`(is_sso_user = false)`),
index("users_instance_id_email_idx").using("btree", sql`instance_id`, sql`lower((email)::text)`),
index("users_instance_id_idx").using("btree", table.instanceId.asc().nullsLast().op("uuid_ops")),
index("users_is_anonymous_idx").using("btree", table.isAnonymous.asc().nullsLast().op("bool_ops")),
check("users_email_change_confirm_status_check", sql`(email_change_confirm_status >= 0) AND (email_change_confirm_status <= 2)`),
]);
```
Contributor guide
Assessment
This issue has not been assessed yet.