O Wedding Finance Planner usa SQLite via Prisma ORM. SQLite é leve, sem servidor, e perfeito para o uso esperado (1 casal = 1 instância, algumas centenas de registros).
prisma/dev.db) — fácil de backup e migrar.provider no
schema.# Aplicar mudanças do schema (sem dropar dados)
npx prisma db push --skip-generate
# Regerar o client TypeScript
npx prisma generate
# Atalho npm que faz os dois
npm run db:push
# Abrir a UI web do Prisma Studio (http://localhost:5555)
npm run db:studio
# Rodar o seed (idempotente)
npm run db:seed
db push vs migrate dev — por quê não usamos migrations?O projeto usa o fluxo schema-first (prisma db push) em vez de
migrations (prisma migrate dev). Motivos:
migrations/
com dezenas de arquivos custa caro para pouco benefício../setup.sh, que aplica o schema atual de uma
vez. Não precisa rejogar histórico.db push.⚠️ Se o projeto crescer (multi-tenant, várias instâncias), migrar para
prisma migrateserá necessário. Discuta antes de fazer.
Arquivo: prisma/schema.prisma.
| Modelo | Propósito |
|---|---|
User |
Usuário do sistema (admins do casamento). Suporta 2FA, mustChangePassword, archivedAt, locale (pt-BR/en/es). |
PasswordResetToken |
Tokens de reset (hash sha256, expira em 60min). |
NotificationLog |
Histórico de envios (email + WhatsApp), idempotência por dia. |
SecuritySettings |
Singleton: lista de roles que exigem 2FA, tamanho mínimo de senha. |
EventSettings |
Singleton: dados do casamento (data, contingência, moeda, nomes, plano B, defaultLocale). Inclui Save the Date: weddingWebsiteUrl, giftRegistryUrl, saveTheDateMessage, saveTheDateFilePath/Mime/Name (a arte) e a exclusão de destinatários saveTheDateExcludeTagIds (JSON de IDs de tag) + saveTheDateExcludePadrinhos. |
Vendor |
Fornecedores com status, categoria, notas, contratos, anexos. |
VendorContact |
Múltiplos contatos por fornecedor (com isPrimary). |
VendorNote |
Histórico de notas livre por fornecedor. |
Contract |
Contratos versionados (status, valor, datas, exclusões). |
Attachment |
Anexos (vendors, contratos, locais). |
Venue |
Locais (capacidade, preço, prós/contras, visitação). |
VenueChecklistItem |
Itens de checklist por local (ordenável). |
BudgetItem |
Itens orçamentários (estimado vs realizado, vinculado a Vendor). |
Payment |
Pagamentos (parcelado, status, vencimento, método). |
Asset |
Caixa/poupança (entradas e saídas). |
Income |
Receitas (salário, donativos, frequência). |
SavingsGoal |
Metas financeiras (target, data, isActive). |
Honeymoon |
Singleton: lua de mel. |
HoneymoonItem |
Itens da lua de mel (atividade, vôo, hospedagem). |
TrousseauItem |
Enxoval (cômodo, prioridade, status compra). |
Guest |
Convidados (RSVP, +1s, dietary, padrinho, checkin, language opcional para RSVP localizado, rsvpTokenExpiresAt opcional, age opcional para crianças). Relação M:N com GuestTag via GuestTagOnGuest. |
GuestGroup |
Convite de família com rsvpToken único e rsvpTokenExpiresAt opcional. rsvpPin opcional (informativo, vindo de imports externos). |
GuestTag |
Tags de convidados (Padrinhos, Florista, etc.), com name @unique e color opcional. |
GuestTagOnGuest |
Junction table M:N entre Guest e GuestTag (@@id([guestId, tagId]), cascade no delete). |
SeatingTable |
Mesa do evento (capacidade, forma, posição x/y). |
Gift |
Presentes (cash/item, status recebimento). |
Task |
Tarefas (status, prioridade, responsável, deadline). |
AuditLog |
Auditoria (entity, action, payload, userId). |
Broadcast |
Campanha de envio em massa (kind, ex.: SAVE_THE_DATE; status, total). Reaproveitável para o convite formal futuro. |
BroadcastRecipient |
Item da fila de um Broadcast (refType/refId, phone, email, status PENDING/SENT/FAILED/SKIPPED). Cascade no delete do Broadcast. |
cuid() (string opaca).deletedAt: DateTime?. Filtre por
deletedAt: null ao consultar.createdAt @default(now()) e updatedAt @updatedAt quase
sempre presentes.EventSettings, Honeymoon, SecuritySettings usam
id: String @id @default("singleton"). Sempre via upsert.Adicionados em prisma/schema.prisma para cobrir queries de listagem e do cron de lembretes:
| Modelo | Índices |
|---|---|
Vendor |
[status, deletedAt], [categoryKey], [deletedAt] |
Payment |
[vendorId], [status, dueDate], [deletedAt, status, dueDate] |
Asset |
[goalId], [date], [deletedAt] |
Income |
[status, expectedDate], [deletedAt] |
BudgetItem |
[vendorId, deletedAt] |
Gift |
[status], [guestId], [deletedAt] |
Task |
[status, deadline], [deadline], [vendorId], [venueId], [deletedAt] |
Contract |
[vendorId], [vendorId, status] |
NotificationLog |
[kind, refType, refId], [createdAt], [kind, status, createdAt] |
AuditLog |
[entity, entityId], [createdAt], [userId, createdAt] |
Em SQLite o ganho concreto é menor que em Postgres, mas mantém o plano de
queries estável (eviota table scans nas listagens autenticadas e no
agregado groupBy que faz a idempotência do cron — veja
notificacoes.md).
npm run db:studio
Abre http://localhost:5555 com uma tabela editável de cada modelo.
sqlite3 prisma/dev.db
sqlite> .tables
sqlite> SELECT id, name, email, role FROM User;
sqlite> .exit
node
> const { PrismaClient } = require('@prisma/client');
> const p = new PrismaClient();
> await p.eventSettings.findUnique({ where: { id: 'singleton' } });
prisma/schema.prisma.npx prisma db push --skip-generate.npx prisma generate.🚨 Para alterações destrutivas (drop coluna, mudar tipo), faça backup antes (
cp prisma/dev.db prisma/dev.db.bak). Oprisma db pushperguntará se quer continuar.
A partir da v0.3.0 o singleton em src/lib/prisma.ts aplica automaticamente, na primeira inicialização do PrismaClient:
PRAGMA journal_mode = WAL — permite reads concorrentes durante writes.PRAGMA busy_timeout = 5000 — espera até 5s antes de dar SQLITE_BUSY
quando há contenção (útil quando o cron de reminders escreve junto com o
usuário).Se você quiser verificar manualmente:
sqlite3 prisma/dev.db "PRAGMA journal_mode" # deve retornar 'wal'
WAL gera dois arquivos auxiliares ao lado do dev.db: dev.db-wal e
dev.db-shm. Inclua os três no backup ou use VACUUM INTO para gerar um
snapshot consolidado.
16 modelos têm deletedAt: DateTime?. Em vez de escrever
where: { deletedAt: null } em cada query, o singleton aplica uma
Client Extension ($extends) que:
deletedAt: null automaticamente em findMany, findFirst,
findFirstOrThrow, count, aggregate, groupBy.findUnique/findUniqueOrThrow, pós-filtra: se o registro encontrado
tiver deletedAt != null, retorna null (ou lança em OrThrow).delete e deleteMany, converte automaticamente para update
setando deletedAt = new Date() — ou seja, todo .delete() em modelo
com deletedAt vira soft delete sem mudar código existente.Lista de modelos soft delete (constante SOFT_DELETE_MODELS em
src/lib/prisma.ts):
Vendor, VendorContact, VendorNote, Contract, Attachment,
Venue, BudgetItem, Payment, Asset, Income, SavingsGoal,
HoneymoonItem, TrousseauItem, Guest, Gift, Task, SeatingTable,
GuestGroup.
Se precisar ler registros soft-deletados (telas de “lixeira”), passe
where: { deletedAt: { not: null } }explicitamente — a extension só injetanullquando você não especifica o campo.
Se quiser sair do SQLite (ex.: para MySQL/Postgres na nuvem):
provider = "mysql" (ou "postgresql") em schema.prisma.DATABASE_URL.DATETIME. Para a maioria dos casos o Prisma cuida da tradução.prisma migrate dev a partir daí (mais seguro em bancos
“de verdade”).npm run db:studio ou
sqlite3 prisma/dev.db .dump > dump.sql, e importe no novo banco.