feat(db): mesh data model — meshes, members, invites, audit log
- 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>
This commit is contained in:
101
packages/db/src/scripts/status.ts
Normal file
101
packages/db/src/scripts/status.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user