import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; import Database from 'better-sqlite3'; import { literal, type Member } from '@wellformed/shared'; import type { Db } from '../db/driver.js'; import { newId } from '../engine/ids.js'; export interface WorkspaceSnapshot { /** The file to stream. Call `dispose` once it has been sent. */ file: string; dispose(): void; } /** * Write a consistent, self-contained copy of one workspace. * * `VACUUM INTO` is the whole trick: SQLite takes the snapshot itself, at a * single point in time, without blocking writers and without WAL sidecars to * ship alongside. What comes out is one file that opens in any SQLite tool. * * Because identity lives in the control plane, a raw copy would arrive with no * record of who had access. The roster is written into the copy — never the * original — as `_wellformed_member`, so the export answers that question on its * own. Password hashes are deliberately not included: this is a data export, * not a way to move credentials around. */ export function snapshotWorkspace(db: Db, members: Member[]): WorkspaceSnapshot { const directory = mkdtempSync(path.join(tmpdir(), 'wellformed-export-')); const file = path.join(directory, `${newId('snap')}.db`); // VACUUM INTO takes a SQL string literal, not a bound parameter. db.sqlite.exec(`VACUUM INTO ${literal(file)}`); const copy = new Database(file); try { copy.exec(` CREATE TABLE _wellformed_member ( id TEXT PRIMARY KEY, email TEXT NOT NULL, name TEXT NOT NULL, role TEXT NOT NULL, joined_at TEXT, disabled INTEGER NOT NULL DEFAULT 0 ); `); const insert = copy.prepare( 'INSERT INTO _wellformed_member (id, email, name, role, joined_at, disabled) VALUES (?, ?, ?, ?, ?, ?)', ); const writeAll = copy.transaction((rows: Member[]) => { for (const m of rows) { insert.run(m.id, m.email, m.name, m.role, m.joinedAt, m.disabled ? 1 : 0); } }); writeAll(members); } finally { copy.close(); } return { file, dispose: () => rmSync(directory, { recursive: true, force: true }), }; } /** `wellformed-acme-2026-08-06.db` — obvious in a downloads folder a year later. */ export function snapshotFilename(slug: string, now = new Date()): string { const day = now.toISOString().slice(0, 10); return `wellformed-${slug}-${day}.db`; }