Files
adaptador-planilhas/src/infra/db/migration-manifest.ts
T
camilafagundesandCursor 8cfeeb0464
ci / lint, typecheck, testes e build (push) Canceled after 0s
ci / Postgres e Redis reais (push) Canceled after 0s
ci / imagem Docker Linux (push) Canceled after 0s
fix(db): desacopla configuracao administrativa das migrations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 01:14:00 -03:00

75 lines
2.7 KiB
TypeScript

import { identityShimEnabled } from "@/config/env";
import type { Db } from "./types";
/**
* Migrations que **este build** espera encontrar aplicadas.
*
* É uma lista estática de propósito. O health check e o worker precisam
* responder "o banco está na versão que este código espera?", não "o banco
* combina com os arquivos que por acaso estão no disco". Ler o diretório aqui
* também fazia o Next tracear o projeto inteiro para dentro da saída
* `standalone`.
*
* `migrate()` confere que esta lista e o diretório `migrations/` não divergiram.
*/
export const EXPECTED_MIGRATIONS = [
"0001_platform_shim.sql",
"0002_spreadsheet_adapter.sql",
"0003_rls.sql",
"0004_audit_append_only.sql",
"0005_maintenance_roles.sql",
] as const;
/** Migrations que criam o shim local de identidade do Elo Pro. */
export const SHIM_MIGRATIONS: ReadonlySet<string> = new Set(["0001_platform_shim.sql"]);
export function isShimMigration(file: string): boolean {
return SHIM_MIGRATIONS.has(file);
}
export type PendingOptions = { applyIdentityShim?: boolean };
export type MigrationLedger = { state: "ausente" } | { state: "presente"; ids: string[] };
/** A leitura do histórico falhou. Não é o mesmo que tabela ausente. */
export class MigrationLedgerError extends Error {
readonly code = "historico_indisponivel";
constructor() {
super("historico_indisponivel");
this.name = "MigrationLedgerError";
}
}
/**
* Lê o histórico sem criá-lo. Tabela ausente é um estado explícito. Falha de
* permissão, conexão ou consulta interrompe: não vira lista vazia.
*/
export async function readMigrationLedger(db: Db): Promise<MigrationLedger> {
let presence: { name: string | null }[];
try {
presence = await db.withService((query) =>
query.query<{ name: string | null }>("select to_regclass('public.schema_migrations')::text as name"),
);
} catch {
throw new MigrationLedgerError();
}
if (!presence[0]?.name) return { state: "ausente" };
try {
const rows = await db.withService((query) =>
query.query<{ id: string }>("select id from public.schema_migrations order by id"),
);
return { state: "presente", ids: rows.map((row) => row.id) };
} catch {
throw new MigrationLedgerError();
}
}
/** Migrations esperadas que ainda não constam em `schema_migrations`. */
export async function pendingMigrations(db: Db, options: PendingOptions = {}): Promise<string[]> {
const applyShim = options.applyIdentityShim ?? identityShimEnabled();
const ledger = await readMigrationLedger(db);
const applied = new Set(ledger.state === "presente" ? ledger.ids : []);
return EXPECTED_MIGRATIONS.filter((file) => !applied.has(file) && (applyShim || !isShimMigration(file)));
}