Spreadsheet-UX op een echte relationele engine

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

2.5 kB Ruw bestand
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`;
}