diff --git a/docs/superpowers/plans/2026-07-31-prisma-orm-migrations.md b/docs/superpowers/plans/2026-07-31-prisma-orm-migrations.md index 3aaba1e..6132adc 100644 --- a/docs/superpowers/plans/2026-07-31-prisma-orm-migrations.md +++ b/docs/superpowers/plans/2026-07-31-prisma-orm-migrations.md @@ -10,7 +10,7 @@ ## Global Constraints -- `prisma/schema.prisma` must reproduce `db/schema.sql` column-for-column, so the initial migration is a no-op against databases that already have the table (spec: "Schema"). +- `prisma/schema.prisma` must reproduce `db/schema.sql` column-for-column, so the initial migration is a no-op against databases that already have the table (spec: "Schema"). Two documented, accepted exceptions where Prisma's MySQL provider has no schema-level equivalent (verified empirically against the real DB's `SHOW CREATE TABLE conversion_jobs`, and confirmed acceptable by the user on 2026-07-31): (1) table collation — the live MariaDB server defaults to `utf8mb4_uca1400_ai_ci` (a MariaDB-specific collation), but Prisma always emits `utf8mb4_unicode_ci` in generated DDL with no schema attribute to override it; (2) `updatedAt`'s `ON UPDATE CURRENT_TIMESTAMP` — Prisma's `@updatedAt` is implemented client-side only for the `mysql` provider and never emits a DB-level `ON UPDATE` clause. Neither has practical impact here: the baseline (Task 3) marks the initial migration applied without ever executing its SQL against the real database, and every write to `conversion_jobs` after this migration goes through Prisma Client, which sets `updatedAt` itself. All other columns (types, defaults, index/constraint names) must match exactly — see Task 1's schema, which uses `@db.DateTime(0)` and explicit `map:` names for this reason. - `DATABASE_URL` is never written into `.env` or `.env.local` — it is always computed on demand from the existing `DB_HOST`/`DB_USER`/`DB_PASSWORD`/`DB_NAME` variables (spec: "Running Prisma CLI locally"). `.env` holds production credentials and must never be loaded for local work (CLAUDE.md). - The connection string includes `?connection_limit=10`, preserving the current pool's `connectionLimit: 10` (spec: "Running Prisma CLI locally"). - `getJobByUuid`, `findPendingJobs`, and `findExpiredJobs` must never expose `errorLog` — only `getJobErrorLog` does (spec: "Data access layer"). @@ -60,7 +60,7 @@ enum JobStatus { model ConversionJob { id Int @id @default(autoincrement()) @db.UnsignedInt - uuid String @unique @db.Char(36) + uuid String @unique(map: "uniq_uuid") @db.Char(36) status JobStatus @default(pending) family String @db.VarChar(32) sourceFormat String @map("source_format") @db.VarChar(16) @@ -69,24 +69,26 @@ model ConversionJob { inputPath String @map("input_path") @db.VarChar(255) outputPath String? @map("output_path") @db.VarChar(255) inputMimeType String @map("input_mime_type") @db.VarChar(128) - outputMimeType String? @map("output_mime_type") @db.VarChar(128) - inputSizeBytes Int @map("input_size_bytes") @db.UnsignedInt - outputSizeBytes Int? @map("output_size_bytes") @db.UnsignedInt - quality Int? @db.UnsignedSmallInt - conversionDurationSeconds Decimal? @map("conversion_duration_seconds") @db.Decimal(10, 3) - errorMessage String? @map("error_message") @db.VarChar(255) - errorLog String? @map("error_log") @db.Text - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @default(now()) @updatedAt @map("updated_at") - expiresAt DateTime @map("expires_at") - cleanedAt DateTime? @map("cleaned_at") + outputMimeType String? @map("output_mime_type") @db.VarChar(128) + inputSizeBytes Int @map("input_size_bytes") @db.UnsignedInt + outputSizeBytes Int? @map("output_size_bytes") @db.UnsignedInt + quality Int? @db.UnsignedSmallInt + conversionDurationSeconds Decimal? @map("conversion_duration_seconds") @db.Decimal(10, 3) + errorMessage String? @map("error_message") @db.VarChar(255) + errorLog String? @map("error_log") @db.Text + createdAt DateTime @default(now()) @map("created_at") @db.DateTime(0) + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(0) + expiresAt DateTime @map("expires_at") @db.DateTime(0) + cleanedAt DateTime? @map("cleaned_at") @db.DateTime(0) - @@index([status]) - @@index([expiresAt]) + @@index([status], map: "idx_status") + @@index([expiresAt], map: "idx_expires_at") @@map("conversion_jobs") } ``` +`@db.DateTime(0)` matches the real table's `datetime` columns (no fractional seconds) — without it Prisma defaults to `DATETIME(3)`/`CURRENT_TIMESTAMP(3)`, which would both mismatch the existing column type and risk an "Invalid default value" error on a fresh `prisma migrate deploy` (MySQL/MariaDB require the `CURRENT_TIMESTAMP(n)` default's precision to match the column's own precision). The `map: "..."` arguments make Prisma reuse the existing constraint/index names (`uniq_uuid`, `idx_status`, `idx_expires_at`) instead of auto-generating new ones. Verified empirically via `prisma migrate diff --from-empty --to-schema-datamodel` in a scratch directory on 2026-07-31. + - [ ] **Step 3: Add the postinstall script** Edit `package.json`: