- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
102 lines
2.4 KiB
TypeScript
102 lines
2.4 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
import { logger } from "@turbostarter/shared/logger";
|
|
|
|
import { db } from "../server";
|
|
|
|
interface JournalEntry {
|
|
idx: number;
|
|
version: string;
|
|
when: number;
|
|
tag: string;
|
|
breakpoints: boolean;
|
|
}
|
|
|
|
interface JournalFile {
|
|
version: string;
|
|
dialect: string;
|
|
entries?: JournalEntry[];
|
|
}
|
|
|
|
const JOURNAL_PATH = path.resolve("migrations/meta/_journal.json");
|
|
|
|
function loadJournalEntries(): JournalEntry[] {
|
|
if (!fs.existsSync(JOURNAL_PATH)) {
|
|
throw new Error(`Migrations journal not found at ${JOURNAL_PATH}`);
|
|
}
|
|
|
|
const journalFile = fs.readFileSync(JOURNAL_PATH, "utf-8");
|
|
const parsed = JSON.parse(journalFile) as JournalFile;
|
|
|
|
return [...(parsed.entries ?? [])].sort((a, b) => a.idx - b.idx);
|
|
}
|
|
|
|
function toTimestamp(value: unknown): number | null {
|
|
if (typeof value === "number") {
|
|
return value;
|
|
}
|
|
|
|
if (typeof value === "string") {
|
|
const parsed = Number(value);
|
|
return Number.isNaN(parsed) ? null : parsed;
|
|
}
|
|
|
|
if (typeof value === "bigint") {
|
|
return Number(value);
|
|
}
|
|
|
|
if (value instanceof Date) {
|
|
return value.getTime();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async function fetchAppliedMigrationTimestamps(): Promise<number[]> {
|
|
const result = await db.execute(
|
|
sql`SELECT created_at FROM "drizzle"."__drizzle_migrations" ORDER BY created_at`,
|
|
);
|
|
|
|
return Array.from(result)
|
|
.map((row) => toTimestamp((row as { created_at?: unknown }).created_at))
|
|
.filter((timestamp): timestamp is number => timestamp !== null);
|
|
}
|
|
|
|
async function main() {
|
|
const journalEntries = loadJournalEntries();
|
|
const appliedTimestamps = await fetchAppliedMigrationTimestamps();
|
|
const appliedTimestampSet = new Set(appliedTimestamps);
|
|
|
|
const appliedMigrations = journalEntries.filter((entry) =>
|
|
appliedTimestampSet.has(entry.when),
|
|
);
|
|
const pendingMigrations = journalEntries.filter(
|
|
(entry) => !appliedTimestampSet.has(entry.when),
|
|
);
|
|
|
|
logger.info("\nApplied migrations:");
|
|
if (appliedMigrations.length === 0) {
|
|
logger.info("(none)");
|
|
} else {
|
|
appliedMigrations.forEach((entry) => logger.info(`- ${entry.tag}`));
|
|
}
|
|
|
|
logger.info("\nPending migrations:");
|
|
if (pendingMigrations.length === 0) {
|
|
logger.info("(none)");
|
|
} else {
|
|
pendingMigrations.forEach((entry) => logger.info(`- ${entry.tag}`));
|
|
}
|
|
}
|
|
|
|
void main()
|
|
.catch((err) => {
|
|
logger.error(err);
|
|
process.exit(1);
|
|
})
|
|
.then(() => {
|
|
process.exit(0);
|
|
});
|